Showing preview only (2,107K chars total). Download the full file or copy to clipboard to get everything.
Repository: firecrawl/open-lovable
Branch: main
Commit: 69bd93bae7a9
Files: 326
Total size: 2.0 MB
Directory structure:
gitextract__mw591t9/
├── .cursor/
│ └── mcp.json
├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── api/
│ │ ├── analyze-edit-intent/
│ │ │ └── route.ts
│ │ ├── apply-ai-code/
│ │ │ └── route.ts
│ │ ├── apply-ai-code-stream/
│ │ │ └── route.ts
│ │ ├── check-vite-errors/
│ │ │ └── route.ts
│ │ ├── clear-vite-errors-cache/
│ │ │ └── route.ts
│ │ ├── conversation-state/
│ │ │ └── route.ts
│ │ ├── create-ai-sandbox/
│ │ │ └── route.ts
│ │ ├── create-ai-sandbox-v2/
│ │ │ └── route.ts
│ │ ├── create-zip/
│ │ │ └── route.ts
│ │ ├── detect-and-install-packages/
│ │ │ └── route.ts
│ │ ├── extract-brand-styles/
│ │ │ └── route.ts
│ │ ├── generate-ai-code-stream/
│ │ │ └── route.ts
│ │ ├── get-sandbox-files/
│ │ │ └── route.ts
│ │ ├── install-packages/
│ │ │ └── route.ts
│ │ ├── install-packages-v2/
│ │ │ └── route.ts
│ │ ├── kill-sandbox/
│ │ │ └── route.ts
│ │ ├── monitor-vite-logs/
│ │ │ └── route.ts
│ │ ├── report-vite-error/
│ │ │ └── route.ts
│ │ ├── restart-vite/
│ │ │ └── route.ts
│ │ ├── run-command/
│ │ │ └── route.ts
│ │ ├── run-command-v2/
│ │ │ └── route.ts
│ │ ├── sandbox-logs/
│ │ │ └── route.ts
│ │ ├── sandbox-status/
│ │ │ └── route.ts
│ │ ├── scrape-screenshot/
│ │ │ └── route.ts
│ │ ├── scrape-url-enhanced/
│ │ │ └── route.ts
│ │ ├── scrape-website/
│ │ │ └── route.ts
│ │ └── search/
│ │ └── route.ts
│ ├── builder/
│ │ └── page.tsx
│ ├── generation/
│ │ └── page.tsx
│ ├── globals.css
│ ├── landing.tsx
│ ├── layout.tsx
│ └── page.tsx
├── atoms/
│ └── sheets.ts
├── colors.json
├── components/
│ ├── CodeApplicationProgress.tsx
│ ├── FirecrawlIcon.tsx
│ ├── FirecrawlLogo.tsx
│ ├── HMRErrorDetector.tsx
│ ├── HeroInput.tsx
│ ├── SandboxPreview.tsx
│ ├── app/
│ │ ├── (home)/
│ │ │ └── sections/
│ │ │ ├── ai-readiness/
│ │ │ │ ├── ControlPanel.tsx
│ │ │ │ ├── InlineResults.tsx
│ │ │ │ ├── MetricBars.tsx
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── ScoreChart.tsx
│ │ │ ├── endpoints/
│ │ │ │ ├── EndpointsCrawl/
│ │ │ │ │ └── EndpointsCrawl.tsx
│ │ │ │ ├── EndpointsExtract/
│ │ │ │ │ └── EndpointsExtract.tsx
│ │ │ │ ├── EndpointsMap/
│ │ │ │ │ └── EndpointsMap.tsx
│ │ │ │ ├── EndpointsScrape/
│ │ │ │ │ └── EndpointsScrape.tsx
│ │ │ │ ├── EndpointsSearch/
│ │ │ │ │ └── EndpointsSearch.tsx
│ │ │ │ ├── Extract/
│ │ │ │ │ └── Extract.tsx
│ │ │ │ └── Mcp/
│ │ │ │ └── Mcp.tsx
│ │ │ ├── hero/
│ │ │ │ ├── Background/
│ │ │ │ │ ├── Background.tsx
│ │ │ │ │ ├── BackgroundOuterPiece.tsx
│ │ │ │ │ └── _svg/
│ │ │ │ │ └── CenterStar.tsx
│ │ │ │ ├── Badge/
│ │ │ │ │ └── Badge.tsx
│ │ │ │ ├── Hero.tsx
│ │ │ │ ├── Pixi/
│ │ │ │ │ ├── Pixi.tsx
│ │ │ │ │ └── tickers/
│ │ │ │ │ ├── ascii.ts
│ │ │ │ │ └── features/
│ │ │ │ │ ├── cell.ts
│ │ │ │ │ ├── cellReveal.ts
│ │ │ │ │ ├── components/
│ │ │ │ │ │ ├── AnimatedRect.ts
│ │ │ │ │ │ ├── BlinkingContainer.ts
│ │ │ │ │ │ └── Dot.ts
│ │ │ │ │ ├── crawl.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── mapping.ts
│ │ │ │ │ ├── scrape.ts
│ │ │ │ │ └── search.ts
│ │ │ │ └── Title/
│ │ │ │ └── Title.tsx
│ │ │ ├── hero-flame/
│ │ │ │ ├── HeroFlame.tsx
│ │ │ │ └── data.json
│ │ │ ├── hero-input/
│ │ │ │ ├── Button/
│ │ │ │ │ └── Button.tsx
│ │ │ │ ├── HeroInput.tsx
│ │ │ │ ├── Tabs/
│ │ │ │ │ ├── Mobile/
│ │ │ │ │ │ └── Mobile.tsx
│ │ │ │ │ └── Tabs.tsx
│ │ │ │ └── _svg/
│ │ │ │ ├── ArrowRight.tsx
│ │ │ │ └── Globe.tsx
│ │ │ └── hero-scraping/
│ │ │ ├── Code/
│ │ │ │ ├── Code.tsx
│ │ │ │ └── Loading/
│ │ │ │ ├── Loading.tsx
│ │ │ │ └── _svg/
│ │ │ │ └── Check.tsx
│ │ │ ├── HeroScraping.css
│ │ │ ├── HeroScraping.tsx
│ │ │ ├── Tag/
│ │ │ │ └── Tag.tsx
│ │ │ └── _svg/
│ │ │ ├── BrowserMobile.tsx
│ │ │ └── BrowserTab.tsx
│ │ ├── .cursor/
│ │ │ └── rules/
│ │ │ └── home-page-components.md
│ │ └── generation/
│ │ ├── SidebarInput.tsx
│ │ └── SidebarQuickInput.tsx
│ ├── shared/
│ │ ├── Playground/
│ │ │ └── Context/
│ │ │ └── types.ts
│ │ ├── animated-dot-icon.tsx
│ │ ├── animated-height.tsx
│ │ ├── ascii-background.tsx
│ │ ├── ascii-flame-background.tsx
│ │ ├── button/
│ │ │ ├── Button.css
│ │ │ └── Button.tsx
│ │ ├── buttons/
│ │ │ ├── capsule-button.tsx
│ │ │ ├── fire-action-link.tsx
│ │ │ ├── index.ts
│ │ │ └── slate-button.tsx
│ │ ├── color-styles/
│ │ │ └── color-styles.tsx
│ │ ├── combobox/
│ │ │ └── combobox.tsx
│ │ ├── effects/
│ │ │ ├── .cursor/
│ │ │ │ └── rules/
│ │ │ │ └── flame-effects.md
│ │ │ ├── flame/
│ │ │ │ ├── Flame.tsx
│ │ │ │ ├── ascii-explosion.tsx
│ │ │ │ ├── auth-pulse/
│ │ │ │ │ ├── auth-pulse.tsx
│ │ │ │ │ └── pulse-data.json
│ │ │ │ ├── core-flame.json
│ │ │ │ ├── core-flame.tsx
│ │ │ │ ├── explosion-data.json
│ │ │ │ ├── flame-background.tsx
│ │ │ │ ├── hero-flame-data.json
│ │ │ │ ├── hero-flame.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── slate-grid/
│ │ │ │ │ ├── grid-data.json
│ │ │ │ │ └── slate-grid.tsx
│ │ │ │ ├── subtle-explosion.tsx
│ │ │ │ └── subtle-wave/
│ │ │ │ ├── subtle-wave.tsx
│ │ │ │ └── wave-data.json
│ │ │ ├── index.ts
│ │ │ └── subtle-ascii-animation.tsx
│ │ ├── firecrawl-icon/
│ │ │ ├── firecrawl-icon-static.tsx
│ │ │ └── firecrawl-icon.tsx
│ │ ├── header/
│ │ │ ├── BrandKit/
│ │ │ │ ├── BrandKit.tsx
│ │ │ │ └── _svg/
│ │ │ │ ├── Download.tsx
│ │ │ │ ├── Guidelines.tsx
│ │ │ │ └── Icon.tsx
│ │ │ ├── Dropdown/
│ │ │ │ ├── Content/
│ │ │ │ │ ├── Content.tsx
│ │ │ │ │ └── NavItemRow.tsx
│ │ │ │ ├── Github/
│ │ │ │ │ ├── Flame/
│ │ │ │ │ │ ├── Flame.tsx
│ │ │ │ │ │ └── data.json
│ │ │ │ │ └── Github.tsx
│ │ │ │ ├── Mobile/
│ │ │ │ │ ├── Item/
│ │ │ │ │ │ └── Item.tsx
│ │ │ │ │ └── Mobile.tsx
│ │ │ │ ├── Stories/
│ │ │ │ │ ├── Flame/
│ │ │ │ │ │ └── Flame.tsx
│ │ │ │ │ ├── Stories.tsx
│ │ │ │ │ └── _svg/
│ │ │ │ │ ├── ArrowUp.tsx
│ │ │ │ │ └── Replit.tsx
│ │ │ │ └── Wrapper/
│ │ │ │ └── Wrapper.tsx
│ │ │ ├── Github/
│ │ │ │ ├── GithubClient.tsx
│ │ │ │ └── _svg/
│ │ │ │ └── GithubIcon.tsx
│ │ │ ├── HeaderContext.tsx
│ │ │ ├── Nav/
│ │ │ │ ├── Item/
│ │ │ │ │ ├── Item.tsx
│ │ │ │ │ └── _svg/
│ │ │ │ │ └── ChevronDown.tsx
│ │ │ │ ├── Nav.tsx
│ │ │ │ ├── RenderEndpointIcon.tsx
│ │ │ │ └── _svg/
│ │ │ │ ├── Affiliate.tsx
│ │ │ │ ├── Api.tsx
│ │ │ │ ├── ArrowRight.tsx
│ │ │ │ ├── Careers.tsx
│ │ │ │ ├── Changelog.tsx
│ │ │ │ ├── Chats.tsx
│ │ │ │ ├── Lead.tsx
│ │ │ │ ├── MCP.tsx
│ │ │ │ ├── Platforms.tsx
│ │ │ │ ├── Research.tsx
│ │ │ │ ├── Student.tsx
│ │ │ │ └── Templates.tsx
│ │ │ ├── Toggle/
│ │ │ │ └── Toggle.tsx
│ │ │ ├── Wrapper/
│ │ │ │ └── Wrapper.tsx
│ │ │ └── _svg/
│ │ │ └── Logo.tsx
│ │ ├── hero-flame.tsx
│ │ ├── icons/
│ │ │ ├── GitHub.tsx
│ │ │ ├── Logo.tsx
│ │ │ ├── animated-chevron.tsx
│ │ │ ├── animated-icons.tsx
│ │ │ ├── arrow-animated.tsx
│ │ │ ├── check.tsx
│ │ │ ├── chevron-slide.tsx
│ │ │ ├── copied.tsx
│ │ │ ├── copy.tsx
│ │ │ ├── curve.tsx
│ │ │ ├── fingerprint-icon.tsx
│ │ │ ├── openai.tsx
│ │ │ ├── source-icon.tsx
│ │ │ ├── symbol-colored.tsx
│ │ │ ├── symbol-white.tsx
│ │ │ ├── tremor-placeholder.tsx
│ │ │ ├── wordmark-colored.tsx
│ │ │ └── wordmark-white.tsx
│ │ ├── image/
│ │ │ ├── Image.tsx
│ │ │ └── getImageSrc.ts
│ │ ├── layout/
│ │ │ ├── animated-height.tsx
│ │ │ ├── animated-width.tsx
│ │ │ ├── curvy-rect-divider.tsx
│ │ │ └── curvy-rect.tsx
│ │ ├── loading/
│ │ │ ├── Shimmer.tsx
│ │ │ └── usage-loading.tsx
│ │ ├── lockBody.tsx
│ │ ├── logo-cloud/
│ │ │ ├── index.ts
│ │ │ ├── logo-cloud.tsx
│ │ │ └── logo-cloud2/
│ │ │ ├── Logocloud.css
│ │ │ └── Logocloud.tsx
│ │ ├── notifications/
│ │ │ └── slack-notification.tsx
│ │ ├── pixi/
│ │ │ ├── Pixi.tsx
│ │ │ ├── PixiAssetManager.ts
│ │ │ └── utils.ts
│ │ ├── portal-to-body/
│ │ │ └── PortalToBody.tsx
│ │ ├── preview/
│ │ │ ├── json-error-highlighter.tsx
│ │ │ ├── live-preview-frame.tsx
│ │ │ ├── multiple-web-browsers.tsx
│ │ │ └── web-browser.tsx
│ │ ├── pylon.tsx
│ │ ├── search-params-provider/
│ │ │ └── search-params-provider.tsx
│ │ ├── section-head/
│ │ │ ├── SectionHead.css
│ │ │ └── SectionHead.tsx
│ │ ├── section-title/
│ │ │ └── SectionTitle.tsx
│ │ ├── tabs/
│ │ │ └── Tabs.tsx
│ │ ├── ui/
│ │ │ ├── app-dialog.tsx
│ │ │ ├── ascii-dot-loader.tsx
│ │ │ ├── dot-grid-loader.tsx
│ │ │ ├── empty-state.tsx
│ │ │ ├── index.ts
│ │ │ ├── loading-state.tsx
│ │ │ ├── mobile-sheet.tsx
│ │ │ └── stat-card.tsx
│ │ └── utils/
│ │ └── portal-to-body.tsx
│ └── ui/
│ ├── button.tsx
│ ├── checkbox.tsx
│ ├── code.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── motion/
│ │ └── scramble-text.tsx
│ ├── select.tsx
│ ├── shadcn/
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── badge.tsx
│ │ ├── button.css
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── combobox.tsx
│ │ ├── context-menu.tsx
│ │ ├── data-table.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toggle.tsx
│ │ ├── tooltip-radix.tsx
│ │ └── tooltip.tsx
│ ├── spinner.tsx
│ └── textarea.tsx
├── config/
│ └── app.config.ts
├── eslint.config.mjs
├── hooks/
│ ├── useDebouncedCallback.ts
│ ├── useDebouncedEffect.ts
│ └── useSwitchingCode.ts
├── lib/
│ ├── ai/
│ │ └── provider-manager.ts
│ ├── build-validator.ts
│ ├── context-selector.ts
│ ├── edit-examples.ts
│ ├── edit-intent-analyzer.ts
│ ├── file-parser.ts
│ ├── file-search-executor.ts
│ ├── icons.ts
│ ├── morph-fast-apply.ts
│ ├── sandbox/
│ │ ├── factory.ts
│ │ ├── providers/
│ │ │ ├── e2b-provider.ts
│ │ │ └── vercel-provider.ts
│ │ ├── sandbox-manager.ts
│ │ └── types.ts
│ └── utils.ts
├── next.config.ts
├── package.json
├── packages/
│ └── create-open-lovable/
│ ├── index.js
│ ├── lib/
│ │ ├── installer.js
│ │ └── prompts.js
│ ├── package.json
│ └── templates/
│ ├── e2b/
│ │ ├── .env.example
│ │ └── README.md
│ └── vercel/
│ ├── .env.example
│ └── README.md
├── postcss.config.mjs
├── public/
│ ├── compressor.json
│ └── firecrawl-logo
├── styles/
│ ├── additional-styles/
│ │ ├── custom-fonts.css
│ │ ├── theme.css
│ │ └── utility-patterns.css
│ ├── chrome-bug.css
│ ├── colors.json
│ ├── components/
│ │ ├── .cursor/
│ │ │ └── rules/
│ │ │ └── component-styles.md
│ │ ├── button.css
│ │ ├── code.css
│ │ └── index.css
│ ├── design-system/
│ │ ├── .cursor/
│ │ │ └── rules/
│ │ │ └── design-system.md
│ │ ├── animations.css
│ │ ├── base/
│ │ │ ├── body.css
│ │ │ ├── layout.css
│ │ │ └── reset.css
│ │ ├── colors.css
│ │ ├── fonts.css
│ │ ├── typography.css
│ │ └── utilities.css
│ ├── fire.css
│ ├── inside-border-fix.css
│ └── main.css
├── tailwind.config.ts
├── tsconfig.json
├── types/
│ ├── conversation.ts
│ ├── file-manifest.ts
│ └── sandbox.ts
└── utils/
├── cn.ts
├── init-canvas.ts
├── set-timeout-on-visible.ts
└── sleep.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .cursor/mcp.json
================================================
{
"mcpServers": {
"dev3000": {
"type": "http",
"url": "http://localhost:3684/mcp"
}
}
}
================================================
FILE: .env.example
================================================
# Required
FIRECRAWL_API_KEY=your_firecrawl_api_key # Get from https://firecrawl.dev (Web scraping)
# =================================================================================
# SANDBOX PROVIDER - Choose Option 1 OR 2
# =================================================================================
# Option 1: Vercel Sandbox (recommended - default)
# Set SANDBOX_PROVIDER=vercel and choose authentication method below
SANDBOX_PROVIDER=vercel
# Vercel Authentication - Choose method a OR b
# Method a: OIDC Token (recommended for development)
# Run `vercel link` then `vercel env pull` to get VERCEL_OIDC_TOKEN automatically
VERCEL_OIDC_TOKEN=auto_generated_by_vercel_env_pull
# Method b: Personal Access Token (for production or when OIDC unavailable)
# VERCEL_TEAM_ID=team_xxxxxxxxx # Your Vercel team ID
# VERCEL_PROJECT_ID=prj_xxxxxxxxx # Your Vercel project ID
# VERCEL_TOKEN=vercel_xxxxxxxxxxxx # Personal access token from Vercel dashboard
# Get yours at https://console.groq.com
GROQ_API_KEY=your_groq_api_key_here
=======
# Option 2: E2B Sandbox
# Set SANDBOX_PROVIDER=e2b and configure E2B_API_KEY below
# SANDBOX_PROVIDER=e2b
# E2B_API_KEY=your_e2b_api_key # Get from https://e2b.dev
# =================================================================================
# AI PROVIDERS - Need at least one
# =================================================================================
# Vercel AI Gateway (recommended - provides access to multiple models)
AI_GATEWAY_API_KEY=your_ai_gateway_api_key # Get from https://vercel.com/dashboard/ai-gateway/api-keys
# Individual provider keys (used when AI_GATEWAY_API_KEY is not set)
ANTHROPIC_API_KEY=your_anthropic_api_key # Get from https://console.anthropic.com
OPENAI_API_KEY=your_openai_api_key # Get from https://platform.openai.com (GPT-5)
GEMINI_API_KEY=your_gemini_api_key # Get from https://aistudio.google.com/app/apikey
GROQ_API_KEY=your_groq_api_key # Get from https://console.groq.com (Fast inference - Kimi K2 recommended)
# Optional Morph Fast Apply
# Get yours at https://morphllm.com/
MORPH_API_KEY=your_fast_apply_key
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
**/node_modules/
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
.env.local
!.env.example
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# E2B template builds
*.tar.gz
e2b-template-*
# IDE
.vscode/
.idea/
# Temporary files
*.tmp
*.temp
repomix-output.txt
bun.lockb
.env*.local
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Open Lovable
Chat with AI to build React apps instantly. An example app made by the [Firecrawl](https://firecrawl.dev/?ref=open-lovable-github) team. For a complete cloud solution, check out [Lovable.dev](https://lovable.dev/) ❤️.
<img src="https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExbmZtaHFleGRsMTNlaWNydGdianI4NGQ4dHhyZjB0d2VkcjRyeXBucCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ZFVLWMa6dVskQX0qu1/giphy.gif" alt="Open Lovable Demo" width="100%"/>
## Setup
1. **Clone & Install**
```bash
git clone https://github.com/firecrawl/open-lovable.git
cd open-lovable
pnpm install # or npm install / yarn install
```
2. **Add `.env.local`**
```env
# =================================================================
# REQUIRED
# =================================================================
FIRECRAWL_API_KEY=your_firecrawl_api_key # https://firecrawl.dev
# =================================================================
# AI PROVIDER - Choose your LLM
# =================================================================
GEMINI_API_KEY=your_gemini_api_key # https://aistudio.google.com/app/apikey
ANTHROPIC_API_KEY=your_anthropic_api_key # https://console.anthropic.com
OPENAI_API_KEY=your_openai_api_key # https://platform.openai.com
GROQ_API_KEY=your_groq_api_key # https://console.groq.com
# =================================================================
# FAST APPLY (Optional - for faster edits)
# =================================================================
MORPH_API_KEY=your_morphllm_api_key # https://morphllm.com/dashboard
# =================================================================
# SANDBOX PROVIDER - Choose ONE: Vercel (default) or E2B
# =================================================================
SANDBOX_PROVIDER=vercel # or 'e2b'
# Option 1: Vercel Sandbox (default)
# Choose one authentication method:
# Method A: OIDC Token (recommended for development)
# Run `vercel link` then `vercel env pull` to get VERCEL_OIDC_TOKEN automatically
VERCEL_OIDC_TOKEN=auto_generated_by_vercel_env_pull
# Method B: Personal Access Token (for production or when OIDC unavailable)
# VERCEL_TEAM_ID=team_xxxxxxxxx # Your Vercel team ID
# VERCEL_PROJECT_ID=prj_xxxxxxxxx # Your Vercel project ID
# VERCEL_TOKEN=vercel_xxxxxxxxxxxx # Personal access token from Vercel dashboard
# Option 2: E2B Sandbox
# E2B_API_KEY=your_e2b_api_key # https://e2b.dev
```
3. **Run**
```bash
pnpm dev # or npm run dev / yarn dev
```
Open [http://localhost:3000](http://localhost:3000)
## License
MIT
================================================
FILE: app/api/analyze-edit-intent/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server';
import { createGroq } from '@ai-sdk/groq';
import { createAnthropic } from '@ai-sdk/anthropic';
import { createOpenAI } from '@ai-sdk/openai';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { generateObject } from 'ai';
import { z } from 'zod';
// import type { FileManifest } from '@/types/file-manifest'; // Type is used implicitly through manifest parameter
// Check if we're using Vercel AI Gateway
const isUsingAIGateway = !!process.env.AI_GATEWAY_API_KEY;
const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1';
const groq = createGroq({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GROQ_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
});
const anthropic = createAnthropic({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.ANTHROPIC_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : (process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1'),
});
const openai = createOpenAI({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.OPENAI_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : process.env.OPENAI_BASE_URL,
});
const googleGenerativeAI = createGoogleGenerativeAI({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GEMINI_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
});
// Schema for the AI's search plan - not file selection!
const searchPlanSchema = z.object({
editType: z.enum([
'UPDATE_COMPONENT',
'ADD_FEATURE',
'FIX_ISSUE',
'UPDATE_STYLE',
'REFACTOR',
'ADD_DEPENDENCY',
'REMOVE_ELEMENT'
]).describe('The type of edit being requested'),
reasoning: z.string().describe('Explanation of the search strategy'),
searchTerms: z.array(z.string()).describe('Specific text to search for (case-insensitive). Be VERY specific - exact button text, class names, etc.'),
regexPatterns: z.array(z.string()).optional().describe('Regex patterns for finding code structures (e.g., "className=[\\"\\\'].*header.*[\\"\\\']")'),
fileTypesToSearch: z.array(z.string()).default(['.jsx', '.tsx', '.js', '.ts']).describe('File extensions to search'),
expectedMatches: z.number().min(1).max(10).default(1).describe('Expected number of matches (helps validate search worked)'),
fallbackSearch: z.object({
terms: z.array(z.string()),
patterns: z.array(z.string()).optional()
}).optional().describe('Backup search if primary fails')
});
export async function POST(request: NextRequest) {
try {
const { prompt, manifest, model = 'openai/gpt-oss-20b' } = await request.json();
console.log('[analyze-edit-intent] Request received');
console.log('[analyze-edit-intent] Prompt:', prompt);
console.log('[analyze-edit-intent] Model:', model);
console.log('[analyze-edit-intent] Manifest files count:', manifest?.files ? Object.keys(manifest.files).length : 0);
if (!prompt || !manifest) {
return NextResponse.json({
error: 'prompt and manifest are required'
}, { status: 400 });
}
// Create a summary of available files for the AI
const validFiles = Object.entries(manifest.files as Record<string, any>)
.filter(([path]) => {
// Filter out invalid paths
return path.includes('.') && !path.match(/\/\d+$/);
});
const fileSummary = validFiles
.map(([path, info]: [string, any]) => {
const componentName = info.componentInfo?.name || path.split('/').pop();
// const hasImports = info.imports?.length > 0; // Kept for future use
const childComponents = info.componentInfo?.childComponents?.join(', ') || 'none';
return `- ${path} (${componentName}, renders: ${childComponents})`;
})
.join('\n');
console.log('[analyze-edit-intent] Valid files found:', validFiles.length);
if (validFiles.length === 0) {
console.error('[analyze-edit-intent] No valid files found in manifest');
return NextResponse.json({
success: false,
error: 'No valid files found in manifest'
}, { status: 400 });
}
console.log('[analyze-edit-intent] Analyzing prompt:', prompt);
console.log('[analyze-edit-intent] File summary preview:', fileSummary.split('\n').slice(0, 5).join('\n'));
// Select the appropriate AI model based on the request
let aiModel;
if (model.startsWith('anthropic/')) {
aiModel = anthropic(model.replace('anthropic/', ''));
} else if (model.startsWith('openai/')) {
if (model.includes('gpt-oss')) {
aiModel = groq(model);
} else {
aiModel = openai(model.replace('openai/', ''));
}
} else if (model.startsWith('google/')) {
aiModel = googleGenerativeAI(model.replace('google/', ''));
} else {
// Default to groq if model format is unclear
aiModel = groq(model);
}
console.log('[analyze-edit-intent] Using AI model:', model);
// Use AI to create a search plan
const result = await generateObject({
model: aiModel,
schema: searchPlanSchema,
messages: [
{
role: 'system',
content: `You are an expert at planning code searches. Your job is to create a search strategy to find the exact code that needs to be edited.
DO NOT GUESS which files to edit. Instead, provide specific search terms that will locate the code.
SEARCH STRATEGY RULES:
1. For text changes (e.g., "change 'Start Deploying' to 'Go Now'"):
- Search for the EXACT text: "Start Deploying"
2. For style changes (e.g., "make header black"):
- Search for component names: "Header", "<header"
- Search for class names: "header", "navbar"
- Search for className attributes containing relevant words
3. For removing elements (e.g., "remove the deploy button"):
- Search for the button text or aria-label
- Search for relevant IDs or data-testids
4. For navigation/header issues:
- Search for: "navigation", "nav", "Header", "navbar"
- Look for Link components or href attributes
5. Be SPECIFIC:
- Use exact capitalization for user-visible text
- Include multiple search terms for redundancy
- Add regex patterns for structural searches
Current project structure for context:
${fileSummary}`
},
{
role: 'user',
content: `User request: "${prompt}"
Create a search plan to find the exact code that needs to be modified. Include specific search terms and patterns.`
}
]
});
console.log('[analyze-edit-intent] Search plan created:', {
editType: result.object.editType,
searchTerms: result.object.searchTerms,
patterns: result.object.regexPatterns?.length || 0,
reasoning: result.object.reasoning
});
// Return the search plan, not file matches
return NextResponse.json({
success: true,
searchPlan: result.object
});
} catch (error) {
console.error('[analyze-edit-intent] Error:', error);
return NextResponse.json({
success: false,
error: (error as Error).message
}, { status: 500 });
}
}
================================================
FILE: app/api/apply-ai-code/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server';
import { parseMorphEdits, applyMorphEditToFile } from '@/lib/morph-fast-apply';
import type { SandboxState } from '@/types/sandbox';
import type { ConversationState } from '@/types/conversation';
declare global {
var conversationState: ConversationState | null;
}
interface ParsedResponse {
explanation: string;
template: string;
files: Array<{ path: string; content: string }>;
packages: string[];
commands: string[];
structure: string | null;
}
function parseAIResponse(response: string): ParsedResponse {
const sections = {
files: [] as Array<{ path: string; content: string }>,
commands: [] as string[],
packages: [] as string[],
structure: null as string | null,
explanation: '',
template: ''
};
// Parse file sections - handle duplicates and prefer complete versions
const fileMap = new Map<string, { content: string; isComplete: boolean }>();
const fileRegex = /<file path="([^"]+)">([\s\S]*?)(?:<\/file>|$)/g;
let match;
while ((match = fileRegex.exec(response)) !== null) {
const filePath = match[1];
const content = match[2].trim();
const hasClosingTag = response.substring(match.index, match.index + match[0].length).includes('</file>');
// Check if this file already exists in our map
const existing = fileMap.get(filePath);
// Decide whether to keep this version
let shouldReplace = false;
if (!existing) {
shouldReplace = true; // First occurrence
} else if (!existing.isComplete && hasClosingTag) {
shouldReplace = true; // Replace incomplete with complete
console.log(`[parseAIResponse] Replacing incomplete ${filePath} with complete version`);
} else if (existing.isComplete && hasClosingTag && content.length > existing.content.length) {
shouldReplace = true; // Replace with longer complete version
console.log(`[parseAIResponse] Replacing ${filePath} with longer complete version`);
} else if (!existing.isComplete && !hasClosingTag && content.length > existing.content.length) {
shouldReplace = true; // Both incomplete, keep longer one
}
if (shouldReplace) {
// Additional validation: reject obviously broken content
if (content.includes('...') && !content.includes('...props') && !content.includes('...rest')) {
console.warn(`[parseAIResponse] Warning: ${filePath} contains ellipsis, may be truncated`);
// Still use it if it's the only version we have
if (!existing) {
fileMap.set(filePath, { content, isComplete: hasClosingTag });
}
} else {
fileMap.set(filePath, { content, isComplete: hasClosingTag });
}
}
}
// Convert map to array for sections.files
for (const [path, { content, isComplete }] of fileMap.entries()) {
if (!isComplete) {
console.log(`[parseAIResponse] Warning: File ${path} appears to be truncated (no closing tag)`);
}
sections.files.push({
path,
content
});
}
// Parse commands
const cmdRegex = /<command>(.*?)<\/command>/g;
while ((match = cmdRegex.exec(response)) !== null) {
sections.commands.push(match[1].trim());
}
// Parse packages - support both <package> and <packages> tags
const pkgRegex = /<package>(.*?)<\/package>/g;
while ((match = pkgRegex.exec(response)) !== null) {
sections.packages.push(match[1].trim());
}
// Also parse <packages> tag with multiple packages
const packagesRegex = /<packages>([\s\S]*?)<\/packages>/;
const packagesMatch = response.match(packagesRegex);
if (packagesMatch) {
const packagesContent = packagesMatch[1].trim();
// Split by newlines or commas
const packagesList = packagesContent.split(/[\n,]+/)
.map(pkg => pkg.trim())
.filter(pkg => pkg.length > 0);
sections.packages.push(...packagesList);
}
// Parse structure
const structureMatch = /<structure>([\s\S]*?)<\/structure>/;
const structResult = response.match(structureMatch);
if (structResult) {
sections.structure = structResult[1].trim();
}
// Parse explanation
const explanationMatch = /<explanation>([\s\S]*?)<\/explanation>/;
const explResult = response.match(explanationMatch);
if (explResult) {
sections.explanation = explResult[1].trim();
}
// Parse template
const templateMatch = /<template>(.*?)<\/template>/;
const templResult = response.match(templateMatch);
if (templResult) {
sections.template = templResult[1].trim();
}
return sections;
}
declare global {
var activeSandbox: any;
var activeSandboxProvider: any;
var existingFiles: Set<string>;
var sandboxState: SandboxState;
}
export async function POST(request: NextRequest) {
try {
const { response, isEdit = false, packages = [] } = await request.json();
if (!response) {
return NextResponse.json({
error: 'response is required'
}, { status: 400 });
}
// Parse the AI response
const parsed = parseAIResponse(response);
const morphEnabled = Boolean(isEdit && process.env.MORPH_API_KEY);
const morphEdits = morphEnabled ? parseMorphEdits(response) : [];
console.log('[apply-ai-code] Morph Fast Apply mode:', morphEnabled);
if (morphEnabled) {
console.log('[apply-ai-code] Morph edits found:', morphEdits.length);
}
// Initialize existingFiles if not already
if (!global.existingFiles) {
global.existingFiles = new Set<string>();
}
// Get the active sandbox or provider
const sandbox = global.activeSandbox || global.activeSandboxProvider;
// If no active sandbox, just return parsed results
if (!sandbox) {
return NextResponse.json({
success: true,
results: {
filesCreated: parsed.files.map(f => f.path),
packagesInstalled: parsed.packages,
commandsExecuted: parsed.commands,
errors: []
},
explanation: parsed.explanation,
structure: parsed.structure,
parsedFiles: parsed.files,
message: `Parsed ${parsed.files.length} files successfully. Create a sandbox to apply them.`
});
}
// Verify sandbox is ready before applying code
console.log('[apply-ai-code] Verifying sandbox is ready...');
// For Vercel sandboxes, check if Vite is running
if (sandbox.constructor?.name === 'VercelProvider' || sandbox.getSandboxInfo?.()?.provider === 'vercel') {
console.log('[apply-ai-code] Detected Vercel sandbox, checking Vite status...');
try {
// Check if Vite process is running
const checkResult = await sandbox.runCommand('pgrep -f vite');
if (!checkResult || !checkResult.stdout) {
console.log('[apply-ai-code] Vite not running, starting it...');
// Start Vite if not running
await sandbox.runCommand('sh -c "cd /vercel/sandbox && nohup npm run dev > /tmp/vite.log 2>&1 &"');
// Wait for Vite to start
await new Promise(resolve => setTimeout(resolve, 5000));
console.log('[apply-ai-code] Vite started, proceeding with code application');
} else {
console.log('[apply-ai-code] Vite is already running');
}
} catch (e) {
console.log('[apply-ai-code] Could not check Vite status, proceeding anyway:', e);
}
}
// Apply to active sandbox
console.log('[apply-ai-code] Applying code to sandbox...');
console.log('[apply-ai-code] Is edit mode:', isEdit);
console.log('[apply-ai-code] Files to write:', parsed.files.map(f => f.path));
console.log('[apply-ai-code] Existing files:', Array.from(global.existingFiles));
if (morphEnabled) {
console.log('[apply-ai-code] Morph Fast Apply enabled');
if (morphEdits.length > 0) {
console.log('[apply-ai-code] Parsed Morph edits:', morphEdits.map(e => e.targetFile));
} else {
console.log('[apply-ai-code] No <edit> blocks found in response');
}
}
const results = {
filesCreated: [] as string[],
filesUpdated: [] as string[],
packagesInstalled: [] as string[],
packagesAlreadyInstalled: [] as string[],
packagesFailed: [] as string[],
commandsExecuted: [] as string[],
errors: [] as string[]
};
// Combine packages from tool calls and parsed XML tags
const allPackages = [...packages.filter((pkg: any) => pkg && typeof pkg === 'string'), ...parsed.packages];
const uniquePackages = [...new Set(allPackages)]; // Remove duplicates
if (uniquePackages.length > 0) {
console.log('[apply-ai-code] Installing packages from XML tags and tool calls:', uniquePackages);
try {
const installResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/install-packages`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ packages: uniquePackages })
});
if (installResponse.ok) {
const installResult = await installResponse.json();
console.log('[apply-ai-code] Package installation result:', installResult);
if (installResult.installed && installResult.installed.length > 0) {
results.packagesInstalled = installResult.installed;
}
if (installResult.failed && installResult.failed.length > 0) {
results.packagesFailed = installResult.failed;
}
}
} catch (error) {
console.error('[apply-ai-code] Error installing packages:', error);
}
} else {
// Fallback to detecting packages from code
console.log('[apply-ai-code] No packages provided, detecting from generated code...');
console.log('[apply-ai-code] Number of files to scan:', parsed.files.length);
// Filter out config files first
const configFiles = ['tailwind.config.js', 'vite.config.js', 'package.json', 'package-lock.json', 'tsconfig.json', 'postcss.config.js'];
const filteredFilesForDetection = parsed.files.filter(file => {
const fileName = file.path.split('/').pop() || '';
return !configFiles.includes(fileName);
});
// Build files object for package detection
const filesForPackageDetection: Record<string, string> = {};
for (const file of filteredFilesForDetection) {
filesForPackageDetection[file.path] = file.content;
// Log if heroicons is found
if (file.content.includes('heroicons')) {
console.log(`[apply-ai-code] Found heroicons import in ${file.path}`);
}
}
try {
console.log('[apply-ai-code] Calling detect-and-install-packages...');
const packageResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/detect-and-install-packages`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ files: filesForPackageDetection })
});
console.log('[apply-ai-code] Package detection response status:', packageResponse.status);
if (packageResponse.ok) {
const packageResult = await packageResponse.json();
console.log('[apply-ai-code] Package installation result:', JSON.stringify(packageResult, null, 2));
if (packageResult.packagesInstalled && packageResult.packagesInstalled.length > 0) {
results.packagesInstalled = packageResult.packagesInstalled;
console.log(`[apply-ai-code] Installed packages: ${packageResult.packagesInstalled.join(', ')}`);
}
if (packageResult.packagesAlreadyInstalled && packageResult.packagesAlreadyInstalled.length > 0) {
results.packagesAlreadyInstalled = packageResult.packagesAlreadyInstalled;
console.log(`[apply-ai-code] Already installed: ${packageResult.packagesAlreadyInstalled.join(', ')}`);
}
if (packageResult.packagesFailed && packageResult.packagesFailed.length > 0) {
results.packagesFailed = packageResult.packagesFailed;
console.error(`[apply-ai-code] Failed to install packages: ${packageResult.packagesFailed.join(', ')}`);
results.errors.push(`Failed to install packages: ${packageResult.packagesFailed.join(', ')}`);
}
// Force Vite restart after package installation
if (results.packagesInstalled.length > 0) {
console.log('[apply-ai-code] Packages were installed, forcing Vite restart...');
try {
// Call the restart-vite endpoint
const restartResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/restart-vite`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
if (restartResponse.ok) {
const restartResult = await restartResponse.json();
console.log('[apply-ai-code] Vite restart result:', restartResult.message);
} else {
console.error('[apply-ai-code] Failed to restart Vite:', await restartResponse.text());
}
} catch (e) {
console.error('[apply-ai-code] Error calling restart-vite:', e);
}
// Additional delay to ensure files can be written after restart
await new Promise(resolve => setTimeout(resolve, 1000));
}
} else {
console.error('[apply-ai-code] Package detection/installation failed:', await packageResponse.text());
}
} catch (error) {
console.error('[apply-ai-code] Error detecting/installing packages:', error);
// Continue with file writing even if package installation fails
}
}
// Attempt Morph Fast Apply for edits before file creation
const morphUpdatedPaths = new Set<string>();
if (morphEnabled && morphEdits.length > 0) {
if (!global.activeSandbox) {
console.warn('[apply-ai-code] Morph edits found but no active sandbox; skipping Morph application');
} else {
console.log(`[apply-ai-code] Applying ${morphEdits.length} fast edits via Morph...`);
for (const edit of morphEdits) {
try {
const result = await applyMorphEditToFile({
sandbox: global.activeSandbox,
targetPath: edit.targetFile,
instructions: edit.instructions,
updateSnippet: edit.update
});
if (result.success && result.normalizedPath) {
morphUpdatedPaths.add(result.normalizedPath);
results.filesUpdated.push(result.normalizedPath);
console.log('[apply-ai-code] Morph applied to', result.normalizedPath);
} else {
const msg = result.error || 'Unknown Morph error';
console.error('[apply-ai-code] Morph apply failed:', msg);
results.errors.push(`Morph apply failed for ${edit.targetFile}: ${msg}`);
}
} catch (e) {
console.error('[apply-ai-code] Morph apply exception:', e);
results.errors.push(`Morph apply exception for ${edit.targetFile}: ${(e as Error).message}`);
}
}
}
}
if (morphEnabled && morphEdits.length === 0) {
console.warn('[apply-ai-code] Morph enabled but no <edit> blocks found; falling back to full-file flow');
}
// Filter out config files that shouldn't be created
const configFiles = ['tailwind.config.js', 'vite.config.js', 'package.json', 'package-lock.json', 'tsconfig.json', 'postcss.config.js'];
let filteredFiles = parsed.files.filter(file => {
const fileName = file.path.split('/').pop() || '';
if (configFiles.includes(fileName)) {
console.warn(`[apply-ai-code] Skipping config file: ${file.path} - already exists in template`);
return false;
}
return true;
});
// Avoid overwriting files already updated by Morph
if (morphUpdatedPaths.size > 0) {
filteredFiles = filteredFiles.filter(file => {
let normalizedPath = file.path.startsWith('/') ? file.path.slice(1) : file.path;
const fileName = normalizedPath.split('/').pop() || '';
if (!normalizedPath.startsWith('src/') &&
!normalizedPath.startsWith('public/') &&
normalizedPath !== 'index.html' &&
!configFiles.includes(fileName)) {
normalizedPath = 'src/' + normalizedPath;
}
return !morphUpdatedPaths.has(normalizedPath);
});
}
// Create or update files AFTER package installation
for (const file of filteredFiles) {
try {
// Normalize the file path
let normalizedPath = file.path;
// Remove leading slash if present
if (normalizedPath.startsWith('/')) {
normalizedPath = normalizedPath.substring(1);
}
// Ensure src/ prefix for component files
if (!normalizedPath.startsWith('src/') &&
!normalizedPath.startsWith('public/') &&
normalizedPath !== 'index.html' &&
normalizedPath !== 'package.json' &&
normalizedPath !== 'vite.config.js' &&
normalizedPath !== 'tailwind.config.js' &&
normalizedPath !== 'postcss.config.js') {
normalizedPath = 'src/' + normalizedPath;
}
const fullPath = `/home/user/app/${normalizedPath}`;
const isUpdate = global.existingFiles.has(normalizedPath);
// Remove any CSS imports from JSX/JS files (we're using Tailwind)
let fileContent = file.content;
if (file.path.endsWith('.jsx') || file.path.endsWith('.js') || file.path.endsWith('.tsx') || file.path.endsWith('.ts')) {
fileContent = fileContent.replace(/import\s+['"]\.\/[^'"]+\.css['"];?\s*\n?/g, '');
}
// Fix common Tailwind CSS errors in CSS files
if (file.path.endsWith('.css')) {
// Replace shadow-3xl with shadow-2xl (shadow-3xl doesn't exist)
fileContent = fileContent.replace(/shadow-3xl/g, 'shadow-2xl');
// Replace any other non-existent shadow utilities
fileContent = fileContent.replace(/shadow-4xl/g, 'shadow-2xl');
fileContent = fileContent.replace(/shadow-5xl/g, 'shadow-2xl');
}
console.log(`[apply-ai-code] Writing file using E2B files API: ${fullPath}`);
try {
// Check if we're using provider pattern (v2) or direct sandbox (v1)
if (sandbox.writeFile) {
// V2: Provider pattern (Vercel/E2B provider)
await sandbox.writeFile(file.path, fileContent);
} else if (sandbox.files?.write) {
// V1: Direct E2B sandbox
await sandbox.files.write(fullPath, fileContent);
} else {
throw new Error('Unsupported sandbox type');
}
console.log(`[apply-ai-code] Successfully wrote file: ${fullPath}`);
// Update file cache
if (global.sandboxState?.fileCache) {
global.sandboxState.fileCache.files[normalizedPath] = {
content: fileContent,
lastModified: Date.now()
};
console.log(`[apply-ai-code] Updated file cache for: ${normalizedPath}`);
}
} catch (writeError) {
console.error(`[apply-ai-code] E2B file write error:`, writeError);
throw writeError as Error;
}
if (isUpdate) {
results.filesUpdated.push(normalizedPath);
} else {
results.filesCreated.push(normalizedPath);
global.existingFiles.add(normalizedPath);
}
} catch (error) {
results.errors.push(`Failed to create ${file.path}: ${(error as Error).message}`);
}
}
// Only create App.jsx if it's not an edit and doesn't exist
const appFileInParsed = parsed.files.some(f => {
const normalized = f.path.replace(/^\//, '').replace(/^src\//, '');
return normalized === 'App.jsx' || normalized === 'App.tsx';
});
const appFileExists = global.existingFiles.has('src/App.jsx') ||
global.existingFiles.has('src/App.tsx') ||
global.existingFiles.has('App.jsx') ||
global.existingFiles.has('App.tsx');
if (!isEdit && !appFileInParsed && !appFileExists && parsed.files.length > 0) {
// Find all component files
const componentFiles = parsed.files.filter(f =>
(f.path.endsWith('.jsx') || f.path.endsWith('.tsx')) &&
f.path.includes('component')
);
// Generate imports for components
const imports = componentFiles
.filter(f => !f.path.includes('App.') && !f.path.includes('main.') && !f.path.includes('index.'))
.map(f => {
const pathParts = f.path.split('/');
const fileName = pathParts[pathParts.length - 1];
const componentName = fileName.replace(/\.(jsx|tsx)$/, '');
// Fix import path - components are in src/components/
const importPath = f.path.startsWith('src/')
? f.path.replace('src/', './').replace(/\.(jsx|tsx)$/, '')
: './' + f.path.replace(/\.(jsx|tsx)$/, '');
return `import ${componentName} from '${importPath}';`;
})
.join('\n');
// Find the main component
const mainComponent = componentFiles.find(f => {
const name = f.path.toLowerCase();
return name.includes('header') ||
name.includes('hero') ||
name.includes('layout') ||
name.includes('main') ||
name.includes('home');
}) || componentFiles[0];
const mainComponentName = mainComponent
? mainComponent.path.split('/').pop()?.replace(/\.(jsx|tsx)$/, '')
: null;
// Create App.jsx with better structure
const appContent = `import React from 'react';
${imports}
function App() {
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
${mainComponentName ? `<${mainComponentName} />` : '<div className="text-center">\n <h1 className="text-4xl font-bold mb-4">Welcome to your React App</h1>\n <p className="text-gray-400">Your components have been created but need to be added here.</p>\n </div>'}
{/* Generated components: ${componentFiles.map(f => f.path).join(', ')} */}
</div>
);
}
export default App;`;
try {
// Use provider pattern if available
if (sandbox.writeFile) {
await sandbox.writeFile('src/App.jsx', appContent);
} else if (sandbox.writeFiles) {
await sandbox.writeFiles([{
path: 'src/App.jsx',
content: Buffer.from(appContent)
}]);
}
console.log('Auto-generated: src/App.jsx');
results.filesCreated.push('src/App.jsx (auto-generated)');
} catch (error) {
results.errors.push(`Failed to create App.jsx: ${(error as Error).message}`);
}
// Don't auto-generate App.css - we're using Tailwind CSS
// Only create index.css if it doesn't exist
const indexCssInParsed = parsed.files.some(f => {
const normalized = f.path.replace(/^\//, '').replace(/^src\//, '');
return normalized === 'index.css' || f.path === 'src/index.css';
});
const indexCssExists = global.existingFiles.has('src/index.css') ||
global.existingFiles.has('index.css');
if (!isEdit && !indexCssInParsed && !indexCssExists) {
try {
const indexCssContent = `@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: dark;
color: rgba(255, 255, 255, 0.87);
background-color: #0a0a0a;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}`;
// Use provider pattern if available
if (sandbox.writeFile) {
await sandbox.writeFile('src/index.css', indexCssContent);
} else if (sandbox.writeFiles) {
await sandbox.writeFiles([{
path: 'src/index.css',
content: Buffer.from(indexCssContent)
}]);
}
console.log('Auto-generated: src/index.css');
results.filesCreated.push('src/index.css (with Tailwind)');
} catch (error) {
console.error('Failed to create index.css:', error);
results.errors.push('Failed to create index.css with Tailwind');
}
}
}
// Execute commands
for (const cmd of parsed.commands) {
try {
// Parse command and arguments
const commandParts = cmd.trim().split(/\s+/);
const cmdName = commandParts[0];
const args = commandParts.slice(1);
// Execute command using sandbox
let result;
if (sandbox.runCommand && typeof sandbox.runCommand === 'function') {
// Check if this is a provider pattern sandbox
const testResult = await sandbox.runCommand(cmd);
if (testResult && typeof testResult === 'object' && 'stdout' in testResult) {
// Provider returns CommandResult directly
result = testResult;
} else {
// Direct sandbox - expects object with cmd and args
result = await sandbox.runCommand({
cmd: cmdName,
args
});
}
}
console.log(`Executed: ${cmd}`);
// Handle result based on type
let stdout = '';
let stderr = '';
if (result) {
if (typeof result.stdout === 'string') {
stdout = result.stdout;
stderr = result.stderr || '';
} else if (typeof result.stdout === 'function') {
stdout = await result.stdout();
stderr = await result.stderr();
}
}
if (stdout) console.log(stdout);
if (stderr) console.log(`Errors: ${stderr}`);
results.commandsExecuted.push(cmd);
} catch (error) {
results.errors.push(`Failed to execute ${cmd}: ${(error as Error).message}`);
}
}
// Check for missing imports in App.jsx
const missingImports: string[] = [];
const appFile = parsed.files.find(f =>
f.path === 'src/App.jsx' || f.path === 'App.jsx'
);
if (appFile) {
// Extract imports from App.jsx
const importRegex = /import\s+(?:\w+|\{[^}]+\})\s+from\s+['"]([^'"]+)['"]/g;
let match;
const imports: string[] = [];
while ((match = importRegex.exec(appFile.content)) !== null) {
const importPath = match[1];
if (importPath.startsWith('./') || importPath.startsWith('../')) {
imports.push(importPath);
}
}
// Check if all imported files exist
for (const imp of imports) {
// Skip CSS imports for this check
if (imp.endsWith('.css')) continue;
// Convert import path to expected file paths
const basePath = imp.replace('./', 'src/');
const possiblePaths = [
basePath + '.jsx',
basePath + '.js',
basePath + '/index.jsx',
basePath + '/index.js'
];
const fileExists = parsed.files.some(f =>
possiblePaths.some(path => f.path === path)
);
if (!fileExists) {
missingImports.push(imp);
}
}
}
// Prepare response
const responseData: any = {
success: true,
results,
explanation: parsed.explanation,
structure: parsed.structure,
message: `Applied ${results.filesCreated.length} files successfully`
};
// Handle missing imports automatically
if (missingImports.length > 0) {
console.warn('[apply-ai-code] Missing imports detected:', missingImports);
// Automatically generate missing components
try {
console.log('[apply-ai-code] Auto-generating missing components...');
const autoCompleteResponse = await fetch(
`${request.nextUrl.origin}/api/auto-complete-components`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
missingImports,
model: 'claude-sonnet-4-20250514'
})
}
);
const autoCompleteData = await autoCompleteResponse.json();
if (autoCompleteData.success) {
responseData.autoCompleted = true;
responseData.autoCompletedComponents = autoCompleteData.components;
responseData.message = `Applied ${results.filesCreated.length} files + auto-generated ${autoCompleteData.files} missing components`;
// Add auto-completed files to results
results.filesCreated.push(...autoCompleteData.components);
} else {
// If auto-complete fails, still warn the user
responseData.warning = `Missing ${missingImports.length} imported components: ${missingImports.join(', ')}`;
responseData.missingImports = missingImports;
}
} catch (error) {
console.error('[apply-ai-code] Auto-complete failed:', error);
responseData.warning = `Missing ${missingImports.length} imported components: ${missingImports.join(', ')}`;
responseData.missingImports = missingImports;
}
}
// Track applied files in conversation state
if (global.conversationState && results.filesCreated.length > 0) {
// Update the last message metadata with edited files
const messages = global.conversationState.context.messages;
if (messages.length > 0) {
const lastMessage = messages[messages.length - 1];
if (lastMessage.role === 'user') {
lastMessage.metadata = {
...lastMessage.metadata,
editedFiles: results.filesCreated
};
}
}
// Track applied code in project evolution
if (global.conversationState.context.projectEvolution) {
global.conversationState.context.projectEvolution.majorChanges.push({
timestamp: Date.now(),
description: parsed.explanation || 'Code applied',
filesAffected: results.filesCreated
});
}
// Update last updated timestamp
global.conversationState.lastUpdated = Date.now();
console.log('[apply-ai-code] Updated conversation state with applied files:', results.filesCreated);
}
return NextResponse.json(responseData);
} catch (error) {
console.error('Apply AI code error:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to parse AI code' },
{ status: 500 }
);
}
}
================================================
FILE: app/api/apply-ai-code-stream/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server';
import { parseMorphEdits, applyMorphEditToFile } from '@/lib/morph-fast-apply';
// Sandbox import not needed - using global sandbox from sandbox-manager
import type { SandboxState } from '@/types/sandbox';
import type { ConversationState } from '@/types/conversation';
import { sandboxManager } from '@/lib/sandbox/sandbox-manager';
declare global {
var conversationState: ConversationState | null;
var activeSandboxProvider: any;
var existingFiles: Set<string>;
var sandboxState: SandboxState;
}
interface ParsedResponse {
explanation: string;
template: string;
files: Array<{ path: string; content: string }>;
packages: string[];
commands: string[];
structure: string | null;
}
function parseAIResponse(response: string): ParsedResponse {
const sections = {
files: [] as Array<{ path: string; content: string }>,
commands: [] as string[],
packages: [] as string[],
structure: null as string | null,
explanation: '',
template: ''
};
// Function to extract packages from import statements
function extractPackagesFromCode(content: string): string[] {
const packages: string[] = [];
// Match ES6 imports
const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+|\w+))*\s+from\s+)?['"]([^'"]+)['"]/g;
let importMatch;
while ((importMatch = importRegex.exec(content)) !== null) {
const importPath = importMatch[1];
// Skip relative imports and built-in React
if (!importPath.startsWith('.') && !importPath.startsWith('/') &&
importPath !== 'react' && importPath !== 'react-dom' &&
!importPath.startsWith('@/')) {
// Extract package name (handle scoped packages like @heroicons/react)
const packageName = importPath.startsWith('@')
? importPath.split('/').slice(0, 2).join('/')
: importPath.split('/')[0];
if (!packages.includes(packageName)) {
packages.push(packageName);
// Log important packages for debugging
if (packageName === 'react-router-dom' || packageName.includes('router') || packageName.includes('icon')) {
console.log(`[apply-ai-code-stream] Detected package from imports: ${packageName}`);
}
}
}
}
return packages;
}
// Parse file sections - handle duplicates and prefer complete versions
const fileMap = new Map<string, { content: string; isComplete: boolean }>();
// First pass: Find all file declarations
const fileRegex = /<file path="([^"]+)">([\s\S]*?)(?:<\/file>|$)/g;
let match;
while ((match = fileRegex.exec(response)) !== null) {
const filePath = match[1];
const content = match[2].trim();
const hasClosingTag = response.substring(match.index, match.index + match[0].length).includes('</file>');
// Check if this file already exists in our map
const existing = fileMap.get(filePath);
// Decide whether to keep this version
let shouldReplace = false;
if (!existing) {
shouldReplace = true; // First occurrence
} else if (!existing.isComplete && hasClosingTag) {
shouldReplace = true; // Replace incomplete with complete
console.log(`[apply-ai-code-stream] Replacing incomplete ${filePath} with complete version`);
} else if (existing.isComplete && hasClosingTag && content.length > existing.content.length) {
shouldReplace = true; // Replace with longer complete version
console.log(`[apply-ai-code-stream] Replacing ${filePath} with longer complete version`);
} else if (!existing.isComplete && !hasClosingTag && content.length > existing.content.length) {
shouldReplace = true; // Both incomplete, keep longer one
}
if (shouldReplace) {
// Additional validation: reject obviously broken content
if (content.includes('...') && !content.includes('...props') && !content.includes('...rest')) {
console.warn(`[apply-ai-code-stream] Warning: ${filePath} contains ellipsis, may be truncated`);
// Still use it if it's the only version we have
if (!existing) {
fileMap.set(filePath, { content, isComplete: hasClosingTag });
}
} else {
fileMap.set(filePath, { content, isComplete: hasClosingTag });
}
}
}
// Convert map to array for sections.files
for (const [path, { content, isComplete }] of fileMap.entries()) {
if (!isComplete) {
console.log(`[apply-ai-code-stream] Warning: File ${path} appears to be truncated (no closing tag)`);
}
sections.files.push({
path,
content
});
// Extract packages from file content
const filePackages = extractPackagesFromCode(content);
for (const pkg of filePackages) {
if (!sections.packages.includes(pkg)) {
sections.packages.push(pkg);
console.log(`[apply-ai-code-stream] 📦 Package detected from imports: ${pkg}`);
}
}
}
// Also parse markdown code blocks with file paths
const markdownFileRegex = /```(?:file )?path="([^"]+)"\n([\s\S]*?)```/g;
while ((match = markdownFileRegex.exec(response)) !== null) {
const filePath = match[1];
const content = match[2].trim();
sections.files.push({
path: filePath,
content: content
});
// Extract packages from file content
const filePackages = extractPackagesFromCode(content);
for (const pkg of filePackages) {
if (!sections.packages.includes(pkg)) {
sections.packages.push(pkg);
console.log(`[apply-ai-code-stream] 📦 Package detected from imports: ${pkg}`);
}
}
}
// Parse plain text format like "Generated Files: Header.jsx, index.css"
const generatedFilesMatch = response.match(/Generated Files?:\s*([^\n]+)/i);
if (generatedFilesMatch) {
// Split by comma first, then trim whitespace, to preserve filenames with dots
const filesList = generatedFilesMatch[1]
.split(',')
.map(f => f.trim())
.filter(f => f.endsWith('.jsx') || f.endsWith('.js') || f.endsWith('.tsx') || f.endsWith('.ts') || f.endsWith('.css') || f.endsWith('.json') || f.endsWith('.html'));
console.log(`[apply-ai-code-stream] Detected generated files from plain text: ${filesList.join(', ')}`);
// Try to extract the actual file content if it follows
for (const fileName of filesList) {
// Look for the file content after the file name
const fileContentRegex = new RegExp(`${fileName}[\\s\\S]*?(?:import[\\s\\S]+?)(?=Generated Files:|Applying code|$)`, 'i');
const fileContentMatch = response.match(fileContentRegex);
if (fileContentMatch) {
// Extract just the code part (starting from import statements)
const codeMatch = fileContentMatch[0].match(/^(import[\s\S]+)$/m);
if (codeMatch) {
const filePath = fileName.includes('/') ? fileName : `src/components/${fileName}`;
sections.files.push({
path: filePath,
content: codeMatch[1].trim()
});
console.log(`[apply-ai-code-stream] Extracted content for ${filePath}`);
// Extract packages from this file
const filePackages = extractPackagesFromCode(codeMatch[1]);
for (const pkg of filePackages) {
if (!sections.packages.includes(pkg)) {
sections.packages.push(pkg);
console.log(`[apply-ai-code-stream] Package detected from imports: ${pkg}`);
}
}
}
}
}
}
// Also try to parse if the response contains raw JSX/JS code blocks
const codeBlockRegex = /```(?:jsx?|tsx?|javascript|typescript)?\n([\s\S]*?)```/g;
while ((match = codeBlockRegex.exec(response)) !== null) {
const content = match[1].trim();
// Try to detect the file name from comments or context
const fileNameMatch = content.match(/\/\/\s*(?:File:|Component:)\s*([^\n]+)/);
if (fileNameMatch) {
const fileName = fileNameMatch[1].trim();
const filePath = fileName.includes('/') ? fileName : `src/components/${fileName}`;
// Don't add duplicate files
if (!sections.files.some(f => f.path === filePath)) {
sections.files.push({
path: filePath,
content: content
});
// Extract packages
const filePackages = extractPackagesFromCode(content);
for (const pkg of filePackages) {
if (!sections.packages.includes(pkg)) {
sections.packages.push(pkg);
}
}
}
}
}
// Parse commands
const cmdRegex = /<command>(.*?)<\/command>/g;
while ((match = cmdRegex.exec(response)) !== null) {
sections.commands.push(match[1].trim());
}
// Parse packages - support both <package> and <packages> tags
const pkgRegex = /<package>(.*?)<\/package>/g;
while ((match = pkgRegex.exec(response)) !== null) {
sections.packages.push(match[1].trim());
}
// Also parse <packages> tag with multiple packages
const packagesRegex = /<packages>([\s\S]*?)<\/packages>/;
const packagesMatch = response.match(packagesRegex);
if (packagesMatch) {
const packagesContent = packagesMatch[1].trim();
// Split by newlines or commas
const packagesList = packagesContent.split(/[\n,]+/)
.map(pkg => pkg.trim())
.filter(pkg => pkg.length > 0);
sections.packages.push(...packagesList);
}
// Parse structure
const structureMatch = /<structure>([\s\S]*?)<\/structure>/;
const structResult = response.match(structureMatch);
if (structResult) {
sections.structure = structResult[1].trim();
}
// Parse explanation
const explanationMatch = /<explanation>([\s\S]*?)<\/explanation>/;
const explResult = response.match(explanationMatch);
if (explResult) {
sections.explanation = explResult[1].trim();
}
// Parse template
const templateMatch = /<template>(.*?)<\/template>/;
const templResult = response.match(templateMatch);
if (templResult) {
sections.template = templResult[1].trim();
}
return sections;
}
export async function POST(request: NextRequest) {
try {
const { response, isEdit = false, packages = [], sandboxId } = await request.json();
if (!response) {
return NextResponse.json({
error: 'response is required'
}, { status: 400 });
}
// Debug log the response
console.log('[apply-ai-code-stream] Received response to parse:');
console.log('[apply-ai-code-stream] Response length:', response.length);
console.log('[apply-ai-code-stream] Response preview:', response.substring(0, 500));
console.log('[apply-ai-code-stream] isEdit:', isEdit);
console.log('[apply-ai-code-stream] packages:', packages);
// Parse the AI response
const parsed = parseAIResponse(response);
const morphEnabled = Boolean(isEdit && process.env.MORPH_API_KEY);
const morphEdits = morphEnabled ? parseMorphEdits(response) : [];
console.log('[apply-ai-code-stream] Morph Fast Apply mode:', morphEnabled);
if (morphEnabled) {
console.log('[apply-ai-code-stream] Morph edits found:', morphEdits.length);
}
// Log what was parsed
console.log('[apply-ai-code-stream] Parsed result:');
console.log('[apply-ai-code-stream] Files found:', parsed.files.length);
if (parsed.files.length > 0) {
parsed.files.forEach(f => {
console.log(`[apply-ai-code-stream] - ${f.path} (${f.content.length} chars)`);
});
}
console.log('[apply-ai-code-stream] Packages found:', parsed.packages);
// Initialize existingFiles if not already
if (!global.existingFiles) {
global.existingFiles = new Set<string>();
}
// Try to get provider from sandbox manager first
let provider = sandboxId ? sandboxManager.getProvider(sandboxId) : sandboxManager.getActiveProvider();
// Fall back to global state if not found in manager
if (!provider) {
provider = global.activeSandboxProvider;
}
// If we have a sandboxId but no provider, try to get or create one
if (!provider && sandboxId) {
console.log(`[apply-ai-code-stream] No provider found for sandbox ${sandboxId}, attempting to get or create...`);
try {
provider = await sandboxManager.getOrCreateProvider(sandboxId);
// If we got a new provider (not reconnected), we need to create a new sandbox
if (!provider.getSandboxInfo()) {
console.log(`[apply-ai-code-stream] Creating new sandbox since reconnection failed for ${sandboxId}`);
await provider.createSandbox();
await provider.setupViteApp();
sandboxManager.registerSandbox(sandboxId, provider);
}
// Update legacy global state
global.activeSandboxProvider = provider;
console.log(`[apply-ai-code-stream] Successfully got provider for sandbox ${sandboxId}`);
} catch (providerError) {
console.error(`[apply-ai-code-stream] Failed to get or create provider for sandbox ${sandboxId}:`, providerError);
return NextResponse.json({
success: false,
error: `Failed to create sandbox provider for ${sandboxId}. The sandbox may have expired.`,
results: {
filesCreated: [],
packagesInstalled: [],
commandsExecuted: [],
errors: [`Sandbox provider creation failed: ${(providerError as Error).message}`]
},
explanation: parsed.explanation,
structure: parsed.structure,
parsedFiles: parsed.files,
message: `Parsed ${parsed.files.length} files but couldn't apply them - sandbox reconnection failed.`
}, { status: 500 });
}
}
// If we still don't have a provider, create a new one
if (!provider) {
console.log(`[apply-ai-code-stream] No active provider found, creating new sandbox...`);
try {
const { SandboxFactory } = await import('@/lib/sandbox/factory');
provider = SandboxFactory.create();
const sandboxInfo = await provider.createSandbox();
await provider.setupViteApp();
// Register with sandbox manager
sandboxManager.registerSandbox(sandboxInfo.sandboxId, provider);
// Store in legacy global state
global.activeSandboxProvider = provider;
global.sandboxData = {
sandboxId: sandboxInfo.sandboxId,
url: sandboxInfo.url
};
console.log(`[apply-ai-code-stream] Created new sandbox successfully`);
} catch (createError) {
console.error(`[apply-ai-code-stream] Failed to create new sandbox:`, createError);
return NextResponse.json({
success: false,
error: `Failed to create new sandbox: ${createError instanceof Error ? createError.message : 'Unknown error'}`,
results: {
filesCreated: [],
packagesInstalled: [],
commandsExecuted: [],
errors: [`Sandbox creation failed: ${createError instanceof Error ? createError.message : 'Unknown error'}`]
},
explanation: parsed.explanation,
structure: parsed.structure,
parsedFiles: parsed.files,
message: `Parsed ${parsed.files.length} files but couldn't apply them - sandbox creation failed.`
}, { status: 500 });
}
}
// Create a response stream for real-time updates
const encoder = new TextEncoder();
const stream = new TransformStream();
const writer = stream.writable.getWriter();
// Function to send progress updates
const sendProgress = async (data: any) => {
const message = `data: ${JSON.stringify(data)}\n\n`;
await writer.write(encoder.encode(message));
};
// Start processing in background (pass provider and request to the async function)
(async (providerInstance, req) => {
const results = {
filesCreated: [] as string[],
filesUpdated: [] as string[],
packagesInstalled: [] as string[],
packagesAlreadyInstalled: [] as string[],
packagesFailed: [] as string[],
commandsExecuted: [] as string[],
errors: [] as string[]
};
try {
await sendProgress({
type: 'start',
message: 'Starting code application...',
totalSteps: 3
});
if (morphEnabled) {
await sendProgress({ type: 'info', message: 'Morph Fast Apply enabled' });
await sendProgress({ type: 'info', message: `Parsed ${morphEdits.length} Morph edits` });
if (morphEdits.length === 0) {
console.warn('[apply-ai-code-stream] Morph enabled but no <edit> blocks found; falling back to full-file flow');
await sendProgress({ type: 'warning', message: 'Morph enabled but no <edit> blocks found; falling back to full-file flow' });
}
}
// Step 1: Install packages
const packagesArray = Array.isArray(packages) ? packages : [];
const parsedPackages = Array.isArray(parsed.packages) ? parsed.packages : [];
// Combine and deduplicate packages
const allPackages = [...packagesArray.filter(pkg => pkg && typeof pkg === 'string'), ...parsedPackages];
// Use Set to remove duplicates, then filter out pre-installed packages
const uniquePackages = [...new Set(allPackages)]
.filter(pkg => pkg && typeof pkg === 'string' && pkg.trim() !== '') // Remove empty strings
.filter(pkg => pkg !== 'react' && pkg !== 'react-dom'); // Filter pre-installed
// Log if we found duplicates
if (allPackages.length !== uniquePackages.length) {
console.log(`[apply-ai-code-stream] Removed ${allPackages.length - uniquePackages.length} duplicate packages`);
console.log(`[apply-ai-code-stream] Original packages:`, allPackages);
console.log(`[apply-ai-code-stream] Deduplicated packages:`, uniquePackages);
}
if (uniquePackages.length > 0) {
await sendProgress({
type: 'step',
step: 1,
message: `Installing ${uniquePackages.length} packages...`,
packages: uniquePackages
});
// Use streaming package installation
try {
// Construct the API URL properly for both dev and production
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
const host = req.headers.get('host') || 'localhost:3000';
const apiUrl = `${protocol}://${host}/api/install-packages`;
const installResponse = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packages: uniquePackages,
sandboxId: sandboxId || providerInstance.getSandboxInfo()?.sandboxId
})
});
if (installResponse.ok && installResponse.body) {
const reader = installResponse.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
if (!chunk) continue;
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(6));
// Forward package installation progress
await sendProgress({
type: 'package-progress',
...data
});
// Track results
if (data.type === 'success' && data.installedPackages) {
results.packagesInstalled = data.installedPackages;
}
} catch (parseError) {
console.debug('Error parsing terminal output:', parseError);
}
}
}
}
}
} catch (error) {
console.error('[apply-ai-code-stream] Error installing packages:', error);
await sendProgress({
type: 'warning',
message: `Package installation skipped (${(error as Error).message}). Continuing with file creation...`
});
results.errors.push(`Package installation failed: ${(error as Error).message}`);
}
} else {
await sendProgress({
type: 'step',
step: 1,
message: 'No additional packages to install, skipping...'
});
}
// Step 2: Create/update files
const filesArray = Array.isArray(parsed.files) ? parsed.files : [];
await sendProgress({
type: 'step',
step: 2,
message: `Creating ${filesArray.length} files...`
});
// Filter out config files that shouldn't be created
const configFiles = ['tailwind.config.js', 'vite.config.js', 'package.json', 'package-lock.json', 'tsconfig.json', 'postcss.config.js'];
let filteredFiles = filesArray.filter(file => {
if (!file || typeof file !== 'object') return false;
const fileName = (file.path || '').split('/').pop() || '';
return !configFiles.includes(fileName);
});
// If Morph is enabled and we have edits, apply them before file writes
const morphUpdatedPaths = new Set<string>();
if (morphEnabled && morphEdits.length > 0) {
const morphSandbox = (global as any).activeSandbox || providerInstance;
if (!morphSandbox) {
console.warn('[apply-ai-code-stream] No sandbox available to apply Morph edits');
await sendProgress({ type: 'warning', message: 'No sandbox available to apply Morph edits' });
} else {
await sendProgress({ type: 'info', message: `Applying ${morphEdits.length} fast edits via Morph...` });
for (const [idx, edit] of morphEdits.entries()) {
try {
await sendProgress({ type: 'file-progress', current: idx + 1, total: morphEdits.length, fileName: edit.targetFile, action: 'morph-applying' });
const result = await applyMorphEditToFile({
sandbox: morphSandbox,
targetPath: edit.targetFile,
instructions: edit.instructions,
updateSnippet: edit.update
});
if (result.success && result.normalizedPath) {
console.log('[apply-ai-code-stream] Morph updated', result.normalizedPath);
morphUpdatedPaths.add(result.normalizedPath);
if (results.filesUpdated) results.filesUpdated.push(result.normalizedPath);
await sendProgress({ type: 'file-complete', fileName: result.normalizedPath, action: 'morph-updated' });
} else {
const msg = result.error || 'Unknown Morph error';
console.error('[apply-ai-code-stream] Morph apply failed for', edit.targetFile, msg);
if (results.errors) results.errors.push(`Morph apply failed for ${edit.targetFile}: ${msg}`);
await sendProgress({ type: 'file-error', fileName: edit.targetFile, error: msg });
}
} catch (err) {
const msg = (err as Error).message;
console.error('[apply-ai-code-stream] Morph apply exception for', edit.targetFile, msg);
if (results.errors) results.errors.push(`Morph apply exception for ${edit.targetFile}: ${msg}`);
await sendProgress({ type: 'file-error', fileName: edit.targetFile, error: msg });
}
}
}
}
// Avoid overwriting Morph-updated files in the file write loop
if (morphUpdatedPaths.size > 0) {
filteredFiles = filteredFiles.filter(file => {
if (!file?.path) return true;
let normalizedPath = file.path.startsWith('/') ? file.path.slice(1) : file.path;
const fileName = normalizedPath.split('/').pop() || '';
if (!normalizedPath.startsWith('src/') &&
!normalizedPath.startsWith('public/') &&
normalizedPath !== 'index.html' &&
!configFiles.includes(fileName)) {
normalizedPath = 'src/' + normalizedPath;
}
return !morphUpdatedPaths.has(normalizedPath);
});
}
for (const [index, file] of filteredFiles.entries()) {
try {
// Send progress for each file
await sendProgress({
type: 'file-progress',
current: index + 1,
total: filteredFiles.length,
fileName: file.path,
action: 'creating'
});
// Normalize the file path
let normalizedPath = file.path;
if (normalizedPath.startsWith('/')) {
normalizedPath = normalizedPath.substring(1);
}
if (!normalizedPath.startsWith('src/') &&
!normalizedPath.startsWith('public/') &&
normalizedPath !== 'index.html' &&
!configFiles.includes(normalizedPath.split('/').pop() || '')) {
normalizedPath = 'src/' + normalizedPath;
}
const isUpdate = global.existingFiles.has(normalizedPath);
// Remove any CSS imports from JSX/JS files (we're using Tailwind)
let fileContent = file.content;
if (file.path.endsWith('.jsx') || file.path.endsWith('.js') || file.path.endsWith('.tsx') || file.path.endsWith('.ts')) {
fileContent = fileContent.replace(/import\s+['"]\.\/[^'"]+\.css['"];?\s*\n?/g, '');
}
// Fix common Tailwind CSS errors in CSS files
if (file.path.endsWith('.css')) {
// Replace shadow-3xl with shadow-2xl (shadow-3xl doesn't exist)
fileContent = fileContent.replace(/shadow-3xl/g, 'shadow-2xl');
// Replace any other non-existent shadow utilities
fileContent = fileContent.replace(/shadow-4xl/g, 'shadow-2xl');
fileContent = fileContent.replace(/shadow-5xl/g, 'shadow-2xl');
}
// Create directory if needed
const dirPath = normalizedPath.includes('/') ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/')) : '';
if (dirPath) {
await providerInstance.runCommand(`mkdir -p ${dirPath}`);
}
// Write the file using provider
await providerInstance.writeFile(normalizedPath, fileContent);
// Update file cache
if (global.sandboxState?.fileCache) {
global.sandboxState.fileCache.files[normalizedPath] = {
content: fileContent,
lastModified: Date.now()
};
}
if (isUpdate) {
if (results.filesUpdated) results.filesUpdated.push(normalizedPath);
} else {
if (results.filesCreated) results.filesCreated.push(normalizedPath);
if (global.existingFiles) global.existingFiles.add(normalizedPath);
}
await sendProgress({
type: 'file-complete',
fileName: normalizedPath,
action: isUpdate ? 'updated' : 'created'
});
} catch (error) {
if (results.errors) {
results.errors.push(`Failed to create ${file.path}: ${(error as Error).message}`);
}
await sendProgress({
type: 'file-error',
fileName: file.path,
error: (error as Error).message
});
}
}
// Step 3: Execute commands
const commandsArray = Array.isArray(parsed.commands) ? parsed.commands : [];
if (commandsArray.length > 0) {
await sendProgress({
type: 'step',
step: 3,
message: `Executing ${commandsArray.length} commands...`
});
for (const [index, cmd] of commandsArray.entries()) {
try {
await sendProgress({
type: 'command-progress',
current: index + 1,
total: parsed.commands.length,
command: cmd,
action: 'executing'
});
// Use provider runCommand
const result = await providerInstance.runCommand(cmd);
// Get command output from provider result
const stdout = result.stdout;
const stderr = result.stderr;
if (stdout) {
await sendProgress({
type: 'command-output',
command: cmd,
output: stdout,
stream: 'stdout'
});
}
if (stderr) {
await sendProgress({
type: 'command-output',
command: cmd,
output: stderr,
stream: 'stderr'
});
}
if (results.commandsExecuted) {
results.commandsExecuted.push(cmd);
}
await sendProgress({
type: 'command-complete',
command: cmd,
exitCode: result.exitCode,
success: result.exitCode === 0
});
} catch (error) {
if (results.errors) {
results.errors.push(`Failed to execute ${cmd}: ${(error as Error).message}`);
}
await sendProgress({
type: 'command-error',
command: cmd,
error: (error as Error).message
});
}
}
}
// Send final results
await sendProgress({
type: 'complete',
results,
explanation: parsed.explanation,
structure: parsed.structure,
message: `Successfully applied ${results.filesCreated.length} files`
});
// Track applied files in conversation state
if (global.conversationState && results.filesCreated.length > 0) {
const messages = global.conversationState.context.messages;
if (messages.length > 0) {
const lastMessage = messages[messages.length - 1];
if (lastMessage.role === 'user') {
lastMessage.metadata = {
...lastMessage.metadata,
editedFiles: results.filesCreated
};
}
}
// Track applied code in project evolution
if (global.conversationState.context.projectEvolution) {
global.conversationState.context.projectEvolution.majorChanges.push({
timestamp: Date.now(),
description: parsed.explanation || 'Code applied',
filesAffected: results.filesCreated || []
});
}
global.conversationState.lastUpdated = Date.now();
}
} catch (error) {
await sendProgress({
type: 'error',
error: (error as Error).message
});
} finally {
await writer.close();
}
})(provider, request);
// Return the stream
return new Response(stream.readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
} catch (error) {
console.error('Apply AI code stream error:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to parse AI code' },
{ status: 500 }
);
}
}
================================================
FILE: app/api/check-vite-errors/route.ts
================================================
import { NextResponse } from 'next/server';
// Stub endpoint to prevent 404 errors
// This endpoint is being called but the source is unknown
// Returns empty errors array to satisfy any calling code
export async function GET() {
return NextResponse.json({
success: true,
errors: [],
message: 'No Vite errors detected'
});
}
================================================
FILE: app/api/clear-vite-errors-cache/route.ts
================================================
import { NextResponse } from 'next/server';
declare global {
var viteErrorsCache: { errors: any[], timestamp: number } | null;
}
export async function POST() {
try {
// Clear the cache
global.viteErrorsCache = null;
console.log('[clear-vite-errors-cache] Cache cleared');
return NextResponse.json({
success: true,
message: 'Vite errors cache cleared'
});
} catch (error) {
console.error('[clear-vite-errors-cache] Error:', error);
return NextResponse.json({
success: false,
error: (error as Error).message
}, { status: 500 });
}
}
================================================
FILE: app/api/conversation-state/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server';
import type { ConversationState } from '@/types/conversation';
declare global {
var conversationState: ConversationState | null;
}
// GET: Retrieve current conversation state
export async function GET() {
try {
if (!global.conversationState) {
return NextResponse.json({
success: true,
state: null,
message: 'No active conversation'
});
}
return NextResponse.json({
success: true,
state: global.conversationState
});
} catch (error) {
console.error('[conversation-state] Error getting state:', error);
return NextResponse.json({
success: false,
error: (error as Error).message
}, { status: 500 });
}
}
// POST: Reset or update conversation state
export async function POST(request: NextRequest) {
try {
const { action, data } = await request.json();
switch (action) {
case 'reset':
global.conversationState = {
conversationId: `conv-${Date.now()}`,
startedAt: Date.now(),
lastUpdated: Date.now(),
context: {
messages: [],
edits: [],
projectEvolution: { majorChanges: [] },
userPreferences: {}
}
};
console.log('[conversation-state] Reset conversation state');
return NextResponse.json({
success: true,
message: 'Conversation state reset',
state: global.conversationState
});
case 'clear-old':
// Clear old conversation data but keep recent context
if (!global.conversationState) {
// Initialize conversation state if it doesn't exist
global.conversationState = {
conversationId: `conv-${Date.now()}`,
startedAt: Date.now(),
lastUpdated: Date.now(),
context: {
messages: [],
edits: [],
projectEvolution: { majorChanges: [] },
userPreferences: {}
}
};
console.log('[conversation-state] Initialized new conversation state for clear-old');
return NextResponse.json({
success: true,
message: 'New conversation state initialized',
state: global.conversationState
});
}
// Keep only recent data
global.conversationState.context.messages = global.conversationState.context.messages.slice(-5);
global.conversationState.context.edits = global.conversationState.context.edits.slice(-3);
global.conversationState.context.projectEvolution.majorChanges =
global.conversationState.context.projectEvolution.majorChanges.slice(-2);
console.log('[conversation-state] Cleared old conversation data');
return NextResponse.json({
success: true,
message: 'Old conversation data cleared',
state: global.conversationState
});
case 'update':
if (!global.conversationState) {
return NextResponse.json({
success: false,
error: 'No active conversation to update'
}, { status: 400 });
}
// Update specific fields if provided
if (data) {
if (data.currentTopic) {
global.conversationState.context.currentTopic = data.currentTopic;
}
if (data.userPreferences) {
global.conversationState.context.userPreferences = {
...global.conversationState.context.userPreferences,
...data.userPreferences
};
}
global.conversationState.lastUpdated = Date.now();
}
return NextResponse.json({
success: true,
message: 'Conversation state updated',
state: global.conversationState
});
default:
return NextResponse.json({
success: false,
error: 'Invalid action. Use "reset" or "update"'
}, { status: 400 });
}
} catch (error) {
console.error('[conversation-state] Error:', error);
return NextResponse.json({
success: false,
error: (error as Error).message
}, { status: 500 });
}
}
// DELETE: Clear conversation state
export async function DELETE() {
try {
global.conversationState = null;
console.log('[conversation-state] Cleared conversation state');
return NextResponse.json({
success: true,
message: 'Conversation state cleared'
});
} catch (error) {
console.error('[conversation-state] Error clearing state:', error);
return NextResponse.json({
success: false,
error: (error as Error).message
}, { status: 500 });
}
}
================================================
FILE: app/api/create-ai-sandbox/route.ts
================================================
import { NextResponse } from 'next/server';
import { Sandbox } from '@vercel/sandbox';
import type { SandboxState } from '@/types/sandbox';
import { appConfig } from '@/config/app.config';
// Store active sandbox globally
declare global {
var activeSandbox: any;
var sandboxData: any;
var existingFiles: Set<string>;
var sandboxState: SandboxState;
var sandboxCreationInProgress: boolean;
var sandboxCreationPromise: Promise<any> | null;
}
export async function POST() {
// Check if sandbox creation is already in progress
if (global.sandboxCreationInProgress && global.sandboxCreationPromise) {
console.log('[create-ai-sandbox] Sandbox creation already in progress, waiting for existing creation...');
try {
const existingResult = await global.sandboxCreationPromise;
console.log('[create-ai-sandbox] Returning existing sandbox creation result');
return NextResponse.json(existingResult);
} catch (error) {
console.error('[create-ai-sandbox] Existing sandbox creation failed:', error);
// Continue with new creation if the existing one failed
}
}
// Check if we already have an active sandbox
if (global.activeSandbox && global.sandboxData) {
console.log('[create-ai-sandbox] Returning existing active sandbox');
return NextResponse.json({
success: true,
sandboxId: global.sandboxData.sandboxId,
url: global.sandboxData.url
});
}
// Set the creation flag
global.sandboxCreationInProgress = true;
// Create the promise that other requests can await
global.sandboxCreationPromise = createSandboxInternal();
try {
const result = await global.sandboxCreationPromise;
return NextResponse.json(result);
} catch (error) {
console.error('[create-ai-sandbox] Sandbox creation failed:', error);
return NextResponse.json(
{
error: error instanceof Error ? error.message : 'Failed to create sandbox',
details: error instanceof Error ? error.stack : undefined
},
{ status: 500 }
);
} finally {
global.sandboxCreationInProgress = false;
global.sandboxCreationPromise = null;
}
}
async function createSandboxInternal() {
let sandbox: any = null;
try {
console.log('[create-ai-sandbox] Creating Vercel sandbox...');
// Kill existing sandbox if any
if (global.activeSandbox) {
console.log('[create-ai-sandbox] Stopping existing sandbox...');
try {
await global.activeSandbox.stop();
} catch (e) {
console.error('Failed to stop existing sandbox:', e);
}
global.activeSandbox = null;
global.sandboxData = null;
}
// Clear existing files tracking
if (global.existingFiles) {
global.existingFiles.clear();
} else {
global.existingFiles = new Set<string>();
}
// Create Vercel sandbox with flexible authentication
console.log(`[create-ai-sandbox] Creating Vercel sandbox with ${appConfig.vercelSandbox.timeoutMinutes} minute timeout...`);
// Prepare sandbox configuration
const sandboxConfig: any = {
timeout: appConfig.vercelSandbox.timeoutMs,
runtime: appConfig.vercelSandbox.runtime,
ports: [appConfig.vercelSandbox.devPort]
};
// Add authentication parameters if using personal access token
if (process.env.VERCEL_TOKEN && process.env.VERCEL_TEAM_ID && process.env.VERCEL_PROJECT_ID) {
console.log('[create-ai-sandbox] Using personal access token authentication');
sandboxConfig.teamId = process.env.VERCEL_TEAM_ID;
sandboxConfig.projectId = process.env.VERCEL_PROJECT_ID;
sandboxConfig.token = process.env.VERCEL_TOKEN;
} else if (process.env.VERCEL_OIDC_TOKEN) {
console.log('[create-ai-sandbox] Using OIDC token authentication');
} else {
console.log('[create-ai-sandbox] No authentication found - relying on default Vercel authentication');
}
sandbox = await Sandbox.create(sandboxConfig);
const sandboxId = sandbox.sandboxId;
console.log(`[create-ai-sandbox] Sandbox created: ${sandboxId}`);
// Set up a basic Vite React app
console.log('[create-ai-sandbox] Setting up Vite React app...');
// First, change to the working directory
await sandbox.runCommand('pwd');
// workDir is defined in appConfig - not needed here
// Get the sandbox URL using the correct Vercel Sandbox API
const sandboxUrl = sandbox.domain(appConfig.vercelSandbox.devPort);
// Extract the hostname from the sandbox URL for Vite config
const sandboxHostname = new URL(sandboxUrl).hostname;
console.log(`[create-ai-sandbox] Sandbox hostname: ${sandboxHostname}`);
// Create the Vite config content with the proper hostname (using string concatenation)
const viteConfigContent = `import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// Vercel Sandbox compatible Vite configuration
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: ${appConfig.vercelSandbox.devPort},
strictPort: true,
hmr: true,
allowedHosts: [
'localhost',
'127.0.0.1',
'` + sandboxHostname + `', // Allow the Vercel Sandbox domain
'.vercel.run', // Allow all Vercel sandbox domains
'.vercel-sandbox.dev' // Fallback pattern
]
}
})`;
// Create the project files (now we have the sandbox hostname)
const projectFiles = [
{
path: 'package.json',
content: Buffer.from(JSON.stringify({
"name": "sandbox-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --host --port 3000",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.0.0",
"vite": "^4.3.9",
"tailwindcss": "^3.3.0",
"postcss": "^8.4.31",
"autoprefixer": "^10.4.16"
}
}, null, 2))
},
{
path: 'vite.config.js',
content: Buffer.from(viteConfigContent)
},
{
path: 'tailwind.config.js',
content: Buffer.from(`/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}`)
},
{
path: 'postcss.config.js',
content: Buffer.from(`export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}`)
},
{
path: 'index.html',
content: Buffer.from(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sandbox App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>`)
},
{
path: 'src/main.jsx',
content: Buffer.from(`import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)`)
},
{
path: 'src/App.jsx',
content: Buffer.from(`function App() {
return (
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center p-4">
<div className="text-center max-w-2xl">
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-blue-500 to-purple-600 bg-clip-text text-transparent">
Sandbox Ready
</h1>
<p className="text-lg text-gray-400">
Start building your React app with Vite and Tailwind CSS!
</p>
</div>
</div>
)
}
export default App`)
},
{
path: 'src/index.css',
content: Buffer.from(`@tailwind base;
@tailwind components;
@tailwind utilities;
/* Force Tailwind to load */
@layer base {
:root {
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background-color: rgb(17 24 39);
}`)
}
];
// Create directory structure first
await sandbox.runCommand({
cmd: 'mkdir',
args: ['-p', 'src']
});
// Write all files
await sandbox.writeFiles(projectFiles);
console.log('[create-ai-sandbox] ✓ Project files created');
// Install dependencies
console.log('[create-ai-sandbox] Installing dependencies...');
const installResult = await sandbox.runCommand({
cmd: 'npm',
args: ['install', '--loglevel', 'info']
});
if (installResult.exitCode === 0) {
console.log('[create-ai-sandbox] ✓ Dependencies installed successfully');
} else {
console.log('[create-ai-sandbox] ⚠ Warning: npm install had issues but continuing...');
}
// Start Vite dev server in detached mode
console.log('[create-ai-sandbox] Starting Vite dev server...');
const viteProcess = await sandbox.runCommand({
cmd: 'npm',
args: ['run', 'dev'],
detached: true
});
console.log('[create-ai-sandbox] ✓ Vite dev server started');
// Wait for Vite to be fully ready
await new Promise(resolve => setTimeout(resolve, appConfig.vercelSandbox.devServerStartupDelay));
// Store sandbox globally
global.activeSandbox = sandbox;
global.sandboxData = {
sandboxId,
url: sandboxUrl,
viteProcess
};
// Initialize sandbox state
global.sandboxState = {
fileCache: {
files: {},
lastSync: Date.now(),
sandboxId
},
sandbox,
sandboxData: {
sandboxId,
url: sandboxUrl
}
};
// Track initial files
global.existingFiles.add('src/App.jsx');
global.existingFiles.add('src/main.jsx');
global.existingFiles.add('src/index.css');
global.existingFiles.add('index.html');
global.existingFiles.add('package.json');
global.existingFiles.add('vite.config.js');
global.existingFiles.add('tailwind.config.js');
global.existingFiles.add('postcss.config.js');
console.log('[create-ai-sandbox] Sandbox ready at:', sandboxUrl);
const result = {
success: true,
sandboxId,
url: sandboxUrl,
message: 'Vercel sandbox created and Vite React app initialized'
};
// Store the result for reuse
global.sandboxData = {
...global.sandboxData,
...result
};
return result;
} catch (error) {
console.error('[create-ai-sandbox] Error:', error);
// Clean up on error
if (sandbox) {
try {
await sandbox.stop();
} catch (e) {
console.error('Failed to stop sandbox on error:', e);
}
}
// Clear global state on error
global.activeSandbox = null;
global.sandboxData = null;
throw error; // Throw to be caught by the outer handler
}
}
================================================
FILE: app/api/create-ai-sandbox-v2/route.ts
================================================
import { NextResponse } from 'next/server';
import { SandboxFactory } from '@/lib/sandbox/factory';
// SandboxProvider type is used through SandboxFactory
import type { SandboxState } from '@/types/sandbox';
import { sandboxManager } from '@/lib/sandbox/sandbox-manager';
// Store active sandbox globally
declare global {
var activeSandboxProvider: any;
var sandboxData: any;
var existingFiles: Set<string>;
var sandboxState: SandboxState;
}
export async function POST() {
try {
console.log('[create-ai-sandbox-v2] Creating sandbox...');
// Clean up all existing sandboxes
console.log('[create-ai-sandbox-v2] Cleaning up existing sandboxes...');
await sandboxManager.terminateAll();
// Also clean up legacy global state
if (global.activeSandboxProvider) {
try {
await global.activeSandboxProvider.terminate();
} catch (e) {
console.error('Failed to terminate legacy global sandbox:', e);
}
global.activeSandboxProvider = null;
}
// Clear existing files tracking
if (global.existingFiles) {
global.existingFiles.clear();
} else {
global.existingFiles = new Set<string>();
}
// Create new sandbox using factory
const provider = SandboxFactory.create();
const sandboxInfo = await provider.createSandbox();
console.log('[create-ai-sandbox-v2] Setting up Vite React app...');
await provider.setupViteApp();
// Register with sandbox manager
sandboxManager.registerSandbox(sandboxInfo.sandboxId, provider);
// Also store in legacy global state for backward compatibility
global.activeSandboxProvider = provider;
global.sandboxData = {
sandboxId: sandboxInfo.sandboxId,
url: sandboxInfo.url
};
// Initialize sandbox state
global.sandboxState = {
fileCache: {
files: {},
lastSync: Date.now(),
sandboxId: sandboxInfo.sandboxId
},
sandbox: provider, // Store the provider instead of raw sandbox
sandboxData: {
sandboxId: sandboxInfo.sandboxId,
url: sandboxInfo.url
}
};
console.log('[create-ai-sandbox-v2] Sandbox ready at:', sandboxInfo.url);
return NextResponse.json({
success: true,
sandboxId: sandboxInfo.sandboxId,
url: sandboxInfo.url,
provider: sandboxInfo.provider,
message: 'Sandbox created and Vite React app initialized'
});
} catch (error) {
console.error('[create-ai-sandbox-v2] Error:', error);
// Clean up on error
await sandboxManager.terminateAll();
if (global.activeSandboxProvider) {
try {
await global.activeSandboxProvider.terminate();
} catch (e) {
console.error('Failed to terminate sandbox on error:', e);
}
global.activeSandboxProvider = null;
}
return NextResponse.json(
{
error: error instanceof Error ? error.message : 'Failed to create sandbox',
details: error instanceof Error ? error.stack : undefined
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/create-zip/route.ts
================================================
import { NextResponse } from 'next/server';
declare global {
var activeSandbox: any;
}
export async function POST() {
try {
if (!global.activeSandbox) {
return NextResponse.json({
success: false,
error: 'No active sandbox'
}, { status: 400 });
}
console.log('[create-zip] Creating project zip...');
// Create zip file in sandbox using standard commands
const zipResult = await global.activeSandbox.runCommand({
cmd: 'bash',
args: ['-c', `zip -r /tmp/project.zip . -x "node_modules/*" ".git/*" ".next/*" "dist/*" "build/*" "*.log"`]
});
if (zipResult.exitCode !== 0) {
const error = await zipResult.stderr();
throw new Error(`Failed to create zip: ${error}`);
}
const sizeResult = await global.activeSandbox.runCommand({
cmd: 'bash',
args: ['-c', `ls -la /tmp/project.zip | awk '{print $5}'`]
});
const fileSize = await sizeResult.stdout();
console.log(`[create-zip] Created project.zip (${fileSize.trim()} bytes)`);
// Read the zip file and convert to base64
const readResult = await global.activeSandbox.runCommand({
cmd: 'base64',
args: ['/tmp/project.zip']
});
if (readResult.exitCode !== 0) {
const error = await readResult.stderr();
throw new Error(`Failed to read zip file: ${error}`);
}
const base64Content = (await readResult.stdout()).trim();
// Create a data URL for download
const dataUrl = `data:application/zip;base64,${base64Content}`;
return NextResponse.json({
success: true,
dataUrl,
fileName: 'vercel-sandbox-project.zip',
message: 'Zip file created successfully'
});
} catch (error) {
console.error('[create-zip] Error:', error);
return NextResponse.json(
{
success: false,
error: (error as Error).message
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/detect-and-install-packages/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server';
declare global {
var activeSandbox: any;
}
export async function POST(request: NextRequest) {
try {
const { files } = await request.json();
if (!files || typeof files !== 'object') {
return NextResponse.json({
success: false,
error: 'Files object is required'
}, { status: 400 });
}
if (!global.activeSandbox) {
return NextResponse.json({
success: false,
error: 'No active sandbox'
}, { status: 404 });
}
console.log('[detect-and-install-packages] Processing files:', Object.keys(files));
// Extract all import statements from the files
const imports = new Set<string>();
const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s*,?\s*)*(?:from\s+)?['"]([^'"]+)['"]/g;
const requireRegex = /require\s*\(['"]([^'"]+)['"]\)/g;
for (const [filePath, content] of Object.entries(files)) {
if (typeof content !== 'string') continue;
// Skip non-JS/JSX/TS/TSX files
if (!filePath.match(/\.(jsx?|tsx?)$/)) continue;
// Find ES6 imports
let match;
while ((match = importRegex.exec(content)) !== null) {
imports.add(match[1]);
}
// Find CommonJS requires
while ((match = requireRegex.exec(content)) !== null) {
imports.add(match[1]);
}
}
console.log('[detect-and-install-packages] Found imports:', Array.from(imports));
// Log specific heroicons imports
const heroiconImports = Array.from(imports).filter(imp => imp.includes('heroicons'));
if (heroiconImports.length > 0) {
console.log('[detect-and-install-packages] Heroicon imports:', heroiconImports);
}
// Filter out relative imports and built-in modules
const packages = Array.from(imports).filter(imp => {
// Skip relative imports
if (imp.startsWith('.') || imp.startsWith('/')) return false;
// Skip built-in Node modules
const builtins = ['fs', 'path', 'http', 'https', 'crypto', 'stream', 'util', 'os', 'url', 'querystring', 'child_process'];
if (builtins.includes(imp)) return false;
return true;
});
// Extract just the package names (without subpaths)
const packageNames = packages.map(pkg => {
if (pkg.startsWith('@')) {
// Scoped package: @scope/package or @scope/package/subpath
const parts = pkg.split('/');
return parts.slice(0, 2).join('/');
} else {
// Regular package: package or package/subpath
return pkg.split('/')[0];
}
});
// Remove duplicates
const uniquePackages = [...new Set(packageNames)];
console.log('[detect-and-install-packages] Packages to install:', uniquePackages);
if (uniquePackages.length === 0) {
return NextResponse.json({
success: true,
packagesInstalled: [],
message: 'No new packages to install'
});
}
// Check which packages are already installed
const installed: string[] = [];
const missing: string[] = [];
for (const packageName of uniquePackages) {
try {
const checkResult = await global.activeSandbox.runCommand({
cmd: 'test',
args: ['-d', `node_modules/${packageName}`]
});
if (checkResult.exitCode === 0) {
installed.push(packageName);
} else {
missing.push(packageName);
}
} catch (checkError) {
// If test command fails, assume package is missing
console.debug(`Package check failed for ${packageName}:`, checkError);
missing.push(packageName);
}
}
console.log('[detect-and-install-packages] Package status:', { installed, missing });
if (missing.length === 0) {
return NextResponse.json({
success: true,
packagesInstalled: [],
packagesAlreadyInstalled: installed,
message: 'All packages already installed'
});
}
// Install missing packages
console.log('[detect-and-install-packages] Installing packages:', missing);
const installResult = await global.activeSandbox.runCommand({
cmd: 'npm',
args: ['install', '--save', ...missing]
});
const stdout = await installResult.stdout();
const stderr = await installResult.stderr();
console.log('[detect-and-install-packages] Install stdout:', stdout);
if (stderr) {
console.log('[detect-and-install-packages] Install stderr:', stderr);
}
// Verify installation
const finalInstalled: string[] = [];
const failed: string[] = [];
for (const packageName of missing) {
try {
const verifyResult = await global.activeSandbox.runCommand({
cmd: 'test',
args: ['-d', `node_modules/${packageName}`]
});
if (verifyResult.exitCode === 0) {
finalInstalled.push(packageName);
console.log(`✓ Verified installation of ${packageName}`);
} else {
failed.push(packageName);
console.log(`✗ Failed to verify installation of ${packageName}`);
}
} catch (error) {
failed.push(packageName);
console.log(`✗ Error verifying ${packageName}:`, error);
}
}
if (failed.length > 0) {
console.error('[detect-and-install-packages] Failed to install:', failed);
}
return NextResponse.json({
success: true,
packagesInstalled: finalInstalled,
packagesFailed: failed,
packagesAlreadyInstalled: installed,
message: `Installed ${finalInstalled.length} packages`,
logs: stdout
});
} catch (error) {
console.error('[detect-and-install-packages] Error:', error);
return NextResponse.json({
success: false,
error: (error as Error).message
}, { status: 500 });
}
}
================================================
FILE: app/api/extract-brand-styles/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const url = body.url;
const prompt = body.prompt;
console.log('[extract-brand-styles] Extracting brand styles for:', url);
console.log('[extract-brand-styles] User prompt:', prompt);
// Call Firecrawl API to extract branding information
const FIRECRAWL_API_KEY = process.env.FIRECRAWL_API_KEY;
if (!FIRECRAWL_API_KEY) {
console.error('[extract-brand-styles] No Firecrawl API key found');
throw new Error('Firecrawl API key not configured');
}
console.log('[extract-brand-styles] Calling Firecrawl branding API for:', url);
const firecrawlResponse = await fetch('https://api.firecrawl.dev/v2/scrape', {
method: 'POST',
headers: {
'Authorization': `Bearer ${FIRECRAWL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: url,
formats: ['branding'],
}),
});
if (!firecrawlResponse.ok) {
const errorText = await firecrawlResponse.text();
console.error('[extract-brand-styles] Firecrawl API error:', firecrawlResponse.status, errorText);
throw new Error(`Firecrawl API returned ${firecrawlResponse.status}`);
}
const firecrawlData = await firecrawlResponse.json();
console.log('[extract-brand-styles] Firecrawl response received successfully');
// Extract branding data from response
const brandingData = firecrawlData.data?.branding || firecrawlData.branding;
if (!brandingData) {
console.error('[extract-brand-styles] No branding data in Firecrawl response');
console.log('[extract-brand-styles] Response structure:', JSON.stringify(firecrawlData, null, 2));
throw new Error('No branding data in Firecrawl response');
}
console.log('[extract-brand-styles] Successfully extracted branding data');
// Return the branding data
return NextResponse.json({
success: true,
url,
styleName: brandingData.name || url,
guidelines: brandingData,
});
} catch (error) {
console.error('[extract-brand-styles] Error occurred:', error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to extract brand styles'
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/generate-ai-code-stream/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server';
import { createGroq } from '@ai-sdk/groq';
import { createAnthropic } from '@ai-sdk/anthropic';
import { createOpenAI } from '@ai-sdk/openai';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { streamText } from 'ai';
import type { SandboxState } from '@/types/sandbox';
import { selectFilesForEdit, getFileContents, formatFilesForAI } from '@/lib/context-selector';
import { executeSearchPlan, formatSearchResultsForAI, selectTargetFile } from '@/lib/file-search-executor';
import { FileManifest } from '@/types/file-manifest';
import type { ConversationState, ConversationMessage, ConversationEdit } from '@/types/conversation';
import { appConfig } from '@/config/app.config';
// Force dynamic route to enable streaming
export const dynamic = 'force-dynamic';
// Check if we're using Vercel AI Gateway
const isUsingAIGateway = !!process.env.AI_GATEWAY_API_KEY;
const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1';
console.log('[generate-ai-code-stream] AI Gateway config:', {
isUsingAIGateway,
hasGroqKey: !!process.env.GROQ_API_KEY,
hasAIGatewayKey: !!process.env.AI_GATEWAY_API_KEY
});
const groq = createGroq({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GROQ_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
});
const anthropic = createAnthropic({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.ANTHROPIC_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : (process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1'),
});
const googleGenerativeAI = createGoogleGenerativeAI({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GEMINI_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
});
const openai = createOpenAI({
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.OPENAI_API_KEY,
baseURL: isUsingAIGateway ? aiGatewayBaseURL : process.env.OPENAI_BASE_URL,
});
// Helper function to analyze user preferences from conversation history
function analyzeUserPreferences(messages: ConversationMessage[]): {
commonPatterns: string[];
preferredEditStyle: 'targeted' | 'comprehensive';
} {
const userMessages = messages.filter(m => m.role === 'user');
const patterns: string[] = [];
// Count edit-related keywords
let targetedEditCount = 0;
let comprehensiveEditCount = 0;
userMessages.forEach(msg => {
const content = msg.content.toLowerCase();
// Check for targeted edit patterns
if (content.match(/\b(update|change|fix|modify|edit|remove|delete)\s+(\w+\s+)?(\w+)\b/)) {
targetedEditCount++;
}
// Check for comprehensive edit patterns
if (content.match(/\b(rebuild|recreate|redesign|overhaul|refactor)\b/)) {
comprehensiveEditCount++;
}
// Extract common request patterns
if (content.includes('hero')) patterns.push('hero section edits');
if (content.includes('header')) patterns.push('header modifications');
if (content.includes('color') || content.includes('style')) patterns.push('styling changes');
if (content.includes('button')) patterns.push('button updates');
if (content.includes('animation')) patterns.push('animation requests');
});
return {
commonPatterns: [...new Set(patterns)].slice(0, 3), // Top 3 unique patterns
preferredEditStyle: targetedEditCount > comprehensiveEditCount ? 'targeted' : 'comprehensive'
};
}
declare global {
var sandboxState: SandboxState;
var conversationState: ConversationState | null;
}
export async function POST(request: NextRequest) {
try {
const { prompt, model = 'openai/gpt-oss-20b', context, isEdit = false } = await request.json();
console.log('[generate-ai-code-stream] Received request:');
console.log('[generate-ai-code-stream] - prompt:', prompt);
console.log('[generate-ai-code-stream] - isEdit:', isEdit);
console.log('[generate-ai-code-stream] - context.sandboxId:', context?.sandboxId);
console.log('[generate-ai-code-stream] - context.currentFiles:', context?.currentFiles ? Object.keys(context.currentFiles) : 'none');
console.log('[generate-ai-code-stream] - currentFiles count:', context?.currentFiles ? Object.keys(context.currentFiles).length : 0);
// Initialize conversation state if not exists
if (!global.conversationState) {
global.conversationState = {
conversationId: `conv-${Date.now()}`,
startedAt: Date.now(),
lastUpdated: Date.now(),
context: {
messages: [],
edits: [],
projectEvolution: { majorChanges: [] },
userPreferences: {}
}
};
}
// Add user message to conversation history
const userMessage: ConversationMessage = {
id: `msg-${Date.now()}`,
role: 'user',
content: prompt,
timestamp: Date.now(),
metadata: {
sandboxId: context?.sandboxId
}
};
global.conversationState.context.messages.push(userMessage);
// Clean up old messages to prevent unbounded growth
if (global.conversationState.context.messages.length > 20) {
// Keep only the last 15 messages
global.conversationState.context.messages = global.conversationState.context.messages.slice(-15);
console.log('[generate-ai-code-stream] Trimmed conversation history to prevent context overflow');
}
// Clean up old edits
if (global.conversationState.context.edits.length > 10) {
global.conversationState.context.edits = global.conversationState.context.edits.slice(-8);
}
// Debug: Show a sample of actual file content
if (context?.currentFiles && Object.keys(context.currentFiles).length > 0) {
const firstFile = Object.entries(context.currentFiles)[0];
console.log('[generate-ai-code-stream] - sample file:', firstFile[0]);
console.log('[generate-ai-code-stream] - sample content preview:',
typeof firstFile[1] === 'string' ? firstFile[1].substring(0, 100) + '...' : 'not a string');
}
if (!prompt) {
return NextResponse.json({
success: false,
error: 'Prompt is required'
}, { status: 400 });
}
// Create a stream for real-time updates
const encoder = new TextEncoder();
const stream = new TransformStream();
const writer = stream.writable.getWriter();
// Function to send progress updates with flushing
const sendProgress = async (data: any) => {
const message = `data: ${JSON.stringify(data)}\n\n`;
try {
await writer.write(encoder.encode(message));
// Force flush by writing a keep-alive comment
if (data.type === 'stream' || data.type === 'conversation') {
await writer.write(encoder.encode(': keepalive\n\n'));
}
} catch (error) {
console.error('[generate-ai-code-stream] Error writing to stream:', error);
}
};
// Start processing in background
(async () => {
try {
// Send initial status
await sendProgress({ type: 'status', message: 'Initializing AI...' });
// No keep-alive needed - sandbox provisioned for 10 minutes
// Check if we have a file manifest for edit mode
let editContext = null;
let enhancedSystemPrompt = '';
if (isEdit) {
console.log('[generate-ai-code-stream] Edit mode detected - starting agentic search workflow');
console.log('[generate-ai-code-stream] Has fileCache:', !!global.sandboxState?.fileCache);
console.log('[generate-ai-code-stream] Has manifest:', !!global.sandboxState?.fileCache?.manifest);
const manifest: FileManifest | undefined = global.sandboxState?.fileCache?.manifest;
if (manifest) {
await sendProgress({ type: 'status', message: '🔍 Creating search plan...' });
const fileContents = global.sandboxState.fileCache?.files || {};
console.log('[generate-ai-code-stream] Files available for search:', Object.keys(fileContents).length);
// STEP 1: Get search plan from AI
try {
const intentResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/analyze-edit-intent`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, manifest, model })
});
if (intentResponse.ok) {
const { searchPlan } = await intentResponse.json();
console.log('[generate-ai-code-stream] Search plan received:', searchPlan);
await sendProgress({
type: 'status',
message: `🔎 Searching for: "${searchPlan.searchTerms.join('", "')}"`
});
// STEP 2: Execute the search plan
const searchExecution = executeSearchPlan(searchPlan,
Object.fromEntries(
Object.entries(fileContents).map(([path, data]) => [
path.startsWith('/') ? path : `/home/user/app/${path}`,
data.content
])
)
);
console.log('[generate-ai-code-stream] Search execution:', {
success: searchExecution.success,
resultsCount: searchExecution.results.length,
filesSearched: searchExecution.filesSearched,
time: searchExecution.executionTime + 'ms'
});
if (searchExecution.success && searchExecution.results.length > 0) {
// STEP 3: Select the best target file
const target = selectTargetFile(searchExecution.results, searchPlan.editType);
if (target) {
await sendProgress({
type: 'status',
message: `✅ Found code in ${target.filePath.split('/').pop()} at line ${target.lineNumber}`
});
console.log('[generate-ai-code-stream] Target selected:', target);
// Create surgical edit context with exact location
// normalizedPath would be: target.filePath.replace('/home/user/app/', '');
// fileContent available but not used in current implementation
// const fileContent = fileContents[normalizedPath]?.content || '';
// Build enhanced context with search results
enhancedSystemPrompt = `
${formatSearchResultsForAI(searchExecution.results)}
SURGICAL EDIT INSTRUCTIONS:
You have been given the EXACT location of the code to edit.
- File: ${target.filePath}
- Line: ${target.lineNumber}
- Reason: ${target.reason}
Make ONLY the change requested by the user. Do not modify any other code.
User request: "${prompt}"`;
// Set up edit context with just this one file
editContext = {
primaryFiles: [target.filePath],
contextFiles: [],
systemPrompt: enhancedSystemPrompt,
editIntent: {
type: searchPlan.editType,
description: searchPlan.reasoning,
targetFiles: [target.filePath],
confidence: 0.95, // High confidence since we found exact location
searchTerms: searchPlan.searchTerms
}
};
console.log('[generate-ai-code-stream] Surgical edit context created');
}
} else {
// Search failed - fall back to old behavior but inform user
console.warn('[generate-ai-code-stream] Search found no results, falling back to broader context');
await sendProgress({
type: 'status',
message: '⚠️ Could not find exact match, using broader search...'
});
}
} else {
console.error('[generate-ai-code-stream] Failed to get search plan');
}
} catch (error) {
console.error('[generate-ai-code-stream] Error in agentic search workflow:', error);
await sendProgress({
type: 'status',
message: '⚠️ Search workflow error, falling back to keyword method...'
});
// Fall back to old method on any error if we have a manifest
if (manifest) {
editContext = selectFilesForEdit(prompt, manifest);
}
}
} else {
// Fall back to old method if AI analysis fails
console.warn('[generate-ai-code-stream] AI intent analysis failed, falling back to keyword method');
if (manifest) {
editContext = selectFilesForEdit(prompt, manifest);
} else {
console.log('[generate-ai-code-stream] No manifest available for fallback');
await sendProgress({
type: 'status',
message: '⚠️ No file manifest available, will use broad context'
});
}
}
// If we got an edit context from any method, use its system prompt
if (editContext) {
enhancedSystemPrompt = editContext.systemPrompt;
await sendProgress({
type: 'status',
message: `Identified edit type: ${editContext.editIntent?.description || 'Code modification'}`
});
} else if (!manifest) {
console.log('[generate-ai-code-stream] WARNING: No manifest available for edit mode!');
// Try to fetch files from sandbox if we have one
if (global.activeSandbox) {
await sendProgress({ type: 'status', message: 'Fetching current files from sandbox...' });
try {
// Fetch files directly from sandbox
const filesResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/get-sandbox-files`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (filesResponse.ok) {
const filesData = await filesResponse.json();
if (filesData.success && filesData.manifest) {
console.log('[generate-ai-code-stream] Successfully fetched manifest from sandbox');
const manifest = filesData.manifest;
// Now try to analyze edit intent with the fetched manifest
try {
const intentResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/analyze-edit-intent`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, manifest, model })
});
if (intentResponse.ok) {
const { searchPlan } = await intentResponse.json();
console.log('[generate-ai-code-stream] Search plan received (after fetch):', searchPlan);
// For now, fall back to keyword search since we don't have file contents for search execution
// This path happens when no manifest was initially available
let targetFiles: any[] = [];
if (!searchPlan || searchPlan.searchTerms.length === 0) {
console.warn('[generate-ai-code-stream] No target files after fetch, searching for relevant files');
const promptLower = prompt.toLowerCase();
const allFilePaths = Object.keys(manifest.files);
// Look for component names mentioned in the prompt
if (promptLower.includes('hero')) {
targetFiles = allFilePaths.filter(p => p.toLowerCase().includes('hero'));
} else if (promptLower.includes('header')) {
targetFiles = allFilePaths.filter(p => p.toLowerCase().includes('header'));
} else if (promptLower.includes('footer')) {
targetFiles = allFilePaths.filter(p => p.toLowerCase().includes('footer'));
} else if (promptLower.includes('nav')) {
targetFiles = allFilePaths.filter(p => p.toLowerCase().includes('nav'));
} else if (promptLower.includes('button')) {
targetFiles = allFilePaths.filter(p => p.toLowerCase().includes('button'));
}
if (targetFiles.length > 0) {
console.log('[generate-ai-code-stream] Found target files by keyword search after fetch:', targetFiles);
}
}
const allFiles = Object.keys(manifest.files)
.filter(path => !targetFiles.includes(path));
editContext = {
primaryFiles: targetFiles,
contextFiles: allFiles,
systemPrompt: `
You are an expert senior software engineer performing a surgical, context-aware code modification. Your primary directive is **precision and preservation**.
Think of yourself as a surgeon making a precise incision, not a construction worker demolishing a wall.
## Search-Based Edit
Search Terms: ${searchPlan?.searchTerms?.join(', ') || 'keyword-based'}
Edit Type: ${searchPlan?.editType || 'UPDATE_COMPONENT'}
Reasoning: ${searchPlan?.reasoning || 'Modifying based on user request'}
Files to Edit: ${targetFiles.join(', ') || 'To be determined'}
User Request: "${prompt}"
## Your Mandatory Thought Process (Execute Internally):
Before writing ANY code, you MUST follow these steps:
1. **Understand Intent:**
- What is the user's core goal? (adding feature, fixing bug, changing style?)
- Does the conversation history provide extra clues?
2. **Locate the Code:**
- First examine the Primary Files provided
- Check the "ALL PROJECT FILES" list to find the EXACT file name
- "nav" might be Navigation.tsx, NavBar.tsx, Nav.tsx, or Header.tsx
- DO NOT create a new file if a similar one exists!
3. **Plan the Changes (Mental Diff):**
- What is the *minimal* set of changes required?
- Which exact lines need to be added, modified, or deleted?
- Will this require new packages?
4. **Verify Preservation:**
- What existing code, props, state, and logic must NOT be touched?
- How can I make my change without disrupting surrounding code?
5. **Construct the Final Code:**
- Only after completing steps above, generate the final code
- Provide the ENTIRE file content with modifications integrated
## Critical Rules & Constraints:
**PRESERVATION IS KEY:** You MUST NOT rewrite entire components or files. Integrate your changes into the existing code. Preserve all existing logic, props, state, and comments not directly related to the user's request.
**MINIMALISM:** Only output files you have actually changed. If a file doesn't need modification, don't include it.
**COMPLETENESS:** Each file must be COMPLETE from first line to last:
- NEVER TRUNCATE - Include EVERY line
- NO ellipsis (...) to skip content
- ALL imports, functions, JSX, and closing tags must be present
- The file MUST be runnable
**SURGICAL PRECISION:**
- Change ONLY what's explicitly requested
- If user says "change background to green", change ONLY the background class
- 99% of the original code should remain untouched
- NO refactoring, reformatting, or "improvements" unless requested
**NO CONVERSATION:** Your output must contain ONLY the code. No explanations or apologies.
## EXAMPLES:
### CORRECT APPROACH for "change hero background to blue":
<thinking>
I need to change the background color of the Hero component. Looking at the file, I see the main div has 'bg-gray-900'. I will change ONLY this to 'bg-blue-500' and leave everything else exactly as is.
</thinking>
Then return the EXACT same file with only 'bg-gray-900' changed to 'bg-blue-500'.
### WRONG APPROACH (DO NOT DO THIS):
- Rewriting the Hero component from scratch
- Changing the structure or reorganizing imports
- Adding or removing unrelated code
- Reformatting or "cleaning up" the code
Remember: You are a SURGEON making a precise incision, not an artist repainting the canvas!`,
editIntent: {
type: searchPlan?.editType || 'UPDATE_COMPONENT',
targetFiles: targetFiles,
confidence: searchPlan ? 0.85 : 0.6,
description: searchPlan?.reasoning || 'Keyword-based file selection',
suggestedContext: []
}
};
enhancedSystemPrompt = editContext.systemPrompt;
await sendProgress({
type: 'status',
message: `Identified edit type: ${editContext.editIntent.description}`
});
}
} catch (error) {
console.error('[generate-ai-code-stream] Error analyzing intent after fetch:', error);
}
} else {
console.error('[generate-ai-code-stream] Failed to get manifest from sandbox files');
}
} else {
console.error('[generate-ai-code-stream] Failed to fetch sandbox files:', filesResponse.status);
}
} catch (error) {
console.error('[generate-ai-code-stream] Error fetching sandbox files:', error);
await sendProgress({
type: 'warning',
message: 'Could not analyze existing files for targeted edits. Proceeding with general edit mode.'
});
}
} else {
console.log('[generate-ai-code-stream] No active sandbox to fetch files from');
await sendProgress({
type: 'warning',
message: 'No existing files found. Consider generating initial code first.'
});
}
}
}
// Build conversation context for system prompt
let conversationContext = '';
if (global.conversationState && global.conversationState.context.messages.length > 1) {
console.log('[generate-ai-code-stream] Building conversation context');
console.log('[generate-ai-code-stream] Total messages:', global.conversationState.context.messages.length);
console.log('[generate-ai-code-stream] Total edits:', global.conversationState.context.edits.length);
conversationContext = `\n\n## Conversation History (Recent)\n`;
// Include only the last 3 edits to save context
const recentEdits = global.conversationState.context.edits.slice(-3);
if (recentEdits.length > 0) {
console.log('[generate-ai-code-stream] Including', recentEdits.length, 'recent edits in context');
conversationContext += `\n### Recent Edits:\n`;
recentEdits.forEach(edit => {
conversationContext += `- "${edit.userRequest}" → ${edit.editType} (${edit.targetFiles.map(f => f.split('/').pop()).join(', ')})\n`;
});
}
// Include recently created files - CRITICAL for preventing duplicates
const recentMsgs = global.conversationState.context.messages.slice(-5);
const recentlyCreatedFiles: string[] = [];
recentMsgs.forEach(msg => {
if (msg.metadata?.editedFiles) {
recentlyCreatedFiles.push(...msg.metadata.editedFiles);
}
});
if (recentlyCreatedFiles.length > 0) {
const uniqueFiles = [...new Set(recentlyCreatedFiles)];
conversationContext += `\n### 🚨 RECENTLY CREATED/EDITED FILES (DO NOT RECREATE THESE):\n`;
uniqueFiles.forEach(file => {
conversationContext += `- ${file}\n`;
});
conversationContext += `\nIf the user mentions any of these components, UPDATE the existing file!\n`;
}
// Include only last 5 messages for context (reduced from 10)
const recentMessages = recentMsgs;
if (recentMessages.length > 2) { // More than just current message
conversationContext += `\n### Recent Messages:\n`;
recentMessages.slice(0, -1).forEach(msg => { // Exclude current message
if (msg.role === 'user') {
const truncatedContent = msg.content.length > 100 ? msg.content.substring(0, 100) + '...' : msg.content;
conversationContext += `- "${truncatedContent}"\n`;
}
});
}
// Include only last 2 major changes
const majorChanges = global.conversationState.context.projectEvolution.majorChanges.slice(-2);
if (majorChanges.length > 0) {
conversationContext += `\n### Recent Changes:\n`;
majorChanges.forEach(change => {
conversationContext += `- ${change.description}\n`;
});
}
// Keep user preferences - they're concise
const userPrefs = analyzeUserPreferences(global.conversationState.context.messages);
if (userPrefs.commonPatterns.length > 0) {
conversationContext += `\n### User Preferences:\n`;
conversationContext += `- Edit style: ${userPrefs.preferredEditStyle}\n`;
}
// Limit total conversation context length
if (conversationContext.length > 2000) {
conversationContext = conversationContext.substring(0, 2000) + '\n[Context truncated to prevent length errors]';
}
}
// Build system prompt with conversation awareness
let systemPrompt = `You are an expert React developer with perfect memory of the conversation. You maintain context across messages and remember scraped websites, generated components, and applied code. Generate clean, modern React code for Vite applications.
${conversationContext}
🚨 CRITICAL RULES - YOUR MOST IMPORTANT INSTRUCTIONS:
1. **DO EXACTLY WHAT IS ASKED - NOTHING MORE, NOTHING LESS**
- Don't add features not requested
- Don't fix unrelated issues
- Don't improve things not mentioned
2. **CHECK App.jsx FIRST** - ALWAYS see what components exist before creating new ones
3. **NAVIGATION LIVES IN Header.jsx** - Don't create Nav.jsx if Header exists with nav
4. **USE STANDARD TAILWIND CLASSES ONLY**:
- ✅ CORRECT: bg-white, text-black, bg-blue-500, bg-gray-100, text-gray-900
- ❌ WRONG: bg-background, text-foreground, bg-primary, bg-muted, text-secondary
- Use ONLY classes from the official Tailwind CSS documentation
5. **FILE COUNT LIMITS**:
- Simple style/text change = 1 file ONLY
- New component = 2 files MAX (component + parent)
- If >3 files, YOU'RE DOING TOO MUCH
6. **DO NOT CREATE SVGs FROM SCRATCH**:
- NEVER generate custom SVG code unless explicitly asked
- Use existing icon libraries (lucide-react, heroicons, etc.)
- Or use placeholder elements/text if icons are not critical
- Only create custom SVGs when user specifically requests "create an SVG" or "draw an SVG"
COMPONENT RELATIONSHIPS (CHECK THESE FIRST):
- Navigation usually lives INSIDE Header.jsx, not separate Nav.jsx
- Logo is typically in Header, not standalone
- Footer often contains nav links already
- Menu/Hamburger is part of Header, not separate
PACKAGE USAGE RULES:
- DO NOT use react-router-dom unless user explicitly asks for routing
- For simple nav links in a single-page app, use scroll-to-section or href="#"
- Only add routing if building a multi-page application
- Common packages are auto-installed from your imports
WEBSITE CLONING REQUIREMENTS:
When recreating/cloning a website, you MUST include:
1. **Header with Navigation** - Usually Header.jsx containing nav
2. **Hero Section** - The main landing area (Hero.jsx)
3. **Main Content Sections** - Features, Services, About, etc.
4. **Footer** - Contact info, links, copyright (Footer.jsx)
5. **App.jsx** - Main app component that imports and uses all components
${isEdit ? `CRITICAL: THIS IS AN EDIT TO AN EXISTING APPLICATION
YOU MUST FOLLOW THESE EDIT RULES:
0. NEVER create tailwind.config.js, vite.config.js, package.json, or any other config files - they already exist!
1. DO NOT regenerate the entire application
2. DO NOT create files that already exist (like App.jsx, index.css, tailwind.config.js)
3. ONLY edit the EXACT files needed for the requested change - NO MORE, NO LESS
4. If the user says "update the header", ONLY edit the Header component - DO NOT touch Footer, Hero, or any other components
5. If the user says "change the color", ONLY edit the relevant style or component file - DO NOT "improve" other parts
6. If you're unsure which file to edit, choose the SINGLE most specific one related to the request
7. IMPORTANT: When adding new components or libraries:
- Create the new component file
- UPDATE ONLY the parent component that will use it
- Example: Adding a Newsletter component means:
* Create Newsletter.jsx
* Update ONLY the file that will use it (e.g., Footer.jsx OR App.jsx) - NOT both
8. When adding npm packages:
- Import them ONLY in the files where they're actually used
- The system will auto-install missing packages
CRITICAL FILE MODIFICATION RULES - VIOLATION = FAILURE:
- **NEVER TRUNCATE FILES** - Always return COMPLETE files with ALL content
- **NO ELLIPSIS (...)** - Include every single line of code, no skipping
- Files MUST be complete and runnable - include ALL imports, functions, JSX, and closing tags
- Count the files you're about to generate
- If the user asked to change ONE thing, you should generate ONE file (or at most two if adding a new component)
- DO NOT "fix" or "improve" files that weren't mentioned in the request
- DO NOT update multiple components when only one was requested
- DO NOT add features the user didn't ask for
- RESIST the urge to be "helpful" by updating related files
CRITICAL: DO NOT REDESIGN OR REIMAGINE COMPONENTS
- "update" means make a small change, NOT redesign the entire component
- "change X to Y" means ONLY change X to Y, nothing else
- "fix" means repair what's broken, NOT rewrite everything
- "remove X" means delete X from the existing file, NOT create a new file
- "delete X" means remove X from where it currently exists
- Preserve ALL existing functionality and design unless explicitly asked to change it
NEVER CREATE NEW FILES WHEN THE USER ASKS TO REMOVE/DELETE SOMETHING
If the user says "remove X", you must:
1. Find which existing file contains X
2. Edit that file to remove X
3. DO NOT create any new files
${editContext ? `
TARGETED EDIT MODE ACTIVE
- Edit Type: ${editContext.editIntent.type}
- Confidence: ${editContext.editIntent.confidence}
- Files to Edit: ${editContext.primaryFiles.join(', ')}
🚨 CRITICAL RULE - VIOLATION WILL RESULT IN FAILURE 🚨
YOU MUST ***ONLY*** GENERATE THE FILES LISTED ABOVE!
ABSOLUTE REQUIREMENTS:
1. COUNT the files in "Files to Edit" - that's EXACTLY how many files you must generate
2. If "Files to Edit" shows ONE file, generate ONLY that ONE file
3. DO NOT generate App.jsx unless it's EXPLICITLY listed in "Files to Edit"
4. DO NOT generate ANY components that aren't listed in "Files to Edit"
5. DO NOT "helpfully" update related files
6. DO NOT fix unrelated issues you notice
7. DO NOT improve code quality in files not being edited
8. DO NOT add bonus features
EXAMPLE VIOLATIONS (THESE ARE FAILURES):
❌ User says "update the hero" → You update Hero, Header, Footer, and App.jsx
❌ User says "change header color" → You redesign the entire header
❌ User says "fix the button" → You update multiple components
❌ Files to Edit shows "Hero.jsx" → You also generate App.jsx "to integrate it"
❌ Files to Edit shows "Header.jsx" → You also update Footer.jsx "for consistency"
CORRECT BEHAVIOR (THIS IS SUCCESS):
✅ User says "update the hero" → You ONLY edit Hero.jsx with the requested change
✅ User says "change header color" → You ONLY change the color in Header.jsx
✅ User says "fix the button" → You ONLY fix the specific button issue
✅ Files to Edit shows "Hero.jsx" → You generate ONLY Hero.jsx
✅ Files to Edit shows "Header.jsx, Nav.jsx" → You generate EXACTLY 2 files: Header.jsx and Nav.jsx
THE AI INTENT ANALYZER HAS ALREADY DETERMINED THE FILES.
DO NOT SECOND-GUESS IT.
DO NOT ADD MORE FILES.
ONLY OUTPUT THE EXACT FILES LISTED IN "Files to Edit".
` : ''}
VIOLATION OF THESE RULES WILL RESULT IN FAILURE!
` : ''}
CRITICAL INCREMENTAL UPDATE RULES:
- When the user asks for additions or modifications (like "add a videos page", "create a new component", "update the header"):
- DO NOT regenerate the entire application
- DO NOT recreate files that already exist unless explicitly asked
- ONLY create/modify the specific files needed for the requested change
- Preserve all existing functionality and files
- If adding a new page/route, integrate it with the existing routing system
- Reference existing components and styles rather than duplicating them
- NEVER recreate config files (tailwind.config.js, vite.config.js, package.json, etc.)
IMPORTANT: When the user asks for edits or modifications:
- You have access to the current file contents in the context
- Make targeted changes to existing files rather than regenerating everything
- Preserve the existing structure and only modify what's requested
- If you need to see a specific file that's not in context, mention it
IMPORTANT: You have access to the full conversation context including:
- Previously scraped websites and their content
- Components already generated and applied
- The current project being worked on
- Recent conversation history
- Any Vite errors that need to be resolved
When the user references "the app", "the website", or "the site" without specifics, refer to:
1. The most recently scraped website in the context
2. The current project name in the context
3. The files currently in the sandbox
If you see scraped websites in the context, you're working on a clone/recreation of that site.
CRITICAL UI/UX RULES:
- NEVER use emojis in any code, text, console logs, or UI elements
- ALWAYS ensure responsive design using proper Tailwind classes (sm:, md:, lg:, xl:)
- ALWAYS use proper mobile-first responsive design patterns
- NEVER hardcode pixel widths - use relative units and responsive classes
- ALWAYS test that the layout works on mobile devices (320px and up)
- ALWAYS make sections full-width by default - avoid max-w-7xl or similar constraints
- For full-width layouts: use className="w-full" or no width constraint at all
- Only add max-width constraints when explicitly needed for readability (like blog posts)
- Prefer system fonts and clean typography
- Ensure all interactive elements have proper hover/focus states
- Use proper semantic HTML elements for accessibility
CRITICAL STYLING RULES - MUST FOLLOW:
- NEVER use inline styles with style={{ }} in JSX
- NEVER use <style jsx> tags or any CSS-in-JS solutions
- NEVER create App.css, Component.css, or any component-specific CSS files
- NEVER import './App.css' or any CSS files except index.css
- ALWAYS use Tailwind CSS classes for ALL styling
- ONLY create src/index.css with the @tailwind directives
- The ONLY CSS file should be src/index.css with:
@tailwind base;
@tailwind components;
@tailwind utilities;
- Use Tailwind's full utility set: spacing, colors, typography, flexbox, grid, animations, etc.
- ALWAYS add smooth transitions and animations where appropriate:
- Use transition-all, transition-colors, transition-opacity for hover states
- Use animate-fade-in, animate-pulse, animate-bounce for engaging UI elements
- Add hover:scale-105 or hover:scale-110 for interactive elements
- Use transform and transition utilities for smooth interactions
- For complex layouts, combine Tailwind utilities rather than writing custom CSS
- NEVER use non-standard Tailwind classes like "border-border", "bg-background", "text-foreground", etc.
- Use standard Tailwind classes only:
- For borders: use "border-gray-200", "border-gray-300", etc. NOT "border-border"
- For backgrounds: use "bg-white", "bg-gray-100", etc. NOT "bg-background"
- For text: use "text-gray-900", "text-black", etc. NOT "text-foreground"
- Examples of good Tailwind usage:
- Buttons: className="px-4 py-2 bg-blue-600 text-white rounded-lg shadow-md hover:bg-blue-700 hover:shadow-lg transform hover:scale-105 transition-all duration-200"
- Cards: className="bg-white rounded-lg shadow-md p-6 border border-gray-200 hover:shadow-xl transition-shadow duration-300"
- Full-width sections: className="w-full px-4 sm:px-6 lg:px-8"
- Constrained content (only when needed): className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
- Dark backgrounds: className="min-h-screen bg-gray-900 text-white"
- Hero sections: className="animate-fade-in-up"
- Feature cards: className="transform hover:scale-105 transition-transform duration-300"
- CTAs: className="animate-pulse hover:animate-none"
CRITICAL STRING AND SYNTAX RULES:
- ALWAYS escape apostrophes in strings: use \' instead of ' or use double quotes
- ALWAYS escape quotes properly in JSX attributes
- NEVER use curly quotes or smart quotes ('' "" '' "") - only straight quotes (' ")
- ALWAYS convert smart/curly quotes to straight quotes:
- ' and ' → '
- " and " → "
- Any other Unicode quotes → straight quotes
- When strings contain apostrophes, either:
1. Use double quotes: "you're" instead of 'you're'
2. Escape the apostrophe: 'you\'re'
- When working with scraped content, ALWAYS sanitize quotes first
- Replace all smart quotes with straight quotes before using in code
- Be extra careful with user-generated content or scraped text
- Always validate that JSX syntax is correct before generating
CRITICAL CODE SNIPPET DISPLAY RULES:
- When displaying code examples in JSX, NEVER put raw curly braces { } in text
- ALWAYS wrap code snippets in template literals with backticks
- For code examples in components, use one of these patterns:
1. Template literals: <div>{\`const example = { key: 'value' }\`}</div>
2. Pre/code blocks: <pre><code>{\`your code here\`}</code></pre>
3. Escape braces: <div>{'{'}key: value{'}'}</div>
- NEVER do this: <div>const example = { key: 'value' }</div> (causes parse errors)
- For multi-line code snippets, always use:
<pre className="bg-gray-900 text-gray-100 p-4 rounded">
<code>{\`
// Your code here
const example = {
key: 'value'
}
\`}</code>
</pre>
CRITICAL: When asked to create a React app or components:
- ALWAYS CREATE ALL FILES IN FULL - never provide partial implementations
- ALWAYS CREATE EVERY COMPONENT that you import - no placeholders
- ALWAYS IMPLEMENT COMPLETE FUNCTIONALITY - don't leave TODOs unless explicitly asked
- If you're recreating a website, implement ALL sections and features completely
- NEVER create tailwind.config.js - it's already configured in the template
- ALWAYS include a Navigation/Header component (Nav.jsx or Header.jsx) - websites need navigation!
REQUIRED COMPONENTS for website clones:
1. Nav.jsx or Header.jsx - Navigation bar with links (NEVER SKIP THIS!)
2. Hero.jsx - Main landing section
3. Features/Services/Products sections - Based on the site content
4. Footer.jsx - Footer with links and info
5. App.jsx - Main component that imports and arranges all components
- NEVER create vite.config.js - it's already configured in the template
- NEVER create package.json - it's already configured in the template
WHEN WORKING WITH SCRAPED CONTENT:
- ALWAYS sanitize all text content before using in code
- Convert ALL smart quotes to straight quotes
- Example transformations:
- "Firecrawl's API" → "Firecrawl's API" or "Firecrawl\\'s API"
- 'It's amazing' → "It's amazing" or 'It\\'s amazing'
- "Best tool ever" → "Best tool ever"
- When in doubt, use double quotes for strings containing apostrophes
- For testimonials or quotes from scraped content, ALWAYS clean the text:
- Bad: content: 'Moved our internal agent's web scraping...'
- Good: content: "Moved our internal agent's web scraping..."
- Also good: content: 'Moved our internal agent\\'s web scraping...'
When generating code, FOLLOW THIS PROCESS:
1. ALWAYS generate src/index.css FIRST - this establishes the styling foundation
2. List ALL components you plan to import in App.jsx
3. Count them - if there are 10 imports, you MUST create 10 component files
4. Generate src/index.css first (with proper CSS reset and base styles)
5. Generate App.jsx second
6. Then generate EVERY SINGLE component file you imported
7. Do NOT stop until all imports are satisfied
Use this XML format for React components only (DO NOT create tailwind.config.js - it already exists):
<file path="src/index.css">
@tailwind base;
@tailwind components;
@tailwind utilities;
</file>
<file path="src/App.jsx">
// Main App component that imports and uses other components
// Use Tailwind classes: className="min-h-screen bg-gray-50"
</file>
<file path="src/components/Example.jsx">
// Your React component code here
// Use Tailwind classes for ALL styling
</file>
CRITICAL COMPLETION RULES:
1. NEVER say "I'll continue with the remaining components"
2. NEVER say "Would you like me to proceed?"
3. NEVER use <continue> tags
4. Generate ALL components in ONE response
5. If App.jsx imports 10 components, generate ALL 10
6. Complete EVERYTHING before ending your response
With 16,000 tokens available, you have plenty of space to generate a complete application. Use it!
UNDERSTANDING USER INTENT FOR INCREMENTAL VS FULL GENERATION:
- "add/create/make a [specific feature]" → Add ONLY that feature to existing app
- "add a videos page" → Create ONLY Videos.jsx and update routing
- "update the header" → Modify ONLY header component
- "fix the styling" → Update ONLY the affected components
- "change X to Y" → Find the file containing X and modify it
- "make the header black" → Find Header component and change its color
- "rebuild/recreate/start over" → Full regeneration
- Default to incremental updates when working on an existing app
SURGICAL EDIT RULES (CRITICAL FOR PERFORMANCE):
- **PREFER TARGETED CHANGES**: Don't regenerate entire components for small edits
- For color/style changes: Edit ONLY the specific className or style prop
- For text changes: Change ONLY the text content, keep everything else
- For adding elements: INSERT into existing JSX, don't rewrite the whole return
- **PRESERVE EXISTING CODE**: Keep all imports, functions, and unrelated code exactly as-is
- Maximum files to edit:
- Style change = 1 file ONLY
- Text change = 1 file ONLY
- New feature = 2 files MAX (feature + parent)
- If you're editing >3 files for a simple request, STOP - you're doing too much
EXAMPLES OF CORRECT SURGICAL EDITS:
✅ "change header to black" → Find className="..." in Header.jsx, change ONLY color classes
✅ "update hero text" → Find the <h1> or <p> in Hero.jsx, change ONLY the text inside
✅ "add a button to hero" → Find the return statement, ADD button, keep everything else
❌ WRONG: Regenerating entire Header.jsx to change one color
❌ WRONG: Rewriting Hero.jsx to add one button
NAVIGATION/HEADER INTELLIGENCE:
- ALWAYS check App.jsx imports first
- Navigation is usually INSIDE Header.jsx, not separate
- If user says "nav", check Header.jsx FIRST
- Only create Nav.jsx if no navigation exists anywhere
- Logo, menu, hamburger = all typically in Header
CRITICAL: When files are provided in the context:
1. The user is asking you to MODIFY the existing app, not create a new one
2. Find the relevant file(s) from the provided context
3. Generate ONLY the files that need changes
4. Do NOT ask to see files - they are already provided in the context above
5. Make the requested change immediately`;
// If Morph Fast Apply is enabled (edit mode + MORPH_API_KEY), force <edit> block output
const morphFastApplyEnabled = Boolean(isEdit && process.env.MORPH_API_KEY);
if (morphFastApplyEnabled) {
systemPrompt += `
MORPH FAST APPLY MODE (EDIT-ONLY):
- Output edits as <edit> blocks, not full <file> blocks, for files that already exist.
- Format for each edit:
<edit target_file="src/components/Header.jsx">
<instructions>Describe the minimal change, single sentence.</instructions>
<update>Provide the SMALLEST code snippet necessary to perform the change.</update>
</edit>
- Only use <file> blocks when you must CREATE a brand-new file.
- Prefer ONE edit block for a simple change; multiple edits only if absolutely needed for separate files.
- Keep updates minimal and precise; do not rewrite entire files.
`;
}
// Build full prompt with context
let fullPrompt = prompt;
if (context) {
const contextParts = [];
if (context.sandboxId) {
contextParts.push(`Current sandbox ID: ${context.sandboxId}`);
}
if (context.structure) {
contextParts.push(`Current file structure:\n${context.structure}`);
}
// Use backend file cache instead of frontend-provided files
let backendFiles = global.sandboxState?.fileCache?.files || {};
let hasBackendFiles = Object.keys(backendFiles).length > 0;
console.log('[generate-ai-code-stream] Backend file cache status:');
console.log('[generate-ai-code-stream] - Has sandboxState:', !!global.sandboxState);
console.log('[generate-ai-code-stream] - Has fileCache:', !!global.sandboxState?.fileCache);
console.log('[generate-ai-code-stream] - File count:', Object.keys(backendFiles).length);
console.log('[generate-ai-code-stream] - Has manifest:', !!global.sandboxState?.fileCache?.manifest);
// If no backend files and we're in edit mode, try to fetch from sandbox
if (!hasBackendFiles && isEdit && (global.activeSandbox || context?.sandboxId)) {
console.log('[generate-ai-code-stream] No backend files, attempting to fetch from sandbox...');
try {
const filesResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/get-sandbox-files`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (filesResponse.ok) {
const filesData = await filesResponse.json();
if (filesData.success && filesData.files) {
console.log('[generate-ai-code-stream] Successfully fetched', Object.keys(filesData.files).length, 'files from sandbox');
// Initialize sandboxState if needed
if (!global.sandboxState) {
global.sandboxState = {
fileCache: {
files: {},
lastSync: Date.now(),
sandboxId: context?.sandboxId || 'unknown'
}
} as any;
} else if (!global.sandboxState.fileCache) {
global.sandboxState.fileCache = {
files: {},
lastSync: Date.now(),
sandboxId: context?.sandboxId || 'unknown'
};
}
// Store files in cache
for (const [path, content] of Object.entries(filesData.files)) {
const normalizedPath = path.replace('/home/user/app/', '');
if (global.sandboxState.fileCache) {
global.sandboxState.fileCache.files[normalizedPath] = {
content: content as string,
lastModified: Date.now()
};
}
}
if (filesData.manifest && global.sandboxState.fileCache) {
global.sandboxState.fileCache.manifest = filesData.manifest;
// Now try to analyze edit intent with the fetched manifest
if (!editContext) {
console.log('[generate-ai-code-stream] Analyzing edit intent with fetched manifest');
try {
const intentResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/analyze-edit-intent`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, manifest: filesData.manifest, model })
});
if (intentResponse.ok) {
const { searchPlan } = await intentResponse.json();
console.log('[generate-ai-code-stream] Search plan received:', searchPlan);
// Create edit context from AI analysis
// Note: We can't execute search here without file contents, so fall back to keyword method
const fileContext = selectFilesForEdit(prompt, filesData.manifest);
editContext = fileContext;
enhancedSystemPrompt = fileContext.systemPrompt;
console.log('[generate-ai-code-stream] Edit context created with', editContext.primaryFiles.length, 'primary files');
}
} catch (error) {
console.error('[generate-ai-code-stream] Failed to analyze edit intent:', error);
}
}
}
// Update variables
backendFiles = global.sandboxState.fileCache?.files || {};
hasBackendFiles = Object.keys(backendFiles).length > 0;
console.log('[generate-ai-code-stream] Updated backend cache with fetched files');
}
}
} catch (error) {
console.error('[generate-ai-code-stream] Failed to fetch sandbox files:', error);
}
}
// Include current file contents from backend cache
if (hasBackendFiles) {
// If we have edit context, use intelligent file selection
if (editContext && editContext.primaryFiles.length > 0) {
contextParts.push('\nEXISTING APPLICATION - TARGETED EDIT MODE');
contextParts.push(`\n${editContext.systemPrompt || enhancedSystemPrompt}\n`);
// Get contents of primary and context files
const primaryFileContents = await getFileContents(editContext.primaryFiles, global.sandboxState!.fileCache!.manifest!);
const contextFileContents = await getFileContents(editContext.contextFiles, global.sandboxState!.fileCache!.manifest!);
// Format files for AI
const formattedFiles = formatFilesForAI(primaryFileContents, contextFileContents);
contextParts.push(formattedFiles);
contextParts.push('\nIMPORTANT: Only modify the files listed under "Files to Edit". The context files are provided for reference only.');
} else {
// Fallback to showing all files if no edit context
console.log('[generate-ai-code-stream] WARNING: Using fallback mode - no edit context available');
contextParts.push('\nEXISTING APPLICATION - TARGETED EDIT REQUIRED');
contextParts.push('\nYou MUST analyze the user request and determine which specific file(s) to edit.');
contextParts.push('\nCurrent project files (DO NOT regenerate all of these):');
const fileEntries = Object.entries(backendFiles);
console.log(`[generate-ai-code-stream] Using backend cache: ${fileEntries.length} files`);
// Show file list first for reference
contextParts.push('\n### File List:');
for (const [path] of fileEntries) {
contextParts.push(`- ${path}`);
}
// Include ALL files as context in fallback mode
contextParts.push('\n### File Contents (ALL FILES FOR CONTEXT):');
for (const [path, fileData] of fileEntries) {
const content = fileData.content;
if (typeof content === 'string') {
contextParts.push(`\n<file path="${path}">\n${content}\n</file>`);
}
}
contextParts.push('\n🚨 CRITICAL INSTRUCTIONS - VIOLATION = FAILURE 🚨');
contextParts.push('1. Analyze the user request: "' + prompt + '"');
contextParts.push('2. Identify the MINIMUM number of files that need editing (usually just ONE)');
contextParts.push('3. PRESERVE ALL EXISTING CONTENT in those files');
contextParts.push('4. ONLY ADD/MODIFY the specific part requested');
contextParts.push('5. DO NOT regenerate entire components from scratch');
contextParts.push('6. DO NOT change unrelated parts of any file');
contextParts.push('7. Generate ONLY the files that MUST be changed - NO EXTRAS');
contextParts.push('\n⚠️ FILE COUNT RULE:');
contextParts.push('- Simple change (color, text, spacing) = 1 file ONLY');
contextParts.push('- Adding new component = 2 files MAX (new component + parent that imports it)');
contextParts.push('- DO NOT exceed these limits unless absolutely necessary');
contextParts.push('\nEXAMPLES OF CORRECT BEHAVIOR:');
contextParts.push('✅ "add a chart to the hero" → Edit ONLY Hero.jsx, ADD the chart, KEEP everything else');
contextParts.push('✅ "change header to black" → Edit ONLY Header.jsx, change ONLY the color');
contextParts.push('✅ "fix spacing in footer" → Edit ONLY Footer.jsx, adjust ONLY spacing');
contextParts.push('\nEXAMPLES OF FAILURES:');
contextParts.push('❌ "change header color" → You edit Header, Footer, and App "for consistency"');
contextParts.push('❌ "add chart to hero" → You regenerate the entire Hero component');
contextParts.push('❌ "fix button" → You update 5 different component files');
contextParts.push('\n⚠️ FINAL WARNING:');
contextParts.push('If you generate MORE files than necessary, you have FAILED');
contextParts.push('If you DELETE or REWRITE existing functionality, you have FAILED');
contextParts.push('ONLY change what was EXPLICITLY requested - NOTHING MORE');
}
} else if (context.currentFiles && Object.keys(context.currentFiles).length > 0) {
// Fallback to frontend-provided files if backend cache is empty
console.log('[generate-ai-code-stream] Warning: Backend cache empty, using frontend files');
contextParts.push('\nEXISTING APPLICATION - DO NOT REGENERATE FROM SCRATCH');
contextParts.push('Current project files (modify these, do not recreate):');
const fileEntries = Object.entries(context.currentFiles);
for (const [path, content] of fileEntries) {
if (typeof content === 'string') {
contextParts.push(`\n<file path="${path}">\n${content}\n</file>`);
}
}
contextParts.push('\nThe above files already exist. When the user asks to modify something (like "change the header color to black"), find the relevant file above and generate ONLY that file with the requested changes.');
}
// Add explicit edit mode indicator
if (isEdit) {
contextParts.push('\nEDIT MODE ACTIVE');
contextParts.push('This is an incremental update to an existing application.');
contextParts.push('DO NOT regenerate App.jsx, index.css, or other core files unless explicitly requested.');
contextParts.push('ONLY create or modify the specific files needed for the user\'s request.');
contextParts.push('\n⚠️ CRITICAL FILE OUTPUT FORMAT - VIOLATION = FAILURE:');
contextParts.push('YOU MUST OUTPUT EVERY FILE IN THIS EXACT XML FORMAT:');
contextParts.push('<file path="src/components/ComponentName.jsx">');
contextParts.push('// Complete file content here');
contextParts.push('</file>');
contextParts.push('<file path="src/index.css">');
contextParts.push('/* CSS content here */');
contextParts.push('</file>');
contextParts.push('\n❌ NEVER OUTPUT: "Generated Files: index.css, App.jsx"');
contextParts.push('❌ NEVER LIST FILE NAMES WITHOUT CONTENT');
contextParts.push('✅ ALWAYS: One <file> tag per file with COMPLETE content');
contextParts.push('✅ ALWAYS: Include EVERY file you modified');
} else if (!hasBackendFiles) {
// First generation mode - make it beautiful!
contextParts.push('\n🎨 FIRST GENERATION MODE - CREATE SOMETHING BEAUTIFUL!');
contextParts.push('\nThis is the user\'s FIRST experience. Make it impressive:');
contextParts.push('1. **USE TAILWIND PROPERLY** - Use standard Tailwind color classes');
contextParts.push('2. **NO PLACEHOLDERS** - Use real content, not lorem ipsum');
contextParts.push('3. **COMPLETE COMPONENTS** - Header, Hero, Features, Footer minimum');
contextParts.push('4. **VISUAL POLISH** - Shadows, hover states, transitions');
contextParts.push('5. **STANDARD CLASSES** - bg-white, text-gray-900, bg-blue-500, NOT bg-background');
contextParts.push('\nCreate a polished, professional application that works perfectly on first load.');
contextParts.push('\n⚠️ OUTPUT FORMAT:');
contextParts.push('Use <file path="...">content</file> tags for EVERY file');
contextParts.push('NEVER output "Generated Files:" as plain text');
}
// Add conversation context (scraped websites, etc)
if (context.conversationContext) {
if (context.conversationContext.scrapedWebsites?.length > 0) {
contextParts.push('\nScraped Websites in Context:');
context.conversationContext.scrapedWebsites.forEach((site: any) => {
contextParts.push(`\nURL: ${site.url}`);
contextParts.push(`Scraped: ${new Date(site.timestamp).toLocaleString()}`);
if (site.content) {
// Include a summary of the scraped content
const contentPreview = typeof site.content === 'string'
? site.content.substring(0, 1000)
: JSON.stringify(site.content).substring(0, 1000);
contextParts.push(`Content Preview: ${contentPreview}...`);
}
});
}
if (context.conversationContext.currentProject) {
contextParts.push(`\nCurrent Project: ${context.conversationContext.currentProject}`);
}
}
if (contextParts.length > 0) {
if (morphFastApplyEnabled) {
contextParts.push('\nOUTPUT FORMAT (REQUIRED IN MORPH MODE):');
contextParts.push('<edit target_file="src/components/Component.jsx">');
contextParts.push('<instructions>Minimal, precise instruction.</instructions>');
contextParts.push('<update>// Smallest necessary snippet</update>');
contextParts.push('</edit>');
contextParts.push('\nIf you need to create a NEW file, then and only then output a full file:');
contextParts.push('<file path="src/components/NewComponent.jsx">');
contextParts.push('// Full file content when creating new files');
contextParts.push('</file>');
}
fullPrompt = `CONTEXT:\n${contextParts.join('\n')}\n\nUSER REQUEST:\n${prompt}`;
}
}
await sendProgress({ type: 'status', message: 'Planning application structure...' });
console.log('\n[generate-ai-code-stream] Starting streaming response...\n');
// Track packages that need to be installed
const packagesToInstall: string[] = [];
// Determine which provider to use based on model
const isAnthropic = model.startsWith('anthropic/');
const isGoogle = model.startsWith('google/');
const isOpenAI = model.startsWith('openai/');
const isKimiGroq = model === 'moonshotai/kimi-k2-instruct-0905';
const modelProvider = isAnthropic ? anthropic :
(isOpenAI ? openai :
(isGoogle ? googleGenerativeAI :
(isKimiGroq ? groq : groq)));
// Fix model name transformation for different providers
let actualModel: string;
if (isAnthropic) {
actualModel = model.replace('anthropic/', '');
} else if (isOpenAI) {
actualModel = model.replace('openai/', '');
} else if (isKimiGroq) {
// Kimi on Groq - use full model string
actualModel = 'moonshotai/kimi-k2-instruct-0905';
} else if (isGoogle) {
// Google uses specific model names - convert our naming to theirs
actualModel = model.replace('google/', '');
} else {
actualModel = model;
}
console.log(`[generate-ai-code-stream] Using provider: ${isAnthropic ? 'Anthropic' : isGoogle ? 'Google' : isOpenAI ? 'OpenAI' : 'Groq'}, model: ${actualModel}`);
console.log(`[generate-ai-code-stream] AI Gateway enabled: ${isUsingAIGateway}`);
console.log(`[generate-ai-code-stream] Model string: ${model}`);
// Make streaming API call with appropriate provider
const streamOptions: any = {
model: modelProvider(actualModel),
messages: [
{
role: 'system',
content: systemPrompt + `
🚨 CRITICAL CODE GENERATION RULES - VIOLATION = FAILURE 🚨:
1. NEVER truncate ANY code - ALWAYS write COMPLETE files
2. NEVER use "..." anywhere in your code - this causes syntax errors
3. NEVER cut off strings mid-sentence - COMPLETE every string
4. NEVER leave incomplete class names or attributes
5. ALWAYS close ALL tags, quotes, brackets, and parentheses
6. If you run out of space, prioritize completing the current file
CRITICAL STRING RULES TO PREVENT SYNTAX ERRORS:
- NEVER write: className="px-8 py-4 bg-black text-white font-bold neobrut-border neobr...
- ALWAYS write: className="px-8 py-4 bg-black text-white font-bold neobrut-border neobrut-shadow"
- COMPLETE every className attribute
- COMPLETE every string literal
- NO ellipsis (...) ANYWHERE in code
PACKAGE RULES:
- For INITIAL generation: Use ONLY React, no external packages
- For EDITS: You may use packages, specify them with <package> tags
- NEVER install packages like @mendable/firecrawl-js unless explicitly requested
Examples of SYNTAX ERRORS (NEVER DO THIS):
❌ className="px-4 py-2 bg-blue-600 hover:bg-blue-7...
❌ <button className="btn btn-primary btn-...
❌ const title = "Welcome to our...
❌ import { useState, useEffect, ... } from 'react'
Examples of CORRECT CODE (ALWAYS DO THIS):
✅ className="px-4 py-2 bg-blue-600 hover:bg-blue-700"
✅ <button className="btn btn-primary btn-large">
✅ const title = "Welcome to our application"
✅ import { useState, useEffect, useCallback } from 'react'
REMEMBER: It's better to generate fewer COMPLETE files than many INCOMPLETE files.`
},
{
role: 'user',
content: fullPrompt + `
CRITICAL: You MUST complete EVERY file you start. If you write:
<file path="src/components/Hero.jsx">
You MUST include the closing </file> tag and ALL the code in between.
NEVER write partial code like:
<h1>Build and deploy on the AI Cloud.</h1>
<p>Some text...</p> ❌ WRONG
ALWAYS write complete code:
<h1>Build and deploy on the AI Cloud.</h1>
<p>Some text here with full content</p> ✅ CORRECT
If you're running out of space, generate FEWER files but make them COMPLETE.
It's better to have 3 complete files than 10 incomplete files.`
}
],
maxTokens: 8192, // Reduce to ensure completion
stopSequences: [] // Don't stop early
// Note: Neither Groq nor Anthropic models support tool/function calling in this context
// We use XML tags for package detection instead
};
// Add temperature for non-reasoning models
if (!model.startsWith('openai/gpt-5')) {
streamOptions.temperature = 0.7;
}
// Add reasoning effort for GPT-5 models
if (isOpenAI) {
streamOptions.experimental_providerMetadata = {
openai: {
reasoningEffort: 'high'
}
};
}
let result;
let retryCount = 0;
const maxRetries = 2;
while (retryCount <= maxRetries) {
try {
result = await streamText(streamOptions);
break; // Success, exit retry loop
} catch (streamError: any) {
console.error(`[generate-ai-code-stream] Error calling streamText (attempt ${retryCount + 1}/${maxRetries + 1}):`, streamError);
// Check if this is a Groq service unavailable error
const isGroqServiceError = isKimiGroq && streamError.message?.includes('Service unavailable');
const isRetryableError = streamError.message?.includes('Service unavailable') ||
streamError.message?.includes('rate limit') ||
streamError.message?.includes('timeout');
if (retryCount < maxRetries && isRetryableError) {
retryCount++;
console.log(`[generate-ai-code-stream] Retrying in ${retryCount * 2} seconds...`);
// Send progress update about retry
await sendProgress({
type: 'info',
message: `Service temporarily unavailable, retrying (attempt ${retryCount + 1}/${maxRetries + 1})...`
});
// Wait before retry with exponential backoff
await new Promise(resolve => setTimeout(resolve, retryCount * 2000));
// If Groq fails, try switching to a fallback model
if (isGroqServiceError && retryCount === maxRetries) {
console.log('[generate-ai-code-stream] Groq service unavailable, falling back to GPT-4');
streamOptions.model = openai('gpt-4-turbo');
actualModel = 'gpt-4-turbo';
}
} else {
// Final error, send to user
await sendProgress({
type: 'error',
message: `Failed to initialize ${isGoogle ? 'Gemini' : isAnthropic ? 'Claude' : isOpenAI ? 'GPT-5' : isKimiGroq ? 'Kimi (Groq)' : 'Groq'} streaming: ${streamError.message}`
});
// If this is a Google model error, provide helpful info
if (isGoogle) {
await sendProgress({
type: 'info',
message: 'Tip: Make sure your GEMINI_API_KEY is set correctly and has proper permissions.'
});
}
throw streamError;
}
}
}
// Stream the response and parse in real-time
let generatedCode = '';
let currentFile = '';
let currentFilePath = '';
let componentCount = 0;
let isInFile = false;
let isInTag = false;
let conversationalBuffer = '';
// Buffer for incomplete tags
let tagBuffer = '';
// Stream the response and parse for packages in real-time
for await (const textPart of result?.textStream || []) {
const text = textPart || '';
generatedCode += text;
currentFile += text;
// Combine with buffer for tag detection
const searchText = tagBuffer + text;
// Log streaming chunks to console
process.stdout.write(text);
// Check if we're entering or leaving a tag
const hasOpenTag = /<(file|package|packages|explanation|command|structure|template)\b/.test(text);
const hasCloseTag = /<\/(file|package|packages|explanation|command|structure|template)>/.test(text);
if (hasOpenTag) {
// Send any buffered conversational text before the tag
if (conversationalBuffer.trim() && !isInTag) {
await sendProgress({
type: 'conversation',
text: conversationalBuffer.trim()
});
conversationalBuffer = '';
}
isInTag = true;
}
if (hasCloseTag) {
isInTag = false;
}
// If we're not in a tag, buffer as conversational text
if (!isInTag && !hasOpenTag) {
conversationalBuffer += text;
}
// Stream the raw text for live preview
await sendProgress({
type: 'stream',
text: text,
raw: true
});
// Debug: Log every 100 characters streamed
if (generatedCode.length % 100 < text.length) {
console.log(`[generate-ai-code-stream] Streamed ${generatedCode.length} chars`);
}
// Check for package tags in buffered text (ONLY for edits, not initial generation)
let lastIndex = 0;
if (isEdit) {
const packageRegex = /<package>([^<]+)<\/package>/g;
let packageMatch;
while ((packageMatch = packageRegex.exec(searchText)) !== null) {
const packageName = packageMatch[1].trim();
if (packageName && !packagesToInstall.includes(packageName)) {
packagesToInstall.push(packageName);
console.log(`[generate-ai-code-stream] Package detected: ${packageName}`);
await sendProgress({
type: 'package',
name: packageName,
message: `Package detected: ${packageName}`
});
}
lastIndex = packageMatch.index + packageMatch[0].length;
}
}
// Keep unmatched portion in buffer for next iteration
tagBuffer = searchText.substring(Math.max(0, lastIndex - 50)); // Keep last 50 chars
// Check for file boundaries
if (text.includes('<file path="')) {
const pathMatch = text.match(/<file path="([^"]+)"/);
if (pathMatch) {
currentFilePath = pathMatch[1];
isInFile = true;
currentFile = text;
}
}
// Check for file end
if (isInFile && currentFile.includes('</file>')) {
isInFile = false;
// Send component progress update
if (currentFilePath.includes('components/')) {
componentCount++;
const componentName = currentFilePath.split('/').pop()?.replace('.jsx', '') || 'Component';
await sendProgress({
type: 'component',
name: componentName,
path: currentFilePath,
index: componentCount
});
} else if (currentFilePath.includes('App.jsx')) {
await sendProgress({
type: 'app',
message: 'Generated main App.jsx',
path: currentFilePath
});
}
currentFile = '';
currentFilePath = '';
}
}
console.log('\n\n[generate-ai-code-stream] Streaming complete.');
// Send any remaining conversational text
if (conversationalBuffer.trim()) {
await sendProgress({
type: 'conversation',
text: conversationalBuffer.trim()
});
}
// Also parse <packages> tag for multiple packages - ONLY for edits
if (isEdit) {
const packagesRegex = /<packages>([\s\S]*?)<\/packages>/g;
let packagesMatch;
while ((packagesMatch = packagesRegex.exec(generatedCode)) !== null) {
const packagesContent = packagesMatch[1].trim();
const packagesList = packagesContent.split(/[\n,]+/)
.map(pkg => pkg.trim())
.filter(pkg => pkg.length > 0);
for (const packageName of packagesList) {
if (!packagesToInstall.includes(packageName)) {
packagesToInstall.push(packageName);
console.log(`[generate-ai-code-stream] Package from <packages> tag: ${packageName}`);
await sendProgress({
type: 'package',
name: packageName,
message: `Package detected: ${packageName}`
});
}
}
}
}
// Function to extract packages from import statements
function extractPackagesFromCode(content: string): string[] {
const packages: string[] = [];
// Match ES6 imports
const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+|\w+))*\s+from\s+)?['"]([^'"]+)['"]/g;
let importMatch;
while ((importMatch = importRegex.exec(content)) !== null) {
const importPath = importMatch[1];
// Skip relative imports and built-in React
if (!importPath.startsWith('.') && !importPath.startsWith('/') &&
importPath !== 'react' && importPath !== 'react-dom' &&
!importPath.startsWith('@/')) {
// Extract package name (handle scoped packages like @heroicons/react)
const packageName = importPath.startsWith('@')
? importPath.split('/').slice(0, 2).join('/')
: importPath.split('/')[0];
if (!packages.includes(packageName)) {
packages.push(packageName);
}
}
}
return packages;
}
// Parse files and send progress for each
const fileRegex = /<file path="([^"]+)">([\s\S]*?)<\/file>/g;
const files = [];
let match;
while ((match = fileRegex.exec(generatedCode)) !== null) {
const filePath = match[1];
const content = match[2].trim();
files.push({ path: filePath, content });
// Extract packages from file content - ONLY for edits
if (isEdit) {
const filePackages = extractPackagesFromCode(content);
for (const pkg of filePackages) {
if (!packagesToInstall.includes(pkg)) {
packagesToInstall.push(pkg);
console.log(`[generate-ai-code-stream] Package detected from imports: ${pkg}`);
await sendProgress({
type: 'package',
name: pkg,
message: `Package detected from imports: ${pkg}`
});
}
}
}
// Send progress for each file (reusing componentCount from streaming)
if (filePath.includes('components/')) {
const componentName = filePath.split('/').pop()?.replace('.jsx', '') || 'Component';
await sendProgress({
type: 'component',
name: componentName,
path: filePath,
index: componentCount
});
} else if (filePath.includes('App.jsx')) {
await sendProgress({
type: 'app',
message: 'Generated main App.jsx',
path: filePath
});
}
}
// Extract explanation
const explanationMatch = generatedCode.match(/<explanation>([\s\S]*?)<\/explanation>/);
const explanation = explanationMatch ? explanationMatch[1].trim() : 'Code generated successfully!';
// Validate generated code for truncation issues
const truncationWarnings: string[] = [];
// Skip ellipsis checking entirely - too many false positives with spread operators, loading text, etc.
// Check for unclosed file tags
const fileOpenCount = (generatedCode.match(/<file path="/g) || []).length;
const fileCloseCount = (generatedCode.match(/<\/file>/g) || []).length;
if (fileOpenCount !== fileCloseCount) {
truncationWarnings.push(`Unclosed file tags detected: ${fileOpenCount} open, ${fileCloseCount} closed`);
}
// Check for files that seem truncated (very short or ending abruptly)
const truncationCheckRegex = /<file path="([^"]+)">([\s\S]*?)(?:<\/file>|$)/g;
let truncationMatch;
while ((truncationMatch = truncationCheckRegex.exec(generatedCode)) !== null) {
const filePath = truncationMatch[1];
const content = truncationMatch[2];
// Only check for really obvious HTML truncation - file ends with opening tag
if (content.trim().endsWith('<') || content.trim().endsWith('</')) {
truncationWarnings.push(`File ${filePath} appears to have incomplete HTML tags`);
}
// Skip "..." check - too many false positives with loading text, etc.
// Only check for SEVERE truncation issues
if (filePath.match(/\.(jsx?|tsx?)$/)) {
// Only check for severely unmatched brackets (more than 3 difference)
const openBraces = (content.match(/{/g) || []).length;
const closeBraces = (content.match(/}/g) || []).length;
const braceDiff = Math.abs(openBraces - closeBraces);
if (braceDiff > 3) { // Only flag severe mismatches
truncationWarnings.push(`File ${filePath} has severely unmatched braces (${openBraces} open, ${closeBraces} closed)`);
}
// Check if file is extremely short and looks incomplete
if (content.length < 20 && content.includes('function') && !content.includes('}')) {
truncationWarnings.push(`File ${filePath} appears severely truncated`);
}
}
}
// Handle truncation with automatic retry (if enabled in config)
if (truncationWarnings.length > 0 && appConfig.codeApplication.enableTruncationRecovery) {
console.warn('[generate-ai-code-stream] Truncation detected, attempting to fix:', truncationWarnings);
await sendProgress({
type: 'warning',
message: 'Detected incomplete code generation. Attempting to complete...',
warnings: truncationWarnings
});
// Try to fix truncated files automatically
const truncatedFiles: string[] = [];
const fileRegex = /<file path="([^"]+)">([\s\S]*?)(?:<\/file>|$)/g;
let match;
while ((match = fileRegex.exec(generatedCode)) !== null) {
const filePath = match[1];
const content = match[2];
// Check if this file appears truncated - be more selective
const hasEllipsis = content.includes('...') &&
!content.includes('...rest') &&
!content.includes('...props') &&
!content.includes('spread');
const endsAbruptly = content.trim().endsWith('...') ||
content.trim().endsWith(',') ||
content.trim().endsWith('(');
const hasUnclosedTags = content.includes('</') &&
!content.match(/<\/[a-zA-Z0-9]+>/) &&
content.includes('<');
const tooShort = content.length < 50 && filePath.match(/\.(jsx?|tsx?)$/);
// Check for unmatched braces specifically
const openBraceCount = (content.match(/{/g) || []).length;
const closeBraceCount = (content.match(/}/g) || []).length;
const hasUnmatchedBraces = Math.abs(openBraceCount - closeBraceCount) > 1;
const isTruncated = (hasEllipsis && endsAbruptly) ||
hasUnclosedTags ||
(tooShort && !content.includes('export')) ||
hasUnmatchedBraces;
if (isTruncated) {
truncatedFiles.push(filePath);
}
}
// If we have truncated files, try to regenerate them
if (truncatedFiles.length > 0) {
console.log('[generate-ai-code-stream] Attempting to regenerate truncated files:', truncatedFiles);
for (const filePath of truncatedFiles) {
await sendProgress({
type: 'info',
message: `Completing ${filePath}...`
});
try {
// Create a focused prompt to complete just this file
const completionPrompt = `Complete the following file that was truncated. Provide the FULL file content.
File: ${filePath}
Original request: ${prompt}
Provide the complete file content without any truncation. Include all necessary imports, complete all functions, and close all tags properly.`;
// Make a focused API call to complete this specific file
// Create a new client for the completion based on the provider
let completionClient;
if (model.includes('gpt') || model.includes('openai')) {
completionClient = openai;
} else if (model.includes('claude')) {
completionClient = anthropic;
} else if (model === 'moonshotai/kimi-k2-instruct-0905') {
completionClient = groq;
} else {
completionClient = groq;
}
// Determine the correct model name for the completion
let completionModelName: string;
if (model === 'moonshotai/kimi-k2-instruct-0905') {
completionModelName = 'moonshotai/kimi-k2-instruct-0905';
} else if (model.includes('openai')) {
completionModelName = model.replace('openai/', '');
} else if (model.includes('anthropic')) {
completionModelName = model.replace('anthropic/', '');
} else if (model.includes('google')) {
completionModelName = model.replace('google/', '');
} else {
completionModelName = model;
}
const completionResult = await streamText({
model: completionClient(completionModelName),
messages: [
{
role: 'system',
content: 'You are completing a truncated file. Provide the complete, working file content.'
},
{ role: 'user', content: completionPrompt }
],
temperature: model.startsWith('openai/gpt-5') ? undefined : appConfig.ai.defaultTemperature
});
// Get the full text from the stream
let completedContent = '';
for await (const chunk of completionResult.textStream) {
completedContent += chunk;
}
// Replace the truncated file in the generatedCode
const filePattern = new RegExp(
`<file path="${filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}">[\\s\\S]*?(?:</file>|$)`,
'g'
);
// Extract just the code content (remove any markdown or explanation)
let cleanContent = completedContent;
if (cleanContent.includes('```')) {
const codeMatch = cleanContent.match(/```[\w]*\n([\s\S]*?)```/);
if (codeMatch) {
cleanContent = codeMatch[1];
}
}
generatedCode = generatedCode.replace(
filePattern,
`<file path="${filePath}">\n${cleanContent}\n</file>`
);
console.log(`[generate-ai-code-stream] Successfully completed ${filePath}`);
} catch (completionError) {
console.error(`[generate-ai-code-stream] Failed to complete ${filePath}:`, completionError);
await sendProgress({
type: 'warning',
message: `Could not auto-complete ${filePath}. Manual review may be needed.`
});
}
}
// Clear the warnings after attempting fixes
truncationWarnings.length = 0;
await sendProgress({
type: 'info',
message: 'Truncation recovery complete'
});
}
}
// Send completion with packages info
await sendProgress({
type: 'complete',
generatedCode,
explanation,
files: files.length,
components: componentCount,
model,
packagesToInstall: packagesToInstall.length > 0 ? packagesToInstall : undefined,
warnings: truncationWarnings.length > 0 ? truncationWarnings : undefined
});
// Track edit in conversation history
if (isEdit && editContext && global.conversationState) {
const editRecord: ConversationEdit = {
timestamp: Date.now(),
userRequest: prompt,
editType: editContext.editIntent.type,
targetFiles: editContext.primaryFiles,
confidence: editContext.editIntent.confidence,
outcome: 'success' // Assuming success if we got here
};
global.conversationState.context.edits.push(editRecord);
// Track major changes
if (editContext.editIntent.type === 'ADD_FEATURE' || files.length > 3) {
global.conversationState.context.projectEvolution.majorChanges.push({
timestamp: Date.now(),
description: editContext.editIntent.description,
filesAffected: editContext.primaryFiles
});
}
// Update last updated timestamp
global.conversationState.lastUpdated = Date.now();
console.log('[generate-ai-code-stream] Updated conversation history with edit:', editRecord);
}
} catch (error) {
console.error('[generate
gitextract__mw591t9/
├── .cursor/
│ └── mcp.json
├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── api/
│ │ ├── analyze-edit-intent/
│ │ │ └── route.ts
│ │ ├── apply-ai-code/
│ │ │ └── route.ts
│ │ ├── apply-ai-code-stream/
│ │ │ └── route.ts
│ │ ├── check-vite-errors/
│ │ │ └── route.ts
│ │ ├── clear-vite-errors-cache/
│ │ │ └── route.ts
│ │ ├── conversation-state/
│ │ │ └── route.ts
│ │ ├── create-ai-sandbox/
│ │ │ └── route.ts
│ │ ├── create-ai-sandbox-v2/
│ │ │ └── route.ts
│ │ ├── create-zip/
│ │ │ └── route.ts
│ │ ├── detect-and-install-packages/
│ │ │ └── route.ts
│ │ ├── extract-brand-styles/
│ │ │ └── route.ts
│ │ ├── generate-ai-code-stream/
│ │ │ └── route.ts
│ │ ├── get-sandbox-files/
│ │ │ └── route.ts
│ │ ├── install-packages/
│ │ │ └── route.ts
│ │ ├── install-packages-v2/
│ │ │ └── route.ts
│ │ ├── kill-sandbox/
│ │ │ └── route.ts
│ │ ├── monitor-vite-logs/
│ │ │ └── route.ts
│ │ ├── report-vite-error/
│ │ │ └── route.ts
│ │ ├── restart-vite/
│ │ │ └── route.ts
│ │ ├── run-command/
│ │ │ └── route.ts
│ │ ├── run-command-v2/
│ │ │ └── route.ts
│ │ ├── sandbox-logs/
│ │ │ └── route.ts
│ │ ├── sandbox-status/
│ │ │ └── route.ts
│ │ ├── scrape-screenshot/
│ │ │ └── route.ts
│ │ ├── scrape-url-enhanced/
│ │ │ └── route.ts
│ │ ├── scrape-website/
│ │ │ └── route.ts
│ │ └── search/
│ │ └── route.ts
│ ├── builder/
│ │ └── page.tsx
│ ├── generation/
│ │ └── page.tsx
│ ├── globals.css
│ ├── landing.tsx
│ ├── layout.tsx
│ └── page.tsx
├── atoms/
│ └── sheets.ts
├── colors.json
├── components/
│ ├── CodeApplicationProgress.tsx
│ ├── FirecrawlIcon.tsx
│ ├── FirecrawlLogo.tsx
│ ├── HMRErrorDetector.tsx
│ ├── HeroInput.tsx
│ ├── SandboxPreview.tsx
│ ├── app/
│ │ ├── (home)/
│ │ │ └── sections/
│ │ │ ├── ai-readiness/
│ │ │ │ ├── ControlPanel.tsx
│ │ │ │ ├── InlineResults.tsx
│ │ │ │ ├── MetricBars.tsx
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── ScoreChart.tsx
│ │ │ ├── endpoints/
│ │ │ │ ├── EndpointsCrawl/
│ │ │ │ │ └── EndpointsCrawl.tsx
│ │ │ │ ├── EndpointsExtract/
│ │ │ │ │ └── EndpointsExtract.tsx
│ │ │ │ ├── EndpointsMap/
│ │ │ │ │ └── EndpointsMap.tsx
│ │ │ │ ├── EndpointsScrape/
│ │ │ │ │ └── EndpointsScrape.tsx
│ │ │ │ ├── EndpointsSearch/
│ │ │ │ │ └── EndpointsSearch.tsx
│ │ │ │ ├── Extract/
│ │ │ │ │ └── Extract.tsx
│ │ │ │ └── Mcp/
│ │ │ │ └── Mcp.tsx
│ │ │ ├── hero/
│ │ │ │ ├── Background/
│ │ │ │ │ ├── Background.tsx
│ │ │ │ │ ├── BackgroundOuterPiece.tsx
│ │ │ │ │ └── _svg/
│ │ │ │ │ └── CenterStar.tsx
│ │ │ │ ├── Badge/
│ │ │ │ │ └── Badge.tsx
│ │ │ │ ├── Hero.tsx
│ │ │ │ ├── Pixi/
│ │ │ │ │ ├── Pixi.tsx
│ │ │ │ │ └── tickers/
│ │ │ │ │ ├── ascii.ts
│ │ │ │ │ └── features/
│ │ │ │ │ ├── cell.ts
│ │ │ │ │ ├── cellReveal.ts
│ │ │ │ │ ├── components/
│ │ │ │ │ │ ├── AnimatedRect.ts
│ │ │ │ │ │ ├── BlinkingContainer.ts
│ │ │ │ │ │ └── Dot.ts
│ │ │ │ │ ├── crawl.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── mapping.ts
│ │ │ │ │ ├── scrape.ts
│ │ │ │ │ └── search.ts
│ │ │ │ └── Title/
│ │ │ │ └── Title.tsx
│ │ │ ├── hero-flame/
│ │ │ │ ├── HeroFlame.tsx
│ │ │ │ └── data.json
│ │ │ ├── hero-input/
│ │ │ │ ├── Button/
│ │ │ │ │ └── Button.tsx
│ │ │ │ ├── HeroInput.tsx
│ │ │ │ ├── Tabs/
│ │ │ │ │ ├── Mobile/
│ │ │ │ │ │ └── Mobile.tsx
│ │ │ │ │ └── Tabs.tsx
│ │ │ │ └── _svg/
│ │ │ │ ├── ArrowRight.tsx
│ │ │ │ └── Globe.tsx
│ │ │ └── hero-scraping/
│ │ │ ├── Code/
│ │ │ │ ├── Code.tsx
│ │ │ │ └── Loading/
│ │ │ │ ├── Loading.tsx
│ │ │ │ └── _svg/
│ │ │ │ └── Check.tsx
│ │ │ ├── HeroScraping.css
│ │ │ ├── HeroScraping.tsx
│ │ │ ├── Tag/
│ │ │ │ └── Tag.tsx
│ │ │ └── _svg/
│ │ │ ├── BrowserMobile.tsx
│ │ │ └── BrowserTab.tsx
│ │ ├── .cursor/
│ │ │ └── rules/
│ │ │ └── home-page-components.md
│ │ └── generation/
│ │ ├── SidebarInput.tsx
│ │ └── SidebarQuickInput.tsx
│ ├── shared/
│ │ ├── Playground/
│ │ │ └── Context/
│ │ │ └── types.ts
│ │ ├── animated-dot-icon.tsx
│ │ ├── animated-height.tsx
│ │ ├── ascii-background.tsx
│ │ ├── ascii-flame-background.tsx
│ │ ├── button/
│ │ │ ├── Button.css
│ │ │ └── Button.tsx
│ │ ├── buttons/
│ │ │ ├── capsule-button.tsx
│ │ │ ├── fire-action-link.tsx
│ │ │ ├── index.ts
│ │ │ └── slate-button.tsx
│ │ ├── color-styles/
│ │ │ └── color-styles.tsx
│ │ ├── combobox/
│ │ │ └── combobox.tsx
│ │ ├── effects/
│ │ │ ├── .cursor/
│ │ │ │ └── rules/
│ │ │ │ └── flame-effects.md
│ │ │ ├── flame/
│ │ │ │ ├── Flame.tsx
│ │ │ │ ├── ascii-explosion.tsx
│ │ │ │ ├── auth-pulse/
│ │ │ │ │ ├── auth-pulse.tsx
│ │ │ │ │ └── pulse-data.json
│ │ │ │ ├── core-flame.json
│ │ │ │ ├── core-flame.tsx
│ │ │ │ ├── explosion-data.json
│ │ │ │ ├── flame-background.tsx
│ │ │ │ ├── hero-flame-data.json
│ │ │ │ ├── hero-flame.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── slate-grid/
│ │ │ │ │ ├── grid-data.json
│ │ │ │ │ └── slate-grid.tsx
│ │ │ │ ├── subtle-explosion.tsx
│ │ │ │ └── subtle-wave/
│ │ │ │ ├── subtle-wave.tsx
│ │ │ │ └── wave-data.json
│ │ │ ├── index.ts
│ │ │ └── subtle-ascii-animation.tsx
│ │ ├── firecrawl-icon/
│ │ │ ├── firecrawl-icon-static.tsx
│ │ │ └── firecrawl-icon.tsx
│ │ ├── header/
│ │ │ ├── BrandKit/
│ │ │ │ ├── BrandKit.tsx
│ │ │ │ └── _svg/
│ │ │ │ ├── Download.tsx
│ │ │ │ ├── Guidelines.tsx
│ │ │ │ └── Icon.tsx
│ │ │ ├── Dropdown/
│ │ │ │ ├── Content/
│ │ │ │ │ ├── Content.tsx
│ │ │ │ │ └── NavItemRow.tsx
│ │ │ │ ├── Github/
│ │ │ │ │ ├── Flame/
│ │ │ │ │ │ ├── Flame.tsx
│ │ │ │ │ │ └── data.json
│ │ │ │ │ └── Github.tsx
│ │ │ │ ├── Mobile/
│ │ │ │ │ ├── Item/
│ │ │ │ │ │ └── Item.tsx
│ │ │ │ │ └── Mobile.tsx
│ │ │ │ ├── Stories/
│ │ │ │ │ ├── Flame/
│ │ │ │ │ │ └── Flame.tsx
│ │ │ │ │ ├── Stories.tsx
│ │ │ │ │ └── _svg/
│ │ │ │ │ ├── ArrowUp.tsx
│ │ │ │ │ └── Replit.tsx
│ │ │ │ └── Wrapper/
│ │ │ │ └── Wrapper.tsx
│ │ │ ├── Github/
│ │ │ │ ├── GithubClient.tsx
│ │ │ │ └── _svg/
│ │ │ │ └── GithubIcon.tsx
│ │ │ ├── HeaderContext.tsx
│ │ │ ├── Nav/
│ │ │ │ ├── Item/
│ │ │ │ │ ├── Item.tsx
│ │ │ │ │ └── _svg/
│ │ │ │ │ └── ChevronDown.tsx
│ │ │ │ ├── Nav.tsx
│ │ │ │ ├── RenderEndpointIcon.tsx
│ │ │ │ └── _svg/
│ │ │ │ ├── Affiliate.tsx
│ │ │ │ ├── Api.tsx
│ │ │ │ ├── ArrowRight.tsx
│ │ │ │ ├── Careers.tsx
│ │ │ │ ├── Changelog.tsx
│ │ │ │ ├── Chats.tsx
│ │ │ │ ├── Lead.tsx
│ │ │ │ ├── MCP.tsx
│ │ │ │ ├── Platforms.tsx
│ │ │ │ ├── Research.tsx
│ │ │ │ ├── Student.tsx
│ │ │ │ └── Templates.tsx
│ │ │ ├── Toggle/
│ │ │ │ └── Toggle.tsx
│ │ │ ├── Wrapper/
│ │ │ │ └── Wrapper.tsx
│ │ │ └── _svg/
│ │ │ └── Logo.tsx
│ │ ├── hero-flame.tsx
│ │ ├── icons/
│ │ │ ├── GitHub.tsx
│ │ │ ├── Logo.tsx
│ │ │ ├── animated-chevron.tsx
│ │ │ ├── animated-icons.tsx
│ │ │ ├── arrow-animated.tsx
│ │ │ ├── check.tsx
│ │ │ ├── chevron-slide.tsx
│ │ │ ├── copied.tsx
│ │ │ ├── copy.tsx
│ │ │ ├── curve.tsx
│ │ │ ├── fingerprint-icon.tsx
│ │ │ ├── openai.tsx
│ │ │ ├── source-icon.tsx
│ │ │ ├── symbol-colored.tsx
│ │ │ ├── symbol-white.tsx
│ │ │ ├── tremor-placeholder.tsx
│ │ │ ├── wordmark-colored.tsx
│ │ │ └── wordmark-white.tsx
│ │ ├── image/
│ │ │ ├── Image.tsx
│ │ │ └── getImageSrc.ts
│ │ ├── layout/
│ │ │ ├── animated-height.tsx
│ │ │ ├── animated-width.tsx
│ │ │ ├── curvy-rect-divider.tsx
│ │ │ └── curvy-rect.tsx
│ │ ├── loading/
│ │ │ ├── Shimmer.tsx
│ │ │ └── usage-loading.tsx
│ │ ├── lockBody.tsx
│ │ ├── logo-cloud/
│ │ │ ├── index.ts
│ │ │ ├── logo-cloud.tsx
│ │ │ └── logo-cloud2/
│ │ │ ├── Logocloud.css
│ │ │ └── Logocloud.tsx
│ │ ├── notifications/
│ │ │ └── slack-notification.tsx
│ │ ├── pixi/
│ │ │ ├── Pixi.tsx
│ │ │ ├── PixiAssetManager.ts
│ │ │ └── utils.ts
│ │ ├── portal-to-body/
│ │ │ └── PortalToBody.tsx
│ │ ├── preview/
│ │ │ ├── json-error-highlighter.tsx
│ │ │ ├── live-preview-frame.tsx
│ │ │ ├── multiple-web-browsers.tsx
│ │ │ └── web-browser.tsx
│ │ ├── pylon.tsx
│ │ ├── search-params-provider/
│ │ │ └── search-params-provider.tsx
│ │ ├── section-head/
│ │ │ ├── SectionHead.css
│ │ │ └── SectionHead.tsx
│ │ ├── section-title/
│ │ │ └── SectionTitle.tsx
│ │ ├── tabs/
│ │ │ └── Tabs.tsx
│ │ ├── ui/
│ │ │ ├── app-dialog.tsx
│ │ │ ├── ascii-dot-loader.tsx
│ │ │ ├── dot-grid-loader.tsx
│ │ │ ├── empty-state.tsx
│ │ │ ├── index.ts
│ │ │ ├── loading-state.tsx
│ │ │ ├── mobile-sheet.tsx
│ │ │ └── stat-card.tsx
│ │ └── utils/
│ │ └── portal-to-body.tsx
│ └── ui/
│ ├── button.tsx
│ ├── checkbox.tsx
│ ├── code.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── motion/
│ │ └── scramble-text.tsx
│ ├── select.tsx
│ ├── shadcn/
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── badge.tsx
│ │ ├── button.css
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── combobox.tsx
│ │ ├── context-menu.tsx
│ │ ├── data-table.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toggle.tsx
│ │ ├── tooltip-radix.tsx
│ │ └── tooltip.tsx
│ ├── spinner.tsx
│ └── textarea.tsx
├── config/
│ └── app.config.ts
├── eslint.config.mjs
├── hooks/
│ ├── useDebouncedCallback.ts
│ ├── useDebouncedEffect.ts
│ └── useSwitchingCode.ts
├── lib/
│ ├── ai/
│ │ └── provider-manager.ts
│ ├── build-validator.ts
│ ├── context-selector.ts
│ ├── edit-examples.ts
│ ├── edit-intent-analyzer.ts
│ ├── file-parser.ts
│ ├── file-search-executor.ts
│ ├── icons.ts
│ ├── morph-fast-apply.ts
│ ├── sandbox/
│ │ ├── factory.ts
│ │ ├── providers/
│ │ │ ├── e2b-provider.ts
│ │ │ └── vercel-provider.ts
│ │ ├── sandbox-manager.ts
│ │ └── types.ts
│ └── utils.ts
├── next.config.ts
├── package.json
├── packages/
│ └── create-open-lovable/
│ ├── index.js
│ ├── lib/
│ │ ├── installer.js
│ │ └── prompts.js
│ ├── package.json
│ └── templates/
│ ├── e2b/
│ │ ├── .env.example
│ │ └── README.md
│ └── vercel/
│ ├── .env.example
│ └── README.md
├── postcss.config.mjs
├── public/
│ ├── compressor.json
│ └── firecrawl-logo
├── styles/
│ ├── additional-styles/
│ │ ├── custom-fonts.css
│ │ ├── theme.css
│ │ └── utility-patterns.css
│ ├── chrome-bug.css
│ ├── colors.json
│ ├── components/
│ │ ├── .cursor/
│ │ │ └── rules/
│ │ │ └── component-styles.md
│ │ ├── button.css
│ │ ├── code.css
│ │ └── index.css
│ ├── design-system/
│ │ ├── .cursor/
│ │ │ └── rules/
│ │ │ └── design-system.md
│ │ ├── animations.css
│ │ ├── base/
│ │ │ ├── body.css
│ │ │ ├── layout.css
│ │ │ └── reset.css
│ │ ├── colors.css
│ │ ├── fonts.css
│ │ ├── typography.css
│ │ └── utilities.css
│ ├── fire.css
│ ├── inside-border-fix.css
│ └── main.css
├── tailwind.config.ts
├── tsconfig.json
├── types/
│ ├── conversation.ts
│ ├── file-manifest.ts
│ └── sandbox.ts
└── utils/
├── cn.ts
├── init-canvas.ts
├── set-timeout-on-visible.ts
└── sleep.ts
SYMBOL INDEX (453 symbols across 228 files)
FILE: app/api/analyze-edit-intent/route.ts
function POST (line 62) | async function POST(request: NextRequest) {
FILE: app/api/apply-ai-code-stream/route.ts
type ParsedResponse (line 15) | interface ParsedResponse {
function parseAIResponse (line 24) | function parseAIResponse(response: string): ParsedResponse {
function POST (line 264) | async function POST(request: NextRequest) {
FILE: app/api/apply-ai-code/route.ts
type ParsedResponse (line 10) | interface ParsedResponse {
function parseAIResponse (line 19) | function parseAIResponse(response: string): ParsedResponse {
function POST (line 137) | async function POST(request: NextRequest) {
FILE: app/api/check-vite-errors/route.ts
function GET (line 6) | async function GET() {
FILE: app/api/clear-vite-errors-cache/route.ts
function POST (line 7) | async function POST() {
FILE: app/api/conversation-state/route.ts
function GET (line 9) | async function GET() {
function POST (line 33) | async function POST(request: NextRequest) {
function DELETE (line 143) | async function DELETE() {
FILE: app/api/create-ai-sandbox-v2/route.ts
function POST (line 15) | async function POST() {
FILE: app/api/create-ai-sandbox/route.ts
function POST (line 16) | async function POST() {
function createSandboxInternal (line 64) | async function createSandboxInternal() {
FILE: app/api/create-zip/route.ts
function POST (line 7) | async function POST() {
FILE: app/api/detect-and-install-packages/route.ts
function POST (line 7) | async function POST(request: NextRequest) {
FILE: app/api/extract-brand-styles/route.ts
function POST (line 3) | async function POST(request: NextRequest) {
FILE: app/api/generate-ai-code-stream/route.ts
function analyzeUserPreferences (line 48) | function analyzeUserPreferences(messages: ConversationMessage[]): {
function POST (line 91) | async function POST(request: NextRequest) {
FILE: app/api/get-sandbox-files/route.ts
function GET (line 10) | async function GET() {
function extractRoutes (line 174) | function extractRoutes(files: Record<string, FileInfo>): RouteInfo[] {
FILE: app/api/install-packages-v2/route.ts
function POST (line 9) | async function POST(request: NextRequest) {
FILE: app/api/install-packages/route.ts
function POST (line 9) | async function POST(request: NextRequest) {
FILE: app/api/kill-sandbox/route.ts
function POST (line 9) | async function POST() {
FILE: app/api/monitor-vite-logs/route.ts
function GET (line 7) | async function GET() {
FILE: app/api/report-vite-error/route.ts
function POST (line 12) | async function POST(request: NextRequest) {
FILE: app/api/restart-vite/route.ts
constant RESTART_COOLDOWN_MS (line 10) | const RESTART_COOLDOWN_MS = 5000;
function POST (line 12) | async function POST() {
FILE: app/api/run-command-v2/route.ts
function POST (line 10) | async function POST(request: NextRequest) {
FILE: app/api/run-command/route.ts
function POST (line 8) | async function POST(request: NextRequest) {
FILE: app/api/sandbox-logs/route.ts
function GET (line 7) | async function GET() {
FILE: app/api/sandbox-status/route.ts
function GET (line 10) | async function GET() {
FILE: app/api/scrape-screenshot/route.ts
function POST (line 4) | async function POST(req: NextRequest) {
FILE: app/api/scrape-url-enhanced/route.ts
function sanitizeQuotes (line 4) | function sanitizeQuotes(text: string): string {
function POST (line 19) | async function POST(request: NextRequest) {
FILE: app/api/scrape-website/route.ts
function POST (line 4) | async function POST(request: NextRequest) {
function OPTIONS (line 101) | async function OPTIONS() {
FILE: app/api/search/route.ts
function POST (line 3) | async function POST(req: NextRequest) {
FILE: app/builder/page.tsx
function BuilderPage (line 7) | function BuilderPage() {
FILE: app/generation/page.tsx
type SandboxData (line 29) | interface SandboxData {
type ChatMessage (line 35) | interface ChatMessage {
type ScrapeData (line 50) | interface ScrapeData {
function AISandboxPage (line 63) | function AISandboxPage() {
function Page (line 3952) | function Page() {
FILE: app/landing.tsx
function LandingPage (line 25) | function LandingPage() {
FILE: app/layout.tsx
function RootLayout (line 33) | function RootLayout({
FILE: app/page.tsx
type SearchResult (line 32) | interface SearchResult {
function HomePage (line 40) | function HomePage() {
FILE: components/CodeApplicationProgress.tsx
type CodeApplicationState (line 4) | interface CodeApplicationState {
type CodeApplicationProgressProps (line 12) | interface CodeApplicationProgressProps {
function CodeApplicationProgress (line 16) | function CodeApplicationProgress({ state }: CodeApplicationProgressProps) {
FILE: components/FirecrawlIcon.tsx
function FirecrawlIcon (line 1) | function FirecrawlIcon({ className = "w-5 h-5" }: { className?: string }) {
FILE: components/FirecrawlLogo.tsx
function FirecrawlLogo (line 1) | function FirecrawlLogo() {
FILE: components/HMRErrorDetector.tsx
type HMRErrorDetectorProps (line 3) | interface HMRErrorDetectorProps {
function HMRErrorDetector (line 8) | function HMRErrorDetector({ iframeRef, onErrorDetected }: HMRErrorDetect...
FILE: components/HeroInput.tsx
type HeroInputProps (line 5) | interface HeroInputProps {
function isURL (line 14) | function isURL(str: string): boolean {
function HeroInput (line 20) | function HeroInput({
FILE: components/SandboxPreview.tsx
type SandboxPreviewProps (line 4) | interface SandboxPreviewProps {
function SandboxPreview (line 11) | function SandboxPreview({
FILE: components/app/(home)/sections/ai-readiness/ControlPanel.tsx
type ControlPanelProps (line 29) | interface ControlPanelProps {
type CheckItem (line 37) | interface CheckItem {
function ControlPanel (line 50) | function ControlPanel({
FILE: components/app/(home)/sections/ai-readiness/InlineResults.tsx
type InlineResultsProps (line 8) | interface InlineResultsProps {
function InlineResults (line 35) | function InlineResults({
FILE: components/app/(home)/sections/ai-readiness/MetricBars.tsx
type MetricBarsProps (line 7) | interface MetricBarsProps {
function MetricBars (line 19) | function MetricBars({ metrics }: MetricBarsProps) {
FILE: components/app/(home)/sections/ai-readiness/RadarChart.tsx
type RadarChartProps (line 6) | interface RadarChartProps {
function RadarChart (line 15) | function RadarChart({ data, size = 300 }: RadarChartProps) {
FILE: components/app/(home)/sections/ai-readiness/ScoreChart.tsx
type ScoreChartProps (line 6) | interface ScoreChartProps {
function ScoreChart (line 12) | function ScoreChart({ score, enhanced = false, size = 200 }: ScoreChartP...
FILE: components/app/(home)/sections/endpoints/EndpointsCrawl/EndpointsCrawl.tsx
function EndpointsCrawl (line 9) | function EndpointsCrawl({
FILE: components/app/(home)/sections/endpoints/EndpointsExtract/EndpointsExtract.tsx
function EndpointsExtract (line 9) | function EndpointsExtract({
FILE: components/app/(home)/sections/endpoints/EndpointsMap/EndpointsMap.tsx
function EndpointsMap (line 7) | function EndpointsMap(
FILE: components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape.tsx
function EndpointsScrape (line 9) | function EndpointsScrape({
FILE: components/app/(home)/sections/endpoints/EndpointsSearch/EndpointsSearch.tsx
function EndpointsSearch (line 7) | function EndpointsSearch({
FILE: components/app/(home)/sections/endpoints/Extract/Extract.tsx
function EndpointsExtract (line 8) | function EndpointsExtract({ size = 20 }: { size?: number }) {
FILE: components/app/(home)/sections/endpoints/Mcp/Mcp.tsx
function EndpointsMcp (line 9) | function EndpointsMcp({
FILE: components/app/(home)/sections/hero-flame/HeroFlame.tsx
function HeroFlame (line 9) | function HeroFlame() {
FILE: components/app/(home)/sections/hero-input/Button/Button.tsx
function HeroInputSubmitButton (line 7) | function HeroInputSubmitButton({
FILE: components/app/(home)/sections/hero-input/HeroInput.tsx
function HeroInput (line 13) | function HeroInput() {
FILE: components/app/(home)/sections/hero-input/Tabs/Mobile/Mobile.tsx
function HeroInputTabsMobile (line 8) | function HeroInputTabsMobile(props: {
function MenuItems (line 103) | function MenuItems(props: {
FILE: components/app/(home)/sections/hero-input/Tabs/Tabs.tsx
function HeroInputTabs (line 57) | function HeroInputTabs(props: {
FILE: components/app/(home)/sections/hero-input/_svg/ArrowRight.tsx
function ArrowRight (line 1) | function ArrowRight() {
FILE: components/app/(home)/sections/hero-input/_svg/Globe.tsx
function Globe (line 1) | function Globe() {
FILE: components/app/(home)/sections/hero-scraping/Code/Code.tsx
constant URL (line 9) | const URL = {
constant MARKDOWN (line 13) | const MARKDOWN = {
constant TITLE (line 17) | const TITLE = {
constant SCREENSHOT (line 21) | const SCREENSHOT = {
function HeroScrapingCode (line 26) | function HeroScrapingCode({ step }: { step: number }) {
FILE: components/app/(home)/sections/hero-scraping/Code/Loading/Loading.tsx
function HeroScrapingCodeLoading (line 8) | function HeroScrapingCodeLoading({
FILE: components/app/(home)/sections/hero-scraping/Code/Loading/_svg/Check.tsx
function Check (line 1) | function Check() {
FILE: components/app/(home)/sections/hero-scraping/HeroScraping.tsx
function HeroScraping (line 16) | function HeroScraping() {
FILE: components/app/(home)/sections/hero-scraping/Tag/Tag.tsx
function HeroScrapingTag (line 7) | function HeroScrapingTag({
FILE: components/app/(home)/sections/hero-scraping/_svg/BrowserMobile.tsx
function BrowserMobile (line 1) | function BrowserMobile(props: React.SVGProps<SVGSVGElement>) {
FILE: components/app/(home)/sections/hero-scraping/_svg/BrowserTab.tsx
function BrowserTab (line 3) | function BrowserTab(attrs: HTMLAttributes<SVGSVGElement>) {
FILE: components/app/(home)/sections/hero/Background/Background.tsx
function HomeHeroBackground (line 9) | function HomeHeroBackground() {
FILE: components/app/(home)/sections/hero/Background/_svg/CenterStar.tsx
function CenterStar (line 1) | function CenterStar({
FILE: components/app/(home)/sections/hero/Badge/Badge.tsx
function HomeHeroBadge (line 3) | function HomeHeroBadge() {
FILE: components/app/(home)/sections/hero/Hero.tsx
function HomeHero (line 14) | function HomeHero() {
FILE: components/app/(home)/sections/hero/Pixi/Pixi.tsx
function PixiContent (line 8) | function PixiContent() {
function HomeHeroPixi (line 22) | function HomeHeroPixi() {
FILE: components/app/(home)/sections/hero/Pixi/tickers/ascii.ts
constant ASCII_CHARS (line 7) | const ASCII_CHARS = ' .":,-_^=+';
function getAsciiChar (line 9) | function getAsciiChar(luminance: number) {
function sprinkleAscii (line 24) | function sprinkleAscii(line: string) {
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/cell.ts
type Props (line 10) | type Props = Parameters<Ticker>[0] & {
constant CELL_SIZE (line 15) | const CELL_SIZE = 80;
constant MAIN_COLOR (line 17) | const MAIN_COLOR = 0xe6e6e6;
function cell (line 23) | function cell(props: Props) {
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/cellReveal.ts
type Props (line 4) | type Props = Parameters<Ticker>[0] & {
function cellReveal (line 9) | function cellReveal(props: Props) {
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/components/AnimatedRect.ts
type Props (line 7) | type Props = {
type IAnimatedRect (line 24) | type IAnimatedRect = ReturnType<typeof AnimatedRect>;
function AnimatedRect (line 26) | function AnimatedRect(props: Props) {
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/components/BlinkingContainer.ts
type IBlinkingContainer (line 8) | type IBlinkingContainer = ReturnType<typeof BlinkingContainer>;
function BlinkingContainer (line 10) | function BlinkingContainer({
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/components/Dot.ts
function Dot (line 5) | function Dot(
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/crawl.ts
type Props (line 11) | type Props = Parameters<Ticker>[0] & {
function crawl (line 18) | async function crawl(props: Props) {
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/index.ts
constant CELL_GRID (line 7) | const CELL_GRID = [
constant REVEAL_ANIMATION_GRID (line 15) | const REVEAL_ANIMATION_GRID = [
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/mapping.ts
type Props (line 9) | type Props = Parameters<Ticker>[0] & {
function mapping (line 16) | async function mapping(props: Props) {
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/scrape.ts
type Props (line 9) | type Props = Parameters<Ticker>[0] & {
function scrape (line 16) | async function scrape(props: Props) {
FILE: components/app/(home)/sections/hero/Pixi/tickers/features/search.ts
type Props (line 9) | type Props = Parameters<Ticker>[0] & {
function search (line 16) | async function search(props: Props) {
FILE: components/app/(home)/sections/hero/Title/Title.tsx
type Options (line 9) | type Options = {
function HomeHeroTitle (line 257) | function HomeHeroTitle() {
FILE: components/app/generation/SidebarInput.tsx
type SidebarInputProps (line 7) | interface SidebarInputProps {
function SidebarInput (line 12) | function SidebarInput({ onSubmit, disabled = false }: SidebarInputProps) {
FILE: components/app/generation/SidebarQuickInput.tsx
type SidebarQuickInputProps (line 6) | interface SidebarQuickInputProps {
function SidebarQuickInput (line 11) | function SidebarQuickInput({ onSubmit, disabled = false }: SidebarQuickI...
FILE: components/shared/Playground/Context/types.ts
type Endpoint (line 1) | enum Endpoint {
type AgentModel (line 9) | enum AgentModel {
type FormatType (line 13) | enum FormatType {
type SearchFormatType (line 24) | enum SearchFormatType {
type Prev (line 30) | type Prev = [never, 0, 1, 2, 3, 4, 5];
type Join (line 32) | type Join<K, P> = K extends string | number
type Paths (line 38) | type Paths<T, D extends number = 5> = [D] extends [never]
FILE: components/shared/animated-dot-icon.tsx
type AnimatedDotIconProps (line 7) | interface AnimatedDotIconProps {
function AnimatedDotIcon (line 225) | function AnimatedDotIcon({
FILE: components/shared/animated-height.tsx
function AnimatedHeight (line 9) | function AnimatedHeight({
FILE: components/shared/ascii-background.tsx
type AsciiBackgroundProps (line 29) | interface AsciiBackgroundProps {
function AsciiBackground (line 34) | function AsciiBackground({
FILE: components/shared/ascii-flame-background.tsx
type AsciiFlameBackgroundProps (line 8) | interface AsciiFlameBackgroundProps {
function AsciiFlameBackground (line 16) | function AsciiFlameBackground({
FILE: components/shared/button/Button.tsx
type Props (line 5) | interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
function Button (line 11) | function Button({
FILE: components/shared/buttons/capsule-button.tsx
type CapsuleButtonProps (line 9) | interface CapsuleButtonProps
function CapsuleButton (line 20) | function CapsuleButton({
FILE: components/shared/buttons/fire-action-link.tsx
type FireActionLinkProps (line 4) | interface FireActionLinkProps {
function FireActionLink (line 12) | function FireActionLink({
FILE: components/shared/buttons/slate-button.tsx
type SlateButtonProps (line 7) | interface SlateButtonProps
FILE: components/shared/color-styles/color-styles.tsx
constant TYPED_COLORS (line 3) | const TYPED_COLORS = colors as unknown as Record<
function ColorStyles (line 29) | function ColorStyles() {
FILE: components/shared/combobox/combobox.tsx
function Combobox (line 8) | function Combobox({
FILE: components/shared/effects/flame/Flame.tsx
function CoreFlame (line 10) | function CoreFlame(attrs: HTMLAttributes<HTMLDivElement>) {
FILE: components/shared/effects/flame/ascii-explosion.tsx
function AsciiExplosion (line 10) | function AsciiExplosion(attrs: HTMLAttributes<HTMLDivElement>) {
FILE: components/shared/effects/flame/auth-pulse/auth-pulse.tsx
type AuthPulseProps (line 8) | interface AuthPulseProps extends HTMLAttributes<HTMLDivElement> {
function AuthPulse (line 13) | function AuthPulse({
FILE: components/shared/effects/flame/core-flame.tsx
function CoreFlame (line 8) | function CoreFlame(attrs: HTMLAttributes<HTMLDivElement>) {
FILE: components/shared/effects/flame/flame-background.tsx
type FlameBackgroundProps (line 7) | interface FlameBackgroundProps {
function FlameBackground (line 14) | function FlameBackground({
FILE: components/shared/effects/flame/hero-flame.tsx
function HeroFlame (line 8) | function HeroFlame() {
FILE: components/shared/effects/flame/slate-grid/slate-grid.tsx
type SlateGridProps (line 8) | interface SlateGridProps extends HTMLAttributes<HTMLDivElement> {
function SlateGrid (line 13) | function SlateGrid({
FILE: components/shared/effects/flame/subtle-explosion.tsx
type SubtleExplosionProps (line 8) | interface SubtleExplosionProps extends HTMLAttributes<HTMLDivElement> {
function SubtleExplosion (line 14) | function SubtleExplosion({
FILE: components/shared/effects/flame/subtle-wave/subtle-wave.tsx
function SubtleWave (line 7) | function SubtleWave({ className = "" }: { className?: string }) {
FILE: components/shared/effects/subtle-ascii-animation.tsx
function SubtleAsciiAnimation (line 6) | function SubtleAsciiAnimation({
FILE: components/shared/firecrawl-icon/firecrawl-icon-static.tsx
function FirecrawlIconStatic (line 3) | function FirecrawlIconStatic({
FILE: components/shared/firecrawl-icon/firecrawl-icon.tsx
function FirecrawlIcon (line 3) | function FirecrawlIcon({
FILE: components/shared/header/BrandKit/BrandKit.tsx
function HeaderBrandKit (line 18) | function HeaderBrandKit() {
FILE: components/shared/header/BrandKit/_svg/Download.tsx
function Download (line 1) | function Download() {
FILE: components/shared/header/BrandKit/_svg/Guidelines.tsx
function Guidelines (line 1) | function Guidelines() {
FILE: components/shared/header/BrandKit/_svg/Icon.tsx
function Icon (line 1) | function Icon() {
FILE: components/shared/header/Dropdown/Content/Content.tsx
type Props (line 5) | interface Props {
function HeaderDropdownContent (line 31) | function HeaderDropdownContent({
FILE: components/shared/header/Dropdown/Content/NavItemRow.tsx
type NavItemRowProps (line 4) | interface NavItemRowProps {
function NavItemRow (line 14) | function NavItemRow({
type NavItemRowBigProps (line 45) | interface NavItemRowBigProps extends Omit<NavItemRowProps, "target"> {
function NavItemRowBig (line 49) | function NavItemRowBig({
FILE: components/shared/header/Dropdown/Github/Flame/Flame.tsx
function GithubFlame (line 10) | function GithubFlame(attrs: HTMLAttributes<HTMLDivElement>) {
FILE: components/shared/header/Dropdown/Github/Github.tsx
function HeaderDropdownGithub (line 5) | function HeaderDropdownGithub() {
FILE: components/shared/header/Dropdown/Mobile/Item/Item.tsx
function HeaderDropdownMobileItem (line 12) | function HeaderDropdownMobileItem({
FILE: components/shared/header/Dropdown/Mobile/Mobile.tsx
function HeaderDropdownMobile (line 15) | function HeaderDropdownMobile({
FILE: components/shared/header/Dropdown/Stories/Flame/Flame.tsx
function StoriesFlame (line 9) | function StoriesFlame(attrs: HTMLAttributes<HTMLDivElement>) {
FILE: components/shared/header/Dropdown/Stories/Stories.tsx
function HeaderDropdownStories (line 5) | function HeaderDropdownStories() {
FILE: components/shared/header/Dropdown/Stories/_svg/ArrowUp.tsx
function ArrowUp (line 1) | function ArrowUp() {
FILE: components/shared/header/Dropdown/Stories/_svg/Replit.tsx
function Replit (line 1) | function Replit() {
FILE: components/shared/header/Dropdown/Wrapper/Wrapper.tsx
function HeaderDropdownWrapper (line 10) | function HeaderDropdownWrapper() {
FILE: components/shared/header/Github/GithubClient.tsx
function HeaderGithubClient (line 6) | function HeaderGithubClient() {
FILE: components/shared/header/Github/_svg/GithubIcon.tsx
function GithubIcon (line 1) | function GithubIcon() {
FILE: components/shared/header/HeaderContext.tsx
type HeaderContextType (line 12) | interface HeaderContextType {
FILE: components/shared/header/Nav/Item/Item.tsx
function HeaderNavItem (line 10) | function HeaderNavItem({
FILE: components/shared/header/Nav/Item/_svg/ChevronDown.tsx
function ChevronDown (line 1) | function ChevronDown() {
FILE: components/shared/header/Nav/Nav.tsx
function HeaderNav (line 28) | function HeaderNav() {
constant NAV_ITEMS (line 38) | const NAV_ITEMS = [
FILE: components/shared/header/Nav/_svg/Affiliate.tsx
function Affiliate (line 1) | function Affiliate() {
FILE: components/shared/header/Nav/_svg/Api.tsx
function Api (line 1) | function Api() {
FILE: components/shared/header/Nav/_svg/ArrowRight.tsx
function ArrowRight (line 1) | function ArrowRight() {
FILE: components/shared/header/Nav/_svg/Careers.tsx
function Careers (line 1) | function Careers() {
FILE: components/shared/header/Nav/_svg/Changelog.tsx
function Changelog (line 1) | function Changelog() {
FILE: components/shared/header/Nav/_svg/Chats.tsx
function Ai (line 1) | function Ai() {
FILE: components/shared/header/Nav/_svg/Lead.tsx
function Lead (line 1) | function Lead() {
FILE: components/shared/header/Nav/_svg/MCP.tsx
function MCPIcon (line 1) | function MCPIcon() {
FILE: components/shared/header/Nav/_svg/Platforms.tsx
function Platforms (line 1) | function Platforms() {
FILE: components/shared/header/Nav/_svg/Research.tsx
function Research (line 1) | function Research() {
FILE: components/shared/header/Nav/_svg/Student.tsx
function Student (line 1) | function Student() {
FILE: components/shared/header/Nav/_svg/Templates.tsx
function Templates (line 1) | function Templates() {
FILE: components/shared/header/Toggle/Toggle.tsx
function HeaderToggle (line 7) | function HeaderToggle({
FILE: components/shared/header/Wrapper/Wrapper.tsx
function HeaderWrapper (line 8) | function HeaderWrapper({
FILE: components/shared/header/_svg/Logo.tsx
function Logo (line 1) | function Logo() {
FILE: components/shared/hero-flame.tsx
type HeroFlameProps (line 66) | interface HeroFlameProps {
function HeroFlame (line 71) | function HeroFlame({ className, size = "medium" }: HeroFlameProps) {
FILE: components/shared/icons/animated-chevron.tsx
type AnimatedChevronProps (line 8) | interface AnimatedChevronProps {
function AnimatedChevron (line 14) | function AnimatedChevron({
FILE: components/shared/icons/arrow-animated.tsx
function ArrowAnimated (line 3) | function ArrowAnimated({
FILE: components/shared/icons/check.tsx
function Check (line 1) | function Check() {
FILE: components/shared/icons/chevron-slide.tsx
type Direction (line 6) | type Direction = "left" | "right";
type ChevronSlideProps (line 8) | interface ChevronSlideProps extends React.SVGAttributes<SVGElement> {
function ChevronSlide (line 13) | function ChevronSlide({
FILE: components/shared/icons/copied.tsx
function CopiedIcon (line 1) | function CopiedIcon() {
FILE: components/shared/icons/copy.tsx
function CopyIcon (line 1) | function CopyIcon() {
FILE: components/shared/icons/curve.tsx
type CurveProps (line 1) | interface CurveProps extends React.SVGAttributes<SVGSVGElement> {
function Curve (line 5) | function Curve({
FILE: components/shared/icons/fingerprint-icon.tsx
type FingerprintIconHandle (line 15) | interface FingerprintIconHandle {
type FingerprintIconProps (line 20) | interface FingerprintIconProps extends HTMLAttributes<HTMLDivElement> {
FILE: components/shared/icons/openai.tsx
function IconOpenai (line 3) | function IconOpenai(props: React.SVGProps<SVGSVGElement>) {
FILE: components/shared/image/Image.tsx
type Props (line 6) | interface Props extends ComponentProps<"img"> {
constant BASE_SRC (line 12) | const BASE_SRC = "/assets/";
constant RAW_SRC (line 13) | const RAW_SRC = "/assets-original/";
function Image (line 15) | function Image({ src, raw, ...attrs }: Props) {
FILE: components/shared/image/getImageSrc.ts
function getImageSrc (line 10) | async function getImageSrc(src: string) {
function supportsEncode (line 22) | async function supportsEncode() {
FILE: components/shared/layout/animated-height.tsx
type AnimatedHeight (line 8) | type AnimatedHeight = {
function AnimatedHeight (line 17) | function AnimatedHeight({ children, ...attrs }: AnimatedHeight) {
FILE: components/shared/layout/animated-width.tsx
type AnimatedWidthProps (line 6) | type AnimatedWidthProps = {
function AnimatedWidth (line 13) | function AnimatedWidth({
FILE: components/shared/layout/curvy-rect-divider.tsx
function CurvyRectDivider (line 5) | function CurvyRectDivider() {
FILE: components/shared/layout/curvy-rect.tsx
type CurvyRectProps (line 5) | interface CurvyRectProps extends React.HTMLAttributes<HTMLDivElement> {
function CurvyRect (line 22) | function CurvyRect({
FILE: components/shared/loading/usage-loading.tsx
function UsageLoadingText (line 6) | function UsageLoadingText({ text = "Loading..." }: { text?: string }) {
FILE: components/shared/lockBody.tsx
function lockBody (line 11) | function lockBody(
FILE: components/shared/logo-cloud/logo-cloud.tsx
function LogoCloud (line 1) | function LogoCloud() {
FILE: components/shared/logo-cloud/logo-cloud2/Logocloud.tsx
function Logocloud (line 5) | function Logocloud() {
FILE: components/shared/notifications/slack-notification.tsx
function SlackNotification (line 6) | function SlackNotification({
FILE: components/shared/pixi/Pixi.tsx
type TickerResult (line 12) | type TickerResult = void;
type Ticker (line 14) | type Ticker = ({
type PixiProps (line 22) | interface PixiProps {
function Pixi (line 33) | function Pixi({
FILE: components/shared/pixi/PixiAssetManager.ts
class PixiAssetManager (line 3) | class PixiAssetManager {
method load (line 9) | public static load<T = any>(...sources: string[]): Promise<T> {
FILE: components/shared/portal-to-body/PortalToBody.tsx
function PortalToBody (line 4) | function PortalToBody({ children }: { children: React.ReactNode }) {
FILE: components/shared/preview/json-error-highlighter.tsx
function JsonErrorHighlighter (line 4) | function JsonErrorHighlighter({
FILE: components/shared/preview/live-preview-frame.tsx
function LivePreviewFrame (line 5) | function LivePreviewFrame({
FILE: components/shared/preview/multiple-web-browsers.tsx
type BrowserData (line 8) | type BrowserData = {
function MultipleWebBrowsers (line 15) | function MultipleWebBrowsers({
FILE: components/shared/preview/web-browser.tsx
function WebBrowser (line 4) | function WebBrowser({
FILE: components/shared/pylon.tsx
type PylonChatProps (line 8) | interface PylonChatProps {
FILE: components/shared/search-params-provider/search-params-provider.tsx
type SearchParamsContextType (line 5) | type SearchParamsContextType = { [key: string]: string | string[] | unde...
FILE: components/shared/section-head/SectionHead.tsx
type SectionHeadProps (line 7) | type SectionHeadProps = {
function SectionHead (line 21) | function SectionHead({
FILE: components/shared/section-title/SectionTitle.tsx
function SectionTitle (line 7) | function SectionTitle({
FILE: components/shared/tabs/Tabs.tsx
type Props (line 7) | type Props = {
function Tabs (line 23) | function Tabs({
FILE: components/shared/ui/app-dialog.tsx
type AppDialogContentProps (line 20) | type AppDialogContentProps = React.ComponentPropsWithoutRef<
function AppDialogContent (line 27) | function AppDialogContent({
FILE: components/shared/ui/ascii-dot-loader.tsx
type AsciiDotLoaderProps (line 6) | interface AsciiDotLoaderProps {
function AsciiDotLoader (line 14) | function AsciiDotLoader({
FILE: components/shared/ui/dot-grid-loader.tsx
type DotGridLoaderProps (line 6) | interface DotGridLoaderProps {
function DotGridLoader (line 15) | function DotGridLoader({
FILE: components/shared/ui/empty-state.tsx
type EmptyStateProps (line 7) | interface EmptyStateProps {
function EmptyState (line 16) | function EmptyState({
FILE: components/shared/ui/loading-state.tsx
type LoadingStateProps (line 7) | interface LoadingStateProps {
function LoadingState (line 14) | function LoadingState({
FILE: components/shared/ui/mobile-sheet.tsx
type MobileSheetProps (line 10) | interface MobileSheetProps {
function useAutoCloseOnDesktop (line 25) | function useAutoCloseOnDesktop(isOpen: boolean, onClose: () => void) {
function MobileSheet (line 47) | function MobileSheet({
FILE: components/shared/ui/stat-card.tsx
type StatCardProps (line 7) | interface StatCardProps {
function StatCard (line 18) | function StatCard({
FILE: components/shared/utils/portal-to-body.tsx
function PortalToBody (line 4) | function PortalToBody({ children }: { children: React.ReactNode }) {
FILE: components/ui/button.tsx
type ButtonProps (line 32) | interface ButtonProps
FILE: components/ui/checkbox.tsx
type CheckboxProps (line 6) | interface CheckboxProps {
FILE: components/ui/code.tsx
type CodeProps (line 3) | interface CodeProps {
function Code (line 9) | function Code({ code, language = 'json', className = '' }: CodeProps) {
FILE: components/ui/input.tsx
type InputProps (line 5) | type InputProps = React.InputHTMLAttributes<HTMLInputElement>
FILE: components/ui/motion/scramble-text.tsx
type ScrambleTextProps (line 3) | interface ScrambleTextProps {
function ScrambleText (line 11) | function ScrambleText({ text, className = '', duration = 1, delay = 0, i...
FILE: components/ui/select.tsx
type SelectProps (line 5) | type SelectProps = React.SelectHTMLAttributes<HTMLSelectElement>
FILE: components/ui/shadcn/badge.tsx
function Badge (line 6) | function Badge({ ...attrs }: HTMLAttributes<HTMLDivElement>) {
FILE: components/ui/shadcn/button.tsx
type Props (line 7) | interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
FILE: components/ui/shadcn/checkbox.tsx
function Checkbox (line 5) | function Checkbox({
FILE: components/ui/shadcn/combobox.tsx
function Combobox (line 8) | function Combobox({
FILE: components/ui/shadcn/form.tsx
type FormFieldContextValue (line 20) | type FormFieldContextValue<
type FormItemContextValue (line 67) | type FormItemContextValue = {
FILE: components/ui/shadcn/sheet.tsx
type SheetContentProps (line 52) | interface SheetContentProps
FILE: components/ui/shadcn/switch.tsx
type SwitchProps (line 40) | interface SwitchProps
FILE: components/ui/shadcn/textarea.tsx
function Textarea (line 3) | function Textarea(
FILE: components/ui/shadcn/toast.tsx
type ToasterProps (line 6) | type ToasterProps = React.ComponentProps<typeof Sonner>;
FILE: components/ui/shadcn/toggle.tsx
function Toggle (line 6) | function Toggle({
FILE: components/ui/shadcn/tooltip.tsx
function Tooltip (line 10) | function Tooltip({
FILE: components/ui/spinner.tsx
type SpinnerProps (line 3) | interface SpinnerProps {
function Spinner (line 9) | function Spinner({ className = '', size = 'md', finished = false }: Spin...
FILE: components/ui/textarea.tsx
type TextareaProps (line 5) | type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
FILE: config/app.config.ts
method timeoutMs (line 11) | get timeoutMs() {
method timeoutMs (line 37) | get timeoutMs() {
function getConfig (line 187) | function getConfig<K extends keyof typeof appConfig>(key: K): typeof app...
function getConfigValue (line 192) | function getConfigValue(path: string): any {
FILE: hooks/useDebouncedCallback.ts
constant DEFAULT_CONFIG (line 5) | const DEFAULT_CONFIG = { timeout: 0 };
function useDebouncedCallback (line 7) | function useDebouncedCallback<T extends (...args: any[]) => any>(
FILE: hooks/useDebouncedEffect.ts
constant DEFAULT_CONFIG (line 5) | const DEFAULT_CONFIG = {
function useDebouncedEffect (line 10) | function useDebouncedEffect(
FILE: hooks/useSwitchingCode.ts
function useSwitchingCode (line 6) | function useSwitchingCode(code: string, ms = 20, progress = 1, fill = tr...
FILE: lib/ai/provider-manager.ts
type ProviderName (line 7) | type ProviderName = 'openai' | 'anthropic' | 'groq' | 'google';
type ProviderClient (line 10) | type ProviderClient =
type ProviderResolution (line 16) | interface ProviderResolution {
function getEnvDefaults (line 28) | function getEnvDefaults(provider: ProviderName): { apiKey?: string; base...
function getOrCreateClient (line 48) | function getOrCreateClient(provider: ProviderName, apiKey?: string, base...
function getProviderForModel (line 79) | function getProviderForModel(modelId: string): ProviderResolution {
FILE: lib/build-validator.ts
type BuildValidation (line 1) | interface BuildValidation {
function validateBuild (line 12) | async function validateBuild(sandboxUrl: string, sandboxId: string): Pro...
function extractMissingPackages (line 86) | function extractMissingPackages(error: any): string[] {
type ErrorType (line 114) | type ErrorType = 'missing-package' | 'syntax-error' | 'sandbox-timeout' ...
function classifyError (line 116) | function classifyError(error: any): ErrorType {
function calculateRetryDelay (line 154) | function calculateRetryDelay(attempt: number, errorType: ErrorType): num...
FILE: lib/context-selector.ts
type FileContext (line 5) | interface FileContext {
function selectFilesForEdit (line 15) | function selectFilesForEdit(
function buildSystemPrompt (line 76) | function buildSystemPrompt(
function buildFileStructureSection (line 138) | function buildFileStructureSection(manifest: FileManifest): string {
function buildEditInstructions (line 188) | function buildEditInstructions(editType: EditType): string {
function buildComponentRelationships (line 253) | function buildComponentRelationships(
function getFileContents (line 289) | async function getFileContents(
function formatFilesForAI (line 308) | function formatFilesForAI(
function getFileExtension (line 352) | function getFileExtension(path: string): string {
FILE: lib/edit-examples.ts
constant EDIT_EXAMPLES (line 5) | const EDIT_EXAMPLES = `
function getEditExamplesPrompt (line 228) | function getEditExamplesPrompt(): string {
function getComponentPatternPrompt (line 232) | function getComponentPatternPrompt(fileStructure: string): string {
FILE: lib/edit-intent-analyzer.ts
function analyzeEditIntent (line 6) | function analyzeEditIntent(
function findComponentFiles (line 124) | function findComponentFiles(prompt: string, manifest: FileManifest): str...
function findFeatureInsertionPoints (line 188) | function findFeatureInsertionPoints(prompt: string, manifest: FileManife...
function findProblemFiles (line 244) | function findProblemFiles(prompt: string, manifest: FileManifest): strin...
function findStyleFiles (line 267) | function findStyleFiles(prompt: string, manifest: FileManifest): string[] {
function findRefactorTargets (line 289) | function findRefactorTargets(prompt: string, manifest: FileManifest): st...
function findPackageFiles (line 297) | function findPackageFiles(manifest: FileManifest): string[] {
function findComponentByContent (line 314) | function findComponentByContent(prompt: string, manifest: FileManifest):...
function extractComponentNames (line 363) | function extractComponentNames(prompt: string): string[] {
function getSuggestedContext (line 386) | function getSuggestedContext(
function resolveImportPath (line 398) | function resolveImportPath(
function resolveRelativePath (line 436) | function resolveRelativePath(fromDir: string, relativePath: string): str...
function calculateConfidence (line 454) | function calculateConfidence(
function generateDescription (line 485) | function generateDescription(
FILE: lib/file-parser.ts
function parseJavaScriptFile (line 6) | function parseJavaScriptFile(content: string, filePath: string): Partial...
function extractImports (line 23) | function extractImports(content: string): ImportInfo[] {
function extractExports (line 64) | function extractExports(content: string): string[] {
function extractComponentInfo (line 104) | function extractComponentInfo(content: string, filePath: string): Compon...
function determineFileType (line 170) | function determineFileType(
function buildComponentTree (line 224) | function buildComponentTree(files: Record<string, FileInfo>) {
FILE: lib/file-search-executor.ts
type SearchResult (line 6) | interface SearchResult {
type SearchPlan (line 17) | interface SearchPlan {
type SearchExecutionResult (line 30) | interface SearchExecutionResult {
function executeSearchPlan (line 42) | function executeSearchPlan(
function formatSearchResultsForAI (line 169) | function formatSearchResultsForAI(results: SearchResult[]): string {
function selectTargetFile (line 226) | function selectTargetFile(
FILE: lib/morph-fast-apply.ts
type MorphEditBlock (line 3) | interface MorphEditBlock {
type MorphApplyResult (line 9) | interface MorphApplyResult {
function normalizeProjectPath (line 17) | function normalizeProjectPath(inputPath: string): { normalizedPath: stri...
function morphChatCompletionsCreate (line 42) | async function morphChatCompletionsCreate(payload: any) {
function parseMorphEdits (line 60) | function parseMorphEdits(text: string): MorphEditBlock[] {
function readFileFromSandbox (line 79) | async function readFileFromSandbox(sandbox: any, normalizedPath: string,...
function writeFileToSandbox (line 119) | async function writeFileToSandbox(sandbox: any, normalizedPath: string, ...
function applyMorphEditToFile (line 180) | async function applyMorphEditToFile(params: {
FILE: lib/sandbox/factory.ts
class SandboxFactory (line 5) | class SandboxFactory {
method create (line 6) | static create(provider?: string, config?: SandboxProviderConfig): Sand...
method getAvailableProviders (line 23) | static getAvailableProviders(): string[] {
method isProviderAvailable (line 27) | static isProviderAvailable(provider: string): boolean {
FILE: lib/sandbox/providers/e2b-provider.ts
class E2BProvider (line 6) | class E2BProvider extends SandboxProvider {
method reconnect (line 12) | async reconnect(sandboxId: string): Promise<boolean> {
method createSandbox (line 27) | async createSandbox(): Promise<SandboxInfo> {
method runCommand (line 73) | async runCommand(command: string): Promise<CommandResult> {
method writeFile (line 108) | async writeFile(path: string, content: string): Promise<void> {
method readFile (line 139) | async readFile(path: string): Promise<string> {
method listFiles (line 155) | async listFiles(directory: string = '/home/user/app'): Promise<string[...
method installPackages (line 185) | async installPackages(packages: string[]): Promise<CommandResult> {
method setupViteApp (line 231) | async setupViteApp(): Promise<void> {
method restartViteServer (line 454) | async restartViteServer(): Promise<void> {
method getSandboxUrl (line 489) | getSandboxUrl(): string | null {
method getSandboxInfo (line 493) | getSandboxInfo(): SandboxInfo | null {
method terminate (line 497) | async terminate(): Promise<void> {
method isAlive (line 509) | isAlive(): boolean {
FILE: lib/sandbox/providers/vercel-provider.ts
class VercelProvider (line 5) | class VercelProvider extends SandboxProvider {
method createSandbox (line 8) | async createSandbox(): Promise<SandboxInfo> {
method runCommand (line 64) | async runCommand(command: string): Promise<CommandResult> {
method writeFile (line 124) | async writeFile(path: string, content: string): Promise<void> {
method readFile (line 191) | async readFile(path: string): Promise<string> {
method listFiles (line 235) | async listFiles(directory: string = '/vercel/sandbox'): Promise<string...
method installPackages (line 266) | async installPackages(packages: string[]): Promise<CommandResult> {
method setupViteApp (line 325) | async setupViteApp(): Promise<void> {
method restartViteServer (line 547) | async restartViteServer(): Promise<void> {
method getSandboxUrl (line 577) | getSandboxUrl(): string | null {
method getSandboxInfo (line 581) | getSandboxInfo(): SandboxInfo | null {
method terminate (line 585) | async terminate(): Promise<void> {
method isAlive (line 597) | isAlive(): boolean {
FILE: lib/sandbox/sandbox-manager.ts
type SandboxInfo (line 4) | interface SandboxInfo {
class SandboxManager (line 11) | class SandboxManager {
method getOrCreateProvider (line 18) | async getOrCreateProvider(sandboxId: string): Promise<SandboxProvider> {
method registerSandbox (line 59) | registerSandbox(sandboxId: string, provider: SandboxProvider): void {
method getActiveProvider (line 72) | getActiveProvider(): SandboxProvider | null {
method getProvider (line 89) | getProvider(sandboxId: string): SandboxProvider | null {
method setActiveSandbox (line 101) | setActiveSandbox(sandboxId: string): boolean {
method terminateSandbox (line 112) | async terminateSandbox(sandboxId: string): Promise<void> {
method terminateAll (line 131) | async terminateAll(): Promise<void> {
method cleanup (line 146) | async cleanup(maxAge: number = 3600000): Promise<void> {
FILE: lib/sandbox/types.ts
type SandboxFile (line 1) | interface SandboxFile {
type SandboxInfo (line 7) | interface SandboxInfo {
type CommandResult (line 14) | interface CommandResult {
type SandboxProviderConfig (line 21) | interface SandboxProviderConfig {
method constructor (line 40) | constructor(config: SandboxProviderConfig) {
method setupViteApp (line 56) | async setupViteApp(): Promise<void> {
method restartViteServer (line 61) | async restartViteServer(): Promise<void> {
FILE: lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: packages/create-open-lovable/index.js
function main (line 30) | async function main() {
FILE: packages/create-open-lovable/lib/installer.js
function installer (line 8) | async function installer(config) {
function copyTemplate (line 82) | async function copyTemplate(src, dest) {
function copyMainProject (line 100) | async function copyMainProject(mainProjectPath, projectPath, sandbox) {
function createEnvFile (line 138) | async function createEnvFile(projectPath, sandbox, answers) {
function createEnvExample (line 195) | async function createEnvExample(projectPath, sandbox) {
function updatePackageJson (line 231) | async function updatePackageJson(projectPath, name) {
function updateAppConfig (line 241) | async function updateAppConfig(projectPath, sandbox) {
FILE: packages/create-open-lovable/lib/prompts.js
function getPrompts (line 1) | function getPrompts(config) {
function getEnvPrompts (line 53) | function getEnvPrompts(provider) {
FILE: types/conversation.ts
type ConversationMessage (line 3) | interface ConversationMessage {
type ConversationEdit (line 16) | interface ConversationEdit {
type ConversationContext (line 26) | interface ConversationContext {
type ConversationState (line 45) | interface ConversationState {
FILE: types/file-manifest.ts
type FileInfo (line 3) | interface FileInfo {
type ImportInfo (line 14) | interface ImportInfo {
type ComponentInfo (line 21) | interface ComponentInfo {
type RouteInfo (line 29) | interface RouteInfo {
type ComponentTree (line 35) | interface ComponentTree {
type FileManifest (line 44) | interface FileManifest {
type EditType (line 54) | enum EditType {
type EditIntent (line 64) | interface EditIntent {
type IntentPattern (line 73) | interface IntentPattern {
FILE: types/sandbox.ts
type SandboxFile (line 3) | interface SandboxFile {
type SandboxFileCache (line 8) | interface SandboxFileCache {
type SandboxState (line 15) | interface SandboxState {
FILE: utils/cn.ts
function cn (line 3) | function cn(...classes: classNames.ArgumentArray) {
FILE: utils/set-timeout-on-visible.ts
function setTimeoutOnVisible (line 1) | function setTimeoutOnVisible({
function setIntervalOnVisible (line 70) | function setIntervalOnVisible({
Condensed preview — 326 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,135K chars).
[
{
"path": ".cursor/mcp.json",
"chars": 112,
"preview": "{\n \"mcpServers\": {\n \"dev3000\": {\n \"type\": \"http\",\n \"url\": \"http://localhost:3684/mcp\"\n }\n }\n}\n"
},
{
"path": ".env.example",
"chars": 2134,
"preview": "# Required\nFIRECRAWL_API_KEY=your_firecrawl_api_key # Get from https://firecrawl.dev (Web scraping)\n\n# ================"
},
{
"path": ".gitignore",
"chars": 665,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n**/n"
},
{
"path": "LICENSE",
"chars": 1055,
"preview": "MIT License\n\nCopyright (c) 2024\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this so"
},
{
"path": "README.md",
"chars": 2578,
"preview": "# Open Lovable\n\nChat with AI to build React apps instantly. An example app made by the [Firecrawl](https://firecrawl.dev"
},
{
"path": "app/api/analyze-edit-intent/route.ts",
"chars": 7162,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { createGroq } from '@ai-sdk/groq';\nimport { createAnthr"
},
{
"path": "app/api/apply-ai-code/route.ts",
"chars": 31387,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { parseMorphEdits, applyMorphEditToFile } from '@/lib/mo"
},
{
"path": "app/api/apply-ai-code-stream/route.ts",
"chars": 32071,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { parseMorphEdits, applyMorphEditToFile } from '@/lib/mo"
},
{
"path": "app/api/check-vite-errors/route.ts",
"chars": 341,
"preview": "import { NextResponse } from 'next/server';\n\n// Stub endpoint to prevent 404 errors\n// This endpoint is being called but"
},
{
"path": "app/api/clear-vite-errors-cache/route.ts",
"chars": 614,
"preview": "import { NextResponse } from 'next/server';\n\ndeclare global {\n var viteErrorsCache: { errors: any[], timestamp: number "
},
{
"path": "app/api/conversation-state/route.ts",
"chars": 4876,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport type { ConversationState } from '@/types/conversation';\n"
},
{
"path": "app/api/create-ai-sandbox/route.ts",
"chars": 11394,
"preview": "import { NextResponse } from 'next/server';\nimport { Sandbox } from '@vercel/sandbox';\nimport type { SandboxState } from"
},
{
"path": "app/api/create-ai-sandbox-v2/route.ts",
"chars": 3099,
"preview": "import { NextResponse } from 'next/server';\nimport { SandboxFactory } from '@/lib/sandbox/factory';\n// SandboxProvider t"
},
{
"path": "app/api/create-zip/route.ts",
"chars": 1968,
"preview": "import { NextResponse } from 'next/server';\n\ndeclare global {\n var activeSandbox: any;\n}\n\nexport async function POST() "
},
{
"path": "app/api/detect-and-install-packages/route.ts",
"chars": 5890,
"preview": "import { NextRequest, NextResponse } from 'next/server';\n\ndeclare global {\n var activeSandbox: any;\n}\n\nexport async fun"
},
{
"path": "app/api/extract-brand-styles/route.ts",
"chars": 2437,
"preview": "import { NextRequest, NextResponse } from 'next/server';\n\nexport async function POST(request: NextRequest) {\n try {\n "
},
{
"path": "app/api/generate-ai-code-stream/route.ts",
"chars": 91576,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { createGroq } from '@ai-sdk/groq';\nimport { createAnthr"
},
{
"path": "app/api/get-sandbox-files/route.ts",
"chars": 6421,
"preview": "import { NextResponse } from 'next/server';\nimport { parseJavaScriptFile, buildComponentTree } from '@/lib/file-parser';"
},
{
"path": "app/api/install-packages/route.ts",
"chars": 8891,
"preview": "import { NextRequest, NextResponse } from 'next/server';\n\ndeclare global {\n var activeSandbox: any;\n var activeSandbox"
},
{
"path": "app/api/install-packages-v2/route.ts",
"chars": 1443,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { SandboxProvider } from '@/lib/sandbox/types';\nimport {"
},
{
"path": "app/api/kill-sandbox/route.ts",
"chars": 1187,
"preview": "import { NextResponse } from 'next/server';\n\ndeclare global {\n var activeSandboxProvider: any;\n var sandboxData: any;\n"
},
{
"path": "app/api/monitor-vite-logs/route.ts",
"chars": 3830,
"preview": "import { NextResponse } from 'next/server';\n\ndeclare global {\n var activeSandbox: any;\n}\n\nexport async function GET() {"
},
{
"path": "app/api/report-vite-error/route.ts",
"chars": 1646,
"preview": "import { NextRequest, NextResponse } from 'next/server';\n\ndeclare global {\n var viteErrors: any[];\n}\n\n// Initialize glo"
},
{
"path": "app/api/restart-vite/route.ts",
"chars": 3422,
"preview": "import { NextResponse } from 'next/server';\n\ndeclare global {\n var activeSandbox: any;\n var activeSandboxProvider: any"
},
{
"path": "app/api/run-command/route.ts",
"chars": 1719,
"preview": "import { NextRequest, NextResponse } from 'next/server';\n\n// Get active sandbox from global state (in production, use a "
},
{
"path": "app/api/run-command-v2/route.ts",
"chars": 1419,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { SandboxProvider } from '@/lib/sandbox/types';\nimport {"
},
{
"path": "app/api/sandbox-logs/route.ts",
"chars": 2501,
"preview": "import { NextResponse } from 'next/server';\n\ndeclare global {\n var activeSandbox: any;\n}\n\nexport async function GET() {"
},
{
"path": "app/api/sandbox-status/route.ts",
"chars": 1745,
"preview": "import { NextResponse } from 'next/server';\nimport { sandboxManager } from '@/lib/sandbox/sandbox-manager';\n\ndeclare glo"
},
{
"path": "app/api/scrape-screenshot/route.ts",
"chars": 3180,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport FirecrawlApp from '@mendable/firecrawl-js';\n\nexport asyn"
},
{
"path": "app/api/scrape-url-enhanced/route.ts",
"chars": 3972,
"preview": "import { NextRequest, NextResponse } from 'next/server';\n\n// Function to sanitize smart quotes and other problematic cha"
},
{
"path": "app/api/scrape-website/route.ts",
"chars": 3834,
"preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport FirecrawlApp from '@mendable/firecrawl-js';\n\nexport asyn"
},
{
"path": "app/api/search/route.ts",
"chars": 1440,
"preview": "import { NextRequest, NextResponse } from 'next/server';\n\nexport async function POST(req: NextRequest) {\n try {\n con"
},
{
"path": "app/builder/page.tsx",
"chars": 8517,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { toast "
},
{
"path": "app/generation/page.tsx",
"chars": 179659,
"preview": "'use client';\n\nimport { useState, useEffect, useRef, Suspense } from 'react';\nimport { useSearchParams, useRouter } from"
},
{
"path": "app/globals.css",
"chars": 29,
"preview": "@import \"../styles/main.css\";"
},
{
"path": "app/landing.tsx",
"chars": 4464,
"preview": "\"use client\";\n\n// useState not currently used but kept for future interactivity\nimport Link from \"next/link\";\n\n// Import"
},
{
"path": "app/layout.tsx",
"chars": 1031,
"preview": "import type { Metadata } from \"next\";\nimport { Inter, Roboto_Mono } from \"next/font/google\";\nimport localFont from \"next"
},
{
"path": "app/page.tsx",
"chars": 45909,
"preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { "
},
{
"path": "atoms/sheets.ts",
"chars": 80,
"preview": "import { atom } from 'jotai';\n\nexport const isMobileSheetOpenAtom = atom(false);"
},
{
"path": "colors.json",
"chars": 4447,
"preview": "{\n \"heat-4\": {\n \"hex\": \"fa5d190a\",\n \"p3\": \"0.980392 0.364706 0.098039 / 0.039216\"\n },\n \"heat-8\": {\n \"hex\": \""
},
{
"path": "components/CodeApplicationProgress.tsx",
"chars": 1805,
"preview": "import React from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\n\nexport interface CodeApplicationSta"
},
{
"path": "components/FirecrawlIcon.tsx",
"chars": 1663,
"preview": "export default function FirecrawlIcon({ className = \"w-5 h-5\" }: { className?: string }) {\n return (\n <svg \n cl"
},
{
"path": "components/FirecrawlLogo.tsx",
"chars": 3160,
"preview": "export default function FirecrawlLogo() {\n return (\n <svg\n fill=\"none\"\n height=\"15\"\n viewBox=\"0 0 79 "
},
{
"path": "components/HMRErrorDetector.tsx",
"chars": 2298,
"preview": "import { useEffect, useRef } from 'react';\n\ninterface HMRErrorDetectorProps {\n iframeRef: React.RefObject<HTMLIFrameEle"
},
{
"path": "components/HeroInput.tsx",
"chars": 7875,
"preview": "\"use client\";\n\nimport { useState, KeyboardEvent, useEffect, useRef } from \"react\";\n\ninterface HeroInputProps {\n value: "
},
{
"path": "components/SandboxPreview.tsx",
"chars": 4078,
"preview": "import { useState } from 'react';\nimport { Loader2, ExternalLink, RefreshCw, Terminal } from 'lucide-react';\n\ninterface "
},
{
"path": "components/app/(home)/sections/ai-readiness/ControlPanel.tsx",
"chars": 32060,
"preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { \n Globe, \n FileText, \n Code, \n Shie"
},
{
"path": "components/app/(home)/sections/ai-readiness/InlineResults.tsx",
"chars": 12004,
"preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Check, X, FileText, Globe, Code, Sparkl"
},
{
"path": "components/app/(home)/sections/ai-readiness/MetricBars.tsx",
"chars": 7403,
"preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useState } from \"react\";\nimport { Chevr"
},
{
"path": "components/app/(home)/sections/ai-readiness/RadarChart.tsx",
"chars": 6843,
"preview": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport { useEffect, useState } from \"react\";\n\ninterface RadarChar"
},
{
"path": "components/app/(home)/sections/ai-readiness/ScoreChart.tsx",
"chars": 3246,
"preview": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport { useEffect, useState } from \"react\";\n\ninterface ScoreChar"
},
{
"path": "components/app/(home)/sections/endpoints/EndpointsCrawl/EndpointsCrawl.tsx",
"chars": 4196,
"preview": "\"use client\";\n\nimport { animate } from \"motion\";\nimport { useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/utils"
},
{
"path": "components/app/(home)/sections/endpoints/EndpointsExtract/EndpointsExtract.tsx",
"chars": 4339,
"preview": "\"use client\";\n\nimport { animate } from \"motion\";\nimport { useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/utils"
},
{
"path": "components/app/(home)/sections/endpoints/EndpointsMap/EndpointsMap.tsx",
"chars": 337,
"preview": "\"use client\";\n\nimport { ComponentProps } from \"react\";\n\nimport EndpointsScrape from \"@/components/app/(home)/sections/en"
},
{
"path": "components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape.tsx",
"chars": 4065,
"preview": "\"use client\";\n\nimport { animate } from \"motion\";\nimport { useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/utils"
},
{
"path": "components/app/(home)/sections/endpoints/EndpointsSearch/EndpointsSearch.tsx",
"chars": 3015,
"preview": "\"use client\";\n\nimport initCanvas from \"@/utils/init-canvas\";\nimport { animate } from \"motion\";\nimport { useEffect, useRe"
},
{
"path": "components/app/(home)/sections/endpoints/Extract/Extract.tsx",
"chars": 2946,
"preview": "\"use client\";\n\nimport { animate } from \"motion\";\nimport { useEffect, useRef } from \"react\";\n\nimport initCanvas from \"@/u"
},
{
"path": "components/app/(home)/sections/endpoints/Mcp/Mcp.tsx",
"chars": 4337,
"preview": "\"use client\";\n\nimport { animate } from \"motion\";\nimport { useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/utils"
},
{
"path": "components/app/(home)/sections/hero/Background/Background.tsx",
"chars": 5646,
"preview": "\"use client\";\n\nimport { Fragment } from \"react\";\n\nimport CurvyRect from \"@/components/shared/layout/curvy-rect\";\n\nimport"
},
{
"path": "components/app/(home)/sections/hero/Background/BackgroundOuterPiece.tsx",
"chars": 1615,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nimport { Connector } from \"@/components/shared/layout/curvy"
},
{
"path": "components/app/(home)/sections/hero/Background/_svg/CenterStar.tsx",
"chars": 588,
"preview": "export default function CenterStar({\n ...props\n}: React.SVGProps<SVGSVGElement>) {\n return (\n <svg\n fill=\"none"
},
{
"path": "components/app/(home)/sections/hero/Badge/Badge.tsx",
"chars": 1371,
"preview": "import Link from \"next/link\";\n\nexport default function HomeHeroBadge() {\n return (\n <Link\n className=\"p-4 round"
},
{
"path": "components/app/(home)/sections/hero/Hero.tsx",
"chars": 2242,
"preview": "import Link from \"next/link\";\n\nimport { Connector } from \"@/components/shared/layout/curvy-rect\";\nimport HeroFlame from "
},
{
"path": "components/app/(home)/sections/hero/Pixi/Pixi.tsx",
"chars": 1197,
"preview": "\"use client\";\n\nimport { Suspense, lazy, useState, useEffect } from \"react\";\n\nconst Pixi = lazy(() => import(\"@/component"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/ascii.ts",
"chars": 3553,
"preview": "import { Ticker } from \"@/components/shared/pixi/Pixi\";\nimport PixiAssetManager from \"@/components/shared/pixi/PixiAsset"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/cell.ts",
"chars": 1320,
"preview": "import { Ticker } from \"@/components/shared/pixi/Pixi\";\n\nimport AnimatedRect from \"./components/AnimatedRect\";\nimport Bl"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/cellReveal.ts",
"chars": 998,
"preview": "import { Ticker } from \"@/components/shared/pixi/Pixi\";\nimport AnimatedRect from \"./components/AnimatedRect\";\n\ntype Prop"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/components/AnimatedRect.ts",
"chars": 2212,
"preview": "\nimport { AnimationOptions, cubicBezier } from \"motion\";\nimport { Application, Container, Graphics, Sprite } from \"pixi."
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/components/BlinkingContainer.ts",
"chars": 1907,
"preview": "\nimport { Application, Graphics } from \"pixi.js\";\n\nimport { CELL_SIZE } from \"@/components/app/(home)/sections/hero/Pixi"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/components/Dot.ts",
"chars": 406,
"preview": "import { MAIN_COLOR } from \"@/components/app/(home)/sections/hero/Pixi/tickers/features/cell\";\n\nimport AnimatedRect from"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/crawl.ts",
"chars": 5488,
"preview": "import { animate } from \"motion\";\n\nimport { Ticker } from \"@/components/shared/pixi/Pixi\";\nimport { sleep } from \"@/util"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/index.ts",
"chars": 2455,
"preview": "import { Ticker } from \"@/components/shared/pixi/Pixi\";\nimport setTimeoutOnVisible from \"@/utils/set-timeout-on-visible\""
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/mapping.ts",
"chars": 5112,
"preview": "import { Ticker } from \"@/components/shared/pixi/Pixi\";\nimport { sleep } from \"@/utils/sleep\";\n\nimport { CELL_SIZE, MAIN"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/scrape.ts",
"chars": 5774,
"preview": "import { Ticker } from \"@/components/shared/pixi/Pixi\";\nimport { sleep } from \"@/utils/sleep\";\n\nimport { CELL_SIZE, MAIN"
},
{
"path": "components/app/(home)/sections/hero/Pixi/tickers/features/search.ts",
"chars": 3476,
"preview": "import { Ticker } from \"@/components/shared/pixi/Pixi\";\nimport { sleep } from \"@/utils/sleep\";\n\nimport { CELL_SIZE, MAIN"
},
{
"path": "components/app/(home)/sections/hero/Title/Title.tsx",
"chars": 6536,
"preview": "\"use client\";\n\n// import dynamic from \"next/dynamic\";\n// import { useRef, useEffect, forwardRef } from \"react\";\n\n// cons"
},
{
"path": "components/app/(home)/sections/hero-flame/HeroFlame.tsx",
"chars": 1669,
"preview": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nimport { setIntervalOnVisible } from \"@/utils/set-timeout-on-"
},
{
"path": "components/app/(home)/sections/hero-flame/data.json",
"chars": 169635,
"preview": "[\n \" "
},
{
"path": "components/app/(home)/sections/hero-input/Button/Button.tsx",
"chars": 1415,
"preview": "import { AnimatePresence, motion } from \"motion/react\";\n\nimport AnimatedWidth from \"@/components/shared/layout/animated-"
},
{
"path": "components/app/(home)/sections/hero-input/HeroInput.tsx",
"chars": 2647,
"preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { useState } from \"react\";\n\nimport Globe from \"./_svg/Globe\";\nimport"
},
{
"path": "components/app/(home)/sections/hero-input/Tabs/Mobile/Mobile.tsx",
"chars": 5873,
"preview": "import { animate, AnimatePresence, cubicBezier, motion } from \"motion/react\";\nimport { useEffect, useRef, useState } fro"
},
{
"path": "components/app/(home)/sections/hero-input/Tabs/Tabs.tsx",
"chars": 5871,
"preview": "import { animate } from \"motion\";\nimport { Fragment, useRef } from \"react\";\n\nimport EndpointsSearch from \"@/components/a"
},
{
"path": "components/app/(home)/sections/hero-input/_svg/ArrowRight.tsx",
"chars": 437,
"preview": "export default function ArrowRight() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\""
},
{
"path": "components/app/(home)/sections/hero-input/_svg/Globe.tsx",
"chars": 744,
"preview": "export default function Globe() {\n return (\n <svg\n fill=\"none\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n "
},
{
"path": "components/app/(home)/sections/hero-scraping/Code/Code.tsx",
"chars": 5064,
"preview": "import { useCallback, useEffect, useState } from \"react\";\n\nimport CurvyRect, { Connector } from \"@/components/shared/lay"
},
{
"path": "components/app/(home)/sections/hero-scraping/Code/Loading/Loading.tsx",
"chars": 1698,
"preview": "import { AnimatePresence, motion } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\n\nimport { encryptTe"
},
{
"path": "components/app/(home)/sections/hero-scraping/Code/Loading/_svg/Check.tsx",
"chars": 812,
"preview": "export default function Check() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/app/(home)/sections/hero-scraping/HeroScraping.css",
"chars": 378,
"preview": ".hero-scraping-highlight::before {\n animation: hero-scraping-highlight-before 1s linear infinite;\n transition: none !i"
},
{
"path": "components/app/(home)/sections/hero-scraping/HeroScraping.tsx",
"chars": 11313,
"preview": "\"use client\";\n\nimport { animate } from \"motion\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport CurvyRect "
},
{
"path": "components/app/(home)/sections/hero-scraping/Tag/Tag.tsx",
"chars": 1582,
"preview": "import { motion } from \"motion/react\";\nimport { ComponentProps, useEffect, useState } from \"react\";\n\nimport { encryptTex"
},
{
"path": "components/app/(home)/sections/hero-scraping/_svg/BrowserMobile.tsx",
"chars": 4382,
"preview": "export default function BrowserMobile(props: React.SVGProps<SVGSVGElement>) {\n return (\n <svg\n fill=\"none\"\n "
},
{
"path": "components/app/(home)/sections/hero-scraping/_svg/BrowserTab.tsx",
"chars": 486,
"preview": "import { HTMLAttributes } from \"react\";\n\nexport default function BrowserTab(attrs: HTMLAttributes<SVGSVGElement>) {\n re"
},
{
"path": "components/app/.cursor/rules/home-page-components.md",
"chars": 1595,
"preview": "# Home Page Components Rules\n\nWhen working with home/landing page components in components-new/app/(home):\n\n## Structure"
},
{
"path": "components/app/generation/SidebarInput.tsx",
"chars": 5835,
"preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport { appConfig } from \"@/config/app.c"
},
{
"path": "components/app/generation/SidebarQuickInput.tsx",
"chars": 1392,
"preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport HeroInputSubmitButton from \"@/components/app/(home)/sections/her"
},
{
"path": "components/shared/Playground/Context/types.ts",
"chars": 942,
"preview": "export enum Endpoint {\n Scrape = \"scrape\",\n Crawl = \"crawl\",\n Search = \"search\",\n Map = \"map\",\n Extract = \"extract\""
},
{
"path": "components/shared/animated-dot-icon.tsx",
"chars": 8091,
"preview": "\"use client\";\n\nimport { animate } from \"framer-motion\";\nimport { useEffect, useRef } from \"react\";\nimport { cn } from \"@"
},
{
"path": "components/shared/animated-height.tsx",
"chars": 1826,
"preview": "\"use client\";\n\nimport React, { useRef, useEffect, ReactNode, useState } from \"react\";\n\n// Smoothly animates its containe"
},
{
"path": "components/shared/ascii-background.tsx",
"chars": 2146,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { cn } from \"@/utils/cn\";\n\nconst asciiPatterns = [\n "
},
{
"path": "components/shared/ascii-flame-background.tsx",
"chars": 1533,
"preview": "\"use client\";\n\nimport React, { useEffect, useRef } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { setIntervalOn"
},
{
"path": "components/shared/button/Button.css",
"chars": 2649,
"preview": ".button {\n transition: all 0.2s cubic-bezier(0.25, 0.1, 0.25, 1),\n scale 0.1s cubic-bezier(0.25, 0.1, 0.25, 1),\n "
},
{
"path": "components/shared/button/Button.tsx",
"chars": 1973,
"preview": "import { Children, ButtonHTMLAttributes } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\ninterface Props extends Butto"
},
{
"path": "components/shared/buttons/capsule-button.tsx",
"chars": 4439,
"preview": "\"use client\";\n\nimport React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { LucideIcon } from \"lucide-react\";\nim"
},
{
"path": "components/shared/buttons/fire-action-link.tsx",
"chars": 1032,
"preview": "import Link from \"next/link\";\nimport { cn } from \"@/utils/cn\";\n\ninterface FireActionLinkProps {\n href?: string;\n label"
},
{
"path": "components/shared/buttons/index.ts",
"chars": 167,
"preview": "// Button Components\nexport { SlateButton } from \"./slate-button\";\n// export { HeatButton } from \"./heat-button\";\nexport"
},
{
"path": "components/shared/buttons/slate-button.tsx",
"chars": 3067,
"preview": "\"use client\";\n\nimport React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { LucideIcon } from \"lucide-react\";\n\ni"
},
{
"path": "components/shared/color-styles/color-styles.tsx",
"chars": 787,
"preview": "import colors from \"@/styles/colors.json\";\n\nconst TYPED_COLORS = colors as unknown as Record<\n string,\n Record<\"hex\" |"
},
{
"path": "components/shared/combobox/combobox.tsx",
"chars": 5125,
"preview": "import { animate, AnimatePresence, cubicBezier, motion } from \"motion/react\";\nimport { useEffect, useMemo, useRef, useSt"
},
{
"path": "components/shared/effects/.cursor/rules/flame-effects.md",
"chars": 2323,
"preview": "# Flame Effects Rules\n\nWhen working with visual effects components in components-new/shared/effects:\n\n## Flame ASCII Sys"
},
{
"path": "components/shared/effects/flame/Flame.tsx",
"chars": 1362,
"preview": "\"use client\";\n\nimport { HTMLAttributes, useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { set"
},
{
"path": "components/shared/effects/flame/ascii-explosion.tsx",
"chars": 1535,
"preview": "\"use client\";\n\nimport { HTMLAttributes, useEffect, useRef, memo } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport"
},
{
"path": "components/shared/effects/flame/auth-pulse/auth-pulse.tsx",
"chars": 1491,
"preview": "\"use client\";\n\nimport { HTMLAttributes, useEffect, useRef } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { setI"
},
{
"path": "components/shared/effects/flame/auth-pulse/pulse-data.json",
"chars": 25063,
"preview": "[\n \" \\n "
},
{
"path": "components/shared/effects/flame/core-flame.json",
"chars": 222029,
"preview": "[\n \" '' '' "
},
{
"path": "components/shared/effects/flame/core-flame.tsx",
"chars": 1405,
"preview": "\"use client\";\n\nimport { HTMLAttributes, useEffect, useRef } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { setI"
},
{
"path": "components/shared/effects/flame/explosion-data.json",
"chars": 98179,
"preview": "[\n \" "
},
{
"path": "components/shared/effects/flame/flame-background.tsx",
"chars": 1017,
"preview": "\"use client\";\n\nimport React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { CoreFlame } from \"./core-flame\";\n\nin"
},
{
"path": "components/shared/effects/flame/hero-flame-data.json",
"chars": 169635,
"preview": "[\n \" "
},
{
"path": "components/shared/effects/flame/hero-flame.tsx",
"chars": 1775,
"preview": "\"use client\";\n\nimport { useEffect, useRef, memo } from \"react\";\n\nimport { setIntervalOnVisible } from \"@/utils/set-timeo"
},
{
"path": "components/shared/effects/flame/index.ts",
"chars": 299,
"preview": "export { CoreFlame } from \"./core-flame\";\nexport { AsciiExplosion } from \"./ascii-explosion\";\nexport { default as HeroFl"
},
{
"path": "components/shared/effects/flame/slate-grid/grid-data.json",
"chars": 8568,
"preview": "[\n \"┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\\n│ │ │ │ │ │ "
},
{
"path": "components/shared/effects/flame/slate-grid/slate-grid.tsx",
"chars": 1539,
"preview": "\"use client\";\n\nimport { HTMLAttributes, useEffect, useRef } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { setI"
},
{
"path": "components/shared/effects/flame/subtle-explosion.tsx",
"chars": 1556,
"preview": "\"use client\";\n\nimport { HTMLAttributes, useEffect, useRef } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { setI"
},
{
"path": "components/shared/effects/flame/subtle-wave/subtle-wave.tsx",
"chars": 1148,
"preview": "\"use client\";\n\nimport React, { useEffect, useRef } from \"react\";\nimport { setIntervalOnVisible } from \"@/utils/set-timeo"
},
{
"path": "components/shared/effects/flame/subtle-wave/wave-data.json",
"chars": 2115,
"preview": "[\n \"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\",\n \"▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░"
},
{
"path": "components/shared/effects/index.ts",
"chars": 127,
"preview": "// Effect Components\nexport { CoreFlame } from \"./flame/core-flame\";\nexport { AsciiExplosion } from \"./flame/ascii-explo"
},
{
"path": "components/shared/effects/subtle-ascii-animation.tsx",
"chars": 1656,
"preview": "\"use client\";\n\nimport React, { useEffect, useRef, useMemo } from \"react\";\nimport { setIntervalOnVisible } from \"@/utils/"
},
{
"path": "components/shared/firecrawl-icon/firecrawl-icon-static.tsx",
"chars": 1814,
"preview": "import { HTMLAttributes } from \"react\";\n\nexport default function FirecrawlIconStatic({\n fill = \"var(--heat-100)\",\n cla"
},
{
"path": "components/shared/firecrawl-icon/firecrawl-icon.tsx",
"chars": 23106,
"preview": "import { HTMLAttributes } from \"react\";\n\nexport default function FirecrawlIcon({\n fill = \"var(--heat-100)\",\n innerFill"
},
{
"path": "components/shared/header/BrandKit/BrandKit.tsx",
"chars": 7018,
"preview": "\"use client\";\n\nimport copy from \"copy-to-clipboard\";\nimport { animate, cubicBezier } from \"motion\";\nimport { AnimatePres"
},
{
"path": "components/shared/header/BrandKit/_svg/Download.tsx",
"chars": 572,
"preview": "export default function Download() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/header/BrandKit/_svg/Guidelines.tsx",
"chars": 1014,
"preview": "export default function Guidelines() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\""
},
{
"path": "components/shared/header/BrandKit/_svg/Icon.tsx",
"chars": 1683,
"preview": "export default function Icon() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/header/Dropdown/Content/Content.tsx",
"chars": 6287,
"preview": "import CurvyRect from \"@/components/shared/layout/curvy-rect\";\nimport { cn } from \"@/utils/cn\";\nimport { ArrowUpRight } "
},
{
"path": "components/shared/header/Dropdown/Content/NavItemRow.tsx",
"chars": 2459,
"preview": "import { ArrowUpRight } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\n\nexport interface NavItemRowProps {\n icon"
},
{
"path": "components/shared/header/Dropdown/Github/Flame/Flame.tsx",
"chars": 1475,
"preview": "\"use client\";\n\nimport { HTMLAttributes, useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { set"
},
{
"path": "components/shared/header/Dropdown/Github/Flame/data.json",
"chars": 58335,
"preview": "[\n \" \\n "
},
{
"path": "components/shared/header/Dropdown/Github/Github.tsx",
"chars": 707,
"preview": "import Image from \"@/components/shared/image/Image\";\n\nimport GithubFlame from \"./Flame/Flame\";\n\nexport default function "
},
{
"path": "components/shared/header/Dropdown/Mobile/Item/Item.tsx",
"chars": 2610,
"preview": "\"use client\";\nimport { AnimatePresence, cubicBezier, motion } from \"motion/react\";\nimport { useState } from \"react\";\n\nim"
},
{
"path": "components/shared/header/Dropdown/Mobile/Mobile.tsx",
"chars": 1432,
"preview": "import { Fragment } from \"react\";\n\nimport Button from \"@/components/ui/shadcn/button\";\nimport {\n ConnectorToBottom,\n C"
},
{
"path": "components/shared/header/Dropdown/Stories/Flame/Flame.tsx",
"chars": 1519,
"preview": "\"use client\";\n\nimport { HTMLAttributes, useEffect, useRef } from \"react\";\n\nimport data from \"@/components/app/(home)/sec"
},
{
"path": "components/shared/header/Dropdown/Stories/Stories.tsx",
"chars": 989,
"preview": "import ArrowUp from \"./_svg/ArrowUp\";\nimport Replit from \"./_svg/Replit\";\nimport StoriesFlame from \"./Flame/Flame\";\n\nexp"
},
{
"path": "components/shared/header/Dropdown/Stories/_svg/ArrowUp.tsx",
"chars": 423,
"preview": "export default function ArrowUp() {\n return (\n <svg\n fill=\"none\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n "
},
{
"path": "components/shared/header/Dropdown/Stories/_svg/Replit.tsx",
"chars": 881,
"preview": "export default function Replit() {\n return (\n <svg\n fill=\"none\"\n height=\"32\"\n viewBox=\"0 0 32 32\"\n "
},
{
"path": "components/shared/header/Dropdown/Wrapper/Wrapper.tsx",
"chars": 3448,
"preview": "\"use client\";\n\nimport { AnimatePresence, cubicBezier, motion } from \"motion/react\";\nimport { useEffect } from \"react\";\n\n"
},
{
"path": "components/shared/header/Github/GithubClient.tsx",
"chars": 386,
"preview": "\"use client\";\n\nimport Button from \"@/components/ui/shadcn/button\";\nimport GithubIcon from \"./_svg/GithubIcon\";\n\nexport d"
},
{
"path": "components/shared/header/Github/_svg/GithubIcon.tsx",
"chars": 1507,
"preview": "export default function GithubIcon() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\""
},
{
"path": "components/shared/header/HeaderContext.tsx",
"chars": 3340,
"preview": "\"use client\";\n\nimport { usePathname } from \"next/navigation\";\nimport React, {\n createContext,\n useContext,\n useEffect"
},
{
"path": "components/shared/header/Nav/Item/Item.tsx",
"chars": 1340,
"preview": "\"use client\";\n\nimport { JSX } from \"react\";\n\nimport { useHeaderContext } from \"@/components/shared/header/HeaderContext\""
},
{
"path": "components/shared/header/Nav/Item/_svg/ChevronDown.tsx",
"chars": 400,
"preview": "export default function ChevronDown() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 18 20"
},
{
"path": "components/shared/header/Nav/Nav.tsx",
"chars": 10895,
"preview": "import EndpointsCrawl from \"@/components/app/(home)/sections/endpoints/EndpointsCrawl/EndpointsCrawl\";\nimport EndpointsS"
},
{
"path": "components/shared/header/Nav/RenderEndpointIcon.tsx",
"chars": 472,
"preview": "\"use client\";\n\nimport EndpointsScrape from \"@/components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape\";"
},
{
"path": "components/shared/header/Nav/_svg/Affiliate.tsx",
"chars": 1287,
"preview": "export default function Affiliate() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n"
},
{
"path": "components/shared/header/Nav/_svg/Api.tsx",
"chars": 799,
"preview": "export default function Api() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/header/Nav/_svg/ArrowRight.tsx",
"chars": 516,
"preview": "export default function ArrowRight() {\n return (\n <svg\n className=\"text-black-alpha-48 group-hover:text-heat-10"
},
{
"path": "components/shared/header/Nav/_svg/Careers.tsx",
"chars": 778,
"preview": "export default function Careers() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/header/Nav/_svg/Changelog.tsx",
"chars": 680,
"preview": "export default function Changelog() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n"
},
{
"path": "components/shared/header/Nav/_svg/Chats.tsx",
"chars": 958,
"preview": "export default function Ai() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n w"
},
{
"path": "components/shared/header/Nav/_svg/Lead.tsx",
"chars": 1001,
"preview": "export default function Lead() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/header/Nav/_svg/MCP.tsx",
"chars": 1072,
"preview": "export default function MCPIcon() {\n return (\n <svg\n fill=\"currentColor\"\n fill-rule=\"evenodd\"\n height"
},
{
"path": "components/shared/header/Nav/_svg/Platforms.tsx",
"chars": 1580,
"preview": "export default function Platforms() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n"
},
{
"path": "components/shared/header/Nav/_svg/Research.tsx",
"chars": 1593,
"preview": "export default function Research() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/header/Nav/_svg/Student.tsx",
"chars": 648,
"preview": "export default function Student() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/header/Nav/_svg/Templates.tsx",
"chars": 753,
"preview": "export default function Templates() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n"
},
{
"path": "components/shared/header/Toggle/Toggle.tsx",
"chars": 1638,
"preview": "\"use client\";\n\nimport Button from \"@/components/ui/shadcn/button\";\nimport { useHeaderContext } from \"@/components/shared"
},
{
"path": "components/shared/header/Wrapper/Wrapper.tsx",
"chars": 931,
"preview": "\"use client\";\n\nimport { usePathname } from \"next/navigation\";\nimport { useEffect, useState } from \"react\";\n\nimport { cn "
},
{
"path": "components/shared/header/_svg/Logo.tsx",
"chars": 3152,
"preview": "export default function Logo() {\n return (\n <svg\n fill=\"none\"\n height=\"15\"\n viewBox=\"0 0 79 15\"\n "
},
{
"path": "components/shared/hero-flame.tsx",
"chars": 2498,
"preview": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/utils/cn\";\n\n// Sample of flame"
},
{
"path": "components/shared/icons/GitHub.tsx",
"chars": 1385,
"preview": "const GitHub = ({ ...props }) => {\n return (\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n "
},
{
"path": "components/shared/icons/Logo.tsx",
"chars": 632,
"preview": "const Logo = ({ ...props }) => (\n // <svg\n // width=\"32\"\n // height=\"32\"\n // viewBox=\"0 0 32 32\"\n // fill=\""
},
{
"path": "components/shared/icons/animated-chevron.tsx",
"chars": 1567,
"preview": "\"use client\";\n\nimport React from \"react\";\nimport { ChevronDown, ChevronUp } from \"lucide-react\";\nimport { motion, Animat"
},
{
"path": "components/shared/icons/animated-icons.tsx",
"chars": 24187,
"preview": "\"use client\";\n\nimport type { Transition, Variants } from \"framer-motion\";\nimport { motion, useAnimation } from \"framer-m"
},
{
"path": "components/shared/icons/arrow-animated.tsx",
"chars": 611,
"preview": "import { cx } from \"class-variance-authority\";\n\nexport function ArrowAnimated({\n className,\n ...props\n}: React.HTMLAtt"
},
{
"path": "components/shared/icons/check.tsx",
"chars": 812,
"preview": "export default function Check() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/icons/chevron-slide.tsx",
"chars": 1107,
"preview": "\"use client\";\n\nimport React from \"react\";\nimport { cn } from \"@/utils/cn\";\n\ntype Direction = \"left\" | \"right\";\n\ninterfac"
},
{
"path": "components/shared/icons/copied.tsx",
"chars": 399,
"preview": "export default function CopiedIcon() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\""
},
{
"path": "components/shared/icons/copy.tsx",
"chars": 786,
"preview": "export default function CopyIcon() {\n return (\n <svg\n fill=\"none\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n "
},
{
"path": "components/shared/icons/curve.tsx",
"chars": 511,
"preview": "interface CurveProps extends React.SVGAttributes<SVGSVGElement> {\n fill?: string;\n}\n\nexport default function Curve({\n "
},
{
"path": "components/shared/icons/fingerprint-icon.tsx",
"chars": 5457,
"preview": "\"use client\";\n\nimport type { Variants } from \"motion/react\";\nimport { motion, useAnimation } from \"motion/react\";\nimport"
},
{
"path": "components/shared/icons/openai.tsx",
"chars": 1522,
"preview": "import * as React from \"react\";\n\nfunction IconOpenai(props: React.SVGProps<SVGSVGElement>) {\n return (\n <svg\n v"
},
{
"path": "components/shared/icons/source-icon.tsx",
"chars": 446,
"preview": "import { JSXElementConstructor } from \"react\";\nimport Image from \"next/image\";\n\nexport const SourceIcon = ({ id }: { id:"
},
{
"path": "components/shared/icons/symbol-colored.tsx",
"chars": 1715,
"preview": "import React from \"react\";\n\nconst SymbolColored = ({ ...props }) => {\n return (\n <svg\n width=\"50\"\n height="
},
{
"path": "components/shared/icons/symbol-white.tsx",
"chars": 1657,
"preview": "import React from \"react\";\n\nconst SymbolWhite = ({ ...props }) => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2"
},
{
"path": "components/shared/icons/tremor-placeholder.tsx",
"chars": 4667,
"preview": "import type { SVGProps } from \"react\";\n\nexport const TremorPlaceholder = (props: SVGProps<SVGSVGElement>) => (\n <svg cl"
},
{
"path": "components/shared/icons/wordmark-colored.tsx",
"chars": 4566,
"preview": "import React from \"react\";\n\nconst WordmarkColored = ({ ...props }) => {\n return (\n <svg\n xmlns=\"http://www.w3.o"
},
{
"path": "components/shared/icons/wordmark-white.tsx",
"chars": 4520,
"preview": "import React from \"react\";\n\nconst WordmarkWhite = ({ ...props }) => {\n return (\n <svg\n xmlns=\"http://www.w3.org"
},
{
"path": "components/shared/image/Image.tsx",
"chars": 1304,
"preview": "/* eslint-disable @next/next/no-img-element */\nimport { ComponentProps } from \"react\";\n\nimport compressorConfig from \"@/"
},
{
"path": "components/shared/image/getImageSrc.ts",
"chars": 1247,
"preview": "import compressorConfig from \"@/public/compressor.json\";\n\nconst avifConfig = compressorConfig.configs.find(\n (c) => c.e"
},
{
"path": "components/shared/layout/animated-height.tsx",
"chars": 1280,
"preview": "\"use client\";\n\nimport { motion, MotionProps, TargetAndTransition } from \"motion/react\";\nimport { useEffect, useRef, useS"
},
{
"path": "components/shared/layout/animated-width.tsx",
"chars": 1194,
"preview": "\"use client\";\n\nimport { motion, TargetAndTransition, Transition } from \"motion/react\";\nimport { useEffect, useRef, useSt"
},
{
"path": "components/shared/layout/curvy-rect-divider.tsx",
"chars": 748,
"preview": "import React from \"react\";\n\nimport CurvyRect from \"./curvy-rect\";\n\nexport function CurvyRectDivider() {\n return (\n <"
},
{
"path": "components/shared/layout/curvy-rect.tsx",
"chars": 4104,
"preview": "import { cn } from \"@/utils/cn\";\n\nimport Curve from \"@/components/shared/icons/curve\";\n\ninterface CurvyRectProps extends"
},
{
"path": "components/shared/loading/Shimmer.tsx",
"chars": 3465,
"preview": "\"use client\";\n\nimport { cn } from \"@/utils/cn\";\nimport { useState, useEffect } from \"react\";\nimport ScrambleText from \"@"
},
{
"path": "components/shared/loading/usage-loading.tsx",
"chars": 462,
"preview": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport ScrambleText from \"@/components/ui/motion/scramble-te"
},
{
"path": "components/shared/lockBody.tsx",
"chars": 831,
"preview": "/**\n * Utility to lock/unlock the document body based on a set of keys.\n * Each key can \"lock\" the body (e.g., prevent s"
},
{
"path": "components/shared/logo-cloud/index.ts",
"chars": 40,
"preview": "export { default } from \"./logo-cloud\";\n"
},
{
"path": "components/shared/logo-cloud/logo-cloud.tsx",
"chars": 1911,
"preview": "export default function LogoCloud() {\n return (\n <div>\n <p className=\"mt-24 text-xs uppercase text-zinc-400 tex"
},
{
"path": "components/shared/logo-cloud/logo-cloud2/Logocloud.css",
"chars": 568,
"preview": ".logocloud-items {\n animation: logocloud-items 100s infinite linear;\n will-change: transform;\n}\n\n@keyframes logocloud-"
},
{
"path": "components/shared/logo-cloud/logo-cloud2/Logocloud.tsx",
"chars": 2019,
"preview": "import { CurvyRect } from \"@/components/shared/ui\";\nimport Image from \"@/components/shared/image/Image\";\nimport \"./Logoc"
},
{
"path": "components/shared/notifications/slack-notification.tsx",
"chars": 1492,
"preview": "\"use client\";\n\nimport Image from \"next/image\";\nimport { useEffect, useState } from \"react\";\n\nexport default function Sla"
}
]
// ... and 126 more files (download for full content)
About this extraction
This page contains the full source code of the firecrawl/open-lovable GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 326 files (2.0 MB), approximately 465.2k tokens, and a symbol index with 453 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.