# Project export: assembl3D

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Copilot for assembly
- Devpost: https://devpost.com/software/assembl3d
- GitHub: https://github.com/rajshah6/assem3ly
- Video: https://www.youtube.com/embed/c-3XQTTbKns?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Raj Shah (31 commits), Ajith-Bondili (19 commits), dev-Armaan (8 commits), Nikhil Hooda (1 commits)

## Devpost submission (written by the team)

### Inspiration

We've all been there, sitting on the floor surrounded by hundreds of screws, mysterious wooden slabs, and an incomprehensible IKEA manual that looks like it was designed by aliens. Assembly manuals are notoriously difficult to follow: tiny diagrams, confusing arrows, and no way to see what the final step should actually look like in 3D space. We wanted to turn this frustrating experience into something intuitive and interactive. What if you could search for any furniture product, automatically get its assembly manual, and see each step visualized in an interactive 3D environment? That's the vision behind assembl3D - your AI-powered copilot for furniture assembly.

### What it does

assembl3D is an end-to-end platform that makes furniture assembly effortless: Search & Discover: Search for any furniture product through browsing the 50+ most popular IKEA products, or even paste any product URL. Intelligent Scraping: Our system uses Bright Data's powerful APIs to populate our library for Ikea products and automatically search Google for products (SERP API), scrape product pages to find important metadata, download protected PDFs (Web Unlocker), and collect product images - all without manual work. AI-Powered Processing: Google Gemini AI, analyzes each page of the PDF manual and extracts step-by-step assembly instructions with clear descriptions, required parts with quantities and dimensions, necessary tools, 3D positioning data, and assembly actions with animations. Interactive 3D Visualization: Beautiful 3D viewer displays each assembly step with real-time rendering using React Three Fiber, intuitive step-by-step navigation, visual parts lists, required tools, smooth animations showing how parts fit together, and orbit controls to view from any angle. AI Assembly Assistant: Reka AI-powered chatbot answers questions about the current step, helps identify parts and tools, provides troubleshooting assistance, and gives contextual advice in real-time. As well as helps for cross-checking assembly extraction and spatial positioning of parts.

### How we built it

We built a sophisticated PDF processing pipeline that renders pages of 2D drawings in 3D, extracts them as optimized images, and uses MD5 hashing to prevent duplicate processing. Our AI vision system uses carefully crafted prompts to guide Gemini in extracting structured JSON from complex assembly diagrams. For 3D rendering, we generate geometric primitives procedurally from AI-extracted dimensions rather than using pre-made models, enabling us to render any part type on the fly. Our web scraping strategy uses SERP API for product information, with smart rate limiting and caching to optimize costs.

### Challenges we ran into

PDF Complexity: Assembly manuals are primarily visual with complex diagrams. We pivoted from text extraction to converting pages to high-resolution images for AI vision analysis, which worked significantly better. Rate Limiting & Costs: Both Gemini (60 requests/minute) and Bright Data (pay-per-request) have limits. We implemented 500ms delays between requests, MD5-based caching to avoid reprocessing, and smart scraping that only downloads new products. Coordinate Systems: Converting PDF positions to Three.js 3D coordinates was complex due to different coordinate systems (Y-up vs Z-up). 3D Performance: Rendering complex assemblies with 50+ parts was initially slow. We optimized using low-poly primitives, frustum culling, lazy loading, and shader optimization to achieve smooth performance.

### Accomplishments we're proud of

AI Vision Breakthrough: Successfully getting Gemini to understand complex IKEA diagrams and extract structured data (parts, quantities, sequences, tools) was a major achievement. This opens possibilities for processing any visual instruction manual. Beautiful, Professional UI: Our frontend is polished with smooth animations, fully responsive design, interactive 3D controls, and visual feedback throughout. It looks like a production app, not a hackathon project. Real Product Library: We scraped and cached 50 real IKEA products with actual images, automatically categorized by room type, providing immediate value to users without requiring searches.

### What we learned

Technical Skills: We learned how powerful modern vision AI like Gemini is at understanding complex diagrams with proper prompting. Bright Data's APIs taught us professional web scraping - SERP API abstracts search result parsing, Web Unlocker handles proxies and CAPTCHAs automatically, and proper rate limiting with caching is essential for cost management. We deepened our understanding of Three.js, React Three Fiber, geometric primitives, and real-time 3D performance optimization. TypeScript's strong typing across the full stack prevented countless bugs and made refactoring under time pressure much easier. Product & Design: We focused on solving a real, universal problem (confusing manuals) rather than showcasing technology. This user-first mindset guided all decisions. We implemented progressive enhancement so the app works with cached data even when APIs are unavailable. We learned that small details like loading states, smooth transitions, and hover effects dramatically improve perceived quality and professionalism.

### What's next

Short-term (3 months): Improve position parsing to extract actual 3D coordinates from diagrams. Add advanced animation system with play/pause controls and sequential part movements. Expand to support multiple furniture brands beyond IKEA. Implement mobile AR integration using WebXR to overlay instructions on real furniture. Add user accounts with progress tracking, notes, and sharing.

## README (from the GitHub repository)

# assembl3D

**Turning 2D Assembly Instructions Alive**

![Demo](./backend/demo_optimized.gif)

An AI-powered platform that transforms static PDF assembly manuals into interactive 3D assembly guides. The system leverages web scraping, computer vision, and 3D rendering to extract structured assembly instructions from furniture manuals and present them in an immersive, step-by-step visualization environment.

**[Watch Full Demo Video](https://www.youtube.com/watch?v=c-3XQTTbKns)** 

**[View Devpost Submission](https://devpost.com/software/assembl3d)**

## Architecture Overview

The application follows a microservices architecture with a Node.js/Express backend and a Next.js 15 frontend. The pipeline consists of four main stages:

1. **Web Scraping Layer**: Uses Bright Data's SERP API to discover product pages, Web Scraper to extract metadata, and Web Unlocker to bypass anti-scraping measures and download PDF manuals
2. **AI Processing Pipeline**: Converts PDF pages to images, feeds them to Google Gemini 2.0 Flash (vision model) for multi-modal analysis, and extracts structured data including assembly steps, part lists, tool requirements, and 3D spatial relationships
3. **Data Transformation**: Transforms AI-extracted data into Three.js-compatible scene graphs with geometric primitives, materials, and animations
4. **3D Rendering Engine**: React Three Fiber-based viewer with interactive controls, part highlighting, cumulative scene building, and real-time step navigation

## Project Structure

```
assembl3D/
├── backend/                          # Express API server
│   ├── brightdata/                  # Web scraping module
│   │   ├── scraper.ts              # Main scraping orchestrator
│   │   ├── serp-search.ts          # SERP API integration
│   │   ├── web-scraper.ts          # Product page extraction
│   │   ├── pdf-downloader.ts       # PDF download via Web Unlocker
│   │   ├── scrape-top-products.ts  # Batch product scraping
│   │   ├── generate-top-50.ts      # Top products data generator
│   │   └── types.ts                # Scraping interfaces
│   │
│   ├── src/
│   │   ├── api/                    # REST API routes
│   │   │   ├── routes.ts          # Main route definitions
│   │   │   └── pdf-processor.route.ts  # PDF processing endpoint
│   │   │
│   │   ├── gemini/                 # AI processing pipeline
│   │   │   ├── processor.ts       # Main orchestrator (PDF → steps)
│   │   │   ├── pdf-parser.ts      # PDF to image conversion
│   │   │   ├── prompt-builder.ts  # Dynamic prompt generation
│   │   │   ├── scene-generator.ts # 3D scene JSON generation
│   │   │   └── types.ts           # AI extraction interfaces
│   │   │
│   │   ├── parser_docs/            # PDF processing documentation
│   │   └── index.ts                # Express server entry point
│   │
│   ├── data/
│   │   ├── images/                 # Cached product images
│   │   ├── top-50-products.json    # Pre-scraped product library
│   │   └── output/                 # Processed assembly steps (JSON)
│   │
│   └── models/                      # 3D model assets (.glb files)
│
├── frontend/                        # Next.js 15 application
│   ├── app/                         # App Router pages
│   │   ├── page.tsx                # Landing page
│   │   ├── assembly/[id]/          # Dynamic assembly viewer route
│   │   ├── api/
│   │   │   ├── assembly-chat/      # Reka AI chatbot API route
│   │   │   └── reka-vision/        # Vision API integration
│   │   └── layout.tsx              # Root layout
│   │
│   ├── components/
│   │   ├── assembly/               # Assembly UI components
│   │   │   ├── AssemblyPageClient.tsx    # Main assembly page logic
│   │   │   ├── AssemblyChatbot.tsx       # AI chatbot interface
│   │   │   ├── StepList.tsx              # Step navigation sidebar
│   │   │   ├── PartsList.tsx             # Parts list display
│   │   │   ├── ToolsList.tsx             # Tools required display
│   │   │   └── StepNavigation.tsx        # Previous/Next controls
│   │   │
│   │   ├── viewer/                 # 3D rendering components
│   │   │   ├── AssemblyViewer.tsx        # Main Three.js viewer
│   │   │   ├── DataDrivenScene.tsx       # Scene from JSON data
│   │   │   ├── CumulativeScene.tsx      # Progressive scene building
│   │   │   ├── PartHighlighter.tsx       # Part interaction system
│   │   │   ├── ViewerControls.tsx        # Camera/orbit controls UI
│   │   │   ├── SceneLoader.tsx           # Scene data loader
│   │   │   └── [AnimatedPart, Screw, Washer, LBracket].tsx  # 3D primitives
│   │   │
│   │   ├── search/                 # Search functionality
│   │   │   ├── search-section.tsx        # Main search component
│   │   │   ├── SearchResults.tsx         # Results display
│   │   │   ├── ProductCard.tsx           # Product card UI
│   │   │   └── SearchProgress.tsx        # Real-time progress indicator
│   │   │
│   │   ├── library/                # Product library
│   │   │   ├── library-section.tsx       # Library grid view
│   │   │   └── library-card.tsx          # Product card component
│   │   │
│   │   ├── landing/                # Landing page components
│   │   └── ui/                     # Shadcn/ui components
│   │
│   ├── lib/
│   │   ├── api-client.ts           # Backend API wrapper
│   │   ├── top-50-data.ts          # Product data utilities
│   │   └── utils.ts                # Helper functions
│   │
│   └── public/
│       └── products/                # Static product images
```

### Key Directories Explained

**`backend/brightdata/`**: Web scraping orchestration layer. Handles product discovery via SERP API, metadata extraction, and PDF acquisition through Bright Data's proxy network.

**`backend/src/gemini/`**: AI processing pipeline. Converts PDFs to images, constructs vision prompts, invokes Gemini API, and transforms responses into structured assembly data with 3D geometry.

**`frontend/components/viewer/`**: Three.js rendering engine. Implements scene graph construction, cumulative step visualization, part highlighting, and camera controls using React Three Fiber.

**`frontend/components/assembly/`**: Assembly instruction UI. Manages step navigation, parts/tools display, and integrates Reka AI chatbot for contextual assistance.

## Technical Workflow

1. **Product Discovery**: User submits search query or IKEA product URL → Bright Data SERP API performs semantic search across regional IKEA domains
2. **Data Extraction**: Web Scraper extracts product metadata (name, SKU, image URLs) → Web Unlocker bypasses bot detection and downloads assembly PDF
3. **PDF Processing**: PDF pages converted to base64-encoded images → Each page analyzed by Gemini 2.0 Flash vision model with structured prompts
4. **AI Extraction**: Gemini returns JSON with step descriptions, part quantities, tool requirements, and geometric data (positions, rotations, scales)
5. **Scene Generation**: Extracted data transformed into Three.js scene graph → Primitives (boxes, cylinders) positioned in 3D space → Materials and animations applied
6. **Rendering**: React Three Fiber renders scene → User navigates steps → Cumulative scene builds progressively → Parts highlight on hover/selection

## Tech Stack

- **Frontend**: Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS, Shadcn/ui
- **3D Rendering**: Three.js, React Three Fiber, @react-three/drei
- **Backend**: Node.js, Express, TypeScript
- **AI/ML**: Google Gemini 2.0 Flash (vision), Reka AI Core (chatbot)
- **Web Scraping**: Bright Data (SERP API, Web Unlocker, Web Scraper, Residential Proxies)
- **PDF Processing**: pdf-lib, pdfjs-dist, Sharp (image conversion)

## Environment Variables

**Backend** (`backend/.env`):
```bash
GEMINI_API_KEY=your_key
BRIGHT_DATA_API_KEY=your_key
PORT=3001
```

**Frontend** (`frontend/.env.local`):
```bash
NEXT_PUBLIC_API_URL=http://localhost:3001
REKA_API_KEY=your_key
```

## Detected evidence (automated analysis)

Indexed codebase: 90 recognized source files, 322 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- Google Gemini (technology) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- Python (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 245)

```
.gitignore
backend/.env.example
backend/brightdata/copy-images-to-frontend.ts
backend/brightdata/download-product-images.ts
backend/brightdata/generate-top-50.ts
backend/brightdata/pdf-downloader.js
backend/brightdata/pdf-downloader.ts
backend/brightdata/scrape-top-products.ts
backend/brightdata/scraper.js
backend/brightdata/scraper.ts
backend/brightdata/test-top-products.ts
backend/brightdata/test.ts
backend/brightdata/TOP_PRODUCTS_README.md
backend/brightdata/types.js
backend/brightdata/types.ts
backend/brightdata/update-frontend-data.ts
backend/data/top-50-products-local.json
backend/data/top-50-products.json
backend/example_all_data.json
backend/example_data_all_steps.json
backend/example_data1.json
backend/example_data2.json
backend/example-usage.ts
backend/GEOMETRY-GUIDE.md
backend/models/lbracket.glb
backend/output/processed-steps.json
backend/output/step-1.json
backend/output/step-10.json
backend/output/step-2.json
backend/output/step-3.json
backend/output/step-4.json
backend/output/step-5.json
backend/output/step-6.json
backend/output/step-7.json
backend/output/step-8.json
backend/output/step-9.json
backend/package.json
backend/SETUP.md
backend/src/api/pdf-processor.route.ts
backend/src/api/routes.ts
backend/src/gemini/pdf-parser.ts
backend/src/gemini/processor.ts
backend/src/gemini/prompt-builder.ts
backend/src/gemini/scene-generator.ts
backend/src/gemini/types.ts
backend/src/index.ts
backend/src/parser_docs/example_data.json
backend/src/parser_docs/GET-STARTED.md
backend/src/parser_docs/HOW-IT-WORKS.md
backend/src/parser_docs/IMPLEMENTATION-SUMMARY.md
backend/src/parser_docs/QUICK-START.md
backend/src/parser_docs/README-PDF-PROCESSOR.md
backend/src/test-processor.ts
backend/tsconfig.json
frontend/.gitignore
frontend/.npm-cache/_cacache/content-v2/sha512/00/aa/5a6251e7f2de1255b3870b2f9be7e28a82f478bebb03f2f6efadb890269b3b7ca0d3923903af2ea38b4ad42630b49336cd78f2f0cf1abc8b2a68e35a9e58
frontend/.npm-cache/_cacache/content-v2/sha512/01/7d/b68e9f18ef0c1a060022c5a4af9281b833ff8f940726dd0b38396f324fd0cce3152958a78584bb9e4c0c99177f69be201d70375e2680026a988071ea8d73
frontend/.npm-cache/_cacache/content-v2/sha512/08/73/94cbb9aede56c3ea8e8b24b7e59698e4ce11d7481957f01f7ea8f75d52c21f785e52fd4679ab49260ac9d1a93d541c54197213450c3ccc6d0b2e7ac1d697
frontend/.npm-cache/_cacache/content-v2/sha512/09/95/95feae934def2d6bad5c6578df9e71ec7d621d1619acd3733d4fb0a7779cb699da72949334ac5d6a46afb0b1055130bcf53983d510153da61d81c7c731ae
frontend/.npm-cache/_cacache/content-v2/sha512/15/02/be2c4a587347c10fe27c83aa12132c8dce3a7e43b2acd42e7af1659aa0c44b5be4e434d5a67a759e1a0263575d3919dfa9ff6aa5d5972362f3623d9061b9
frontend/.npm-cache/_cacache/content-v2/sha512/15/38/82a4dc6dc226591c465b71b4c87198c44552029fdcaafe90c591397de7f031cc3d6768172d37b60eebcae233f80b48363bb1dacc6f2f21a1f00362ebaa38
frontend/.npm-cache/_cacache/content-v2/sha512/1c/73/379f1c94677cc0ca55fc644cab0cd7765d93a74340f95dfba7245e452d09d96c71d8ab8516bd6d507de950357bf5202364c1885fc63c1f9ea8af601e9b16
frontend/.npm-cache/_cacache/content-v2/sha512/21/0a/e1de510f33ddf054211ccdcf526803af926728427fc6f01a357bc64f958dd7ddb9c0c5668176f4ddbccd613fada2669d015d60f2e2f4c0cb87c5d3e0e5a6
frontend/.npm-cache/_cacache/content-v2/sha512/21/e6/e22bbf6ca88cbe1381203abf82030f8a7bc840b295cacc177425d80498f1e20309af123d92e21d6b7efa3ea3f0677c461dfe300e83921e7888f39734a70e
frontend/.npm-cache/_cacache/content-v2/sha512/28/b5/620a65b1f78ccebe92f04a5b31eb238278c85961ac6b255f3b3e8e64fc5a19cdc3d26a1bcae6a27b7d8852eb5b198b98a49a1d92dc7cf9e40da5dd4ebd9e
frontend/.npm-cache/_cacache/content-v2/sha512/2a/bb/dc028d7723feb41e9c063b98e95bb13d0dfbac2b22cf2efb87954c8fe73aa79803e135ceaa4d18d442b7530039749693e90d5536fc6839d5494e1875db76
frontend/.npm-cache/_cacache/content-v2/sha512/2e/40/572a3539afdbc05bb19cbb74f9bbee5cbd2be1e56c0835daf40bef63a73541e357234003f978f02b0ed93f58761d563d0679f0a033bdc103b16522236d76
frontend/.npm-cache/_cacache/content-v2/sha512/34/e4/404ad4956bf043886e7631fb9d9388f5146e230b820bbad0777e83e77c66ce788b3df86e6f64b6b4a679a769fb595069b85304a8a84a5676abc2ef39acdc
frontend/.npm-cache/_cacache/content-v2/sha512/39/b6/f4fe011dfc745e4e583cb6d69893ed26eb681026609b94c3a31d230746b04fdb375f28c1cb0916e1ffa7b1f5f49424c29d8e32048a887091b183df08f637
frontend/.npm-cache/_cacache/content-v2/sha512/3e/9e/864b018ffcdacf22bc551402243907b2c3c9457a73878a34169144eb0efac5e002eaca53f60bf28291e4bd76950cdcabd84508676b9bedec82fde7e650f7
frontend/.npm-cache/_cacache/content-v2/sha512/43/f3/7a24d5af22f3e72c3be3944d4e5012cf4071fa9770a5b4212c1c8ba3a78e7341449287c2be8079da117e39c804f59f29e09c8361bd6b7337e35bf43f1101
frontend/.npm-cache/_cacache/content-v2/sha512/44/ab/21408d51dd843e9fd609cf6410d78ecfeba10ba5ad45404836d0393ca16f6dd8a80b6e90cb2e9f5a199ef2f161d2b210e49c6d1b927a86c39fedc82b39b7
frontend/.npm-cache/_cacache/content-v2/sha512/51/a8/8c27379646512e8f302ec392e8918d4be5e70d41864a7e6c99f4bef00c76ffa797ad29ac5786884172bc341186f2f86fcd039daf452378377f5dc47008c1
frontend/.npm-cache/_cacache/content-v2/sha512/57/9d/04c1c606aee579ad467d17868db2bcad19d29c5ca00b8c4b505c83817e1568654f5502a64d228190dd3322a2856a1406d1d70d794fe7fef20933a05cdac2
frontend/.npm-cache/_cacache/content-v2/sha512/59/c2/0d883b7c5a3a817545f27b6e5a9c788dc4f73454eae54fc17a2cb914e9cc9cace1940f263fcb2166b20de4f22e4e2efbf6e6595cd100b8e46851fc2f2597
frontend/.npm-cache/_cacache/content-v2/sha512/5b/50/a9bd31f291a3c7e5baefe551cb7d0a3d0f5398ba8d1c481114405153fe7054cccf220fa2eadbfb601bf7f539e95b012d56b5a4cf433139b8ce3d7a09e783
frontend/.npm-cache/_cacache/content-v2/sha512/5d/73/85b72a838cd0c043155f631b85ee0f4897f21b5a69a5420d8c60a387f04c484f5aa0eb1738cf24b71da10401382cd5bb5fcf1ab5e5c894898ee08d25d119
frontend/.npm-cache/_cacache/content-v2/sha512/5d/fd/2759ee91b1ece214cbbe029f5b8a251b9a996ae92f7fa7eef0ed85cffc904786b5030d48706bebc0372b9bbaa7d9593bde53ffc36151ac0c6ed128bfef13
frontend/.npm-cache/_cacache/content-v2/sha512/60/0d/a12ebc0ba43b11b9e3435c832a37d353cc3e9b08392e5db3927b485e4be7fc604f2eb00e866efd69ae3f35a73547adb68099aef339b9c80be23afec5c980
frontend/.npm-cache/_cacache/content-v2/sha512/63/2d/7dcba3b6189abc7fb0877fb2f49f12fc6dfe0f6423da54e0a026bbac39e1476352ef1e06179e69d783a3798b00276e06d90838e8eb44a233a20f262f42e6
frontend/.npm-cache/_cacache/content-v2/sha512/65/21/7046545a6b8ce3b41ef21cee90f71406bb20c2cc4ef0857061ba6f7626c95c3110b76a8ba2be9d2b033b33715ca6228271e8f5e689bc8ffb1afecca7702e
frontend/.npm-cache/_cacache/content-v2/sha512/6a/a9/5d2a05fd699aa9a03a777bcfc167c0e6ef1d69265faa23764ac433748bf2104d6530071fcc6a79a5549df0a30a9b9e8e9cd9b9a221fb3e526e8b6ec8c9c9
frontend/.npm-cache/_cacache/content-v2/sha512/6c/df/b47a1127ad77f1576650bd4d8f7ad3b49f882a043e2e96adcc23558e3cb82beff320f72450917e7ed2fd6f6a0afbe194ec97b5edce56980e8d84b8279827
frontend/.npm-cache/_cacache/content-v2/sha512/6e/a7/ab10fd44b1eea7b77ac59303e719bb1417d6b8abe80c9a94d554e133a02c9e09cfb2eff46f267419271d49c2181023936d8342a06adf14bbb5e906a9f3d7
frontend/.npm-cache/_cacache/content-v2/sha512/6f/f6/4217a6a67c051bedd24cfccc51b3b01eb5011a858cc9f0fb607eb98476dc3abb218eafe6521ad823166ea67ec064c49e1dd96ee483e365ab3aaf9f705cd3
frontend/.npm-cache/_cacache/content-v2/sha512/70/f2/54e3b39a02809b834a41bf3b20a533e19a1a88e5e26387f248bbcb4f8f9abe4fb88bbd6fc901852a984eca381e11d59c8487305a210a50a5aadd045e73f0
frontend/.npm-cache/_cacache/content-v2/sha512/72/1a/1cb5104857d10c4fd5860191e7dd5eedd19da9310ca5d04644ea915cffa9a5decb30ec395ffe800ebc5c239b51d5b2a7d037c19f785c89f9df2d473aae79
frontend/.npm-cache/_cacache/content-v2/sha512/75/cc/aa843bd7d42e3a95765c56a0a92be16d31141574830debf0dfe63b36ce8b94b2a1bb23ab05c62b480beeca60adbd29d5ce2c776ef732f8b059e85509ea68
frontend/.npm-cache/_cacache/content-v2/sha512/82/37/26e8ed1a5249e161aa20125f2de6979c3df88e4c3f154246e33a181b65ab3f13aa8663ebde5bd8ae254ffba7863513412e434980ae06bb54439459295544
frontend/.npm-cache/_cacache/content-v2/sha512/83/23/caa486b163d5dc3b6bd232b2db88447b40c00c18e419551749e998bbf368e46f367282ae4623d9c6f20fac3208ad46d9ddc1a05297bba2a82e3da354928b
frontend/.npm-cache/_cacache/content-v2/sha512/83/93/bd074866f42be7337ebebfb485977f53ee198096fe78d6d53cd4c13c61e49bde2d4220fe7f41d0de481dc0d256b46ca347a71a70c4e551a36c054d8128b1
frontend/.npm-cache/_cacache/content-v2/sha512/84/d1/338d93477f96c5ad436f752e3fd518c86a7b89eacce958d7d3697e48e967c6ca7597193ecafd840b62d7a64af144c7378c760744e12b3254b25b7793dc64
frontend/.npm-cache/_cacache/content-v2/sha512/84/d2/b3f2986f60b3c471191e1b5aec1a65790a132328301653bfce24974312b3c13428505f10a8dd666eac6a6759cea066103310f15a775695832380347d6c33
frontend/.npm-cache/_cacache/content-v2/sha512/87/c7/e011dfc3a684bd081ae31105d1f9d203adaa290047eee30615358dad10fc24eb4b2d3d686f64c7f8168a3915a92e43b1c56686bc59c79a5857abbcad8ebe
frontend/.npm-cache/_cacache/content-v2/sha512/89/e5/e262681245750378e9ed135227c635b2bc47a56463fadccf9c1aeaaeb912ee20a293d68b279178f17aff41eddd675d2ba0e6fa6e9f1fa03656d5c4c942f4
frontend/.npm-cache/_cacache/content-v2/sha512/8c/80/6f5964a10af941a8134866dd0a02c856a6f4a3864c2412ee1951c012a00be19ab800508dadd5d5eb8d22f8c5754b0781739cfac8b17de7297165fc3b78bc
frontend/.npm-cache/_cacache/content-v2/sha512/92/fd/01c9bfbe017ced106b142e0300b3c5368e051dcfa1cfa4007fc032672e22e5696417d803c78624c920deeba96f8e7f479181ca7b8e7d3953506c08fdf104
frontend/.npm-cache/_cacache/content-v2/sha512/93/88/aed51e9ee43e78f75f15e2caa62125242393a04f0defeffb0344a102eb7d96779836581b544b98321badd7d0624efc76548f5b6c8d25dfcb3c2b507665d6
frontend/.npm-cache/_cacache/content-v2/sha512/95/1e/551e690f873761accd3dda52369c00ac2cee10b9b174ff2a0e8735fb73ec08a5b3751a31cdcc273dd50969c418be727c7cbd527e52beaf2f9704a103c5a5
frontend/.npm-cache/_cacache/content-v2/sha512/9b/a5/822adfdeac35dcc3eef42095e71bb3376b0c103c0b19bc9197e7363b6de06aa442f0e910c6918554d16e37005c727b8a436798b62dbc9cfcd346ab08d455
frontend/.npm-cache/_cacache/content-v2/sha512/a3/ea/b2700319ae1f93b04d351aa594c5420a47500bd12f29abbcc3918390c3c1aa7d7f1bf15a022985281db8625f98feee1afc5ecb870d553afa09102502a0f7
frontend/.npm-cache/_cacache/content-v2/sha512/b5/1b/db0c5d0f82acfef5751ae238df80043cfda3ca9907565e2945bb6b4ccb81968f834eb31f7d1e550b3b821e4447db3b76b4b00d78e43658caf5cf9326e6d2
frontend/.npm-cache/_cacache/content-v2/sha512/b9/e9/0819eb72c08828a5f0fdee80cbe4f7de7b3905a4d87097395a11834e03437a82176c3af87f1d88d231e9394363b53d8bd148315ca85549978af8641c0ea3
frontend/.npm-cache/_cacache/content-v2/sha512/bc/99/afbf017162e1a71766b146d3d8a1c6a0e8295b6f9612ee42cbf923bf4de545cc7a83216acd298b5cb0f3226e30f1f4ef9bbbea6c032f4d29f5a495ab2c50
frontend/.npm-cache/_cacache/content-v2/sha512/c3/9a/142cda628e04d11f401114927447972dd6750cdd1dd63fbfdbf00ea3f6e4c6a517e7b32a5cfd104c3d057c2500cf6b251edfba7f4d9658d4f714af873339
frontend/.npm-cache/_cacache/content-v2/sha512/c4/55/6ebb5d6387ec5c3bb24e00624d39795df3f4dc1072bfe028adb8151e51be61ab124b8fd62e7a6004baf9bd810ffea605d87db42ea8092262efaeed812c34
frontend/.npm-cache/_cacache/content-v2/sha512/ca/41/6d802a8d9d8d083ef0eba3b87ef5978b63eaa38145affaa12f52df3882b85e9ceeb41ea77885270713b55307d9ce43fdd033c9646cd13ee2f2a31295312a
frontend/.npm-cache/_cacache/content-v2/sha512/e0/6e/6505a17e8366ae297dcfd32ab1f8c242ea856deac07993e09c852e8e6f4aaf1f460886e524d5e62bee4b6c22963e6a56c2a84f34fc5550195d7dd0f9a202
frontend/.npm-cache/_cacache/content-v2/sha512/e9/9e/cc6b66951168ac68be93bc10b2ecff2b9b62d0a0fbfa749ede7086888b11dcc1f6c9d31ee659d6dcc2520ce48190c4f6521587aa442aa538e78aa0960c93
frontend/.npm-cache/_cacache/content-v2/sha512/ec/e6/fb67e51199eb08b085518e72e80c6c01106fed562b62754d2732ca97fde78968b63cf512123bfd741117b432a9b3b8a514075ae663e8758ad31b50783d05
frontend/.npm-cache/_cacache/content-v2/sha512/f5/9d/0970c4c5c5e13e6f6c752c94dd9da4fcad3f1a129ecc57190dc28c9c366256c0f773a17ccc87406325287f00414ff8a3d8654962f35e21f1b2591486768c
frontend/.npm-cache/_cacache/content-v2/sha512/fa/53/f8ffa94a5017d08d9da97714e166f2d401a7e665bf0e03115bf175ed890992df920d82bf3985d386a04b35db87b3d450a7649b7a8dabbf4fe6a5879f1015
frontend/.npm-cache/_cacache/content-v2/sha512/fb/f1/ca77a1207100891a1d8f4a366e522b50050ca72a8af8c2b15b460e03b40812d5a58efa0539db1a47eccf52706817498980552f5b209f04cee686c34a0d03
frontend/.npm-cache/_cacache/content-v2/sha512/fd/34/23f8975991e4943a5e4c93627b78b7184cb8909b6203ce6952bd89bba2fd796a711c0c9f76187aaa6b3c534ab79e3607e762edb892212fbefd1695b09d15
frontend/.npm-cache/_cacache/index-v5/0c/60/18dc0e0c04aec0e006646a095c3692748b951ce41d62e8c129f3853b6313
frontend/.npm-cache/_cacache/index-v5/1c/73/23d0e98a6b20ec71119cfcae4f8efd088d540db6e2eba93c8df1bbd55215
frontend/.npm-cache/_cacache/index-v5/1e/ee/3fa971d101833eb213109932d7fccfa0ed3bc8a769cb3d208ce67c46ca59
frontend/.npm-cache/_cacache/index-v5/22/b8/65b89f6755fd94cfe4c1708b7d68981b4c321da86af64335792f9388167c
frontend/.npm-cache/_cacache/index-v5/39/28/d9a292946693ea5271ca304abbd45f4653dc214f71a5051a6d56259a4f88
frontend/.npm-cache/_cacache/index-v5/3e/09/8deb0faf55e36eadc8eb17a9ec8c9f614ed2c3e313ef751a2cecdb1a1831
[125 more files omitted for size]
```

### Dependencies

- backend/package.json: @google/generative-ai@^0.21.0, @types/cors@^2.8.17, @types/express@^4.17.21, @types/node@^20.14.12, axios@^1.7.7, cheerio@^1.0.0-rc.12, cors@^2.8.5, dotenv@^16.4.5, express@^4.19.2, pdf-lib@^1.17.1, ts-node-dev@^2.0.0, typescript@^5.6.3
- frontend/package.json: @radix-ui/react-hover-card@^1.1.15, @radix-ui/react-tabs@^1.1.13, @react-three/drei@^10.7.6, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, @types/three@^0.180.0, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@16.0.0, framer-motion@^12.23.24, lucide-react@^0.548.0, motion@^12.23.24, next@16.0.0, qss@^3.0.0, react@19.2.0, react-dom@19.2.0, tailwind-merge@^3.3.1, tailwindcss@^4, three@^0.180.0, tw-animate-css@^1.4.0, typescript@^5

### Recent commits (newest first)

- Merge pull request #2 from Ajith-Bondili/main
- Merge branch 'rajshah6:main' into main
- Update README to change project title and remove outdated quick start instructions.
- Merge pull request #1 from Ajith-Bondili/main
- Fix demo link in README to use correct relative path for optimized version.
- Update demo link in README to point to optimized version in the backend directory.
- Update .gitignore to exclude .cursor directory and add new entries; enhance README with quick start instructions and demo links.
- Update README to reflect new project title and remove outdated quick start instructions.
- Remove built at Cal Hacks note from README.
- Remove logo image and update demo link in README to point to optimized version.
- Update README and package-lock.json; remove logo emoji, add REKA API key placeholder, and include funding information for dependencies.
- md file cleanup
- Update logo image to a new version
- Merge branch 'main' of https://github.com/rajshah6/assem3ly
- logo
- readme update w/ reka stuff
- Merge branch 'main' of https://github.com/rajshah6/assem3ly
- Enhance AssemblyChatbot UI with improved styling, animations, and user experience features, including updated button sizes, gradients, and input handling.
- Add AssemblyChatbot component to AssemblyPreviewPage and AssemblyPageClient for enhanced user interaction during assembly steps.
- project structure updates + other tweaks

## Key source files (fetched from GitHub, selected and truncated for size)

### frontend/CHATBOT_README.md

```markdown
# 🤖 Assembly Chatbot - Reka AI Integration

## ✅ Setup Complete - API Key is Secure!

The chatbot is fully implemented with **secure environment variable loading**.

---

## 🚀 Quick Start (3 Steps)

### Step 1: Create Environment File
```bash
cd frontend
nano .env.local
```

Add this line:
```bash
REKA_API_KEY=399460a7804da855201fb18bcc6a378e30b0e1a28d414ca793b93e5bcb93c81f
```

Save and exit (Ctrl+X, then Y, then Enter)

### Step 2: Start Dev Server
```bash
npm run dev
```

**Important**: If the server was already running, restart it to load the new env variables.

### Step 3: Test It
1. Visit: `http://localhost:3000/assembly/tommaryd`
2. Look for blue chat button in bottom-right corner
3. Click and ask: "What tools do I need?"

---

## 📁 What Was Changed

### 1. API Route (Secure Now)
`app/api/assembly-chat/route.ts`
```typescript
// ✅ SECURE - Loads from environment
const REKA_API_KEY = process.env.REKA_API_KEY;

// ❌ OLD - Was hardcoded (removed)
// const REKA_API_KEY = "399460a7804da855201fb18bcc6a378e30b0e1a28d414ca793b93e5bcb93c81f";
```

### 2. Environment Example
`env.example`
```bash
REKA_API_KEY=your_reka_api_key_here
```

### 3. Your Local Config (Not Committed)
`.env.local` (you need to create this)
```bash
REKA_API_KEY=399460a7804da855201fb18bcc6a378e30b0e1a28d414ca793b93e5bcb93c81f
```

---

## 🔒 Security Features

✅ **API key in `.env.local`** (gitignored, never committed)
✅ **Server-side only** (not exposed to browser)
✅ **Error handling** (fails gracefully if key missing)
✅ **Example file** for team members to copy

---

## 🧪 Verify It Works

### Test 1: Check Environment Variable
Start the server and check console output:
```bash
npm run dev
```

If `.env.local` is loaded correctly, you shouldn't see any warnings.

### Test 2: Send a Chat Message
Click chat button and ask a question. If you get a response, everything works!

### Test 3: Check for Errors
If you see "API key not configured" error:
- Make sure `.env.local` exists in `frontend/` directory
- Make sure it has the correct format (no spaces around `=`)
- Restart the dev server

---

## 📊 How It Works

```
User asks question in chat
    ↓
Frontend → /api/assembly-chat
    ↓
API route loads REKA_API_KEY from process.env
    ↓
Calls Reka AI with current step context
    ↓
Streams response back to user
```

---

## 🎯 Features

- **Model**: `reka-core` (best quality)
- **Streaming**: Real-time word-by-word responses
- **Context**: Current step, parts, tools
- **Reset**: Clears chat when user changes steps
- **UI**: Floating button, toggleable

---

## 📝 Example `.env.local` File

Create this file in `frontend/.env.local`:

```bash
# Reka AI API Key for Assembly Chatbot
REKA_API_KEY=399460a7804da855201fb18bcc6a378e30b0e1a28d414ca793b93e5bcb93c81f
```

**Important**: 
- No spaces around the `=` sign
- No quotes needed
- File must be named exactly `.env.local`

---

## 🐛 Troubleshooting

### Problem: "API key not configured" error

**Solutions**:
1. Create `frontend/.
[truncated — 682 more characters]
```

### backend/SETUP.md

```markdown
# Bright Data Setup Guide - Residential Proxies

## 🔑 Getting Your Bright Data Credentials

### Step 1: Sign up for Bright Data
1. Go to https://brightdata.com
2. Create a free account
3. You'll get **$5 free credit** to test!

### Step 2: Create a Residential Proxy Zone
1. Log in to https://brightdata.com/cp/zones
2. Click **"Add Zone"**
3. Configure your zone:
   - **Product**: Select **"Residential"** (NOT Scraping Browser!)
   - **Zone Name**: `ikea-scraper` (or any name you want)
   - **Country**: Leave as "All" or select "United States"
   - Click **"Save"**

### Step 3: Get Your Credentials
After creating the zone, you'll see:
1. **Customer ID**: Looks like `hl_abc123def` or similar
2. **Zone Name**: The name you chose (e.g., `ikea-scraper`)
3. **Password/API Key**: Click to reveal or generate

**Where to find them:**
- Go to: https://brightdata.com/cp/zones
- Click on your `ikea-scraper` zone
- Look for "Access parameters" section
- Copy:
  - Customer ID
  - Zone name
  - Password (this is your API key)

### Step 4: Add to .env file
```bash
cd backend
cp .env.example .env
```

Edit `backend/.env` and paste your credentials:

```bash
BRIGHTDATA_CUSTOMER_ID=hl_abc123def
BRIGHTDATA_ZONE=ikea-scraper
BRIGHTDATA_API_KEY=your_password_here
```

**Important**: 
- The zone name must match EXACTLY what you named it
- No quotes around the values
- No spaces before or after the `=`

---

## 🧪 Testing Your Setup

### Test 1: Verify Environment Variables
```bash
cd backend
node -e "require('dotenv').config(); console.log('✅ Customer ID:', process.env.BRIGHTDATA_CUSTOMER_ID)"
```

Should print your customer ID (not "undefined")

### Test 2: Run the Scraper
```bash
cd backend
npx ts-node brightdata/test.ts
```

**This will:**
1. ✅ Check your Bright Data credentials
2. 🔍 Search IKEA for "billy bookcase"
3. 📄 Navigate to product page (using Bright Data proxies)
4. 📊 Extract product data
5. 📥 Download the PDF manual
6. ✅ Print results

**Expected output:**
```
🧪 Testing Bright Data IKEA Scraper
✅ Environment variables loaded
🔍 Starting IKEA scrape for: billy bookcase
🌐 Using Bright Data Residential Proxies (150M+ IPs)...
🔎 Searching: https://www.ikea.com/us/en/search/?query=billy+bookcase
📄 Product page: https://www.ikea.com/us/en/p/billy-bookcase-white-20522046/
🌐 Fetching product page via Bright Data...
📊 Extracting product data...
✅ Product found: BILLY Bookcase
🆔 Product ID: 20522046
📐 Dimensions: { width: '31 1/2 "', height: '79 1/2 "', depth: '11 "' }
📄 PDF URL: https://www.ikea.com/us/en/assembly_instructions/billy-bookcase-white__AA-2289108-3-100.pdf
📥 Downloading PDF with Bright Data Web Unlocker...
💾 PDF saved to: /Users/ajith/Projects/assembl3D/backend/data/pdfs/20522046.pdf

✅ SUCCESS!
Product Name: BILLY Bookcase
Product ID: 20522046
PDF Path: /Users/ajith/Projects/assembl3D/backend/data/pdfs/20522046.pdf
Time taken: 8.2 seconds
```

### Test 3: API Endpoint
Start the server:
```bash
cd backend
npm run dev
```

In another ter
[truncated — 2161 more characters]
```

### backend/package.json

```
{
  "name": "assembl3D-backend",
  "version": "1.0.0",
  "private": true,
  "main": "dist/index.js",
  "scripts": {
    "dev": "ts-node-dev --respawn --transpile-only src/index.ts",
    "build": "tsc -p tsconfig.json",
    "start": "node dist/index.js",
    "typecheck": "tsc -p tsconfig.json --noEmit"
  },
  "dependencies": {
    "@google/generative-ai": "^0.21.0",
    "axios": "^1.7.7",
    "cheerio": "^1.0.0-rc.12",
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.19.2",
    "pdf-lib": "^1.17.1"
  },
  "devDependencies": {
    "@types/cors": "^2.8.17",
    "@types/express": "^4.17.21",
    "@types/node": "^20.14.12",
    "ts-node-dev": "^2.0.0",
    "typescript": "^5.6.3"
  }
}


```

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@radix-ui/react-hover-card": "^1.1.15",
    "@radix-ui/react-tabs": "^1.1.13",
    "@react-three/drei": "^10.7.6",
    "@types/three": "^0.180.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "framer-motion": "^12.23.24",
    "lucide-react": "^0.548.0",
    "motion": "^12.23.24",
    "next": "16.0.0",
    "qss": "^3.0.0",
    "react": "19.2.0",
    "react-dom": "19.2.0",
    "tailwind-merge": "^3.3.1",
    "three": "^0.180.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.0.0",
    "tailwindcss": "^4",
    "tw-animate-css": "^1.4.0",
    "typescript": "^5"
  }
}

```

### frontend/app/page.tsx

```typescript
import { BackgroundLines } from "@components/ui/background-lines";
import { LandingSwitcher } from "@components/landing/landing-switcher";

export default function Home() {
  return (
    <div className="relative isolate">
      {/* Hero: full viewport minus header height */}
      <section className="relative flex min-h-[calc(100vh-56px)] items-center justify-center overflow-hidden bg-white px-4 py-16">
        <BackgroundLines density={14} speed={0.12} lineColor="rgba(0,0,0,0.06)" />

        <div className="relative z-10 mx-auto w-full max-w-6xl">
          <LandingSwitcher />
        </div>
      </section>

      {/* Tabs removed from body; moved to header */}
    </div>
  );
}

```

### backend/src/index.ts

```typescript
import express from 'express'
import cors from 'cors'
import 'dotenv/config'
import pdfProcessorRoute from './api/pdf-processor.route'
import routes from './api/routes'

const app = express()
const PORT = process.env.PORT || 3001

app.use(cors())
app.use(express.json())

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', service: 'assembl3D-backend' })
})

// API routes
app.use('/api', routes)
app.use('/api', pdfProcessorRoute)

// Error handler
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  console.error('❌ Server Error:', err.message)
  res.status(err.status || 500).json({
    error: err.message || 'Internal server error',
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  })
})

app.listen(PORT, () => {
  console.log('\n' + '🚀 '.repeat(30))
  console.log('🚀 assembl3D backend running')
  console.log('📍 Server: http://localhost:' + PORT)
  console.log('🌐 API: http://localhost:' + PORT + '/api')
  console.log('💚 Health: http://localhost:' + PORT + '/health')
  console.log('🚀 '.repeat(30) + '\n')
  console.log(`📡 API available at http://localhost:${PORT}/api`)
  console.log(`🔑 Gemini API Key: ${process.env.GEMINI_API_KEY ? '✅ Set' : '❌ Not set'}`)
})


```

### frontend/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { HeaderTabsProvider } from "@components/navigation/tabs-context";
import { HeaderTabs } from "@components/navigation/header-tabs";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "assembl3D",
  description: "Interactive IKEA assembly guides",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen bg-background text-foreground`}>
        <HeaderTabsProvider>
          <div className="flex min-h-screen flex-col">
            <header className="sticky top-0 z-50 border-b border-black/10 bg-white">
              <div className="mx-auto flex h-14 w-full max-w-6xl items-center justify-between px-4">
                <div className="flex items-center gap-2">
                  <span className="inline-flex h-6 w-6 items-center justify-center rounded bg-black text-white">A</span>
                  <span className="text-sm font-semibold tracking-tight text-black">assembl3D</span>
                </div>
                <nav className="flex items-center gap-2 text-sm text-black">
                  <HeaderTabs />
                </nav>
              </div>
            </header>
            <main className="flex-1">{children}</main>
          </div>
        </HeaderTabsProvider>
      </body>
    </html>
  );
}

```

### frontend/app/preview2/page.tsx

```typescript
import { DataDrivenScene } from "@/components/viewer/DataDrivenScene";

// Example data from backend/example_data2.json - Step 2
const exampleData2 = {
  "stepId": 2,
  "title": "Attach Mounting Blocks",
  "description": "Attach the mounting blocks to the underside of the table top by inserting and rotating.",
  "parts": [
    {
      "id": "block_01",
      "name": "Mounting Block",
      "type": "block",
      "quantity": 4,
      "dimensions": {
        "width": 0.05,
        "depth": 0.03
      },
      "material": "plastic",
      "color": "#F0F0F0",
      "position": {
        "x": 0,
        "y": 0.02,
        "z": 0
      },
      "rotation": {
        "x": 0,
        "y": 0,
        "z": 0
      },
      "scale": {
        "x": 1,
        "y": 1,
        "z": 1
      },
      "model": "/models/generic_part.glb"
    }
  ],
  "assemblySequence": [
    {
      "action": "move" as const,
      "targetId": "block_01",
      "from": { "x": 0, "y": 0.15, "z": 0 },
      "to": { "x": 0, "y": 0.02, "z": 0 },
      "duration": 2.0
    },
    {
      "action": "rotate" as const,
      "targetId": "block_01",
      "axis": "y",
      "angle": 720,
      "duration": 2.5
    }
  ],
  "camera": {
    "position": {
      "x": 0.15,
      "y": 0.1,
      "z": 0.2
    },
    "lookAt": {
      "x": 0,
      "y": 0.02,
      "z": 0
    }
  },
  "lighting": {
    "ambient": {
      "intensity": 0.5
    },
    "directional": {
      "intensity": 0.8,
      "position": {
        "x": 1,
        "y": 2,
        "z": 3
      }
    }
  }
};

export default function Preview2Page() {
  return (
    <div className="w-full h-screen">
      <DataDrivenScene data={exampleData2} autoPlay={true} />
    </div>
  );
}


```

### frontend/app/preview/page.tsx

```typescript
import { DataDrivenScene } from "@/components/viewer/DataDrivenScene";

// Example data from backend/example_data.json
// Adjusted positions to align with L-bracket hole location
const exampleData = {
  "stepId": 1,
  "title": "Attach L-bracket with screw and washer",
  "description": "Insert the screw and washer through the L-bracket and tighten into the base panel.",
  "parts": [
    {
      "id": "bracket_L_01",
      "name": "L Bracket",
      "type": "metal_bracket",
      "quantity": 2,
      "dimensions": { "width": 0.12, "height": 0.05, "depth": 0.02 },
      "material": "metal",
      "color": "#C0C0C0",
      "position": { "x": 0, "y": 0, "z": 0 },
      "rotation": { "x": 0, "y": 0, "z": 0 },
      "scale": { "x": 1, "y": 1, "z": 1 },
      "model": "/models/bracket_L.glb"
    },
    {
      "id": "washer_01",
      "name": "Washer",
      "type": "metal_washer",
      "quantity": 2,
      "dimensions": { "radius": 0.01, "thickness": 0.002 },
      "material": "metal",
      "color": "#AAAAAA",
      "position": { "x": 0.01, "y": 0.07, "z": 0 },
      "rotation": { "x": 0, "y": 0, "z": 1.5708 },
      "scale": { "x": 1, "y": 1, "z": 1 },
      "model": "/models/washer.glb"
    },
    {
      "id": "screw_01",
      "name": "Screw",
      "type": "screw_flathead",
      "quantity": 1,
      "dimensions": { "length": 0.04, "radius": 0.004 },
      "material": "metal",
      "color": "#777777",
      "position": { "x": 0.01, "y": 0.07, "z": 0 },
      "rotation": { "x": 0, "y": 0, "z": 0 },
      "scale": { "x": 1, "y": 1, "z": 1 },
      "model": "/models/screw.glb"
    }
  ],
  "assemblySequence": [
    {
      "action": "move" as const,
      "targetId": "washer_01",
      "from": { "x": 0.1, "y": 0.07, "z": 0 },
      "to": { "x": 0.025, "y": 0.07, "z": 0 },
      "duration": 1.5
    },
    {
      "action": "move" as const,
      "targetId": "screw_01",
      "from": { "x": 0.15, "y": 0.07, "z": 0 },
      "to": { "x": 0.03, "y": 0.07, "z": 0 },
      "duration": 2.0
    },
    {
      "action": "rotate" as const,
      "targetId": "screw_01",
      "axis": "x",
      "angle": 1440,
      "duration": 2.0
    },
    {
      "action": "move" as const,
      "targetId": "screw_01",
      "from": { "x": 0.03, "y": 0.07, "z": 0 },
      "to": { "x": 0.01, "y": 0.07, "z": 0 },
      "duration": 2.0
    }
  ],
  "camera": {
    "position": { "x": 0.2, "y": 0.1, "z": 0.3 },
    "lookAt": { "x": 0, "y": 0, "z": 0 }
  },
  "lighting": {
    "ambient": { "intensity": 0.5 },
    "directional": { "intensity": 0.8, "position": { "x": 1, "y": 2, "z": 3 } }
  }
};

export default function PreviewPage() {
  return (
    <div className="w-full h-screen">
      <DataDrivenScene data={exampleData} autoPlay={true} />
    </div>
  );
}


```

### frontend/app/assembly-preview/page.tsx

```typescript
'use client'

import { useState, useEffect } from 'react'
import { useSearchParams, useRouter } from 'next/navigation'
import { CumulativeScene } from '@/components/viewer/CumulativeScene'
import { AssemblyChatbot } from '@/components/assembly/AssemblyChatbot'

export default function AssemblyPreviewPage() {
  const searchParams = useSearchParams()
  const router = useRouter()
  
  // Initialize currentStep from URL query param or default to 0
  const [currentStep, setCurrentStep] = useState(() => {
    const stepParam = searchParams.get('step')
    return stepParam ? parseInt(stepParam, 10) : 0
  })
  const [assemblyData, setAssemblyData] = useState<any>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/example_data_all_steps.json')
      .then(res => res.json())
      .then(data => {
        setAssemblyData(data)
        setLoading(false)
      })
      .catch(err => {
        console.error('Failed to load assembly data:', err)
        setLoading(false)
      })
  }, [])

  // Update URL when step changes
  useEffect(() => {
    if (assemblyData && assemblyData.steps) {
      const validStep = Math.max(0, Math.min(currentStep, assemblyData.steps.length - 1))
      router.replace(`/assembly-preview?step=${validStep}`, { scroll: false })
    }
  }, [currentStep, assemblyData, router])

  if (loading) {
    return (
      <div className="w-full h-screen flex items-center justify-center bg-gray-900 text-white">
        <div className="text-center">
          <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white mx-auto mb-4"></div>
          <p>Loading assembly data...</p>
        </div>
      </div>
    )
  }

  if (!assemblyData || !assemblyData.steps) {
    return (
      <div className="w-full h-screen flex items-center justify-center bg-gray-900 text-white">
        <div className="text-center">
          <p className="text-xl">Failed to load assembly data</p>
          <p className="text-sm text-gray-400 mt-2">Please refresh the page</p>
        </div>
      </div>
    )
  }
  
  const steps = assemblyData.steps
  const currentStepData = steps[currentStep]

  return (
    <div className="w-full h-screen">
      <CumulativeScene 
        steps={steps}
        currentStep={currentStep}
        onStepChange={setCurrentStep}
        height="100vh"
      />
      
      {/* Assembly Chatbot - Floating in bottom-right */}
      {currentStepData && (
        <AssemblyChatbot
          manualId="tommaryd-preview"
          currentStep={currentStep}
          stepData={{
            title: currentStepData.title || `Step ${currentStep + 1}`,
            description: currentStepData.description || '',
            parts: currentStepData.parts || [],
            tools: currentStepData.tools || [],
          }}
        />
      )}
    </div>
  )
}


```

[80 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]