Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
84 changes: 59 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# HackToFuture 4.0 — Template
# BlueBerry

Welcome to your official HackToFuture 4 repository.

Expand Down Expand Up @@ -28,59 +28,93 @@ This repository template will be used for development, tracking progress, and fi

# The Final README Template


## Problem Statement / Idea

Clearly describe the problem you are solving.
- **What is the problem?**
Millions of small businesses (restaurants, salons, local shops) either have no online presence or a weak one. Their Google listings are incomplete, they lack websites, and they are often invisible to potential customers searching online.

- **Why is it important?**
In today’s digital-first world, poor online presence directly translates to lost revenue. Small business owners typically lack the time, technical knowledge, or resources to build and maintain a strong digital footprint.

- What is the problem?
- Why is it important?
- Who are the target users?
- **Who are the target users?**
- Local small business owners (restaurants, salons, gyms, repair shops)
- Businesses with weak or no online presence
- Non-technical owners who want simple, managed digital solutions

---

## Proposed Solution

Explain your approach:
- **What are you building?**
Vector++ is an automated platform that identifies businesses with poor online presence, generates ready-to-use websites for them, and continuously improves those websites based on customer feedback.

- **How does it solve the problem?**
- Discovers businesses lacking digital presence using data sources like Google Maps
- Automatically generates a complete website preview using templates and AI-generated content
- Allows instant demonstration of value before any sales interaction
- Continuously monitors customer feedback (reviews, interactions) and improves the website automatically

- What are you building?
- How does it solve the problem?
- What makes your solution unique?
- **What makes your solution unique?**
- Pre-built website previews before pitching (value-first approach)
- Fully automated feedback-to-improvement loop
- Subscription-based continuous optimization instead of one-time delivery
- Combines scraping, AI generation, and autonomous updates into one system

---

## Features

List the core features of your project:

- Feature 1
- Feature 2
- Feature 3
- Automated business discovery and opportunity scoring
- Instant website generation using AI and pre-built templates
- Multi-solution improvement engine for continuous optimization
- Customer feedback ingestion and clustering
- Automatic website updates based on real user feedback
- CRM system for managing outreach and conversions

---

## Tech Stack

Mention all technologies used:
- **Frontend:** React.js, Tailwind CSS
- **Backend:** Python (FastAPI) / Node.js
- **Database:** Supabase (PostgreSQL)

### APIs / Services
- Google Maps data sources
- LLM APIs (OpenAI / Anthropic / Ollama)
- Hosting platforms (Vercel / Netlify)

- Frontend:
- Backend:
- Database:
- APIs / Services:
- Tools / Libraries:
### Tools / Libraries
- sentence-transformers (for embeddings)
- DBSCAN (for clustering feedback)
- Playwright (for scraping)
- Docker (for sandbox/testing)

---

## Project Setup Instructions

Provide clear steps to run your project:

```bash
# Clone the repository
git clone <repo-link>

# Install dependencies
...
# Navigate into the project
cd vector-plus-plus

# Install dependencies (backend)
pip install -r requirements.txt

# Install dependencies (frontend)
cd frontend
npm install

# Run backend server
cd ..
uvicorn main:app --reload

# Run the project
# Run frontend
cd frontend
npm run dev
...
```
Binary file added backend/__pycache__/main.cpython-311.pyc
Binary file not shown.
Binary file added backend/__pycache__/main.cpython-313.pyc
Binary file not shown.
1 change: 1 addition & 0 deletions backend/agents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# agents package
Binary file added backend/agents/__pycache__/__init__.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file added backend/agents/__pycache__/coder.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file added backend/agents/__pycache__/tester.cpython-313.pyc
Binary file not shown.
91 changes: 91 additions & 0 deletions backend/agents/analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import os
import re
import json
from dotenv import load_dotenv

load_dotenv()

from llm.client import chat_text


def _extract_json(raw: str) -> str:
"""Robustly extract a JSON object from LLM output (handles markdown fences, prose, etc.)"""
# Strip markdown fences like ```json ... ``` or ``` ... ```
fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw, re.DOTALL)
if fenced:
return fenced.group(1)
# Find outermost { ... }
start = raw.find("{")
if start != -1:
depth = 0
for i, ch in enumerate(raw[start:], start):
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return raw[start : i + 1]
return raw


def analyze(feedback_texts: list[str], repo_tree: str = "(tree unavailable)") -> dict:
"""
Analyzer Agent: understand the root issue from clustered user feedback.

Args:
feedback_texts: List of raw feedback strings from a cluster
repo_tree: Newline-separated list of actual file paths in the repo

Returns:
Structured analysis dict with issue details
"""
sample = feedback_texts[:10]
combined = "\n---\n".join([f"• {t}" for t in sample])

prompt = f"""You are a senior software engineer analyzing a cluster of user feedback reports.

The following feedback items were automatically grouped together because they describe similar issues:

{combined}

Here is the actual file structure of the repository:
```
{repo_tree}
```

Analyze these reports and respond with a JSON object containing:
- issue_title: A concise, technical title for this issue (max 80 chars)
- issue_type: One of: bug | feature_request | performance | ux | security | docs
- description: A 2-3 sentence technical description of the actual problem
- root_cause: Your hypothesis about what's causing this
- affected_area: Which part of the codebase is likely affected (e.g. "auth module", "API layer", "frontend routing")
- severity: One of: low | medium | high | critical
- suggested_files: A list of 2-5 EXACT file paths from the provided repository file structure that definitely need changes to fix this issue. DO NOT make up generic paths. You must strictly choose paths that exist in the repo_tree provided above.
- user_impact: How this affects users

Return ONLY valid JSON. No markdown, no explanation outside the JSON."""

last_error: Exception | None = None
for attempt in range(3):
try:
raw = chat_text(prompt=prompt, max_tokens=1000, model=os.getenv("ANALYZER_MODEL"))
cleaned = _extract_json(raw)
return json.loads(cleaned)
except json.JSONDecodeError as e:
print(f"[Analyzer] Attempt {attempt + 1}/3 JSON parse error: {e}")
last_error = e
except Exception as e:
print(f"[Analyzer] Error: {e}")
raise

print(f"[Analyzer] All retries failed, using fallback. Last error: {last_error}")
return {
"issue_title": "Unstructured Issue",
"issue_type": "bug",
"description": combined[:300],
"root_cause": "Unknown",
"affected_area": "Unknown",
"severity": "medium",
"suggested_files": [],
"user_impact": "Unknown",
}
176 changes: 176 additions & 0 deletions backend/agents/coder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import os
import re
import json
from dotenv import load_dotenv

load_dotenv()

from llm.client import chat_text


def _extract_json(raw: str) -> str:
"""Robustly extract a JSON object from LLM output."""
fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw, re.DOTALL)
if fenced:
return fenced.group(1)
start = raw.find("{")
if start != -1:
depth = 0
for i, ch in enumerate(raw[start:], start):
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return raw[start : i + 1]
return raw


def apply_patch_to_file(original: str, find: str, replace: str) -> str | None:
"""
Apply a search-and-replace patch to a file's content.
Returns the patched content, or None if `find` was not found.
Tries exact match first, then whitespace-normalised match.
"""
if find in original:
return original.replace(find, replace, 1)

# Fallback: normalise leading whitespace per line and try again
def normalise(s: str) -> str:
return "\n".join(line.rstrip() for line in s.splitlines())

norm_orig = normalise(original)
norm_find = normalise(find)
if norm_find in norm_orig:
idx = norm_orig.index(norm_find)
return original[:idx] + replace + original[idx + len(find):]

return None # patch cannot be applied


def generate_code(plan: dict, file_contents: dict) -> dict:
"""
Coder Agent: generate search-and-replace patches for the bug fix.

Strategy: instead of rewriting entire files (which small models do poorly),
the LLM only writes the buggy snippet and its replacement.
We apply the replacement programmatically, preserving all original code.

Args:
plan: Output from the Planner agent
file_contents: Dict of {file_path: file_content} for relevant files

Returns:
Dict with patches list, each patch having:
- file_path, find, replace, change_summary
- new_code: the fully-patched file (computed here, not by the LLM)
"""
plan_str = json.dumps(plan, indent=2)

if file_contents:
files_str = "\n\n".join([
f"=== FILE: {path} ===\n{content[:3000]}"
for path, content in file_contents.items()
])
else:
files_str = "(No source files available — write a new minimal fix file)"

prompt = f"""You are an expert software developer fixing a bug in an existing codebase.

Fix Plan:
{plan_str}

ORIGINAL FILE CONTENTS (read these carefully — you must fix the actual code below):
{files_str}

Your task: identify the exact buggy code and provide the corrected replacement.

Return a JSON object with:
- patches: list of objects, one per file that needs changing. Each object has:
- file_path: exact file path (must be one of the file paths shown above)
- find: the EXACT buggy code snippet to search for (copy it character-for-character from the file above, including indentation). This should be the SMALLEST snippet that uniquely identifies the bug — usually 1-5 lines.
- replace: the corrected replacement for that snippet only (same indentation style)
- change_summary: one-line description of what changed
- explanation: brief technical explanation of the fix
- breaking_changes: list of breaking changes introduced (empty list if none)

RULES:
- `find` must be text that appears VERBATIM in the original file shown above.
- `replace` should only change what's needed to fix the bug — leave everything else intact.
- Do NOT rewrite entire files. Only target the specific buggy lines.
- The patches list MUST have at least one entry.

Return ONLY valid JSON. No markdown fences. No prose before or after."""

last_error: Exception | None = None
for attempt in range(3):
try:
raw = chat_text(prompt=prompt, max_tokens=2000, model=os.getenv("CODER_MODEL"))
cleaned = _extract_json(raw)
result = json.loads(cleaned)

patches = result.get("patches", [])

# Validate and apply patches to get new_code
valid_patches = []
for p in patches:
file_path = p.get("file_path", "").lstrip("/")
find_str = p.get("find", "")
replace_str = p.get("replace", "")

if not file_path or not find_str:
print(f"[Coder] Skipping patch with missing file_path or find: {p}")
continue

original = file_contents.get(file_path) or file_contents.get("/" + file_path)

if original is None:
# Model may have used wrong path — try fuzzy match
for known_path, content in file_contents.items():
if known_path.endswith(file_path) or file_path.endswith(known_path.split("/")[-1]):
original = content
file_path = known_path
print(f"[Coder] Fuzzy-matched path '{p['file_path']}' → '{file_path}'")
break

if original is not None:
patched = apply_patch_to_file(original, find_str, replace_str)
if patched is None:
print(f"[Coder] ⚠️ Could not find snippet in {file_path}, skipping patch")
print(f"[Coder] Snippet was: {find_str[:100]!r}")
continue
new_code = patched
print(f"[Coder] ✅ Applied patch to {file_path}")
else:
# No original file fetched — use replace as the entire new file
new_code = replace_str
print(f"[Coder] ⚠️ No original for {file_path} — using replace as full content")

valid_patches.append({
"file_path": file_path,
"find": find_str,
"replace": replace_str,
"new_code": new_code,
"change_summary": p.get("change_summary", "auto-fix"),
})

if not valid_patches:
raise ValueError(
f"No valid patches could be applied on attempt {attempt + 1}. "
f"LLM response excerpt: {raw[:400]}"
)

result["patches"] = valid_patches
return result

except (json.JSONDecodeError, ValueError) as e:
print(f"[Coder] Attempt {attempt + 1}/3 failed: {e}")
last_error = e
except Exception as e:
print(f"[Coder] Unexpected error: {e}")
raise

raise RuntimeError(
f"Coder agent could not generate valid patches after 3 attempts. "
f"Last error: {last_error}"
)
Loading