India's AI & VR-Powered Real Estate Platform — Software Developer Intern Assignment
Open index.html in any browser. No build step required.
- Clone or download the repo
- Open
index.htmlin Chrome (for voice search support) - (Optional) Add your OpenRouter API key on line ~220 of the HTML:
const API_KEY = 'sk-or-...'
Without a key: The app uses a smart local parser + heuristic match reasons. All UI features work fully. With a key: LLM query parsing and AI-generated property summaries are live.
- Framework: React 18 (via CDN, no bundler needed — pure portable HTML)
- LLM API: OpenRouter — model
meta-llama/llama-3.1-8b-instruct:free - Fonts: Playfair Display + DM Sans (Google Fonts)
- No backend, no dependencies to install
User types a plain-English query. The app sends it to an LLM which returns structured JSON filters (bhk, maxPrice, location, sunlight, school, metro, etc.). These drive filtering and ranking of property cards. Parsed filters are displayed as colour-coded tags.
10 mock properties with realistic Gurgaon sectors, prices, specs. Each card shows:
- BHK type, area, sector, price, floor, facing direction
- 360° VR badge + match % score badge
- Match reason badge — heuristically generated from parsed filters (e.g. "Great natural light · Near DPS")
Clicking a card opens a modal with a live generateMatchReason() call to OpenRouter. The prompt instructs the model to reference the user's original query and explain why this specific property matches. Result streams into a styled "AI Match Analysis" box.
Voice Input — Browser Speech Recognition API (works in Chrome/Edge). Mic button in the search bar; transcribed text fills the search field and auto-triggers search. Language set to en-IN for Indian accent accuracy.
Similar Properties Rail — Horizontal scrollable rail inside the property modal showing same-BHK properties. Acts as a "keep browsing" nudge without leaving the flow.
AI Follow-up Question — When the LLM detects genuine ambiguity (e.g. "Sector 50 or 57?"), it returns a followUpQuestion which surfaces as a dismissable banner above results.
index.html
└── React App (single-file, no bundler)
├── PROPERTIES[] — 10 mock JSON objects with Gurgaon data
├── callLLM() — OpenRouter fetch wrapper
├── parseQuery() — LLM → structured filters JSON
├── generateMatchReason() — LLM → property summary text
├── localParse() — regex fallback when no API key
├── filterProperties() — filter + sort by matchScore
└── Components
├── App — state, search orchestration
├── PropertyCard — grid card with match reason
├── PropertyModal — detail view + AI summary + similar rail
├── FilterTag — parsed filter pills
└── SkeletonCard — loading state
State is kept in React useState — no Redux, no context, no overkill. Modal opens on card click, closes on overlay click or ✕.
The system prompt instructs the LLM to return only valid JSON — no preamble, no markdown fences. This was the most critical constraint: early tests with Mistral-7B returned explanatory text before the JSON, which broke JSON.parse(). Solution: explicit "Return ONLY valid JSON" instruction + text.replace(/\``json|```/g, '')` sanitisation as a safety net.
The JSON schema is spelled out field-by-field with types and examples inline (maxPrice: 80 or null (number in lakhs)). This reduces unit confusion — models often output "80 lakhs" as a string rather than 80 as a number without the explicit type hint.
Price normalisation is handled in the prompt itself: convert crores: 1Cr=100L. This way the filter logic always works in a single unit (lakhs).
What didn't work:
- Asking the model to infer match scores — too slow and too inconsistent. Moved scoring to a simple heuristic on the frontend.
- Using a single prompt for both parsing + summary — the outputs conflicted. Separated into two focused prompts.
Instructed to be "a knowledgeable friend, not a salesperson" and to stay under 55 words. Giving a word limit dramatically improved quality — without it, models pad to fill space. Injecting the user's original query string directly means the model references their actual words, not generic platitudes.
Fast, free, and instruction-following. For structured JSON output it performs reliably with tight prompts. Gemma-3-27B is stronger but slower on the free tier. For a prototype where latency matters for demo UX, Llama 3.1 8B wins.
| Criterion | Approach |
|---|---|
| Vibe Coding | Playfair Display serif + DM Sans sans, dark header, teal accent system, card hover lift, skeleton loading — feels intentional |
| AI Integration | Two separate LLM calls with task-focused prompts; local fallback ensures demo always works |
| Code Quality | Flat component tree, clear separation of data/logic/UI, no prop drilling beyond 2 levels |
| Product Thinking | Match reason badge surfaces why, not just what; AI summary references the user's own words |
| Bonus Feature | Voice input (real value on mobile) + similar properties rail + follow-up question |