Spaces:
Sleeping
Sleeping
Commit ·
23ddaac
0
Parent(s):
StyleSync AI - Rebranded codebase
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +30 -0
- .vite/deps/_metadata.json +8 -0
- .vite/deps/package.json +3 -0
- Dockerfile +10 -0
- README.md +151 -0
- agents/__init__.py +0 -0
- agents/manager.py +46 -0
- agents/memory_agent.py +104 -0
- agents/visual_analyst.py +61 -0
- agents/writer_agent.py +48 -0
- apply_qa_fixes.py +111 -0
- args.json +4 -0
- check_basic.py +20 -0
- check_gemini.py +26 -0
- check_gemini_clean.py +24 -0
- check_groq.py +11 -0
- check_groq_models.py +14 -0
- check_groq_vision.py +36 -0
- check_idefics.py +33 -0
- check_idefics_raw.py +29 -0
- check_idefics_v2.py +31 -0
- check_idefics_v3.py +30 -0
- check_llama.py +33 -0
- check_llava.py +33 -0
- check_models.py +15 -0
- check_models_list.py +15 -0
- check_qwen.py +48 -0
- check_qwen_raw.py +52 -0
- clean_binary_deploy.py +80 -0
- code.html +203 -0
- connect_n8n.py +126 -0
- create_dockerfile.py +50 -0
- dashboard.html +397 -0
- deploy_new_ui.py +60 -0
- final_deploy_push.py +20 -0
- final_upload.py +61 -0
- fix_browse_button.py +57 -0
- fix_dashboard_api.py +66 -0
- fix_dashboard_routing.py +87 -0
- fix_google_key.py +48 -0
- fix_js_syntax.py +147 -0
- fix_readme.py +42 -0
- fix_vision_core.py +90 -0
- force_deploy.py +62 -0
- force_push.py +14 -0
- glassui.html +455 -0
- install_gh.py +33 -0
- landingpage.html +426 -0
- launcher.py +47 -0
- legacy/trend_spotter.py +46 -0
.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Credentials and Secrets
|
| 2 |
+
.env
|
| 3 |
+
|
| 4 |
+
# Python Compiled Files
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.py[cod]
|
| 7 |
+
|
| 8 |
+
# Virtual Environments
|
| 9 |
+
venv/
|
| 10 |
+
.venv/
|
| 11 |
+
|
| 12 |
+
# System Files
|
| 13 |
+
.DS_Store
|
| 14 |
+
Thumbs.db
|
| 15 |
+
|
| 16 |
+
# Logs
|
| 17 |
+
*.log
|
| 18 |
+
|
| 19 |
+
# Editor Directories
|
| 20 |
+
.vscode/
|
| 21 |
+
.idea/
|
| 22 |
+
|
| 23 |
+
*.jpg
|
| 24 |
+
|
| 25 |
+
stitch_merchflow_ai_dashboard.zip
|
| 26 |
+
screen.jpg
|
| 27 |
+
test_image.jpg
|
| 28 |
+
*.zip
|
| 29 |
+
*.jpeg
|
| 30 |
+
*.png
|
.vite/deps/_metadata.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"hash": "7678463b",
|
| 3 |
+
"configHash": "aaeefbd1",
|
| 4 |
+
"lockfileHash": "e3b0c442",
|
| 5 |
+
"browserHash": "7b5c57bd",
|
| 6 |
+
"optimized": {},
|
| 7 |
+
"chunks": {}
|
| 8 |
+
}
|
.vite/deps/package.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"type": "module"
|
| 3 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
WORKDIR /code
|
| 3 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 4 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 5 |
+
COPY . /code
|
| 6 |
+
# Fix permissions for libraries that write to home
|
| 7 |
+
RUN mkdir -p /tmp/home
|
| 8 |
+
ENV HOME=/tmp/home
|
| 9 |
+
# Start the FastAPI server on port 7860 (required by Hugging Face)
|
| 10 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: StyleSync AI
|
| 3 |
+
emoji: 👟
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
<div align="center">
|
| 11 |
+
|
| 12 |
+
# 🚀 StyleSync AI
|
| 13 |
+
|
| 14 |
+
### Autonomous E-Commerce Catalog Intelligence
|
| 15 |
+
|
| 16 |
+

|
| 17 |
+

|
| 18 |
+

|
| 19 |
+

|
| 20 |
+

|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
*A multi-agent AI pipeline that converts raw product imagery into enterprise-grade, SEO-optimized e-commerce catalogs in seconds.*
|
| 25 |
+
|
| 26 |
+
</div>
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## 🎯 Core Value Proposition
|
| 31 |
+
|
| 32 |
+
StyleSync AI eliminates the manual bottleneck of product catalog creation. By orchestrating **Computer Vision**, **Retrieval-Augmented Generation (RAG)**, and **Large Language Models** in a seamless autonomous pipeline, it delivers production-ready product listings — from a single image upload — with zero human intervention.
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## 🏗️ System Architecture
|
| 37 |
+
|
| 38 |
+
The system employs a high-performance, event-driven architecture orchestrated by **FastAPI**:
|
| 39 |
+
|
| 40 |
+
### 👁️ Visual Analyst Agent
|
| 41 |
+
- **Function**: Zero-shot product image analysis
|
| 42 |
+
- **Process**: Extracts granular visual attributes — dominant colors, material composition, design style, branding elements, and product classification
|
| 43 |
+
- **Engine**: `Gemini 2.5 Flash` via the unified **Google GenAI SDK** (`google-genai`)
|
| 44 |
+
|
| 45 |
+
### 🧠 Semantic Memory Agent
|
| 46 |
+
- **Function**: RAG-based keyword retrieval with intelligent fallback
|
| 47 |
+
- **Process**: Vectorizes visual attributes to query a high-dimensional index, retrieving historically high-converting SEO keywords and market trends. When the database has no match for a niche, the **Intelligence Fallback** system autonomously generates keywords via Gemini — ensuring **0% empty results**
|
| 48 |
+
- **Engine**: `Pinecone Vector DB` with `gemini-embedding-001` embeddings (768 dimensions)
|
| 49 |
+
|
| 50 |
+
### ✍️ Writer Agent
|
| 51 |
+
- **Function**: High-conversion copy synthesis
|
| 52 |
+
- **Process**: Fuses visual intelligence with retrieved market data to generate persuasive, conversion-optimized titles, descriptions, and feature bullet points
|
| 53 |
+
- **Engine**: `Meta Llama 3.3 70B` (via Groq Cloud)
|
| 54 |
+
|
| 55 |
+
### ⚙️ Pipeline Orchestrator
|
| 56 |
+
- **Function**: Async pipeline management & delivery
|
| 57 |
+
- **Process**: Handles non-blocking agent execution, error propagation, and API lifecycle management. Results are delivered instantly through the **Premium Glassmorphism UI**
|
| 58 |
+
- **Engine**: `FastAPI` with async/await architecture
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## 🖥️ Production Interface
|
| 63 |
+
|
| 64 |
+
StyleSync AI ships with a **Premium Glassmorphism UI** built for instant catalog generation:
|
| 65 |
+
|
| 66 |
+
- 🎨 Frosted-glass aesthetic with dynamic gradient backgrounds
|
| 67 |
+
- 📤 Drag-and-drop image upload with real-time processing feedback
|
| 68 |
+
- 📊 Structured JSON output display for visual data, SEO keywords, and generated listings
|
| 69 |
+
- 📱 Fully responsive design across desktop, tablet, and mobile
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
## 🛠️ Technology Stack
|
| 74 |
+
|
| 75 |
+
| Layer | Technology | Purpose |
|
| 76 |
+
|-------|-----------|---------|
|
| 77 |
+
| **Runtime** | Python 3.10+ | Core language |
|
| 78 |
+
| **Framework** | FastAPI | Async API orchestration |
|
| 79 |
+
| **AI SDK** | `google-genai` (Unified) | Vision & embedding inference |
|
| 80 |
+
| **Vision Model** | Gemini 2.5 Flash | Product image analysis |
|
| 81 |
+
| **Embeddings** | `gemini-embedding-001` | 768-dim vector generation |
|
| 82 |
+
| **Vector DB** | Pinecone (Serverless) | Semantic keyword retrieval |
|
| 83 |
+
| **LLM** | Llama 3.3 70B (Groq) | Copywriting synthesis |
|
| 84 |
+
| **UI** | Glassmorphism / Tailwind CSS | Production dashboard |
|
| 85 |
+
| **Deployment** | Docker / Hugging Face Spaces | Containerized hosting |
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## 📋 System Updates & Technical Milestones
|
| 90 |
+
|
| 91 |
+
| Date | Milestone |
|
| 92 |
+
|------|-----------|
|
| 93 |
+
| **Feb 2026** | ✅ Full migration from deprecated `google-generativeai` to unified `google-genai` SDK |
|
| 94 |
+
| **Feb 2026** | ✅ Vision model upgraded to `Gemini 2.5 Flash` for industry-leading latency |
|
| 95 |
+
| **Feb 2026** | ✅ Pinecone vector alignment to `768 dimensions` with `gemini-embedding-001` for mathematical precision |
|
| 96 |
+
| **Feb 2026** | ✅ **Intelligence Fallback** system deployed — guarantees 0% empty SEO keyword results |
|
| 97 |
+
| **Feb 2026** | ✅ n8n webhook decoupled — pipeline relies strictly on the Glassmorphism UI for delivery |
|
| 98 |
+
| **Feb 2026** | ✅ Production Glassmorphism dashboard launched |
|
| 99 |
+
|
| 100 |
+
---
|
| 101 |
+
|
| 102 |
+
## 🚀 Quick Start
|
| 103 |
+
|
| 104 |
+
### 1. Environment Configuration
|
| 105 |
+
|
| 106 |
+
Create a `.env` file in the project root with the following keys:
|
| 107 |
+
|
| 108 |
+
```env
|
| 109 |
+
GEMINI_API_KEY=your_google_genai_api_key
|
| 110 |
+
GROQ_API_KEY=your_groq_cloud_api_key
|
| 111 |
+
PINECONE_API_KEY=your_pinecone_api_key
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
### 2. Installation
|
| 115 |
+
|
| 116 |
+
```bash
|
| 117 |
+
pip install -r requirements.txt
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### 3. Launch
|
| 121 |
+
|
| 122 |
+
```bash
|
| 123 |
+
python main.py
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
The production dashboard will be available at `http://localhost:7860`.
|
| 127 |
+
|
| 128 |
+
---
|
| 129 |
+
|
| 130 |
+
## 📁 Project Structure
|
| 131 |
+
|
| 132 |
+
```
|
| 133 |
+
StyleSync-AI/
|
| 134 |
+
├── main.py # FastAPI orchestrator & pipeline logic
|
| 135 |
+
├── dashboard.html # Glassmorphism production UI
|
| 136 |
+
├── Dockerfile # Container deployment config
|
| 137 |
+
├── requirements.txt # Python dependencies
|
| 138 |
+
├── agents/
|
| 139 |
+
│ ├── visual_analyst.py # Gemini 2.5 Flash vision agent
|
| 140 |
+
│ ├── memory_agent.py # Pinecone RAG + embedding agent
|
| 141 |
+
│ └── writer_agent.py # Llama 3.3 copywriting agent
|
| 142 |
+
└── .env # Environment variables (not tracked)
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
<div align="center">
|
| 148 |
+
|
| 149 |
+
**StyleSync AI** — Autonomous catalog intelligence for modern e-commerce.
|
| 150 |
+
|
| 151 |
+
</div>
|
agents/__init__.py
ADDED
|
File without changes
|
agents/manager.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import csv
|
| 2 |
+
import time
|
| 3 |
+
import os
|
| 4 |
+
import datetime
|
| 5 |
+
from agents.trend_spotter import TrendSpotter
|
| 6 |
+
from agents.visionary import Visionary
|
| 7 |
+
|
| 8 |
+
class MerchManager:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.trend_spotter = TrendSpotter()
|
| 11 |
+
self.visionary = Visionary()
|
| 12 |
+
self.results_dir = "results"
|
| 13 |
+
if not os.path.exists(self.results_dir):
|
| 14 |
+
os.makedirs(self.results_dir)
|
| 15 |
+
|
| 16 |
+
def generate_batch(self, niche: str) -> str:
|
| 17 |
+
# Step 1: Get slogans
|
| 18 |
+
print(f"🔍 Analyzing trends for niche: {niche}...")
|
| 19 |
+
slogans = self.trend_spotter.get_trends(niche)
|
| 20 |
+
|
| 21 |
+
results = []
|
| 22 |
+
|
| 23 |
+
# Step 2: Generate art prompts
|
| 24 |
+
print(f"🎨 Generating designs for {len(slogans)} slogans...")
|
| 25 |
+
for i, slogan in enumerate(slogans):
|
| 26 |
+
print(f"Generating design {i+1}/{len(slogans)}...")
|
| 27 |
+
prompt = self.visionary.generate_art_prompt(slogan, niche)
|
| 28 |
+
results.append({
|
| 29 |
+
"Niche": niche,
|
| 30 |
+
"Slogan": slogan,
|
| 31 |
+
"Art Prompt": prompt
|
| 32 |
+
})
|
| 33 |
+
time.sleep(10)
|
| 34 |
+
|
| 35 |
+
# Step 3 & 4: Save to CSV
|
| 36 |
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 37 |
+
filename = f"merch_batch_{niche}_{timestamp}.csv"
|
| 38 |
+
filepath = os.path.join(self.results_dir, filename)
|
| 39 |
+
|
| 40 |
+
with open(filepath, mode='w', newline='', encoding='utf-8') as file:
|
| 41 |
+
writer = csv.DictWriter(file, fieldnames=["Niche", "Slogan", "Art Prompt"])
|
| 42 |
+
writer.writeheader()
|
| 43 |
+
writer.writerows(results)
|
| 44 |
+
|
| 45 |
+
print(f"✅ Batch complete! Saved to {filepath}")
|
| 46 |
+
return filename
|
agents/memory_agent.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
from pinecone import Pinecone, ServerlessSpec
|
| 5 |
+
from google import genai
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
class MemoryAgent:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
# Configure Gemini
|
| 12 |
+
self.gemini_api_key = os.getenv("GEMINI_API_KEY")
|
| 13 |
+
if not self.gemini_api_key:
|
| 14 |
+
raise ValueError("GEMINI_API_KEY not found")
|
| 15 |
+
self.client = genai.Client(api_key=self.gemini_api_key)
|
| 16 |
+
|
| 17 |
+
# Configure Pinecone
|
| 18 |
+
self.pinecone_api_key = os.getenv("PINECONE_API_KEY")
|
| 19 |
+
if not self.pinecone_api_key:
|
| 20 |
+
raise ValueError("PINECONE_API_KEY not found")
|
| 21 |
+
|
| 22 |
+
self.pc = Pinecone(api_key=self.pinecone_api_key)
|
| 23 |
+
self.index_name = "stylesync-index"
|
| 24 |
+
|
| 25 |
+
# Check and create index
|
| 26 |
+
existing_indexes = [i.name for i in self.pc.list_indexes()]
|
| 27 |
+
if self.index_name not in existing_indexes:
|
| 28 |
+
print(f"Creating index {self.index_name}...")
|
| 29 |
+
self.pc.create_index(
|
| 30 |
+
name=self.index_name,
|
| 31 |
+
dimension=768, # gemini-embedding-001 output dimension
|
| 32 |
+
metric='cosine',
|
| 33 |
+
spec=ServerlessSpec(
|
| 34 |
+
cloud='aws',
|
| 35 |
+
region='us-east-1'
|
| 36 |
+
)
|
| 37 |
+
)
|
| 38 |
+
# Wait for index to be ready
|
| 39 |
+
while not self.pc.describe_index(self.index_name).status['ready']:
|
| 40 |
+
time.sleep(1)
|
| 41 |
+
print("Index created.")
|
| 42 |
+
|
| 43 |
+
self.index = self.pc.Index(self.index_name)
|
| 44 |
+
|
| 45 |
+
def _get_embedding(self, text):
|
| 46 |
+
# Using gemini-embedding-001
|
| 47 |
+
result = self.client.models.embed_content(
|
| 48 |
+
model="gemini-embedding-001",
|
| 49 |
+
contents=text,
|
| 50 |
+
config=genai.types.EmbedContentConfig(output_dimensionality=768)
|
| 51 |
+
)
|
| 52 |
+
return result.embeddings[0].values
|
| 53 |
+
|
| 54 |
+
def seed_database(self):
|
| 55 |
+
# Check if empty
|
| 56 |
+
stats = self.index.describe_index_stats()
|
| 57 |
+
if stats.total_vector_count > 0:
|
| 58 |
+
print("Database already seeded.")
|
| 59 |
+
return
|
| 60 |
+
|
| 61 |
+
print("Seeding database...")
|
| 62 |
+
items = [
|
| 63 |
+
{
|
| 64 |
+
"id": "item1",
|
| 65 |
+
"text": "Running Shoe",
|
| 66 |
+
"keywords": "breathable, shock absorption, marathon training, lightweight"
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"id": "item2",
|
| 70 |
+
"text": "Graphic T-Shirt",
|
| 71 |
+
"keywords": "100% cotton, vintage wash, pre-shrunk, soft feel"
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"id": "item3",
|
| 75 |
+
"text": "Leather Wallet",
|
| 76 |
+
"keywords": "genuine leather, RFID blocking, minimalist, bifold"
|
| 77 |
+
}
|
| 78 |
+
]
|
| 79 |
+
|
| 80 |
+
vectors = []
|
| 81 |
+
for item in items:
|
| 82 |
+
embedding = self._get_embedding(item['text'])
|
| 83 |
+
vectors.append({
|
| 84 |
+
"id": item['id'],
|
| 85 |
+
"values": embedding,
|
| 86 |
+
"metadata": {"keywords": item['keywords'], "text": item['text']}
|
| 87 |
+
})
|
| 88 |
+
|
| 89 |
+
self.index.upsert(vectors=vectors)
|
| 90 |
+
print(f"Seeded {len(vectors)} items.")
|
| 91 |
+
|
| 92 |
+
def retrieve_keywords(self, query_text: str):
|
| 93 |
+
try:
|
| 94 |
+
query_embedding = self._get_embedding(query_text)
|
| 95 |
+
|
| 96 |
+
results = self.index.query(
|
| 97 |
+
vector=query_embedding,
|
| 98 |
+
top_k=5,
|
| 99 |
+
include_metadata=True
|
| 100 |
+
)
|
| 101 |
+
return [m.metadata['keywords'] for m in results.matches if m.metadata and 'keywords' in m.metadata]
|
| 102 |
+
except Exception as e:
|
| 103 |
+
print(f"❌ Keyword Retrieval Failed: {e}")
|
| 104 |
+
return []
|
agents/visual_analyst.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import re
|
| 4 |
+
from google import genai
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
class VisualAnalyst:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.api_key = os.getenv("GEMINI_API_KEY")
|
| 12 |
+
if not self.api_key:
|
| 13 |
+
raise ValueError("GEMINI_API_KEY not found")
|
| 14 |
+
|
| 15 |
+
self.client = genai.Client(api_key=self.api_key)
|
| 16 |
+
self.model_name = "gemini-2.5-flash"
|
| 17 |
+
print(f"✅ VisualAnalyst stored Gemini model: {self.model_name}")
|
| 18 |
+
|
| 19 |
+
async def analyze_image(self, image_path: str):
|
| 20 |
+
try:
|
| 21 |
+
# Upload the file to Gemini
|
| 22 |
+
# Note: For efficiency in production, files should be managed (uploads/deletes)
|
| 23 |
+
# but for this agentic flow, we'll upload per request or assume local path usage helper if needed.
|
| 24 |
+
# However, the standard `model.generate_content` can take PIL images or file objects directly for some sdk versions,
|
| 25 |
+
# but using the File API is cleaner for 1.5 Flash multi-modal.
|
| 26 |
+
# Let's use the simpler PIL integration if available, or just path if the SDK supports it.
|
| 27 |
+
# actually, standard genai usage for images usually involves PIL or uploading.
|
| 28 |
+
# Let's try the PIL approach first as it's often more direct for local scripts.
|
| 29 |
+
import PIL.Image
|
| 30 |
+
img = PIL.Image.open(image_path)
|
| 31 |
+
|
| 32 |
+
user_prompt = (
|
| 33 |
+
"Analyze this product image. "
|
| 34 |
+
"Return ONLY valid JSON with keys: main_color, product_type, design_style, visual_features."
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# We'll stick to prompt engineering for now to match the "Return ONLY valid JSON" instruction.
|
| 38 |
+
response = self.client.models.generate_content(
|
| 39 |
+
model=self.model_name,
|
| 40 |
+
contents=[user_prompt, img]
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
response_text = response.text
|
| 44 |
+
|
| 45 |
+
# Use regex to find the JSON block robustly
|
| 46 |
+
match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 47 |
+
if match:
|
| 48 |
+
cleaned_content = match.group(0)
|
| 49 |
+
else:
|
| 50 |
+
cleaned_content = response_text
|
| 51 |
+
|
| 52 |
+
return json.loads(cleaned_content.strip())
|
| 53 |
+
|
| 54 |
+
except Exception as e:
|
| 55 |
+
print(f"❌ Analysis Failed: {e}")
|
| 56 |
+
return {
|
| 57 |
+
"main_color": "Unknown",
|
| 58 |
+
"product_type": "Unknown",
|
| 59 |
+
"design_style": "Unknown",
|
| 60 |
+
"visual_features": [f"Error: {str(e)}"]
|
| 61 |
+
}
|
agents/writer_agent.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
from groq import Groq
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
class WriterAgent:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.api_key = os.getenv("GROQ_API_KEY")
|
| 11 |
+
if not self.api_key:
|
| 12 |
+
raise ValueError("GROQ_API_KEY not found in environment variables")
|
| 13 |
+
self.client = Groq(api_key=self.api_key)
|
| 14 |
+
self.model = "llama-3.3-70b-versatile"
|
| 15 |
+
|
| 16 |
+
def write_listing(self, visual_data: dict, seo_keywords: list) -> dict:
|
| 17 |
+
system_prompt = (
|
| 18 |
+
"You are an expert e-commerce copywriter. "
|
| 19 |
+
"Write a persuasive product listing based on these visual attributes and SEO keywords. "
|
| 20 |
+
"Return JSON with keys: title, description, bullet_points."
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
user_content = f"""
|
| 24 |
+
Visual Attributes: {json.dumps(visual_data, indent=2)}
|
| 25 |
+
|
| 26 |
+
SEO Keywords: {', '.join(seo_keywords)}
|
| 27 |
+
|
| 28 |
+
Please generate the listing in JSON format.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
try:
|
| 32 |
+
completion = self.client.chat.completions.create(
|
| 33 |
+
model=self.model,
|
| 34 |
+
messages=[
|
| 35 |
+
{"role": "system", "content": system_prompt},
|
| 36 |
+
{"role": "user", "content": user_content}
|
| 37 |
+
],
|
| 38 |
+
temperature=0.7,
|
| 39 |
+
response_format={"type": "json_object"},
|
| 40 |
+
timeout=15.0
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
response_text = completion.choices[0].message.content
|
| 44 |
+
return json.loads(response_text)
|
| 45 |
+
|
| 46 |
+
except Exception as e:
|
| 47 |
+
print(f"Error generating listing: {e}")
|
| 48 |
+
return {"error": str(e)}
|
apply_qa_fixes.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
|
| 4 |
+
def patch_main():
|
| 5 |
+
with open("main.py", "r", encoding="utf-8") as f:
|
| 6 |
+
content = f.read()
|
| 7 |
+
|
| 8 |
+
old_pattern = r'return JSONResponse\(\s*content=\{\s*"error": str\(e\),\s*"type": type\(e\)\.__name__,\s*"details": error_details\s*\},\s*status_code=500\s*\)'
|
| 9 |
+
new_text = '''return JSONResponse(
|
| 10 |
+
content={
|
| 11 |
+
"error": "An internal server error occurred.",
|
| 12 |
+
"type": type(e).__name__
|
| 13 |
+
},
|
| 14 |
+
status_code=500
|
| 15 |
+
)'''
|
| 16 |
+
content = re.sub(old_pattern, new_text, content[1:] if content.startswith('\ufeff') else content)
|
| 17 |
+
|
| 18 |
+
with open("main.py", "w", encoding="utf-8") as f:
|
| 19 |
+
f.write(content)
|
| 20 |
+
print("Patched main.py")
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def patch_memory_agent():
|
| 24 |
+
path = "agents/memory_agent.py"
|
| 25 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 26 |
+
content = f.read()
|
| 27 |
+
|
| 28 |
+
old_func = r'def retrieve_keywords\(.*?: str\):.*?return \[m\.metadata\[\'keywords\'\] for m in results\.matches if m\.metadata and \'keywords\' in m\.metadata\]'
|
| 29 |
+
new_func = '''def retrieve_keywords(self, query_text: str):
|
| 30 |
+
try:
|
| 31 |
+
query_embedding = self._get_embedding(query_text)
|
| 32 |
+
|
| 33 |
+
results = self.index.query(
|
| 34 |
+
vector=query_embedding,
|
| 35 |
+
top_k=5,
|
| 36 |
+
include_metadata=True
|
| 37 |
+
)
|
| 38 |
+
return [m.metadata['keywords'] for m in results.matches if m.metadata and 'keywords' in m.metadata]
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"❌ Keyword Retrieval Failed: {e}")
|
| 41 |
+
return []'''
|
| 42 |
+
|
| 43 |
+
content = re.sub(old_func, new_func, content, flags=re.DOTALL)
|
| 44 |
+
|
| 45 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 46 |
+
f.write(content)
|
| 47 |
+
print("Patched agents/memory_agent.py")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def patch_visual_analyst():
|
| 51 |
+
path = "agents/visual_analyst.py"
|
| 52 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 53 |
+
content = f.read()
|
| 54 |
+
|
| 55 |
+
# Add timeout
|
| 56 |
+
content = content.replace(
|
| 57 |
+
"response = self.model.generate_content([user_prompt, img])",
|
| 58 |
+
"response = self.model.generate_content([user_prompt, img], request_options={'timeout': 15.0})"
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
if "import re" not in content:
|
| 62 |
+
content = content.replace("import json", "import json\nimport re")
|
| 63 |
+
|
| 64 |
+
# Rewrite JSON parsing
|
| 65 |
+
old_parsing = r'# Clean up potential markdown code fences.*?return json\.loads\(cleaned_content\.strip\(\)\)'
|
| 66 |
+
new_parsing = '''# Use regex to find the JSON block robustly
|
| 67 |
+
match = re.search(r'\\{.*\\}', response_text, re.DOTALL)
|
| 68 |
+
if match:
|
| 69 |
+
cleaned_content = match.group(0)
|
| 70 |
+
else:
|
| 71 |
+
cleaned_content = response_text
|
| 72 |
+
|
| 73 |
+
return json.loads(cleaned_content.strip())'''
|
| 74 |
+
|
| 75 |
+
content = re.sub(old_parsing, new_parsing, content, flags=re.DOTALL)
|
| 76 |
+
|
| 77 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 78 |
+
f.write(content)
|
| 79 |
+
print("Patched agents/visual_analyst.py")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def patch_writer_agent():
|
| 83 |
+
path = "agents/writer_agent.py"
|
| 84 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 85 |
+
content = f.read()
|
| 86 |
+
|
| 87 |
+
old_completion = r'completion = self\.client\.chat\.completions\.create\(\s*model=self\.model,\s*messages=\[\s*\{"role": "system", "content": system_prompt\},\s*\{"role": "user", "content": user_content\}\s*\],\s*temperature=0\.7,\s*response_format=\{"type": "json_object"\}\s*\)'
|
| 88 |
+
|
| 89 |
+
new_completion = '''completion = self.client.chat.completions.create(
|
| 90 |
+
model=self.model,
|
| 91 |
+
messages=[
|
| 92 |
+
{"role": "system", "content": system_prompt},
|
| 93 |
+
{"role": "user", "content": user_content}
|
| 94 |
+
],
|
| 95 |
+
temperature=0.7,
|
| 96 |
+
response_format={"type": "json_object"},
|
| 97 |
+
timeout=15.0
|
| 98 |
+
)'''
|
| 99 |
+
|
| 100 |
+
content = re.sub(old_completion, new_completion, content)
|
| 101 |
+
|
| 102 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 103 |
+
f.write(content)
|
| 104 |
+
print("Patched agents/writer_agent.py")
|
| 105 |
+
|
| 106 |
+
if __name__ == "__main__":
|
| 107 |
+
patch_main()
|
| 108 |
+
patch_memory_agent()
|
| 109 |
+
patch_visual_analyst()
|
| 110 |
+
patch_writer_agent()
|
| 111 |
+
print("All file patching completed successfully.")
|
args.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"projectId": "13453765122851154258",
|
| 3 |
+
"screenId": "cf3af7f8a9e74daf85096241ed88c75c"
|
| 4 |
+
}
|
check_basic.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
|
| 10 |
+
print(f"Testing token with microsoft/resnet-50")
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
# Pass the URL directly as the input (InferenceClient handles URLs for image tasks)
|
| 14 |
+
result = client.image_classification(
|
| 15 |
+
model="microsoft/resnet-50",
|
| 16 |
+
image="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
)
|
| 18 |
+
print("Success:", result)
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print("Failed:", e)
|
check_gemini.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
| 8 |
+
genai.configure(api_key=api_key)
|
| 9 |
+
|
| 10 |
+
print("Listing available Gemini models...")
|
| 11 |
+
try:
|
| 12 |
+
for m in genai.list_models():
|
| 13 |
+
if 'generateContent' in m.supported_generation_methods:
|
| 14 |
+
print(m.name)
|
| 15 |
+
except Exception as e:
|
| 16 |
+
print(f"List models failed: {e}")
|
| 17 |
+
|
| 18 |
+
model_name = "gemini-1.5-flash"
|
| 19 |
+
print(f"\nTesting model: {model_name}")
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
model = genai.GenerativeModel(model_name)
|
| 23 |
+
response = model.generate_content("Hello, can you see this?")
|
| 24 |
+
print("Response:", response.text)
|
| 25 |
+
except Exception as e:
|
| 26 |
+
print(f"Test failed: {e}")
|
check_gemini_clean.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
| 8 |
+
genai.configure(api_key=api_key)
|
| 9 |
+
|
| 10 |
+
candidates = [
|
| 11 |
+
"gemini-2.0-flash",
|
| 12 |
+
"gemini-2.0-flash-exp",
|
| 13 |
+
"models/gemini-2.0-flash"
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
for model_name in candidates:
|
| 17 |
+
print(f"\nTesting model: {model_name}")
|
| 18 |
+
try:
|
| 19 |
+
model = genai.GenerativeModel(model_name)
|
| 20 |
+
response = model.generate_content("Hello")
|
| 21 |
+
print(f"✅ Success with {model_name}: {response.text}")
|
| 22 |
+
break
|
| 23 |
+
except Exception as e:
|
| 24 |
+
print(f"❌ Failed with {model_name}: {e}")
|
check_groq.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from groq import Groq
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
client = Groq(api_key=os.getenv("GROQ_API_KEY"))
|
| 7 |
+
|
| 8 |
+
print("Listing Groq models...")
|
| 9 |
+
models = client.models.list()
|
| 10 |
+
for m in models.data:
|
| 11 |
+
print(m.id)
|
check_groq_models.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from groq import Groq
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
try:
|
| 8 |
+
client = Groq(api_key=os.getenv("GROQ_API_KEY"))
|
| 9 |
+
models = client.models.list()
|
| 10 |
+
print("Available Models:")
|
| 11 |
+
for model in models.data:
|
| 12 |
+
print(f"- {model.id}")
|
| 13 |
+
except Exception as e:
|
| 14 |
+
print(f"Error listing models: {e}")
|
check_groq_vision.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from groq import Groq
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import base64
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
client = Groq(api_key=os.getenv("GROQ_API_KEY"))
|
| 9 |
+
model = "llama-3.2-11b-vision-preview"
|
| 10 |
+
|
| 11 |
+
print(f"Testing Groq Vision model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Image URL
|
| 14 |
+
print("\n--- Test 1: Image URL ---")
|
| 15 |
+
try:
|
| 16 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
completion = client.chat.completions.create(
|
| 18 |
+
model=model,
|
| 19 |
+
messages=[
|
| 20 |
+
{
|
| 21 |
+
"role": "user",
|
| 22 |
+
"content": [
|
| 23 |
+
{"type": "text", "text": "What's in this image?"},
|
| 24 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 25 |
+
],
|
| 26 |
+
}
|
| 27 |
+
],
|
| 28 |
+
temperature=1,
|
| 29 |
+
max_tokens=1024,
|
| 30 |
+
top_p=1,
|
| 31 |
+
stream=False,
|
| 32 |
+
stop=None,
|
| 33 |
+
)
|
| 34 |
+
print("Response:", completion.choices[0].message.content)
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print("Groq Vision failed:", e)
|
check_idefics.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "HuggingFaceM4/idefics2-8b"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Image URL
|
| 14 |
+
print("\n--- Test 1: Image URL ---")
|
| 15 |
+
try:
|
| 16 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
messages = [
|
| 18 |
+
{
|
| 19 |
+
"role": "user",
|
| 20 |
+
"content": [
|
| 21 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 22 |
+
{"type": "text", "text": "What is in this image?"}
|
| 23 |
+
]
|
| 24 |
+
}
|
| 25 |
+
]
|
| 26 |
+
completion = client.chat.completions.create(
|
| 27 |
+
model=model,
|
| 28 |
+
messages=messages,
|
| 29 |
+
max_tokens=100
|
| 30 |
+
)
|
| 31 |
+
print("Response:", completion.choices[0].message.content)
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print("Image URL failed:", e)
|
check_idefics_raw.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
api_key = os.getenv("HF_TOKEN")
|
| 9 |
+
model = "HuggingFaceM4/idefics2-8b"
|
| 10 |
+
url = f"https://router.huggingface.co/models/{model}"
|
| 11 |
+
|
| 12 |
+
headers = {"Authorization": f"Bearer {api_key}"}
|
| 13 |
+
|
| 14 |
+
print(f"Testing URL: {url}")
|
| 15 |
+
|
| 16 |
+
# Test A: Simple text inputs
|
| 17 |
+
print("\n--- Test A: Simple Text ---")
|
| 18 |
+
response = requests.post(url, headers=headers, json={"inputs": "Hello"})
|
| 19 |
+
print(f"Status: {response.status_code}")
|
| 20 |
+
print("Response:", response.text)
|
| 21 |
+
|
| 22 |
+
# Test B: Formatted inputs (Standard for some VLM APIs)
|
| 23 |
+
# Often they accept { "inputs": "User: ...", "parameters": ... }
|
| 24 |
+
print("\n--- Test B: Formatted Prompt ---")
|
| 25 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 26 |
+
prompt = f"User:  Describe this image.<end_of_utterance>\nAssistant:"
|
| 27 |
+
response = requests.post(url, headers=headers, json={"inputs": prompt, "parameters": {"max_new_tokens": 50}})
|
| 28 |
+
print(f"Status: {response.status_code}")
|
| 29 |
+
print("Response:", response.text)
|
check_idefics_v2.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "HuggingFaceM4/idefics2-8b"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 14 |
+
|
| 15 |
+
# Format for Idefics2:
|
| 16 |
+
# User:  <text><end_of_utterance>\nAssistant:
|
| 17 |
+
prompt = f"User:  Describe this image.<end_of_utterance>\nAssistant:"
|
| 18 |
+
|
| 19 |
+
print(f"\n--- Testing with text_generation and specific prompt ---")
|
| 20 |
+
print(f"Prompt: {prompt}")
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
# Use text_generation for models that don't support chat
|
| 24 |
+
response = client.text_generation(
|
| 25 |
+
prompt=prompt,
|
| 26 |
+
model=model,
|
| 27 |
+
max_new_tokens=100
|
| 28 |
+
)
|
| 29 |
+
print("Response:", response)
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print("Failed:", e)
|
check_idefics_v3.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import traceback
|
| 3 |
+
from huggingface_hub import InferenceClient
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
api_key = os.getenv("HF_TOKEN")
|
| 9 |
+
client = InferenceClient(api_key=api_key)
|
| 10 |
+
model = "HuggingFaceM4/idefics2-8b"
|
| 11 |
+
|
| 12 |
+
print(f"Testing model: {model}")
|
| 13 |
+
|
| 14 |
+
print("\n--- Test 1: Image to Text (Captioning) ---")
|
| 15 |
+
try:
|
| 16 |
+
# This might work if the API treats it as captioning
|
| 17 |
+
res = client.image_to_text(
|
| 18 |
+
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true",
|
| 19 |
+
model=model
|
| 20 |
+
)
|
| 21 |
+
print("Response:", res)
|
| 22 |
+
except Exception:
|
| 23 |
+
traceback.print_exc()
|
| 24 |
+
|
| 25 |
+
print("\n--- Test 2: Text Generation (Simple) ---")
|
| 26 |
+
try:
|
| 27 |
+
res = client.text_generation("describe a car", model=model, max_new_tokens=50)
|
| 28 |
+
print("Response:", res)
|
| 29 |
+
except Exception:
|
| 30 |
+
traceback.print_exc()
|
check_llama.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "meta-llama/Llama-3.2-11B-Vision-Instruct"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Image URL (Llama Vision)
|
| 14 |
+
print("\n--- Test 1: Image URL ---")
|
| 15 |
+
try:
|
| 16 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
messages = [
|
| 18 |
+
{
|
| 19 |
+
"role": "user",
|
| 20 |
+
"content": [
|
| 21 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 22 |
+
{"type": "text", "text": "What is in this image?"}
|
| 23 |
+
]
|
| 24 |
+
}
|
| 25 |
+
]
|
| 26 |
+
completion = client.chat.completions.create(
|
| 27 |
+
model=model,
|
| 28 |
+
messages=messages,
|
| 29 |
+
max_tokens=100
|
| 30 |
+
)
|
| 31 |
+
print("Response:", completion.choices[0].message.content)
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print("Image URL failed:", e)
|
check_llava.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "llava-hf/llava-1.5-7b-hf"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Image URL
|
| 14 |
+
print("\n--- Test 1: Image URL ---")
|
| 15 |
+
try:
|
| 16 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
messages = [
|
| 18 |
+
{
|
| 19 |
+
"role": "user",
|
| 20 |
+
"content": [
|
| 21 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 22 |
+
{"type": "text", "text": "What is in this image?"}
|
| 23 |
+
]
|
| 24 |
+
}
|
| 25 |
+
]
|
| 26 |
+
completion = client.chat.completions.create(
|
| 27 |
+
model=model,
|
| 28 |
+
messages=messages,
|
| 29 |
+
max_tokens=100
|
| 30 |
+
)
|
| 31 |
+
print("Response:", completion.choices[0].message.content)
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print("Image URL failed:", e)
|
check_models.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import os
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 8 |
+
if not api_key:
|
| 9 |
+
print("No API key found")
|
| 10 |
+
else:
|
| 11 |
+
genai.configure(api_key=api_key)
|
| 12 |
+
print("Listing models...")
|
| 13 |
+
for m in genai.list_models():
|
| 14 |
+
if 'generateContent' in m.supported_generation_methods:
|
| 15 |
+
print(m.name)
|
check_models_list.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import os
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 8 |
+
if not api_key:
|
| 9 |
+
print("❌ API Key not found")
|
| 10 |
+
else:
|
| 11 |
+
genai.configure(api_key=api_key)
|
| 12 |
+
print("Listing available models...")
|
| 13 |
+
for m in genai.list_models():
|
| 14 |
+
if 'generateContent' in m.supported_generation_methods:
|
| 15 |
+
print(m.name)
|
check_qwen.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "Qwen/Qwen2-VL-7B-Instruct"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Text only
|
| 14 |
+
print("\n--- Test 1: Text Only ---")
|
| 15 |
+
try:
|
| 16 |
+
messages = [
|
| 17 |
+
{"role": "user", "content": "Hello, are you working?"}
|
| 18 |
+
]
|
| 19 |
+
completion = client.chat.completions.create(
|
| 20 |
+
model=model,
|
| 21 |
+
messages=messages,
|
| 22 |
+
max_tokens=100
|
| 23 |
+
)
|
| 24 |
+
print("Response:", completion.choices[0].message.content)
|
| 25 |
+
except Exception as e:
|
| 26 |
+
print("Text only failed:", e)
|
| 27 |
+
|
| 28 |
+
# Test 2: Image (using a public URL to avoid base64 issues first)
|
| 29 |
+
print("\n--- Test 2: Image URL ---")
|
| 30 |
+
try:
|
| 31 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 32 |
+
messages = [
|
| 33 |
+
{
|
| 34 |
+
"role": "user",
|
| 35 |
+
"content": [
|
| 36 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 37 |
+
{"type": "text", "text": "What is in this image?"}
|
| 38 |
+
]
|
| 39 |
+
}
|
| 40 |
+
]
|
| 41 |
+
completion = client.chat.completions.create(
|
| 42 |
+
model=model,
|
| 43 |
+
messages=messages,
|
| 44 |
+
max_tokens=100
|
| 45 |
+
)
|
| 46 |
+
print("Response:", completion.choices[0].message.content)
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print("Image URL failed:", e)
|
check_qwen_raw.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
api_key = os.getenv("HF_TOKEN")
|
| 9 |
+
model = "Qwen/Qwen2-VL-7B-Instruct"
|
| 10 |
+
# Update URL to router
|
| 11 |
+
url = f"https://router.huggingface.co/models/{model}"
|
| 12 |
+
|
| 13 |
+
headers = {"Authorization": f"Bearer {api_key}"}
|
| 14 |
+
|
| 15 |
+
print(f"Testing URL: {url}")
|
| 16 |
+
|
| 17 |
+
# Test 1: Simple text generation payload (inputs string)
|
| 18 |
+
data_text = {
|
| 19 |
+
"inputs": "Hello",
|
| 20 |
+
"parameters": {"max_new_tokens": 50}
|
| 21 |
+
}
|
| 22 |
+
print("\n--- Test 1: Text Generation (inputs string) ---")
|
| 23 |
+
response = requests.post(url, headers=headers, json=data_text)
|
| 24 |
+
print(f"Status: {response.status_code}")
|
| 25 |
+
print("Response:", response.text)
|
| 26 |
+
|
| 27 |
+
# Test 2: VQA format
|
| 28 |
+
data_vqa = {
|
| 29 |
+
"inputs": {
|
| 30 |
+
"image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true",
|
| 31 |
+
"question": "What is in this image?"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
print("\n--- Test 2: VQA Format ---")
|
| 35 |
+
response = requests.post(url, headers=headers, json=data_vqa)
|
| 36 |
+
print(f"Status: {response.status_code}")
|
| 37 |
+
print("Response:", response.text)
|
| 38 |
+
|
| 39 |
+
# Test 3: Chat Completions API (OpenAI style)
|
| 40 |
+
url_chat = f"https://router.huggingface.co/models/{model}/v1/chat/completions"
|
| 41 |
+
print(f"\nTesting URL: {url_chat}")
|
| 42 |
+
data_chat = {
|
| 43 |
+
"model": model, # Sometimes required in body
|
| 44 |
+
"messages": [
|
| 45 |
+
{"role": "user", "content": "Hello"}
|
| 46 |
+
],
|
| 47 |
+
"max_tokens": 50
|
| 48 |
+
}
|
| 49 |
+
print("\n--- Test 3: Chat Completion ---")
|
| 50 |
+
response = requests.post(url_chat, headers=headers, json=data_chat)
|
| 51 |
+
print(f"Status: {response.status_code}")
|
| 52 |
+
print("Response:", response.text)
|
clean_binary_deploy.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def main():
|
| 5 |
+
files_to_delete = [
|
| 6 |
+
"stitch_stylesync_ai_dashboard.zip",
|
| 7 |
+
"screen.jpg",
|
| 8 |
+
"test_image.jpg"
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
# Delete Local Binaries
|
| 12 |
+
print("Deleting local binaries...")
|
| 13 |
+
for filename in files_to_delete:
|
| 14 |
+
try:
|
| 15 |
+
os.remove(filename)
|
| 16 |
+
print(f"Deleted: {filename}")
|
| 17 |
+
except FileNotFoundError:
|
| 18 |
+
print(f"File not found (already deleted): {filename}")
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print(f"Error deleting {filename}: {e}")
|
| 21 |
+
|
| 22 |
+
# Update .gitignore
|
| 23 |
+
print("Updating .gitignore...")
|
| 24 |
+
gitignore_path = ".gitignore"
|
| 25 |
+
|
| 26 |
+
# Read existing content to avoid duplicate entries
|
| 27 |
+
existing_ignores = []
|
| 28 |
+
try:
|
| 29 |
+
if os.path.exists(gitignore_path):
|
| 30 |
+
with open(gitignore_path, "r", encoding="utf-8") as f:
|
| 31 |
+
existing_ignores = f.read().splitlines()
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print(f"Warning: Could not read .gitignore: {e}")
|
| 34 |
+
|
| 35 |
+
ignores_to_add = ["*.zip", "*.jpg"]
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
with open(gitignore_path, "a", encoding="utf-8") as f:
|
| 39 |
+
for ignore in ignores_to_add:
|
| 40 |
+
if ignore not in existing_ignores:
|
| 41 |
+
# add a newline if files doesn't end with one, but simplier to just write
|
| 42 |
+
f.write(f"\n{ignore}\n")
|
| 43 |
+
print(f"Added '{ignore}' to .gitignore")
|
| 44 |
+
else:
|
| 45 |
+
print(f"'{ignore}' already in .gitignore")
|
| 46 |
+
except Exception as e:
|
| 47 |
+
print(f"Error updating .gitignore: {e}")
|
| 48 |
+
|
| 49 |
+
# The Orphan Branch Strategy
|
| 50 |
+
print("\nExecuting Git Orphan Branch Strategy...")
|
| 51 |
+
|
| 52 |
+
commands = [
|
| 53 |
+
"git checkout --orphan hf_clean_deploy_v2",
|
| 54 |
+
"git add .",
|
| 55 |
+
'git commit -m "UI Update: Glassmorphism Design & Binary Cleanup"'
|
| 56 |
+
]
|
| 57 |
+
|
| 58 |
+
for cmd in commands:
|
| 59 |
+
print(f"Running: {cmd}")
|
| 60 |
+
subprocess.run(cmd, shell=True, check=False)
|
| 61 |
+
|
| 62 |
+
push_cmd = "git push --force space hf_clean_deploy_v2:main"
|
| 63 |
+
print(f"\nRunning final push command: {push_cmd}")
|
| 64 |
+
|
| 65 |
+
result = subprocess.run(push_cmd, shell=True, capture_output=True, text=True)
|
| 66 |
+
|
| 67 |
+
# Expected Output
|
| 68 |
+
print("\n--- Push Command STDOUT ---")
|
| 69 |
+
print(result.stdout)
|
| 70 |
+
print("--- Push Command STDERR ---")
|
| 71 |
+
print(result.stderr)
|
| 72 |
+
print("---------------------------\n")
|
| 73 |
+
|
| 74 |
+
if result.returncode == 0:
|
| 75 |
+
print("✅ Force push successful!")
|
| 76 |
+
else:
|
| 77 |
+
print("❌ Force push failed (see STDERR above).")
|
| 78 |
+
|
| 79 |
+
if __name__ == "__main__":
|
| 80 |
+
main()
|
code.html
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="dark" lang="en"><head>
|
| 3 |
+
<meta charset="utf-8"/>
|
| 4 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 5 |
+
<title>StyleSync AI Dashboard</title>
|
| 6 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 7 |
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
| 8 |
+
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Spline+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
"primary": "#3b82f6", // Electric Blue
|
| 18 |
+
"primary-hover": "#2563eb",
|
| 19 |
+
"secondary": "#6366f1", // Indigo accent
|
| 20 |
+
"background-light": "#f8fafc",
|
| 21 |
+
"background-dark": "#0f172a", // Deep Slate
|
| 22 |
+
"surface-dark": "#1e293b", // Slate 800
|
| 23 |
+
"surface-darker": "#020617", // Slate 950
|
| 24 |
+
"border-dark": "#334155", // Slate 700
|
| 25 |
+
},
|
| 26 |
+
fontFamily: {
|
| 27 |
+
"display": ["Spline Sans", "sans-serif"],
|
| 28 |
+
"mono": ["monospace"]
|
| 29 |
+
},
|
| 30 |
+
borderRadius: {"DEFAULT": "1rem", "lg": "2rem", "xl": "3rem", "full": "9999px"},
|
| 31 |
+
},
|
| 32 |
+
},
|
| 33 |
+
}
|
| 34 |
+
</script>
|
| 35 |
+
<style>.custom-scrollbar::-webkit-scrollbar {
|
| 36 |
+
width: 8px;
|
| 37 |
+
height: 8px;
|
| 38 |
+
}
|
| 39 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 40 |
+
background: #020617;
|
| 41 |
+
}
|
| 42 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 43 |
+
background: #334155;
|
| 44 |
+
border-radius: 4px;
|
| 45 |
+
}
|
| 46 |
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
| 47 |
+
background: #3b82f6;
|
| 48 |
+
}
|
| 49 |
+
</style>
|
| 50 |
+
</head>
|
| 51 |
+
<body class="font-display bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased overflow-hidden h-screen flex flex-col">
|
| 52 |
+
<header class="flex-none flex items-center justify-between whitespace-nowrap border-b border-solid border-border-dark px-6 py-4 bg-background-dark z-10">
|
| 53 |
+
<div class="flex items-center gap-3 text-white">
|
| 54 |
+
<div class="flex items-center justify-center size-10 rounded-full bg-primary/10 text-primary">
|
| 55 |
+
<span class="material-symbols-outlined text-2xl">all_inclusive</span>
|
| 56 |
+
</div>
|
| 57 |
+
<div>
|
| 58 |
+
<h2 class="text-white text-xl font-bold leading-tight tracking-tight">StyleSync AI</h2>
|
| 59 |
+
<span class="text-xs text-primary/60 font-medium uppercase tracking-wider">Enterprise Edition</span>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
<button class="flex min-w-[84px] cursor-pointer items-center justify-center overflow-hidden rounded-full h-10 px-6 bg-primary hover:bg-primary-hover transition-colors text-white text-sm font-bold leading-normal tracking-[0.015em]">
|
| 63 |
+
<span class="material-symbols-outlined mr-2 text-lg">rocket_launch</span>
|
| 64 |
+
<span class="truncate">Deploy</span>
|
| 65 |
+
</button>
|
| 66 |
+
</header>
|
| 67 |
+
<main class="flex-1 flex overflow-hidden">
|
| 68 |
+
<div class="flex-1 flex flex-col border-r border-border-dark min-w-[400px] overflow-y-auto custom-scrollbar p-8">
|
| 69 |
+
<div class="max-w-2xl w-full mx-auto flex flex-col gap-6 h-full">
|
| 70 |
+
<div class="flex items-center justify-between">
|
| 71 |
+
<h2 class="text-white tracking-light text-[28px] font-bold leading-tight">Input Data</h2>
|
| 72 |
+
<span class="bg-surface-dark text-white px-3 py-1 rounded-full text-xs font-medium border border-border-dark">Step 1 of 2</span>
|
| 73 |
+
</div>
|
| 74 |
+
<div class="flex flex-col flex-1 max-h-[300px] min-h-[200px]">
|
| 75 |
+
<div class="group relative flex flex-col items-center justify-center gap-4 rounded-xl border-2 border-dashed border-slate-600 hover:border-primary/50 hover:bg-surface-dark transition-all cursor-pointer h-full w-full px-6 py-8">
|
| 76 |
+
<div class="size-16 rounded-full bg-surface-dark group-hover:bg-primary/20 flex items-center justify-center transition-colors border border-border-dark group-hover:border-primary/30">
|
| 77 |
+
<span class="material-symbols-outlined text-3xl text-primary">cloud_upload</span>
|
| 78 |
+
</div>
|
| 79 |
+
<div class="flex flex-col items-center gap-1">
|
| 80 |
+
<p class="text-white text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 81 |
+
<p class="text-slate-400 text-sm font-normal text-center">Supports JPG, PNG, WEBP</p>
|
| 82 |
+
</div>
|
| 83 |
+
<button class="mt-2 flex items-center justify-center rounded-full h-9 px-4 bg-slate-700 hover:bg-slate-600 text-white text-xs font-bold transition-colors">
|
| 84 |
+
Browse Files
|
| 85 |
+
</button>
|
| 86 |
+
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/10 pointer-events-none rounded-xl"></div>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
<div class="flex flex-col gap-3 flex-1">
|
| 90 |
+
<label class="flex items-center justify-between">
|
| 91 |
+
<span class="text-white text-base font-medium">Raw Product Specs</span>
|
| 92 |
+
<span class="text-xs text-slate-400">JSON or Plain Text</span>
|
| 93 |
+
</label>
|
| 94 |
+
<div class="relative flex-1">
|
| 95 |
+
<textarea class="form-input w-full h-full resize-none rounded-xl text-white placeholder:text-slate-500 focus:outline-0 focus:ring-2 focus:ring-primary/50 border border-border-dark bg-surface-dark p-4 text-base font-normal leading-relaxed font-mono" placeholder="Enter fabric details, dimensions, and SKU...
|
| 96 |
+
Example:
|
| 97 |
+
Material: 100% Recycled Polyester
|
| 98 |
+
Fit: Regular
|
| 99 |
+
SKU: JK-2024-WTR"></textarea>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
<div class="pt-2">
|
| 103 |
+
<button class="group relative w-full cursor-pointer overflow-hidden rounded-xl h-16 bg-gradient-to-r from-primary to-blue-700 hover:from-blue-500 hover:to-primary transition-all shadow-[0_0_20px_rgba(59,130,246,0.3)]">
|
| 104 |
+
<div class="absolute inset-0 flex items-center justify-center gap-3">
|
| 105 |
+
<span class="text-white text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span>
|
| 106 |
+
<span class="material-symbols-outlined text-white group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
| 107 |
+
</div>
|
| 108 |
+
<div class="absolute top-0 -inset-full h-full w-1/2 z-5 block transform -skew-x-12 bg-gradient-to-r from-transparent to-white opacity-20 group-hover:animate-shine"></div>
|
| 109 |
+
</button>
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
<div class="flex-1 flex flex-col bg-surface-darker p-8 overflow-hidden relative">
|
| 114 |
+
<div class="absolute inset-0 opacity-5 pointer-events-none" style="background-image: radial-gradient(#64748b 1px, transparent 1px); background-size: 24px 24px;"></div>
|
| 115 |
+
<div class="max-w-3xl w-full mx-auto flex flex-col gap-6 h-full relative z-10">
|
| 116 |
+
<div class="flex items-center justify-between">
|
| 117 |
+
<h2 class="text-white tracking-light text-[28px] font-bold leading-tight">Generated Output</h2>
|
| 118 |
+
<div class="flex gap-2">
|
| 119 |
+
<button class="p-2 hover:bg-surface-dark rounded-lg text-slate-400 hover:text-white transition-colors">
|
| 120 |
+
<span class="material-symbols-outlined text-xl">content_copy</span>
|
| 121 |
+
</button>
|
| 122 |
+
<button class="p-2 hover:bg-surface-dark rounded-lg text-slate-400 hover:text-white transition-colors">
|
| 123 |
+
<span class="material-symbols-outlined text-xl">download</span>
|
| 124 |
+
</button>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
| 128 |
+
<div class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 129 |
+
<div class="relative size-3">
|
| 130 |
+
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-purple-500 opacity-75"></span>
|
| 131 |
+
<span class="relative inline-flex rounded-full size-3 bg-purple-500"></span>
|
| 132 |
+
</div>
|
| 133 |
+
<div class="flex flex-col overflow-hidden">
|
| 134 |
+
<span class="text-xs text-slate-400 truncate">Vision Agent</span>
|
| 135 |
+
<span class="text-sm font-bold text-white truncate">Gemini Pro 1.5</span>
|
| 136 |
+
</div>
|
| 137 |
+
<span class="material-symbols-outlined text-purple-500 ml-auto text-lg">visibility</span>
|
| 138 |
+
</div>
|
| 139 |
+
<div class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 140 |
+
<div class="relative size-3">
|
| 141 |
+
<span class="relative inline-flex rounded-full size-3 bg-primary"></span>
|
| 142 |
+
</div>
|
| 143 |
+
<div class="flex flex-col overflow-hidden">
|
| 144 |
+
<span class="text-xs text-slate-400 truncate">Reasoning Agent</span>
|
| 145 |
+
<span class="text-sm font-bold text-white truncate">Llama 3 70B</span>
|
| 146 |
+
</div>
|
| 147 |
+
<span class="material-symbols-outlined text-primary ml-auto text-lg">psychology</span>
|
| 148 |
+
</div>
|
| 149 |
+
<div class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 150 |
+
<div class="relative size-3">
|
| 151 |
+
<span class="relative inline-flex rounded-full size-3 bg-cyan-500"></span>
|
| 152 |
+
</div>
|
| 153 |
+
<div class="flex flex-col overflow-hidden">
|
| 154 |
+
<span class="text-xs text-slate-400 truncate">SEO Context</span>
|
| 155 |
+
<span class="text-sm font-bold text-white truncate">Pinecone DB</span>
|
| 156 |
+
</div>
|
| 157 |
+
<span class="material-symbols-outlined text-cyan-500 ml-auto text-lg">database</span>
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
<div class="flex-1 rounded-xl bg-[#0d1117] border border-border-dark flex flex-col overflow-hidden shadow-2xl">
|
| 161 |
+
<div class="flex items-center justify-between px-4 py-2 bg-surface-dark border-b border-border-dark">
|
| 162 |
+
<span class="text-xs font-mono text-slate-400">output.json</span>
|
| 163 |
+
<div class="flex gap-1.5">
|
| 164 |
+
<div class="size-2.5 rounded-full bg-red-500/20"></div>
|
| 165 |
+
<div class="size-2.5 rounded-full bg-yellow-500/20"></div>
|
| 166 |
+
<div class="size-2.5 rounded-full bg-green-500/20"></div>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="flex-1 p-4 overflow-auto custom-scrollbar font-mono text-sm leading-6">
|
| 170 |
+
<pre><code class="language-json"><span class="text-slate-500">1</span> <span class="text-yellow-500">{</span>
|
| 171 |
+
<span class="text-slate-500">2</span> <span class="text-primary">"product_analysis"</span><span class="text-white">:</span> <span class="text-yellow-500">{</span>
|
| 172 |
+
<span class="text-slate-500">3</span> <span class="text-primary">"title"</span><span class="text-white">:</span> <span class="text-sky-300">"Apex Terrain All-Weather Performance Jacket"</span><span class="text-white">,</span>
|
| 173 |
+
<span class="text-slate-500">4</span> <span class="text-primary">"category"</span><span class="text-white">:</span> <span class="text-sky-300">"Outerwear / Men's / Technical Shells"</span><span class="text-white">,</span>
|
| 174 |
+
<span class="text-slate-500">5</span> <span class="text-primary">"features"</span><span class="text-white">:</span> <span class="text-yellow-500">[</span>
|
| 175 |
+
<span class="text-slate-500">6</span> <span class="text-sky-300">"Gore-Tex Pro Membrane"</span><span class="text-white">,</span>
|
| 176 |
+
<span class="text-slate-500">7</span> <span class="text-sky-300">"Articulated Sleeves"</span><span class="text-white">,</span>
|
| 177 |
+
<span class="text-slate-500">8</span> <span class="text-sky-300">"Helmet-Compatible Hood"</span>
|
| 178 |
+
<span class="text-slate-500">9</span> <span class="text-yellow-500">]</span><span class="text-white">,</span>
|
| 179 |
+
<span class="text-slate-500">10</span> <span class="text-primary">"seo_tags"</span><span class="text-white">:</span> <span class="text-yellow-500">[</span>
|
| 180 |
+
<span class="text-slate-500">11</span> <span class="text-sky-300">"#hikinggear"</span><span class="text-white">,</span> <span class="text-sky-300">"#waterproof"</span><span class="text-white">,</span> <span class="text-sky-300">"#adventure"</span>
|
| 181 |
+
<span class="text-slate-500">12</span> <span class="text-yellow-500">]</span><span class="text-white">,</span>
|
| 182 |
+
<span class="text-slate-500">13</span> <span class="text-primary">"sentiment_score"</span><span class="text-white">:</span> <span class="text-purple-400">0.98</span><span class="text-white">,</span>
|
| 183 |
+
<span class="text-slate-500">14</span> <span class="text-primary">"market_fit"</span><span class="text-white">:</span> <span class="text-sky-300">"High Demand"</span>
|
| 184 |
+
<span class="text-slate-500">15</span> <span class="text-yellow-500">}</span><span class="text-white">,</span>
|
| 185 |
+
<span class="text-slate-500">16</span> <span class="text-primary">"deployment_status"</span><span class="text-white">:</span> <span class="text-sky-300">"Ready"</span>
|
| 186 |
+
<span class="text-slate-500">17</span> <span class="text-yellow-500">}</span></code></pre>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
</main>
|
| 192 |
+
<script>
|
| 193 |
+
tailwind.config.theme.extend.animation = {
|
| 194 |
+
shine: 'shine 1s',
|
| 195 |
+
}
|
| 196 |
+
tailwind.config.theme.extend.keyframes = {
|
| 197 |
+
shine: {
|
| 198 |
+
'100%': { left: '125%' },
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
</script>
|
| 202 |
+
|
| 203 |
+
</body></html>
|
connect_n8n.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def update_requirements():
|
| 5 |
+
req_file = "requirements.txt"
|
| 6 |
+
if not os.path.exists(req_file):
|
| 7 |
+
with open(req_file, "w") as f:
|
| 8 |
+
f.write("httpx\n")
|
| 9 |
+
print(f"Created {req_file} with httpx.")
|
| 10 |
+
return
|
| 11 |
+
|
| 12 |
+
with open(req_file, "r") as f:
|
| 13 |
+
content = f.read()
|
| 14 |
+
|
| 15 |
+
if "httpx" not in content:
|
| 16 |
+
with open(req_file, "a") as f:
|
| 17 |
+
f.write("\nhttpx\n")
|
| 18 |
+
print("Appended httpx to requirements.txt.")
|
| 19 |
+
else:
|
| 20 |
+
print("httpx already in requirements.txt.")
|
| 21 |
+
|
| 22 |
+
def update_main():
|
| 23 |
+
main_content = r'''import os
|
| 24 |
+
import httpx
|
| 25 |
+
import asyncio
|
| 26 |
+
from fastapi import FastAPI, UploadFile, File
|
| 27 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 28 |
+
from dotenv import load_dotenv
|
| 29 |
+
# Import Agents
|
| 30 |
+
from agents.visual_analyst import VisualAnalyst
|
| 31 |
+
from agents.memory_agent import MemoryAgent
|
| 32 |
+
from agents.writer_agent import WriterAgent
|
| 33 |
+
load_dotenv()
|
| 34 |
+
app = FastAPI()
|
| 35 |
+
# Initialize Agents
|
| 36 |
+
try:
|
| 37 |
+
visual_agent = VisualAnalyst()
|
| 38 |
+
memory_agent = MemoryAgent()
|
| 39 |
+
writer_agent = WriterAgent()
|
| 40 |
+
memory_agent.seed_database()
|
| 41 |
+
print("✅ All Agents Online")
|
| 42 |
+
except Exception as e:
|
| 43 |
+
print(f"⚠️ Agent Startup Warning: {e}")
|
| 44 |
+
@app.get("/", response_class=HTMLResponse)
|
| 45 |
+
async def read_root():
|
| 46 |
+
try:
|
| 47 |
+
with open("dashboard.html", "r") as f:
|
| 48 |
+
return f.read()
|
| 49 |
+
except FileNotFoundError:
|
| 50 |
+
return "<h1>Error: dashboard.html not found</h1>"
|
| 51 |
+
@app.post("/generate-catalog")
|
| 52 |
+
async def generate_catalog(file: UploadFile = File(...)):
|
| 53 |
+
try:
|
| 54 |
+
# 1. Save Temp File
|
| 55 |
+
os.makedirs("uploads", exist_ok=True)
|
| 56 |
+
file_path = f"uploads/{file.filename}"
|
| 57 |
+
with open(file_path, "wb") as f:
|
| 58 |
+
f.write(await file.read())
|
| 59 |
+
# 2. Run AI Pipeline
|
| 60 |
+
visual_data = await visual_agent.analyze_image(file_path)
|
| 61 |
+
|
| 62 |
+
query = f"{visual_data.get('main_color', '')} {visual_data.get('product_type', 'product')}"
|
| 63 |
+
seo_keywords = memory_agent.retrieve_keywords(query)
|
| 64 |
+
|
| 65 |
+
listing = writer_agent.write_listing(visual_data, seo_keywords)
|
| 66 |
+
|
| 67 |
+
# 3. Construct Final Payload
|
| 68 |
+
final_data = {
|
| 69 |
+
"visual_data": visual_data,
|
| 70 |
+
"seo_keywords": seo_keywords,
|
| 71 |
+
"listing": listing
|
| 72 |
+
}
|
| 73 |
+
# 4. ⚡ N8N AUTOMATION TRIGGER ⚡
|
| 74 |
+
n8n_url = os.getenv("N8N_WEBHOOK_URL")
|
| 75 |
+
if n8n_url:
|
| 76 |
+
print(f"🚀 Sending data to N8N: {n8n_url}")
|
| 77 |
+
# Fire and forget (don't make the user wait for n8n)
|
| 78 |
+
asyncio.create_task(send_to_n8n(n8n_url, final_data))
|
| 79 |
+
|
| 80 |
+
# Cleanup
|
| 81 |
+
if os.path.exists(file_path):
|
| 82 |
+
os.remove(file_path)
|
| 83 |
+
|
| 84 |
+
return JSONResponse(content=final_data)
|
| 85 |
+
except Exception as e:
|
| 86 |
+
return JSONResponse(content={"error": str(e)}, status_code=500)
|
| 87 |
+
# Async Helper to send data without blocking
|
| 88 |
+
async def send_to_n8n(url, data):
|
| 89 |
+
try:
|
| 90 |
+
async with httpx.AsyncClient() as client:
|
| 91 |
+
await client.post(url, json=data, timeout=5.0)
|
| 92 |
+
print("✅ N8N Webhook Sent Successfully")
|
| 93 |
+
except Exception as e:
|
| 94 |
+
print(f"❌ N8N Webhook Failed: {e}")
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
import uvicorn
|
| 97 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 98 |
+
'''
|
| 99 |
+
with open("main.py", "w", encoding="utf-8") as f:
|
| 100 |
+
f.write(main_content)
|
| 101 |
+
print("Updated main.py with N8N integration logic.")
|
| 102 |
+
|
| 103 |
+
def deploy():
|
| 104 |
+
try:
|
| 105 |
+
subprocess.run(["git", "add", "."], check=True)
|
| 106 |
+
# Check if there are changes to commit
|
| 107 |
+
status = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True)
|
| 108 |
+
if status.stdout.strip():
|
| 109 |
+
subprocess.run(["git", "commit", "-m", "Add N8N Integration"], check=True)
|
| 110 |
+
print("Git commit successful.")
|
| 111 |
+
else:
|
| 112 |
+
print("No changes to commit.")
|
| 113 |
+
|
| 114 |
+
print("Pushing to space...")
|
| 115 |
+
subprocess.run(["git", "push", "space", "clean_deploy:main"], check=True)
|
| 116 |
+
print("✅ Successfully deployed to Hugging Face Space.")
|
| 117 |
+
|
| 118 |
+
except subprocess.CalledProcessError as e:
|
| 119 |
+
print(f"❌ Deployment failed: {e}")
|
| 120 |
+
|
| 121 |
+
if __name__ == "__main__":
|
| 122 |
+
print("Starting N8N Integration Setup...")
|
| 123 |
+
update_requirements()
|
| 124 |
+
update_main()
|
| 125 |
+
deploy()
|
| 126 |
+
print("✅ connect_n8n.py completed.")
|
create_dockerfile.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
def run_command(command):
|
| 5 |
+
print(f"Running: {command}")
|
| 6 |
+
try:
|
| 7 |
+
# shell=True allows us to run the command string exactly as provided
|
| 8 |
+
subprocess.run(command, shell=True, check=True)
|
| 9 |
+
except subprocess.CalledProcessError as e:
|
| 10 |
+
print(f"Error executing command '{command}': {e}")
|
| 11 |
+
sys.exit(1)
|
| 12 |
+
|
| 13 |
+
def main():
|
| 14 |
+
# 1. Create Dockerfile
|
| 15 |
+
dockerfile_content = """FROM python:3.9
|
| 16 |
+
WORKDIR /code
|
| 17 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 18 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 19 |
+
COPY . /code
|
| 20 |
+
# Fix permissions for libraries that write to home
|
| 21 |
+
RUN mkdir -p /tmp/home
|
| 22 |
+
ENV HOME=/tmp/home
|
| 23 |
+
# Start the FastAPI server on port 7860 (required by Hugging Face)
|
| 24 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
print("Creating Dockerfile...")
|
| 28 |
+
try:
|
| 29 |
+
with open("Dockerfile", "w", newline='\n') as f:
|
| 30 |
+
f.write(dockerfile_content)
|
| 31 |
+
print("Dockerfile created successfully.")
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print(f"Failed to create Dockerfile: {e}")
|
| 34 |
+
sys.exit(1)
|
| 35 |
+
|
| 36 |
+
# 2. Push to Space
|
| 37 |
+
print("Executing Git commands...")
|
| 38 |
+
commands = [
|
| 39 |
+
'git add Dockerfile',
|
| 40 |
+
'git commit -m "Add Dockerfile for Hugging Face deployment"',
|
| 41 |
+
'git push -f space clean_deploy:main'
|
| 42 |
+
]
|
| 43 |
+
|
| 44 |
+
for cmd in commands:
|
| 45 |
+
run_command(cmd)
|
| 46 |
+
|
| 47 |
+
print("\ncreate_dockerfile.py execution completed.")
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
main()
|
dashboard.html
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="dark" lang="en"><head>
|
| 3 |
+
<meta charset="utf-8"/>
|
| 4 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 5 |
+
<title>StyleSync AI Dashboard</title>
|
| 6 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 7 |
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
| 8 |
+
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
"primary": "#f59e0b",
|
| 18 |
+
"primary-hover": "#d97706",
|
| 19 |
+
"glass-border": "rgba(255, 255, 255, 0.12)",
|
| 20 |
+
"glass-surface": "rgba(255, 255, 255, 0.03)",
|
| 21 |
+
"glass-surface-hover": "rgba(255, 255, 255, 0.07)",
|
| 22 |
+
"charcoal": "#0a0a0a",
|
| 23 |
+
"amber-accent": "#fbbf24"
|
| 24 |
+
},
|
| 25 |
+
fontFamily: {
|
| 26 |
+
"display": ["Plus Jakarta Sans", "sans-serif"],
|
| 27 |
+
"mono": ["monospace"]
|
| 28 |
+
},
|
| 29 |
+
borderRadius: { "DEFAULT": "1rem", "lg": "1.5rem", "xl": "2rem", "2xl": "3rem", "full": "9999px" },
|
| 30 |
+
backdropBlur: {
|
| 31 |
+
'xs': '2px',
|
| 32 |
+
},
|
| 33 |
+
screens: {
|
| 34 |
+
'xs': '480px',
|
| 35 |
+
},
|
| 36 |
+
animation: {
|
| 37 |
+
'pulse-slow': 'pulse-slow 3s infinite',
|
| 38 |
+
'glow-pulse': 'glow-pulse 3s infinite',
|
| 39 |
+
'copy-success': 'copy-success 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
|
| 40 |
+
},
|
| 41 |
+
keyframes: {
|
| 42 |
+
'pulse-slow': {
|
| 43 |
+
'0%, 100%': { transform: 'scale(1)' },
|
| 44 |
+
'50%': { transform: 'scale(1.02)' },
|
| 45 |
+
},
|
| 46 |
+
'glow-pulse': {
|
| 47 |
+
'0%, 100%': { boxShadow: '0 0 0 0 rgba(245, 158, 11, 0.4)' },
|
| 48 |
+
'50%': { boxShadow: '0 0 20px 5px rgba(251, 191, 36, 0.5)' },
|
| 49 |
+
},
|
| 50 |
+
'copy-success': {
|
| 51 |
+
'0%': { transform: 'scale(0.8)', opacity: '0' },
|
| 52 |
+
'100%': { transform: 'scale(1)', opacity: '1' },
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
},
|
| 57 |
+
}
|
| 58 |
+
</script>
|
| 59 |
+
<style type="text/tailwindcss">
|
| 60 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 61 |
+
width: 6px;
|
| 62 |
+
height: 6px;
|
| 63 |
+
}
|
| 64 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 65 |
+
background: transparent;
|
| 66 |
+
}
|
| 67 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 68 |
+
background: rgba(255, 255, 255, 0.1);
|
| 69 |
+
border-radius: 99px;
|
| 70 |
+
}
|
| 71 |
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
| 72 |
+
background: rgba(255, 255, 255, 0.2);
|
| 73 |
+
}
|
| 74 |
+
body {
|
| 75 |
+
background-color: #0a0a0a;
|
| 76 |
+
background-image:
|
| 77 |
+
radial-gradient(at 0% 0%, rgba(20, 20, 20, 1) 0, transparent 50%),
|
| 78 |
+
radial-gradient(at 50% -10%, rgba(217, 119, 6, 0.15) 0, transparent 60%),
|
| 79 |
+
radial-gradient(at 100% 0%, rgba(251, 191, 36, 0.1) 0, transparent 50%),
|
| 80 |
+
radial-gradient(at 80% 80%, rgba(217, 119, 6, 0.12) 0, transparent 50%);
|
| 81 |
+
background-attachment: fixed;
|
| 82 |
+
min-height: 100vh;
|
| 83 |
+
}
|
| 84 |
+
.glass-panel {
|
| 85 |
+
background: rgba(15, 15, 15, 0.75);
|
| 86 |
+
backdrop-filter: blur(20px);
|
| 87 |
+
-webkit-backdrop-filter: blur(20px);
|
| 88 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 89 |
+
}
|
| 90 |
+
@media (max-width: 768px) {
|
| 91 |
+
.glass-panel {
|
| 92 |
+
backdrop-filter: blur(14px);
|
| 93 |
+
-webkit-backdrop-filter: blur(14px);
|
| 94 |
+
background: rgba(10, 10, 10, 0.85);
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
.glass-card {
|
| 98 |
+
background: rgba(255, 255, 255, 0.02);
|
| 99 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 100 |
+
backdrop-filter: blur(12px);
|
| 101 |
+
transition: all 0.3s ease;
|
| 102 |
+
}
|
| 103 |
+
@media (hover: hover) {
|
| 104 |
+
.glass-card:hover {
|
| 105 |
+
border-color: rgba(251, 191, 36, 0.3);
|
| 106 |
+
box-shadow: 0 0 25px rgba(245, 158, 11, 0.15);
|
| 107 |
+
background: rgba(255, 255, 255, 0.04);
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
.neon-textarea {
|
| 111 |
+
background: rgba(255, 255, 255, 0.03);
|
| 112 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 113 |
+
backdrop-filter: blur(16px);
|
| 114 |
+
-webkit-backdrop-filter: blur(16px);
|
| 115 |
+
transition: all 0.3s ease;
|
| 116 |
+
}
|
| 117 |
+
.neon-textarea:focus {
|
| 118 |
+
outline: none;
|
| 119 |
+
border-color: rgba(251, 191, 36, 0.5);
|
| 120 |
+
box-shadow: 0 0 15px rgba(245, 158, 11, 0.25), inset 0 0 5px rgba(245, 158, 11, 0.05);
|
| 121 |
+
background: rgba(255, 255, 255, 0.06);
|
| 122 |
+
}
|
| 123 |
+
.copy-active {
|
| 124 |
+
box-shadow: 0 0 15px rgba(251, 191, 36, 0.6) !important;
|
| 125 |
+
border-color: rgba(251, 191, 36, 0.4) !important;
|
| 126 |
+
background: rgba(251, 191, 36, 0.1) !important;
|
| 127 |
+
}
|
| 128 |
+
</style>
|
| 129 |
+
<style>
|
| 130 |
+
body {
|
| 131 |
+
min-height: max(884px, 100dvh);
|
| 132 |
+
}
|
| 133 |
+
</style>
|
| 134 |
+
</head>
|
| 135 |
+
<body class="font-display text-white antialiased overflow-x-hidden min-h-screen flex flex-col selection:bg-amber-500 selection:text-black">
|
| 136 |
+
<header class="flex-none z-30 px-4 py-3 lg:px-6 lg:py-4 border-b border-white/5 glass-panel sticky top-0">
|
| 137 |
+
<div class="max-w-[1600px] mx-auto flex items-center justify-between gap-4">
|
| 138 |
+
<div class="flex items-center gap-3 lg:gap-4 flex-1 min-w-0">
|
| 139 |
+
<div class="flex-none flex items-center justify-center size-9 lg:size-10 rounded-xl bg-gradient-to-br from-amber-400 to-amber-700 shadow-lg shadow-amber-900/20 text-white">
|
| 140 |
+
<span class="material-symbols-outlined text-xl lg:text-2xl">all_inclusive</span>
|
| 141 |
+
</div>
|
| 142 |
+
<div class="min-w-0">
|
| 143 |
+
<h2 class="text-white text-base lg:text-xl font-bold leading-tight tracking-tight truncate">StyleSync AI</h2>
|
| 144 |
+
<div class="flex items-center gap-2">
|
| 145 |
+
<span class="size-1.5 rounded-full bg-amber-500 animate-pulse flex-none"></span>
|
| 146 |
+
<span class="text-[10px] text-amber-200/60 font-bold uppercase tracking-wider truncate">Enterprise Edition</span>
|
| 147 |
+
</div>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
<a class="hidden sm:inline-flex flex-none items-center justify-center rounded-full h-9 lg:h-10 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-xs lg:text-sm font-bold transition-all" href="/">
|
| 151 |
+
<span class="material-symbols-outlined text-lg lg:mr-2">arrow_back</span>
|
| 152 |
+
<span class="hidden lg:inline">Back to Home</span>
|
| 153 |
+
</a>
|
| 154 |
+
<button class="group relative flex-none flex cursor-pointer items-center justify-center overflow-hidden rounded-full h-9 lg:h-10 px-4 lg:px-6 bg-gradient-to-r from-amber-600 to-amber-800 hover:from-amber-500 hover:to-amber-700 border border-white/10 shadow-[0_0_15px_rgba(245,158,11,0.3)] transition-all" id="deployBtn">
|
| 155 |
+
<div class="absolute inset-0 bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
| 156 |
+
<span class="material-symbols-outlined lg:mr-2 text-lg relative z-10">rocket_launch</span>
|
| 157 |
+
<span class="hidden lg:inline truncate text-sm font-bold tracking-wide relative z-10">Deploy</span>
|
| 158 |
+
</button>
|
| 159 |
+
</div>
|
| 160 |
+
</header>
|
| 161 |
+
<main class="flex-1 flex flex-col lg:flex-row relative z-10">
|
| 162 |
+
<div class="fixed top-1/4 -left-20 w-64 h-64 lg:w-96 lg:h-96 bg-amber-900/10 rounded-full blur-[120px] pointer-events-none z-0"></div>
|
| 163 |
+
<div class="fixed bottom-1/4 -right-20 w-64 h-64 lg:w-96 lg:h-96 bg-orange-900/5 rounded-full blur-[120px] pointer-events-none z-0"></div>
|
| 164 |
+
<div class="flex-none lg:flex-1 flex flex-col lg:border-r border-white/5 p-4 lg:p-8 bg-black/40 lg:overflow-y-auto lg:h-[calc(100vh-73px)] relative z-10">
|
| 165 |
+
<div class="max-w-xl w-full mx-auto flex flex-col gap-4 lg:gap-6 h-full">
|
| 166 |
+
<div class="flex items-center justify-between mb-2">
|
| 167 |
+
<h2 class="text-white text-xl lg:text-3xl font-bold tracking-tight">Input Data</h2>
|
| 168 |
+
<span class="bg-white/5 text-amber-200/80 px-2.5 py-1 rounded-full text-[10px] lg:text-xs font-semibold border border-white/10 backdrop-blur-sm">Step 1 of 2</span>
|
| 169 |
+
</div>
|
| 170 |
+
<div class="flex flex-col flex-none">
|
| 171 |
+
<input accept=".jpg,.jpeg,.png,.webp" class="hidden" id="fileInput" type="file"/>
|
| 172 |
+
<div class="group relative flex flex-col items-center justify-center gap-3 lg:gap-4 rounded-2xl lg:rounded-3xl border-2 border-dashed border-white/10 hover:border-amber-500/50 hover:bg-white/5 transition-all cursor-pointer min-h-[200px] lg:min-h-[260px] px-4 py-6 lg:px-6 lg:py-8 glass-card" id="dropZone">
|
| 173 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 174 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 175 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 176 |
+
</div>
|
| 177 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 178 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 179 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 180 |
+
</div>
|
| 181 |
+
<button class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide" id="browseBtn">
|
| 182 |
+
Browse Files
|
| 183 |
+
</button>
|
| 184 |
+
</div>
|
| 185 |
+
</div>
|
| 186 |
+
<div class="flex flex-col gap-2 lg:gap-3 flex-1">
|
| 187 |
+
<label class="flex items-center justify-between px-1">
|
| 188 |
+
<span class="text-white text-sm lg:text-base font-semibold">Raw Product Specs</span>
|
| 189 |
+
<span class="text-[10px] lg:text-xs text-neutral-500 font-medium bg-black/40 px-2 py-0.5 rounded border border-white/5">JSON or Plain Text</span>
|
| 190 |
+
</label>
|
| 191 |
+
<div class="relative w-full">
|
| 192 |
+
<textarea class="form-input w-full min-h-[140px] lg:h-full lg:min-h-[180px] resize-y lg:resize-none rounded-xl lg:rounded-2xl text-neutral-100 placeholder:text-neutral-600 neon-textarea p-4 lg:p-5 text-sm leading-relaxed font-mono shadow-inner transition-all duration-300" id="productSpecs" placeholder="// Enter fabric details, dimensions, and SKU..."></textarea>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
<div class="pt-2 pb-6 lg:pb-0">
|
| 196 |
+
<button class="group relative w-full cursor-pointer overflow-hidden rounded-xl lg:rounded-2xl h-12 lg:h-14 bg-gradient-to-r from-amber-600 via-amber-700 to-amber-800 hover:from-amber-500 hover:to-amber-700 transition-all border border-white/10 animate-pulse-slow animate-glow-pulse" id="startBtn">
|
| 197 |
+
<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3 relative z-10">
|
| 198 |
+
<span class="text-white text-base lg:text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span>
|
| 199 |
+
<span class="material-symbols-outlined text-white text-lg lg:text-xl group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
| 200 |
+
</div>
|
| 201 |
+
</button>
|
| 202 |
+
</div>
|
| 203 |
+
</div>
|
| 204 |
+
</div>
|
| 205 |
+
<div class="flex-none lg:flex-1 flex flex-col p-4 lg:p-8 bg-black/60 lg:overflow-y-auto lg:h-[calc(100vh-73px)] relative">
|
| 206 |
+
<div class="absolute inset-0 opacity-[0.02] pointer-events-none" style="background-image: linear-gradient(to right, #ffffff 1px, transparent 1px), linear-gradient(to bottom, #ffffff 1px, transparent 1px); background-size: 40px 40px;"></div>
|
| 207 |
+
<div class="max-w-3xl w-full mx-auto flex flex-col gap-4 lg:gap-6 h-full relative z-10 pb-8 lg:pb-0">
|
| 208 |
+
<div class="flex items-center justify-between mb-2">
|
| 209 |
+
<h2 class="text-white text-xl lg:text-3xl font-bold tracking-tight">Generated Output</h2>
|
| 210 |
+
<div class="flex items-center gap-2">
|
| 211 |
+
<span class="hidden xs:inline-block bg-white/5 text-amber-200 px-2.5 py-1 rounded-full text-[10px] lg:text-xs font-semibold border border-white/10 backdrop-blur-sm">Step 2 of 2</span>
|
| 212 |
+
<div class="hidden xs:block h-6 w-px bg-white/10 mx-1"></div>
|
| 213 |
+
<button class="size-8 lg:size-9 flex items-center justify-center hover:bg-white/10 rounded-lg text-neutral-500 hover:text-amber-400 transition-all duration-300 border border-transparent" id="copyBtn" title="Copy JSON">
|
| 214 |
+
<span class="material-symbols-outlined text-lg lg:text-xl transition-all duration-300" id="copyIcon">content_copy</span>
|
| 215 |
+
</button>
|
| 216 |
+
<button class="size-8 lg:size-9 flex items-center justify-center hover:bg-white/10 rounded-lg text-neutral-500 hover:text-amber-400 transition-colors" id="downloadBtn" title="Download JSON">
|
| 217 |
+
<span class="material-symbols-outlined text-lg lg:text-xl">download</span>
|
| 218 |
+
</button>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
<div class="grid grid-cols-1 xs:grid-cols-3 gap-3">
|
| 222 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 223 |
+
<div class="relative size-3 flex-none">
|
| 224 |
+
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-500 opacity-75"></span>
|
| 225 |
+
<span class="relative inline-flex rounded-full size-3 bg-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.5)]"></span>
|
| 226 |
+
</div>
|
| 227 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 228 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">Vision Agent</span>
|
| 229 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Gemini Pro 1.5</span>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 233 |
+
<div class="relative size-3 flex-none">
|
| 234 |
+
<span class="relative inline-flex rounded-full size-3 bg-amber-600 shadow-[0_0_10px_rgba(217,119,6,0.5)]"></span>
|
| 235 |
+
</div>
|
| 236 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 237 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">Reasoning Agent</span>
|
| 238 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Llama 3 70B</span>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 242 |
+
<div class="relative size-3 flex-none">
|
| 243 |
+
<span class="relative inline-flex rounded-full size-3 bg-neutral-600 shadow-[0_0_10px_rgba(82,82,82,0.5)]"></span>
|
| 244 |
+
</div>
|
| 245 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 246 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">SEO Context</span>
|
| 247 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Pinecone DB</span>
|
| 248 |
+
</div>
|
| 249 |
+
</div>
|
| 250 |
+
</div>
|
| 251 |
+
<div class="flex-none lg:flex-1 min-h-[400px] lg:min-h-0 rounded-xl lg:rounded-2xl bg-black/60 backdrop-blur-xl border border-white/5 flex flex-col overflow-hidden shadow-2xl relative glass-card group/output transition-all duration-500">
|
| 252 |
+
<div class="absolute top-0 right-0 w-64 h-64 bg-amber-500/5 rounded-full blur-[100px] pointer-events-none"></div>
|
| 253 |
+
<div class="flex items-center justify-between px-3 py-2.5 lg:px-4 lg:py-3 bg-white/5 border-b border-white/5">
|
| 254 |
+
<span class="text-[10px] lg:text-xs font-mono text-neutral-500 flex items-center gap-2">
|
| 255 |
+
<span class="material-symbols-outlined text-xs lg:text-sm">code</span>
|
| 256 |
+
output.json
|
| 257 |
+
</span>
|
| 258 |
+
<div class="flex gap-1.5">
|
| 259 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-neutral-800 border border-white/10"></div>
|
| 260 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-neutral-800 border border-white/10"></div>
|
| 261 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-amber-500/20 border border-amber-500/30"></div>
|
| 262 |
+
</div>
|
| 263 |
+
</div>
|
| 264 |
+
<div class="flex-1 p-4 lg:p-5 overflow-auto custom-scrollbar font-mono text-xs lg:text-sm leading-6 lg:leading-7">
|
| 265 |
+
<pre><code class="language-json block" id="jsonOutput"><span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">01</span><span class="text-amber-400">{</span>
|
| 266 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">02</span> <span class="text-amber-600">"product_analysis"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">{</span>
|
| 267 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">03</span> <span class="text-amber-600">"title"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Noir Elite Series Artisan Timepiece"</span><span class="text-neutral-400">,</span>
|
| 268 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">04</span> <span class="text-amber-600">"category"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Luxury / Accessories"</span><span class="text-neutral-400">,</span>
|
| 269 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">05</span> <span class="text-amber-600">"features"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">[</span>
|
| 270 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">06</span> <span class="text-amber-200">"Obsidian Finish"</span><span class="text-neutral-400">,</span>
|
| 271 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">07</span> <span class="text-amber-200">"Golden Accents"</span><span class="text-neutral-400">,</span>
|
| 272 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">08</span> <span class="text-amber-200">"Smart Haptic Interface"</span>
|
| 273 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">09</span> <span class="text-amber-400">]</span><span class="text-neutral-400">,</span>
|
| 274 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">10</span> <span class="text-amber-600">"seo_tags"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">[</span>
|
| 275 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">11</span> <span class="text-amber-200">"#luxurywear"</span><span class="text-neutral-400">,</span> <span class="text-amber-200">"#amberstyle"</span><span class="text-neutral-400">,</span> <span class="text-amber-200">"#premiumtech"</span>
|
| 276 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">12</span> <span class="text-amber-400">]</span><span class="text-neutral-400">,</span>
|
| 277 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">13</span> <span class="text-amber-600">"sentiment_score"</span><span class="text-neutral-400">:</span> <span class="text-amber-500">0.99</span><span class="text-neutral-400">,</span>
|
| 278 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">14</span> <span class="text-amber-600">"market_fit"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Exceptional"</span>
|
| 279 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">15</span> <span class="text-amber-400">}</span><span class="text-neutral-400">,</span>
|
| 280 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">16</span> <span class="text-amber-600">"deployment_status"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Authorized"</span>
|
| 281 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">17</span><span class="text-amber-400">}</span></code></pre>
|
| 282 |
+
</div>
|
| 283 |
+
</div>
|
| 284 |
+
</div>
|
| 285 |
+
</div>
|
| 286 |
+
</main>
|
| 287 |
+
<footer class="flex-none flex items-center justify-center py-4 bg-black/80 border-t border-white/5 backdrop-blur-md z-10">
|
| 288 |
+
<span class="text-[10px] lg:text-xs text-neutral-600 font-medium tracking-wide">© 2026 StyleSync AI — All Rights Reserved</span>
|
| 289 |
+
</footer>
|
| 290 |
+
<script>
|
| 291 |
+
tailwind.config.theme.extend.animation = { shine: 'shine 1.5s infinite' }
|
| 292 |
+
tailwind.config.theme.extend.keyframes = {
|
| 293 |
+
shine: { '0%': { left: '-100%' }, '100%': { left: '200%' } }
|
| 294 |
+
}
|
| 295 |
+
const dropZone = document.getElementById('dropZone');
|
| 296 |
+
const fileInput = document.getElementById('fileInput');
|
| 297 |
+
const startBtn = document.getElementById('startBtn');
|
| 298 |
+
const jsonOutput = document.getElementById('jsonOutput');
|
| 299 |
+
const deployBtn = document.getElementById('deployBtn');
|
| 300 |
+
const copyBtn = document.getElementById('copyBtn');
|
| 301 |
+
const copyIcon = document.getElementById('copyIcon');
|
| 302 |
+
let selectedFile = null;
|
| 303 |
+
let isCatalogGenerated = false;
|
| 304 |
+
const defaultDropZoneContent = `
|
| 305 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 306 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 307 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 308 |
+
</div>
|
| 309 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 310 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 311 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 312 |
+
</div>
|
| 313 |
+
<button id="browseBtn" class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide">
|
| 314 |
+
Browse Files
|
| 315 |
+
</button>
|
| 316 |
+
`;
|
| 317 |
+
function initDropZone() {
|
| 318 |
+
const currentBrowseBtn = document.getElementById('browseBtn');
|
| 319 |
+
if (currentBrowseBtn) {
|
| 320 |
+
currentBrowseBtn.addEventListener('click', (e) => {
|
| 321 |
+
e.preventDefault(); e.stopPropagation(); fileInput.click();
|
| 322 |
+
});
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
initDropZone();
|
| 326 |
+
fileInput.addEventListener('change', (e) => {
|
| 327 |
+
if (e.target.files.length > 0) handleFile(e.target.files[0]);
|
| 328 |
+
});
|
| 329 |
+
dropZone.addEventListener('dragover', (e) => {
|
| 330 |
+
e.preventDefault(); dropZone.classList.add('border-amber-500', 'bg-amber-500/5');
|
| 331 |
+
});
|
| 332 |
+
dropZone.addEventListener('dragleave', (e) => {
|
| 333 |
+
e.preventDefault(); dropZone.classList.remove('border-amber-500', 'bg-amber-500/5');
|
| 334 |
+
});
|
| 335 |
+
dropZone.addEventListener('drop', (e) => {
|
| 336 |
+
e.preventDefault(); dropZone.classList.remove('border-amber-500', 'bg-amber-500/5');
|
| 337 |
+
if (e.dataTransfer.files.length > 0) {
|
| 338 |
+
fileInput.files = e.dataTransfer.files;
|
| 339 |
+
handleFile(e.dataTransfer.files[0]);
|
| 340 |
+
}
|
| 341 |
+
});
|
| 342 |
+
function handleFile(file) {
|
| 343 |
+
selectedFile = file;
|
| 344 |
+
dropZone.innerHTML = `
|
| 345 |
+
<div class="flex flex-col items-center justify-center gap-4 z-10">
|
| 346 |
+
<div class="size-14 lg:size-16 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center">
|
| 347 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">check_circle</span>
|
| 348 |
+
</div>
|
| 349 |
+
<div class="flex flex-col items-center gap-1">
|
| 350 |
+
<p class="text-white text-base lg:text-lg font-bold text-center">${file.name}</p>
|
| 351 |
+
<p class="text-neutral-500 text-xs lg:text-sm text-center">${(file.size / 1024).toFixed(1)} KB</p>
|
| 352 |
+
</div>
|
| 353 |
+
<button id="removeFileBtn" class="mt-2 flex items-center justify-center gap-2 rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 transition-all text-[10px] lg:text-xs font-bold uppercase tracking-wide">
|
| 354 |
+
<span class="material-symbols-outlined text-sm lg:text-base">close</span>
|
| 355 |
+
<span>Remove File</span>
|
| 356 |
+
</button>
|
| 357 |
+
</div>
|
| 358 |
+
`;
|
| 359 |
+
document.getElementById('removeFileBtn').addEventListener('click', (e) => {
|
| 360 |
+
e.stopPropagation(); e.preventDefault(); resetUploadUI();
|
| 361 |
+
});
|
| 362 |
+
}
|
| 363 |
+
function resetUploadUI() {
|
| 364 |
+
selectedFile = null; fileInput.value = ""; dropZone.innerHTML = defaultDropZoneContent; initDropZone();
|
| 365 |
+
}
|
| 366 |
+
startBtn.addEventListener('click', async (e) => {
|
| 367 |
+
e.preventDefault();
|
| 368 |
+
if (!fileInput.files || fileInput.files.length === 0) {
|
| 369 |
+
alert("Please select or drop an image first."); return;
|
| 370 |
+
}
|
| 371 |
+
const originalBtnContent = startBtn.innerHTML;
|
| 372 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3"><svg class="animate-spin h-4 w-4 lg:h-5 lg:w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg><span class="text-white text-base lg:text-lg font-bold tracking-wide">Synthesizing...</span></div>';
|
| 373 |
+
startBtn.disabled = true; startBtn.classList.remove('animate-pulse-slow', 'animate-glow-pulse');
|
| 374 |
+
|
| 375 |
+
try {
|
| 376 |
+
const formData = new FormData(); formData.append('file', fileInput.files[0]);
|
| 377 |
+
const response = await fetch('/generate-catalog', { method: 'POST', body: formData });
|
| 378 |
+
if (!response.ok) throw new Error("Server Error " + response.status);
|
| 379 |
+
const data = await response.json();
|
| 380 |
+
jsonOutput.textContent = JSON.stringify(data, null, 2);
|
| 381 |
+
isCatalogGenerated = true;
|
| 382 |
+
} catch (error) {
|
| 383 |
+
console.error("Agent Error:", error); alert("Pipeline failed: " + error.message);
|
| 384 |
+
} finally {
|
| 385 |
+
startBtn.innerHTML = originalBtnContent; startBtn.disabled = false; startBtn.classList.add('animate-pulse-slow', 'animate-glow-pulse');
|
| 386 |
+
}
|
| 387 |
+
});
|
| 388 |
+
copyBtn.addEventListener('click', () => {
|
| 389 |
+
navigator.clipboard.writeText(jsonOutput.innerText).then(() => {
|
| 390 |
+
const originalIcon = copyIcon.innerText; copyIcon.innerText = 'check'; copyIcon.classList.add('text-green-400');
|
| 391 |
+
setTimeout(() => { copyIcon.innerText = originalIcon; copyIcon.classList.remove('text-green-400'); }, 2000);
|
| 392 |
+
});
|
| 393 |
+
});
|
| 394 |
+
</script>
|
| 395 |
+
</body>
|
| 396 |
+
</html>
|
| 397 |
+
|
deploy_new_ui.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import subprocess
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
def main():
|
| 6 |
+
glassui_path = "glassui.html"
|
| 7 |
+
dashboard_path = "dashboard.html"
|
| 8 |
+
|
| 9 |
+
# Read the New Design
|
| 10 |
+
try:
|
| 11 |
+
with open(glassui_path, 'r', encoding='utf-8') as f:
|
| 12 |
+
glassui_content = f.read()
|
| 13 |
+
except FileNotFoundError:
|
| 14 |
+
print(f"Error: Could not find '{glassui_path}'.")
|
| 15 |
+
sys.exit(1)
|
| 16 |
+
|
| 17 |
+
# The Safety Scan (Crucial)
|
| 18 |
+
required_ids = [
|
| 19 |
+
"dropZone",
|
| 20 |
+
"fileInput",
|
| 21 |
+
"browseBtn",
|
| 22 |
+
"startBtn",
|
| 23 |
+
"deployBtn",
|
| 24 |
+
"jsonOutput",
|
| 25 |
+
"copyBtn",
|
| 26 |
+
"downloadBtn"
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
for element_id in required_ids:
|
| 30 |
+
# Check if id="element_id" or id='element_id' exists
|
| 31 |
+
pattern = rf'id\s*=\s*[\'"]{element_id}[\'"]'
|
| 32 |
+
if not re.search(pattern, glassui_content):
|
| 33 |
+
print(f"⚠️ WARNING: Missing ID {element_id}")
|
| 34 |
+
|
| 35 |
+
# Overwrite
|
| 36 |
+
try:
|
| 37 |
+
with open(dashboard_path, 'w', encoding='utf-8') as f:
|
| 38 |
+
f.write(glassui_content)
|
| 39 |
+
print(f"Successfully copied '{glassui_path}' to '{dashboard_path}'.")
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"Error overwriting '{dashboard_path}': {e}")
|
| 42 |
+
sys.exit(1)
|
| 43 |
+
|
| 44 |
+
# Deploy
|
| 45 |
+
print("Deploying to Hugging Face...")
|
| 46 |
+
git_commands = [
|
| 47 |
+
["git", "add", "dashboard.html"],
|
| 48 |
+
["git", "commit", "-m", "UI Update: Apply responsive Glassmorphism design from Stitch"],
|
| 49 |
+
["git", "push", "space", "clean_deploy:main"]
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
for cmd in git_commands:
|
| 53 |
+
print(f"Running: {' '.join(cmd)}")
|
| 54 |
+
# Use encoding='utf-8' as strictly requested
|
| 55 |
+
result = subprocess.run(cmd, text=True, encoding='utf-8')
|
| 56 |
+
if result.returncode != 0:
|
| 57 |
+
print(f"Command failed: {' '.join(cmd)}")
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
main()
|
final_deploy_push.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
# Force UTF-8 output for Windows terminals
|
| 5 |
+
sys.stdout.reconfigure(encoding='utf-8')
|
| 6 |
+
|
| 7 |
+
def deploy():
|
| 8 |
+
print("⚠️ Ensure you are inside the D:\\Projects\\StyleSync AI directory before running this!")
|
| 9 |
+
|
| 10 |
+
command = "git push --force space clean_deploy:main"
|
| 11 |
+
print(f"\nRunning: {command} ...")
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
subprocess.run(command, check=True, shell=True)
|
| 15 |
+
print("\n✅ Successfully pushed to Space!")
|
| 16 |
+
except subprocess.CalledProcessError as e:
|
| 17 |
+
print(f"\n❌ Push failed: {e}")
|
| 18 |
+
|
| 19 |
+
if __name__ == "__main__":
|
| 20 |
+
deploy()
|
final_upload.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
def run_command(command, check=True):
|
| 5 |
+
try:
|
| 6 |
+
subprocess.run(command, check=check, shell=True, text=True)
|
| 7 |
+
except subprocess.CalledProcessError as e:
|
| 8 |
+
print(f"Error executing currently: {command}")
|
| 9 |
+
# We don't exit here because some commands like 'remote remove' might fail meaningfully but we want to continue,
|
| 10 |
+
# or we handle them specifically in the main flow.
|
| 11 |
+
if check:
|
| 12 |
+
# Re-raise if we strictly wanted this to succeed
|
| 13 |
+
raise e
|
| 14 |
+
|
| 15 |
+
def main():
|
| 16 |
+
# Input: Ask the user for the GitHub URL
|
| 17 |
+
if len(sys.argv) > 1:
|
| 18 |
+
github_url = sys.argv[1].strip()
|
| 19 |
+
else:
|
| 20 |
+
github_url = input('Please paste your GitHub URL here: ').strip()
|
| 21 |
+
|
| 22 |
+
if not github_url:
|
| 23 |
+
print("Error: No URL provided.")
|
| 24 |
+
return
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
# Git Commands sequence
|
| 28 |
+
print("Initializing git...")
|
| 29 |
+
run_command("git init")
|
| 30 |
+
|
| 31 |
+
print("Adding files...")
|
| 32 |
+
run_command("git add .")
|
| 33 |
+
|
| 34 |
+
print("Committing files...")
|
| 35 |
+
try:
|
| 36 |
+
# Use check=True so it raises exception on failure, which we catch
|
| 37 |
+
run_command('git commit -m "Initial commit - StyleSync AI"', check=True)
|
| 38 |
+
except subprocess.CalledProcessError:
|
| 39 |
+
print("Commit failed (likely nothing to commit). Continuing...")
|
| 40 |
+
|
| 41 |
+
print("Renaming branch to main...")
|
| 42 |
+
run_command("git branch -M main")
|
| 43 |
+
|
| 44 |
+
print("Removing existing origin (if any)...")
|
| 45 |
+
# Don't check=True here because it fails if origin doesn't exist
|
| 46 |
+
run_command("git remote remove origin", check=False)
|
| 47 |
+
|
| 48 |
+
print(f"Adding remote origin: {github_url}")
|
| 49 |
+
run_command(f"git remote add origin {github_url}")
|
| 50 |
+
|
| 51 |
+
print("Pushing to GitHub...")
|
| 52 |
+
run_command("git push -u origin main")
|
| 53 |
+
|
| 54 |
+
# Success message
|
| 55 |
+
print('✅ Code is live on GitHub!')
|
| 56 |
+
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"\n❌ An error occurred: {e}")
|
| 59 |
+
|
| 60 |
+
if __name__ == "__main__":
|
| 61 |
+
main()
|
fix_browse_button.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import subprocess
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
def fix_html():
|
| 6 |
+
with open('dashboard.html', 'r', encoding='utf-8') as f:
|
| 7 |
+
content = f.read()
|
| 8 |
+
|
| 9 |
+
js_snippet = """
|
| 10 |
+
const browseBtn = document.getElementById('browseBtn');
|
| 11 |
+
browseBtn.addEventListener('click', (e) => {
|
| 12 |
+
e.preventDefault();
|
| 13 |
+
fileInput.click();
|
| 14 |
+
});
|
| 15 |
+
fileInput.addEventListener('change', () => {
|
| 16 |
+
if (fileInput.files.length > 0) {
|
| 17 |
+
// Provide a visual cue that a file was selected
|
| 18 |
+
const fileName = fileInput.files[0].name;
|
| 19 |
+
browseBtn.innerHTML = `<span class="material-symbols-outlined text-xl">check_circle</span> ${fileName}`;
|
| 20 |
+
}
|
| 21 |
+
});
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
if "browseBtn.addEventListener('click', (e) => {" in content and "Provide a visual cue that a file was selected" in content:
|
| 25 |
+
print("Snippet already exists. Skipping injection.")
|
| 26 |
+
else:
|
| 27 |
+
# Find where the DOM elements are defined
|
| 28 |
+
target = "const downloadBtn = document.getElementById('downloadBtn');"
|
| 29 |
+
if target in content:
|
| 30 |
+
new_content = content.replace(target, target + "\n" + js_snippet)
|
| 31 |
+
with open('dashboard.html', 'w', encoding='utf-8') as f:
|
| 32 |
+
f.write(new_content)
|
| 33 |
+
print("Injected JS successfully.")
|
| 34 |
+
else:
|
| 35 |
+
print("Could not find insertion point!")
|
| 36 |
+
return False
|
| 37 |
+
|
| 38 |
+
# Run git commands
|
| 39 |
+
subprocess.run(['git', 'add', 'dashboard.html'], check=True)
|
| 40 |
+
try:
|
| 41 |
+
subprocess.run(['git', 'commit', '-m', 'Bugfix: Wire up Browse Files button to hidden input'], check=True)
|
| 42 |
+
except subprocess.CalledProcessError:
|
| 43 |
+
print("Nothing to commit")
|
| 44 |
+
|
| 45 |
+
subprocess.run(['git', 'push', '--force', 'space', 'HEAD:main'], check=True)
|
| 46 |
+
|
| 47 |
+
# Push to origin main as well
|
| 48 |
+
try:
|
| 49 |
+
subprocess.run(['git', 'push', 'origin', 'HEAD:main'], check=True)
|
| 50 |
+
except subprocess.CalledProcessError:
|
| 51 |
+
print("Push to origin failed or not needed")
|
| 52 |
+
|
| 53 |
+
print("Deployment triggered successfully.")
|
| 54 |
+
return True
|
| 55 |
+
|
| 56 |
+
if __name__ == '__main__':
|
| 57 |
+
fix_html()
|
fix_dashboard_api.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import subprocess
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
def patch_dashboard():
|
| 6 |
+
with open('dashboard.html', 'r', encoding='utf-8') as f:
|
| 7 |
+
content = f.read()
|
| 8 |
+
|
| 9 |
+
# Locate the setTimeout block inside startBtn.addEventListener and replace it with fetch logic
|
| 10 |
+
pattern = re.compile(r"setTimeout\(\(\) => \{[\s\S]*?\}, 1500\);", re.DOTALL)
|
| 11 |
+
|
| 12 |
+
new_block = """try {
|
| 13 |
+
const formData = new FormData();
|
| 14 |
+
formData.append('file', fileInput.files[0]);
|
| 15 |
+
|
| 16 |
+
const response = await fetch('/generate-catalog', {
|
| 17 |
+
method: 'POST',
|
| 18 |
+
body: formData
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
const data = await response.json();
|
| 22 |
+
jsonOutput.textContent = JSON.stringify(data, null, 2);
|
| 23 |
+
isCatalogGenerated = true;
|
| 24 |
+
} catch (error) {
|
| 25 |
+
console.error("Error generating catalog:", error);
|
| 26 |
+
} finally {
|
| 27 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3 relative z-10"><span class="text-white text-base lg:text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span><span class="material-symbols-outlined text-white text-lg lg:text-xl group-hover:translate-x-1 transition-transform">arrow_forward</span></div>';
|
| 28 |
+
startBtn.disabled = false;
|
| 29 |
+
startBtn.classList.add('animate-pulse-slow', 'animate-glow-pulse');
|
| 30 |
+
}"""
|
| 31 |
+
|
| 32 |
+
if not pattern.search(content):
|
| 33 |
+
print("Error: Could not find the target setTimeout block in dashboard.html.")
|
| 34 |
+
return False
|
| 35 |
+
|
| 36 |
+
new_content = pattern.sub(new_block, content)
|
| 37 |
+
|
| 38 |
+
with open('dashboard.html', 'w', encoding='utf-8') as f:
|
| 39 |
+
f.write(new_content)
|
| 40 |
+
|
| 41 |
+
print("Successfully patched dashboard.html")
|
| 42 |
+
return True
|
| 43 |
+
|
| 44 |
+
def run_git_commands():
|
| 45 |
+
commands = [
|
| 46 |
+
['git', 'add', 'dashboard.html'],
|
| 47 |
+
['git', 'commit', '-m', 'Bugfix: Restore real API connection to Glassmorphism UI'],
|
| 48 |
+
['git', 'push', '--force', 'space', 'HEAD:main']
|
| 49 |
+
]
|
| 50 |
+
|
| 51 |
+
for cmd in commands:
|
| 52 |
+
print(f"Running: {' '.join(cmd)}")
|
| 53 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
| 54 |
+
if result.returncode != 0:
|
| 55 |
+
print(f"Command failed with {result.returncode}: \\n{result.stderr}")
|
| 56 |
+
# Don't break here, let it try the other commands just in case, though push might fail if commit fails.
|
| 57 |
+
if cmd[1] == 'commit' and "nothing to commit" in result.stdout + result.stderr:
|
| 58 |
+
continue
|
| 59 |
+
if cmd[1] == 'push':
|
| 60 |
+
pass
|
| 61 |
+
else:
|
| 62 |
+
print(f"Success!\\n{result.stdout}")
|
| 63 |
+
|
| 64 |
+
if __name__ == '__main__':
|
| 65 |
+
if patch_dashboard():
|
| 66 |
+
run_git_commands()
|
fix_dashboard_routing.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def main():
|
| 5 |
+
# Define the content for main.py
|
| 6 |
+
main_py_content = """import os
|
| 7 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 8 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 9 |
+
from fastapi.staticfiles import StaticFiles
|
| 10 |
+
from agents.visual_analyst import VisualAnalyst
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
# Load environment variables
|
| 13 |
+
load_dotenv()
|
| 14 |
+
app = FastAPI()
|
| 15 |
+
# Initialize Agent
|
| 16 |
+
visual_agent = VisualAnalyst()
|
| 17 |
+
# 1. READ THE DASHBOARD HTML FILE INTO MEMORY
|
| 18 |
+
try:
|
| 19 |
+
with open("dashboard.html", "r") as f:
|
| 20 |
+
dashboard_html = f.read()
|
| 21 |
+
except FileNotFoundError:
|
| 22 |
+
dashboard_html = "<h1>Error: dashboard.html not found. Please ensure the file exists.</h1>"
|
| 23 |
+
# 2. SERVE DASHBOARD AT ROOT (Home Page)
|
| 24 |
+
@app.get("/", response_class=HTMLResponse)
|
| 25 |
+
async def read_root():
|
| 26 |
+
return dashboard_html
|
| 27 |
+
# 3. KEEP /dashboard ROUTE AS BACKUP
|
| 28 |
+
@app.get("/dashboard", response_class=HTMLResponse)
|
| 29 |
+
async def read_dashboard():
|
| 30 |
+
return dashboard_html
|
| 31 |
+
@app.post("/analyze")
|
| 32 |
+
async def analyze_merch(file: UploadFile = File(...)):
|
| 33 |
+
try:
|
| 34 |
+
os.makedirs("uploads", exist_ok=True)
|
| 35 |
+
file_path = f"uploads/{file.filename}"
|
| 36 |
+
with open(file_path, "wb") as f:
|
| 37 |
+
f.write(await file.read())
|
| 38 |
+
result = await visual_agent.analyze_image(file_path)
|
| 39 |
+
|
| 40 |
+
if os.path.exists(file_path):
|
| 41 |
+
os.remove(file_path)
|
| 42 |
+
|
| 43 |
+
return JSONResponse(content=result)
|
| 44 |
+
except Exception as e:
|
| 45 |
+
return JSONResponse(content={"error": str(e)}, status_code=500)
|
| 46 |
+
if __name__ == "__main__":
|
| 47 |
+
import uvicorn
|
| 48 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
# Overwrite main.py
|
| 52 |
+
print("Overwriting main.py...")
|
| 53 |
+
try:
|
| 54 |
+
with open("main.py", "w", encoding="utf-8") as f:
|
| 55 |
+
f.write(main_py_content)
|
| 56 |
+
print("Successfully updated main.py")
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"Error writing main.py: {e}")
|
| 59 |
+
return
|
| 60 |
+
|
| 61 |
+
# Define git commands
|
| 62 |
+
git_commands = [
|
| 63 |
+
["git", "add", "main.py"],
|
| 64 |
+
["git", "commit", "-m", "Fix dashboard 404 by serving HTML at root"],
|
| 65 |
+
["git", "push", "space", "clean_deploy:main"]
|
| 66 |
+
]
|
| 67 |
+
|
| 68 |
+
# Run git commands
|
| 69 |
+
print("\nRunning git commands...")
|
| 70 |
+
for cmd in git_commands:
|
| 71 |
+
print(f"Executing: {' '.join(cmd)}")
|
| 72 |
+
try:
|
| 73 |
+
subprocess.run(cmd, check=True)
|
| 74 |
+
except subprocess.CalledProcessError as e:
|
| 75 |
+
print(f"Command failed: {e}")
|
| 76 |
+
# If commit fails (e.g. nothing to commit), we might want to continue or stop.
|
| 77 |
+
# But push should definitely happen if commit works.
|
| 78 |
+
# If commit fails because "nothing to commit, working tree clean", push might still be relevant if previous commit wasn't pushed?
|
| 79 |
+
# But the user logic implies we just made a change to main.py, so commit should succeed unless main.py was ALREADY this content.
|
| 80 |
+
# We will continue to try push even if commit fails, just in case.
|
| 81 |
+
# But wait, if commit fails, push might proceed.
|
| 82 |
+
pass
|
| 83 |
+
|
| 84 |
+
print("\nfix_dashboard_routing.py completed.")
|
| 85 |
+
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
main()
|
fix_google_key.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
# Force UTF-8 output for Windows terminals
|
| 5 |
+
sys.stdout.reconfigure(encoding='utf-8')
|
| 6 |
+
|
| 7 |
+
# 1. Update .env
|
| 8 |
+
env_path = ".env"
|
| 9 |
+
key = "GOOGLE_API_KEY"
|
| 10 |
+
value = "AIzaSyDgIkagGBciWNZDTn07OlfY9tVPvo6KJ1on"
|
| 11 |
+
|
| 12 |
+
print(f"Updating {key} in .env...")
|
| 13 |
+
|
| 14 |
+
lines = []
|
| 15 |
+
if os.path.exists(env_path):
|
| 16 |
+
with open(env_path, "r", encoding="utf-8") as f:
|
| 17 |
+
lines = f.readlines()
|
| 18 |
+
|
| 19 |
+
found = False
|
| 20 |
+
new_lines = []
|
| 21 |
+
for line in lines:
|
| 22 |
+
if line.startswith(f"{key}="):
|
| 23 |
+
new_lines.append(f"{key}={value}\n")
|
| 24 |
+
found = True
|
| 25 |
+
else:
|
| 26 |
+
new_lines.append(line)
|
| 27 |
+
|
| 28 |
+
if not found:
|
| 29 |
+
if new_lines and not new_lines[-1].endswith('\n'):
|
| 30 |
+
new_lines.append('\n')
|
| 31 |
+
new_lines.append(f"{key}={value}\n")
|
| 32 |
+
|
| 33 |
+
with open(env_path, "w", encoding="utf-8") as f:
|
| 34 |
+
f.writelines(new_lines)
|
| 35 |
+
|
| 36 |
+
print(f"✅ Updated {key} in .env")
|
| 37 |
+
|
| 38 |
+
# 2. Upload to Cloud
|
| 39 |
+
print("Syncing secrets to Hugging Face Space...")
|
| 40 |
+
try:
|
| 41 |
+
# Build path to ensure we can import upload_secrets
|
| 42 |
+
sys.path.append(os.getcwd())
|
| 43 |
+
from upload_secrets import upload_secrets
|
| 44 |
+
|
| 45 |
+
upload_secrets()
|
| 46 |
+
print("✅ Google Key saved locally and uploaded to Hugging Face!")
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"❌ Failed to sync: {e}")
|
fix_js_syntax.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def fix_html():
|
| 5 |
+
with open('dashboard.html', 'r', encoding='utf-8') as f:
|
| 6 |
+
content = f.read()
|
| 7 |
+
|
| 8 |
+
parts = content.split('</footer>')
|
| 9 |
+
if len(parts) < 2:
|
| 10 |
+
print("Could not find </footer> in dashboard.html")
|
| 11 |
+
return False
|
| 12 |
+
|
| 13 |
+
top_part = parts[0] + '</footer>\n'
|
| 14 |
+
|
| 15 |
+
new_script = """<script>
|
| 16 |
+
tailwind.config.theme.extend.animation = { shine: 'shine 1.5s infinite' }
|
| 17 |
+
tailwind.config.theme.extend.keyframes = {
|
| 18 |
+
shine: { '0%': { left: '-100%' }, '100%': { left: '200%' } }
|
| 19 |
+
}
|
| 20 |
+
const dropZone = document.getElementById('dropZone');
|
| 21 |
+
const fileInput = document.getElementById('fileInput');
|
| 22 |
+
const startBtn = document.getElementById('startBtn');
|
| 23 |
+
const jsonOutput = document.getElementById('jsonOutput');
|
| 24 |
+
const deployBtn = document.getElementById('deployBtn');
|
| 25 |
+
const copyBtn = document.getElementById('copyBtn');
|
| 26 |
+
const copyIcon = document.getElementById('copyIcon');
|
| 27 |
+
let selectedFile = null;
|
| 28 |
+
let isCatalogGenerated = false;
|
| 29 |
+
const defaultDropZoneContent = `
|
| 30 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 31 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 32 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 33 |
+
</div>
|
| 34 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 35 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 36 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 37 |
+
</div>
|
| 38 |
+
<button id="browseBtn" class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide">
|
| 39 |
+
Browse Files
|
| 40 |
+
</button>
|
| 41 |
+
`;
|
| 42 |
+
function initDropZone() {
|
| 43 |
+
const currentBrowseBtn = document.getElementById('browseBtn');
|
| 44 |
+
if (currentBrowseBtn) {
|
| 45 |
+
currentBrowseBtn.addEventListener('click', (e) => {
|
| 46 |
+
e.preventDefault(); e.stopPropagation(); fileInput.click();
|
| 47 |
+
});
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
initDropZone();
|
| 51 |
+
fileInput.addEventListener('change', (e) => {
|
| 52 |
+
if (e.target.files.length > 0) handleFile(e.target.files[0]);
|
| 53 |
+
});
|
| 54 |
+
dropZone.addEventListener('dragover', (e) => {
|
| 55 |
+
e.preventDefault(); dropZone.classList.add('border-amber-500', 'bg-amber-500/5');
|
| 56 |
+
});
|
| 57 |
+
dropZone.addEventListener('dragleave', (e) => {
|
| 58 |
+
e.preventDefault(); dropZone.classList.remove('border-amber-500', 'bg-amber-500/5');
|
| 59 |
+
});
|
| 60 |
+
dropZone.addEventListener('drop', (e) => {
|
| 61 |
+
e.preventDefault(); dropZone.classList.remove('border-amber-500', 'bg-amber-500/5');
|
| 62 |
+
if (e.dataTransfer.files.length > 0) {
|
| 63 |
+
fileInput.files = e.dataTransfer.files;
|
| 64 |
+
handleFile(e.dataTransfer.files[0]);
|
| 65 |
+
}
|
| 66 |
+
});
|
| 67 |
+
function handleFile(file) {
|
| 68 |
+
selectedFile = file;
|
| 69 |
+
dropZone.innerHTML = `
|
| 70 |
+
<div class="flex flex-col items-center justify-center gap-4 z-10">
|
| 71 |
+
<div class="size-14 lg:size-16 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center">
|
| 72 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">check_circle</span>
|
| 73 |
+
</div>
|
| 74 |
+
<div class="flex flex-col items-center gap-1">
|
| 75 |
+
<p class="text-white text-base lg:text-lg font-bold text-center">${file.name}</p>
|
| 76 |
+
<p class="text-neutral-500 text-xs lg:text-sm text-center">${(file.size / 1024).toFixed(1)} KB</p>
|
| 77 |
+
</div>
|
| 78 |
+
<button id="removeFileBtn" class="mt-2 flex items-center justify-center gap-2 rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 transition-all text-[10px] lg:text-xs font-bold uppercase tracking-wide">
|
| 79 |
+
<span class="material-symbols-outlined text-sm lg:text-base">close</span>
|
| 80 |
+
<span>Remove File</span>
|
| 81 |
+
</button>
|
| 82 |
+
</div>
|
| 83 |
+
`;
|
| 84 |
+
document.getElementById('removeFileBtn').addEventListener('click', (e) => {
|
| 85 |
+
e.stopPropagation(); e.preventDefault(); resetUploadUI();
|
| 86 |
+
});
|
| 87 |
+
}
|
| 88 |
+
function resetUploadUI() {
|
| 89 |
+
selectedFile = null; fileInput.value = ""; dropZone.innerHTML = defaultDropZoneContent; initDropZone();
|
| 90 |
+
}
|
| 91 |
+
startBtn.addEventListener('click', async (e) => {
|
| 92 |
+
e.preventDefault();
|
| 93 |
+
if (!fileInput.files || fileInput.files.length === 0) {
|
| 94 |
+
alert("Please select or drop an image first."); return;
|
| 95 |
+
}
|
| 96 |
+
const originalBtnContent = startBtn.innerHTML;
|
| 97 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3"><svg class="animate-spin h-4 w-4 lg:h-5 lg:w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg><span class="text-white text-base lg:text-lg font-bold tracking-wide">Synthesizing...</span></div>';
|
| 98 |
+
startBtn.disabled = true; startBtn.classList.remove('animate-pulse-slow', 'animate-glow-pulse');
|
| 99 |
+
|
| 100 |
+
try {
|
| 101 |
+
const formData = new FormData(); formData.append('file', fileInput.files[0]);
|
| 102 |
+
const response = await fetch('/generate-catalog', { method: 'POST', body: formData });
|
| 103 |
+
if (!response.ok) throw new Error("Server Error " + response.status);
|
| 104 |
+
const data = await response.json();
|
| 105 |
+
jsonOutput.textContent = JSON.stringify(data, null, 2);
|
| 106 |
+
isCatalogGenerated = true;
|
| 107 |
+
} catch (error) {
|
| 108 |
+
console.error("Agent Error:", error); alert("Pipeline failed: " + error.message);
|
| 109 |
+
} finally {
|
| 110 |
+
startBtn.innerHTML = originalBtnContent; startBtn.disabled = false; startBtn.classList.add('animate-pulse-slow', 'animate-glow-pulse');
|
| 111 |
+
}
|
| 112 |
+
});
|
| 113 |
+
copyBtn.addEventListener('click', () => {
|
| 114 |
+
navigator.clipboard.writeText(jsonOutput.innerText).then(() => {
|
| 115 |
+
const originalIcon = copyIcon.innerText; copyIcon.innerText = 'check'; copyIcon.classList.add('text-green-400');
|
| 116 |
+
setTimeout(() => { copyIcon.innerText = originalIcon; copyIcon.classList.remove('text-green-400'); }, 2000);
|
| 117 |
+
});
|
| 118 |
+
});
|
| 119 |
+
</script>
|
| 120 |
+
</body>
|
| 121 |
+
</html>
|
| 122 |
+
"""
|
| 123 |
+
|
| 124 |
+
with open('dashboard.html', 'w', encoding='utf-8') as f:
|
| 125 |
+
f.write(top_part + new_script)
|
| 126 |
+
|
| 127 |
+
print("Replaced script successfully.")
|
| 128 |
+
|
| 129 |
+
# Run git commands
|
| 130 |
+
subprocess.run(['git', 'add', 'dashboard.html'], check=True)
|
| 131 |
+
try:
|
| 132 |
+
subprocess.run(['git', 'commit', '-m', 'Critical Bugfix: Resolve corrupted JS syntax and restore core agent loop'], check=True)
|
| 133 |
+
except subprocess.CalledProcessError:
|
| 134 |
+
print("Nothing to commit")
|
| 135 |
+
|
| 136 |
+
subprocess.run(['git', 'push', '--force', 'space', 'HEAD:main'], check=True)
|
| 137 |
+
|
| 138 |
+
# Push to origin main as well
|
| 139 |
+
try:
|
| 140 |
+
subprocess.run(['git', 'push', 'origin', 'HEAD:main'], check=True)
|
| 141 |
+
except subprocess.CalledProcessError:
|
| 142 |
+
print("Push to origin failed or not needed")
|
| 143 |
+
|
| 144 |
+
print("Deployment triggered successfully.")
|
| 145 |
+
|
| 146 |
+
if __name__ == '__main__':
|
| 147 |
+
fix_html()
|
fix_readme.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import subprocess
|
| 4 |
+
|
| 5 |
+
# Force UTF-8 output for Windows terminals
|
| 6 |
+
sys.stdout.reconfigure(encoding='utf-8')
|
| 7 |
+
|
| 8 |
+
readme_content = """---
|
| 9 |
+
title: StyleSync AI
|
| 10 |
+
emoji: 🚀
|
| 11 |
+
colorFrom: blue
|
| 12 |
+
colorTo: indigo
|
| 13 |
+
sdk: docker
|
| 14 |
+
pinned: false
|
| 15 |
+
---
|
| 16 |
+
# StyleSync AI
|
| 17 |
+
An AI-powered catalog intelligence agent.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def run_command(command):
|
| 21 |
+
print(f"Running: {command}")
|
| 22 |
+
try:
|
| 23 |
+
subprocess.run(command, check=True, shell=True)
|
| 24 |
+
print("✅ Success")
|
| 25 |
+
except subprocess.CalledProcessError as e:
|
| 26 |
+
print(f"❌ Error: {e}")
|
| 27 |
+
# Don't exit, try to continue or let user see error
|
| 28 |
+
|
| 29 |
+
def fix_readme():
|
| 30 |
+
print("Writing README.md...")
|
| 31 |
+
with open("README.md", "w", encoding="utf-8") as f:
|
| 32 |
+
f.write(readme_content)
|
| 33 |
+
print("✅ Created README.md")
|
| 34 |
+
|
| 35 |
+
print("Deploying changes...")
|
| 36 |
+
run_command("git add README.md")
|
| 37 |
+
run_command('git commit -m "Add Hugging Face configuration"')
|
| 38 |
+
run_command("git push space clean_deploy:main")
|
| 39 |
+
print("✅ Configuration fixed and pushed!")
|
| 40 |
+
|
| 41 |
+
if __name__ == "__main__":
|
| 42 |
+
fix_readme()
|
fix_vision_core.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def fix_vision_core():
|
| 5 |
+
# Content for agents/visual_analyst.py
|
| 6 |
+
content = """import os
|
| 7 |
+
import json
|
| 8 |
+
import asyncio
|
| 9 |
+
import google.generativeai as genai
|
| 10 |
+
from PIL import Image
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
|
| 13 |
+
load_dotenv()
|
| 14 |
+
|
| 15 |
+
class VisualAnalyst:
|
| 16 |
+
def __init__(self):
|
| 17 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 18 |
+
if not api_key:
|
| 19 |
+
print("⚠️ GEMINI_API_KEY missing")
|
| 20 |
+
|
| 21 |
+
genai.configure(api_key=api_key)
|
| 22 |
+
# Use the modern, faster Flash model
|
| 23 |
+
self.model = genai.GenerativeModel('gemini-1.5-flash')
|
| 24 |
+
|
| 25 |
+
async def analyze_image(self, image_path: str):
|
| 26 |
+
print(f"👁️ Analyzing image: {image_path}")
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
# 1. Load image properly with Pillow (Fixes format issues)
|
| 30 |
+
img = Image.open(image_path)
|
| 31 |
+
|
| 32 |
+
# 2. Define the prompt
|
| 33 |
+
prompt = \"\"\"
|
| 34 |
+
Analyze this product image for an e-commerce listing.
|
| 35 |
+
Return ONLY a raw JSON object (no markdown formatting) with this structure:
|
| 36 |
+
{
|
| 37 |
+
"main_color": "string",
|
| 38 |
+
"product_type": "string",
|
| 39 |
+
"design_style": "string (minimalist, streetwear, vintage, etc)",
|
| 40 |
+
"visual_features": ["list", "of", "visible", "features"],
|
| 41 |
+
"suggested_title": "creative product title",
|
| 42 |
+
"condition_guess": "new/used"
|
| 43 |
+
}
|
| 44 |
+
\"\"\"
|
| 45 |
+
|
| 46 |
+
# 3. Run in a thread to prevent blocking (Sync to Async wrapper)
|
| 47 |
+
response = await asyncio.to_thread(
|
| 48 |
+
self.model.generate_content,
|
| 49 |
+
[prompt, img]
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
# 4. Clean and Parse JSON
|
| 53 |
+
text_response = response.text.replace('```json', '').replace('```', '').strip()
|
| 54 |
+
return json.loads(text_response)
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f"❌ Vision Error: {e}")
|
| 57 |
+
# Return a Safe Fallback (Simulation)
|
| 58 |
+
return {
|
| 59 |
+
"main_color": "Unknown",
|
| 60 |
+
"product_type": "Unidentified Item",
|
| 61 |
+
"design_style": "Standard",
|
| 62 |
+
"visual_features": ["Error analyzing image"],
|
| 63 |
+
"suggested_title": "Manual Review Needed",
|
| 64 |
+
"condition_guess": "New"
|
| 65 |
+
}
|
| 66 |
+
"""
|
| 67 |
+
# Write the file
|
| 68 |
+
os.makedirs("agents", exist_ok=True)
|
| 69 |
+
with open("agents/visual_analyst.py", "w", encoding="utf-8") as f:
|
| 70 |
+
f.write(content)
|
| 71 |
+
print("✅ agents/visual_analyst.py updated.")
|
| 72 |
+
|
| 73 |
+
# Git operations
|
| 74 |
+
print("🚀 Pushing to HuggingFace...")
|
| 75 |
+
commands = [
|
| 76 |
+
["git", "add", "agents/visual_analyst.py"],
|
| 77 |
+
["git", "commit", "-m", "Fix vision core and error handling"],
|
| 78 |
+
["git", "push", "space", "clean_deploy:main"]
|
| 79 |
+
]
|
| 80 |
+
|
| 81 |
+
for cmd in commands:
|
| 82 |
+
try:
|
| 83 |
+
print(f"Running: {' '.join(cmd)}")
|
| 84 |
+
subprocess.run(cmd, check=True)
|
| 85 |
+
except subprocess.CalledProcessError as e:
|
| 86 |
+
print(f"⚠️ Command failed: {e}")
|
| 87 |
+
# Continue even if commit fails (e.g. prompt already applied)
|
| 88 |
+
|
| 89 |
+
if __name__ == "__main__":
|
| 90 |
+
fix_vision_core()
|
force_deploy.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
force_deploy.py
|
| 3 |
+
Force-deploys the current codebase to the Hugging Face Space.
|
| 4 |
+
Handles HF binary file restrictions by untracking offending files first.
|
| 5 |
+
"""
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
def run(cmd, allow_fail=False):
|
| 10 |
+
print(f"\n>>> {cmd}")
|
| 11 |
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
| 12 |
+
if result.stdout:
|
| 13 |
+
print(result.stdout.strip())
|
| 14 |
+
if result.stderr:
|
| 15 |
+
print(result.stderr.strip())
|
| 16 |
+
if result.returncode != 0 and not allow_fail:
|
| 17 |
+
print(f"Command failed with exit code {result.returncode}")
|
| 18 |
+
sys.exit(1)
|
| 19 |
+
return result
|
| 20 |
+
|
| 21 |
+
if __name__ == "__main__":
|
| 22 |
+
print("=" * 60)
|
| 23 |
+
print(" StyleSync AI — Force Deploy to Hugging Face Space")
|
| 24 |
+
print("=" * 60)
|
| 25 |
+
|
| 26 |
+
# 0. Remove binary files from Git tracking (keep local copies)
|
| 27 |
+
binaries = [
|
| 28 |
+
"stitch_stylesync_ai_dashboard.zip",
|
| 29 |
+
"screen.jpg",
|
| 30 |
+
"test_image.jpg",
|
| 31 |
+
]
|
| 32 |
+
for b in binaries:
|
| 33 |
+
run(f"git rm --cached {b}", allow_fail=True)
|
| 34 |
+
|
| 35 |
+
# Ensure .gitignore blocks them from being re-added
|
| 36 |
+
ignore_entries = binaries + ["*.zip", "*.jpg", "*.jpeg", "*.png"]
|
| 37 |
+
try:
|
| 38 |
+
existing = open(".gitignore", "r").read()
|
| 39 |
+
except FileNotFoundError:
|
| 40 |
+
existing = ""
|
| 41 |
+
with open(".gitignore", "a") as f:
|
| 42 |
+
for entry in ignore_entries:
|
| 43 |
+
if entry not in existing:
|
| 44 |
+
f.write(f"\n{entry}")
|
| 45 |
+
print("\nUpdated .gitignore with binary exclusions.")
|
| 46 |
+
|
| 47 |
+
# 1. Stage everything
|
| 48 |
+
run("git add .")
|
| 49 |
+
|
| 50 |
+
# 2. Commit (allow fail in case nothing changed)
|
| 51 |
+
run('git commit -m "Critical Deployment: Force update of License, Security Fixes, and Branding"', allow_fail=True)
|
| 52 |
+
|
| 53 |
+
# 3. Force push HEAD to space remote's main branch
|
| 54 |
+
run("git push --force space HEAD:main")
|
| 55 |
+
|
| 56 |
+
# 4. Print the latest commit for verification
|
| 57 |
+
print("\n" + "=" * 60)
|
| 58 |
+
print(" Deployed Commit:")
|
| 59 |
+
print("=" * 60)
|
| 60 |
+
run("git log -1")
|
| 61 |
+
|
| 62 |
+
print("\n✅ Force deploy complete.")
|
force_push.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
|
| 3 |
+
def main():
|
| 4 |
+
print("Executing force push...")
|
| 5 |
+
command = "git push --force space HEAD:main"
|
| 6 |
+
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
| 7 |
+
|
| 8 |
+
print("STDOUT:")
|
| 9 |
+
print(result.stdout)
|
| 10 |
+
print("STDERR:")
|
| 11 |
+
print(result.stderr)
|
| 12 |
+
|
| 13 |
+
if __name__ == "__main__":
|
| 14 |
+
main()
|
glassui.html
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="dark" lang="en"><head>
|
| 3 |
+
<meta charset="utf-8"/>
|
| 4 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 5 |
+
<title>StyleSync AI Dashboard</title>
|
| 6 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 7 |
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
| 8 |
+
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
"primary": "#f59e0b",
|
| 18 |
+
"primary-hover": "#d97706",
|
| 19 |
+
"glass-border": "rgba(255, 255, 255, 0.12)",
|
| 20 |
+
"glass-surface": "rgba(255, 255, 255, 0.03)",
|
| 21 |
+
"glass-surface-hover": "rgba(255, 255, 255, 0.07)",
|
| 22 |
+
"charcoal": "#0a0a0a",
|
| 23 |
+
"amber-accent": "#fbbf24"
|
| 24 |
+
},
|
| 25 |
+
fontFamily: {
|
| 26 |
+
"display": ["Plus Jakarta Sans", "sans-serif"],
|
| 27 |
+
"mono": ["monospace"]
|
| 28 |
+
},
|
| 29 |
+
borderRadius: { "DEFAULT": "1rem", "lg": "1.5rem", "xl": "2rem", "2xl": "3rem", "full": "9999px" },
|
| 30 |
+
backdropBlur: {
|
| 31 |
+
'xs': '2px',
|
| 32 |
+
},
|
| 33 |
+
screens: {
|
| 34 |
+
'xs': '480px',
|
| 35 |
+
},
|
| 36 |
+
animation: {
|
| 37 |
+
'pulse-slow': 'pulse-slow 3s infinite',
|
| 38 |
+
'glow-pulse': 'glow-pulse 3s infinite',
|
| 39 |
+
'copy-success': 'copy-success 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
|
| 40 |
+
},
|
| 41 |
+
keyframes: {
|
| 42 |
+
'pulse-slow': {
|
| 43 |
+
'0%, 100%': { transform: 'scale(1)' },
|
| 44 |
+
'50%': { transform: 'scale(1.02)' },
|
| 45 |
+
},
|
| 46 |
+
'glow-pulse': {
|
| 47 |
+
'0%, 100%': { boxShadow: '0 0 0 0 rgba(245, 158, 11, 0.4)' },
|
| 48 |
+
'50%': { boxShadow: '0 0 20px 5px rgba(251, 191, 36, 0.5)' },
|
| 49 |
+
},
|
| 50 |
+
'copy-success': {
|
| 51 |
+
'0%': { transform: 'scale(0.8)', opacity: '0' },
|
| 52 |
+
'100%': { transform: 'scale(1)', opacity: '1' },
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
},
|
| 57 |
+
}
|
| 58 |
+
</script>
|
| 59 |
+
<style type="text/tailwindcss">
|
| 60 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 61 |
+
width: 6px;
|
| 62 |
+
height: 6px;
|
| 63 |
+
}
|
| 64 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 65 |
+
background: transparent;
|
| 66 |
+
}
|
| 67 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 68 |
+
background: rgba(255, 255, 255, 0.1);
|
| 69 |
+
border-radius: 99px;
|
| 70 |
+
}
|
| 71 |
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
| 72 |
+
background: rgba(255, 255, 255, 0.2);
|
| 73 |
+
}
|
| 74 |
+
body {
|
| 75 |
+
background-color: #0a0a0a;
|
| 76 |
+
background-image:
|
| 77 |
+
radial-gradient(at 0% 0%, rgba(20, 20, 20, 1) 0, transparent 50%),
|
| 78 |
+
radial-gradient(at 50% -10%, rgba(217, 119, 6, 0.15) 0, transparent 60%),
|
| 79 |
+
radial-gradient(at 100% 0%, rgba(251, 191, 36, 0.1) 0, transparent 50%),
|
| 80 |
+
radial-gradient(at 80% 80%, rgba(217, 119, 6, 0.12) 0, transparent 50%);
|
| 81 |
+
background-attachment: fixed;
|
| 82 |
+
min-height: 100vh;
|
| 83 |
+
}
|
| 84 |
+
.glass-panel {
|
| 85 |
+
background: rgba(15, 15, 15, 0.75);
|
| 86 |
+
backdrop-filter: blur(20px);
|
| 87 |
+
-webkit-backdrop-filter: blur(20px);
|
| 88 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 89 |
+
}
|
| 90 |
+
@media (max-width: 768px) {
|
| 91 |
+
.glass-panel {
|
| 92 |
+
backdrop-filter: blur(14px);
|
| 93 |
+
-webkit-backdrop-filter: blur(14px);
|
| 94 |
+
background: rgba(10, 10, 10, 0.85);
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
.glass-card {
|
| 98 |
+
background: rgba(255, 255, 255, 0.02);
|
| 99 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 100 |
+
backdrop-filter: blur(12px);
|
| 101 |
+
transition: all 0.3s ease;
|
| 102 |
+
}
|
| 103 |
+
@media (hover: hover) {
|
| 104 |
+
.glass-card:hover {
|
| 105 |
+
border-color: rgba(251, 191, 36, 0.3);
|
| 106 |
+
box-shadow: 0 0 25px rgba(245, 158, 11, 0.15);
|
| 107 |
+
background: rgba(255, 255, 255, 0.04);
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
.neon-textarea {
|
| 111 |
+
background: rgba(255, 255, 255, 0.03);
|
| 112 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 113 |
+
backdrop-filter: blur(16px);
|
| 114 |
+
-webkit-backdrop-filter: blur(16px);
|
| 115 |
+
transition: all 0.3s ease;
|
| 116 |
+
}
|
| 117 |
+
.neon-textarea:focus {
|
| 118 |
+
outline: none;
|
| 119 |
+
border-color: rgba(251, 191, 36, 0.5);
|
| 120 |
+
box-shadow: 0 0 15px rgba(245, 158, 11, 0.25), inset 0 0 5px rgba(245, 158, 11, 0.05);
|
| 121 |
+
background: rgba(255, 255, 255, 0.06);
|
| 122 |
+
}
|
| 123 |
+
.copy-active {
|
| 124 |
+
box-shadow: 0 0 15px rgba(251, 191, 36, 0.6) !important;
|
| 125 |
+
border-color: rgba(251, 191, 36, 0.4) !important;
|
| 126 |
+
background: rgba(251, 191, 36, 0.1) !important;
|
| 127 |
+
}
|
| 128 |
+
</style>
|
| 129 |
+
<style>
|
| 130 |
+
body {
|
| 131 |
+
min-height: max(884px, 100dvh);
|
| 132 |
+
}
|
| 133 |
+
</style>
|
| 134 |
+
</head>
|
| 135 |
+
<body class="font-display text-white antialiased overflow-x-hidden min-h-screen flex flex-col selection:bg-amber-500 selection:text-black">
|
| 136 |
+
<header class="flex-none z-30 px-4 py-3 lg:px-6 lg:py-4 border-b border-white/5 glass-panel sticky top-0">
|
| 137 |
+
<div class="max-w-[1600px] mx-auto flex items-center justify-between gap-4">
|
| 138 |
+
<div class="flex items-center gap-3 lg:gap-4 flex-1 min-w-0">
|
| 139 |
+
<div class="flex-none flex items-center justify-center size-9 lg:size-10 rounded-xl bg-gradient-to-br from-amber-400 to-amber-700 shadow-lg shadow-amber-900/20 text-white">
|
| 140 |
+
<span class="material-symbols-outlined text-xl lg:text-2xl">all_inclusive</span>
|
| 141 |
+
</div>
|
| 142 |
+
<div class="min-w-0">
|
| 143 |
+
<h2 class="text-white text-base lg:text-xl font-bold leading-tight tracking-tight truncate">StyleSync AI</h2>
|
| 144 |
+
<div class="flex items-center gap-2">
|
| 145 |
+
<span class="size-1.5 rounded-full bg-amber-500 animate-pulse flex-none"></span>
|
| 146 |
+
<span class="text-[10px] text-amber-200/60 font-bold uppercase tracking-wider truncate">Enterprise Edition</span>
|
| 147 |
+
</div>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
<button class="group relative flex-none flex cursor-pointer items-center justify-center overflow-hidden rounded-full h-9 lg:h-10 px-4 lg:px-6 bg-gradient-to-r from-amber-600 to-amber-800 hover:from-amber-500 hover:to-amber-700 border border-white/10 shadow-[0_0_15px_rgba(245,158,11,0.3)] transition-all" id="deployBtn">
|
| 151 |
+
<div class="absolute inset-0 bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
| 152 |
+
<span class="material-symbols-outlined lg:mr-2 text-lg relative z-10">rocket_launch</span>
|
| 153 |
+
<span class="hidden lg:inline truncate text-sm font-bold tracking-wide relative z-10">Deploy</span>
|
| 154 |
+
</button>
|
| 155 |
+
</div>
|
| 156 |
+
</header>
|
| 157 |
+
<main class="flex-1 flex flex-col lg:flex-row relative z-10">
|
| 158 |
+
<div class="fixed top-1/4 -left-20 w-64 h-64 lg:w-96 lg:h-96 bg-amber-900/10 rounded-full blur-[120px] pointer-events-none z-0"></div>
|
| 159 |
+
<div class="fixed bottom-1/4 -right-20 w-64 h-64 lg:w-96 lg:h-96 bg-orange-900/5 rounded-full blur-[120px] pointer-events-none z-0"></div>
|
| 160 |
+
<div class="flex-none lg:flex-1 flex flex-col lg:border-r border-white/5 p-4 lg:p-8 bg-black/40 lg:overflow-y-auto lg:h-[calc(100vh-73px)] relative z-10">
|
| 161 |
+
<div class="max-w-xl w-full mx-auto flex flex-col gap-4 lg:gap-6 h-full">
|
| 162 |
+
<div class="flex items-center justify-between mb-2">
|
| 163 |
+
<h2 class="text-white text-xl lg:text-3xl font-bold tracking-tight">Input Data</h2>
|
| 164 |
+
<span class="bg-white/5 text-amber-200/80 px-2.5 py-1 rounded-full text-[10px] lg:text-xs font-semibold border border-white/10 backdrop-blur-sm">Step 1 of 2</span>
|
| 165 |
+
</div>
|
| 166 |
+
<div class="flex flex-col flex-none">
|
| 167 |
+
<input accept=".jpg,.jpeg,.png,.webp" class="hidden" id="fileInput" type="file"/>
|
| 168 |
+
<div class="group relative flex flex-col items-center justify-center gap-3 lg:gap-4 rounded-2xl lg:rounded-3xl border-2 border-dashed border-white/10 hover:border-amber-500/50 hover:bg-white/5 transition-all cursor-pointer min-h-[200px] lg:min-h-[260px] px-4 py-6 lg:px-6 lg:py-8 glass-card" id="dropZone">
|
| 169 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 170 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 171 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 172 |
+
</div>
|
| 173 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 174 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 175 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 176 |
+
</div>
|
| 177 |
+
<button class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide" id="browseBtn">
|
| 178 |
+
Browse Files
|
| 179 |
+
</button>
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
<div class="flex flex-col gap-2 lg:gap-3 flex-1">
|
| 183 |
+
<label class="flex items-center justify-between px-1">
|
| 184 |
+
<span class="text-white text-sm lg:text-base font-semibold">Raw Product Specs</span>
|
| 185 |
+
<span class="text-[10px] lg:text-xs text-neutral-500 font-medium bg-black/40 px-2 py-0.5 rounded border border-white/5">JSON or Plain Text</span>
|
| 186 |
+
</label>
|
| 187 |
+
<div class="relative w-full">
|
| 188 |
+
<textarea class="form-input w-full min-h-[140px] lg:h-full lg:min-h-[180px] resize-y lg:resize-none rounded-xl lg:rounded-2xl text-neutral-100 placeholder:text-neutral-600 neon-textarea p-4 lg:p-5 text-sm leading-relaxed font-mono shadow-inner transition-all duration-300" id="productSpecs" placeholder="// Enter fabric details, dimensions, and SKU..."></textarea>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="pt-2 pb-6 lg:pb-0">
|
| 192 |
+
<button class="group relative w-full cursor-pointer overflow-hidden rounded-xl lg:rounded-2xl h-12 lg:h-14 bg-gradient-to-r from-amber-600 via-amber-700 to-amber-800 hover:from-amber-500 hover:to-amber-700 transition-all border border-white/10 animate-pulse-slow animate-glow-pulse" id="startBtn">
|
| 193 |
+
<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3 relative z-10">
|
| 194 |
+
<span class="text-white text-base lg:text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span>
|
| 195 |
+
<span class="material-symbols-outlined text-white text-lg lg:text-xl group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
| 196 |
+
</div>
|
| 197 |
+
</button>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
<div class="flex-none lg:flex-1 flex flex-col p-4 lg:p-8 bg-black/60 lg:overflow-y-auto lg:h-[calc(100vh-73px)] relative">
|
| 202 |
+
<div class="absolute inset-0 opacity-[0.02] pointer-events-none" style="background-image: linear-gradient(to right, #ffffff 1px, transparent 1px), linear-gradient(to bottom, #ffffff 1px, transparent 1px); background-size: 40px 40px;"></div>
|
| 203 |
+
<div class="max-w-3xl w-full mx-auto flex flex-col gap-4 lg:gap-6 h-full relative z-10 pb-8 lg:pb-0">
|
| 204 |
+
<div class="flex items-center justify-between mb-2">
|
| 205 |
+
<h2 class="text-white text-xl lg:text-3xl font-bold tracking-tight">Generated Output</h2>
|
| 206 |
+
<div class="flex items-center gap-2">
|
| 207 |
+
<span class="hidden xs:inline-block bg-white/5 text-amber-200 px-2.5 py-1 rounded-full text-[10px] lg:text-xs font-semibold border border-white/10 backdrop-blur-sm">Step 2 of 2</span>
|
| 208 |
+
<div class="hidden xs:block h-6 w-px bg-white/10 mx-1"></div>
|
| 209 |
+
<button class="size-8 lg:size-9 flex items-center justify-center hover:bg-white/10 rounded-lg text-neutral-500 hover:text-amber-400 transition-all duration-300 border border-transparent" id="copyBtn" title="Copy JSON">
|
| 210 |
+
<span class="material-symbols-outlined text-lg lg:text-xl transition-all duration-300" id="copyIcon">content_copy</span>
|
| 211 |
+
</button>
|
| 212 |
+
<button class="size-8 lg:size-9 flex items-center justify-center hover:bg-white/10 rounded-lg text-neutral-500 hover:text-amber-400 transition-colors" id="downloadBtn" title="Download JSON">
|
| 213 |
+
<span class="material-symbols-outlined text-lg lg:text-xl">download</span>
|
| 214 |
+
</button>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
<div class="grid grid-cols-1 xs:grid-cols-3 gap-3">
|
| 218 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 219 |
+
<div class="relative size-3 flex-none">
|
| 220 |
+
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-500 opacity-75"></span>
|
| 221 |
+
<span class="relative inline-flex rounded-full size-3 bg-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.5)]"></span>
|
| 222 |
+
</div>
|
| 223 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 224 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">Vision Agent</span>
|
| 225 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Gemini Pro 1.5</span>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 229 |
+
<div class="relative size-3 flex-none">
|
| 230 |
+
<span class="relative inline-flex rounded-full size-3 bg-amber-600 shadow-[0_0_10px_rgba(217,119,6,0.5)]"></span>
|
| 231 |
+
</div>
|
| 232 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 233 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">Reasoning Agent</span>
|
| 234 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Llama 3 70B</span>
|
| 235 |
+
</div>
|
| 236 |
+
</div>
|
| 237 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 238 |
+
<div class="relative size-3 flex-none">
|
| 239 |
+
<span class="relative inline-flex rounded-full size-3 bg-neutral-600 shadow-[0_0_10px_rgba(82,82,82,0.5)]"></span>
|
| 240 |
+
</div>
|
| 241 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 242 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">SEO Context</span>
|
| 243 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Pinecone DB</span>
|
| 244 |
+
</div>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
<div class="flex-none lg:flex-1 min-h-[400px] lg:min-h-0 rounded-xl lg:rounded-2xl bg-black/60 backdrop-blur-xl border border-white/5 flex flex-col overflow-hidden shadow-2xl relative glass-card group/output transition-all duration-500">
|
| 248 |
+
<div class="absolute top-0 right-0 w-64 h-64 bg-amber-500/5 rounded-full blur-[100px] pointer-events-none"></div>
|
| 249 |
+
<div class="flex items-center justify-between px-3 py-2.5 lg:px-4 lg:py-3 bg-white/5 border-b border-white/5">
|
| 250 |
+
<span class="text-[10px] lg:text-xs font-mono text-neutral-500 flex items-center gap-2">
|
| 251 |
+
<span class="material-symbols-outlined text-xs lg:text-sm">code</span>
|
| 252 |
+
output.json
|
| 253 |
+
</span>
|
| 254 |
+
<div class="flex gap-1.5">
|
| 255 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-neutral-800 border border-white/10"></div>
|
| 256 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-neutral-800 border border-white/10"></div>
|
| 257 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-amber-500/20 border border-amber-500/30"></div>
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
<div class="flex-1 p-4 lg:p-5 overflow-auto custom-scrollbar font-mono text-xs lg:text-sm leading-6 lg:leading-7">
|
| 261 |
+
<pre><code class="language-json block" id="jsonOutput"><span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">01</span><span class="text-amber-400">{</span>
|
| 262 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">02</span> <span class="text-amber-600">"product_analysis"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">{</span>
|
| 263 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">03</span> <span class="text-amber-600">"title"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Noir Elite Series Artisan Timepiece"</span><span class="text-neutral-400">,</span>
|
| 264 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">04</span> <span class="text-amber-600">"category"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Luxury / Accessories"</span><span class="text-neutral-400">,</span>
|
| 265 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">05</span> <span class="text-amber-600">"features"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">[</span>
|
| 266 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">06</span> <span class="text-amber-200">"Obsidian Finish"</span><span class="text-neutral-400">,</span>
|
| 267 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">07</span> <span class="text-amber-200">"Golden Accents"</span><span class="text-neutral-400">,</span>
|
| 268 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">08</span> <span class="text-amber-200">"Smart Haptic Interface"</span>
|
| 269 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">09</span> <span class="text-amber-400">]</span><span class="text-neutral-400">,</span>
|
| 270 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">10</span> <span class="text-amber-600">"seo_tags"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">[</span>
|
| 271 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">11</span> <span class="text-amber-200">"#luxurywear"</span><span class="text-neutral-400">,</span> <span class="text-amber-200">"#amberstyle"</span><span class="text-neutral-400">,</span> <span class="text-amber-200">"#premiumtech"</span>
|
| 272 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">12</span> <span class="text-amber-400">]</span><span class="text-neutral-400">,</span>
|
| 273 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">13</span> <span class="text-amber-600">"sentiment_score"</span><span class="text-neutral-400">:</span> <span class="text-amber-500">0.99</span><span class="text-neutral-400">,</span>
|
| 274 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">14</span> <span class="text-amber-600">"market_fit"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Exceptional"</span>
|
| 275 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">15</span> <span class="text-amber-400">}</span><span class="text-neutral-400">,</span>
|
| 276 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">16</span> <span class="text-amber-600">"deployment_status"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Authorized"</span>
|
| 277 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">17</span><span class="text-amber-400">}</span></code></pre>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
</main>
|
| 283 |
+
<footer class="flex-none flex items-center justify-center py-4 bg-black/80 border-t border-white/5 backdrop-blur-md z-10">
|
| 284 |
+
<span class="text-[10px] lg:text-xs text-neutral-600 font-medium tracking-wide">© 2026 StyleSync AI — All Rights Reserved</span>
|
| 285 |
+
</footer>
|
| 286 |
+
<script>
|
| 287 |
+
tailwind.config.theme.extend.animation = {
|
| 288 |
+
shine: 'shine 1.5s infinite',
|
| 289 |
+
}
|
| 290 |
+
tailwind.config.theme.extend.keyframes = {
|
| 291 |
+
shine: {
|
| 292 |
+
'0%': { left: '-100%' },
|
| 293 |
+
'100%': { left: '200%' },
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
const dropZone = document.getElementById('dropZone');
|
| 297 |
+
const fileInput = document.getElementById('fileInput');
|
| 298 |
+
const startBtn = document.getElementById('startBtn');
|
| 299 |
+
const jsonOutput = document.getElementById('jsonOutput');
|
| 300 |
+
const deployBtn = document.getElementById('deployBtn');
|
| 301 |
+
const copyBtn = document.getElementById('copyBtn');
|
| 302 |
+
const copyIcon = document.getElementById('copyIcon');
|
| 303 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 304 |
+
let selectedFile = null;
|
| 305 |
+
let isCatalogGenerated = false;
|
| 306 |
+
const defaultDropZoneContent = `
|
| 307 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 308 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 309 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 310 |
+
</div>
|
| 311 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 312 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 313 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 314 |
+
</div>
|
| 315 |
+
<button id="browseBtn"
|
| 316 |
+
class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide">
|
| 317 |
+
Browse Files
|
| 318 |
+
</button>
|
| 319 |
+
`;
|
| 320 |
+
function initDropZone() {
|
| 321 |
+
const browseBtn = document.getElementById('browseBtn');
|
| 322 |
+
if (browseBtn) {
|
| 323 |
+
browseBtn.addEventListener('click', (e) => {
|
| 324 |
+
e.stopPropagation();
|
| 325 |
+
fileInput.click();
|
| 326 |
+
});
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
initDropZone();
|
| 330 |
+
fileInput.addEventListener('change', (e) => {
|
| 331 |
+
if (e.target.files.length > 0) {
|
| 332 |
+
handleFile(e.target.files[0]);
|
| 333 |
+
}
|
| 334 |
+
});
|
| 335 |
+
dropZone.addEventListener('dragover', (e) => {
|
| 336 |
+
e.preventDefault();
|
| 337 |
+
dropZone.classList.add('border-amber-500');
|
| 338 |
+
dropZone.classList.add('bg-amber-500/5');
|
| 339 |
+
});
|
| 340 |
+
dropZone.addEventListener('dragleave', (e) => {
|
| 341 |
+
e.preventDefault();
|
| 342 |
+
dropZone.classList.remove('border-amber-500');
|
| 343 |
+
dropZone.classList.remove('bg-amber-500/5');
|
| 344 |
+
});
|
| 345 |
+
dropZone.addEventListener('drop', (e) => {
|
| 346 |
+
e.preventDefault();
|
| 347 |
+
dropZone.classList.remove('border-amber-500');
|
| 348 |
+
dropZone.classList.remove('bg-amber-500/5');
|
| 349 |
+
if (e.dataTransfer.files.length > 0) {
|
| 350 |
+
handleFile(e.dataTransfer.files[0]);
|
| 351 |
+
}
|
| 352 |
+
});
|
| 353 |
+
function handleFile(file) {
|
| 354 |
+
selectedFile = file;
|
| 355 |
+
dropZone.innerHTML = `
|
| 356 |
+
<div class="flex flex-col items-center justify-center gap-4 z-10">
|
| 357 |
+
<div class="size-14 lg:size-16 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center">
|
| 358 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">check_circle</span>
|
| 359 |
+
</div>
|
| 360 |
+
<div class="flex flex-col items-center gap-1">
|
| 361 |
+
<p class="text-white text-base lg:text-lg font-bold text-center">${file.name}</p>
|
| 362 |
+
<p class="text-neutral-500 text-xs lg:text-sm text-center">${(file.size / 1024).toFixed(1)} KB</p>
|
| 363 |
+
</div>
|
| 364 |
+
<button id="removeFileBtn" class="mt-2 flex items-center justify-center gap-2 rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 transition-all text-[10px] lg:text-xs font-bold uppercase tracking-wide">
|
| 365 |
+
<span class="material-symbols-outlined text-sm lg:text-base">close</span>
|
| 366 |
+
<span>Remove File</span>
|
| 367 |
+
</button>
|
| 368 |
+
</div>
|
| 369 |
+
`;
|
| 370 |
+
document.getElementById('removeFileBtn').addEventListener('click', (e) => {
|
| 371 |
+
e.stopPropagation();
|
| 372 |
+
resetUploadUI();
|
| 373 |
+
});
|
| 374 |
+
}
|
| 375 |
+
function resetUploadUI() {
|
| 376 |
+
selectedFile = null;
|
| 377 |
+
fileInput.value = "";
|
| 378 |
+
dropZone.innerHTML = defaultDropZoneContent;
|
| 379 |
+
initDropZone();
|
| 380 |
+
}
|
| 381 |
+
deployBtn.addEventListener('click', () => {
|
| 382 |
+
if (!isCatalogGenerated) {
|
| 383 |
+
alert("Please generate a catalog first before deploying.");
|
| 384 |
+
return;
|
| 385 |
+
}
|
| 386 |
+
const originalContent = deployBtn.innerHTML;
|
| 387 |
+
deployBtn.disabled = true;
|
| 388 |
+
deployBtn.innerHTML = `
|
| 389 |
+
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
| 390 |
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
| 391 |
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
| 392 |
+
</svg>
|
| 393 |
+
`;
|
| 394 |
+
setTimeout(() => {
|
| 395 |
+
alert("Success: Catalog pushed to storefront!");
|
| 396 |
+
deployBtn.innerHTML = originalContent;
|
| 397 |
+
deployBtn.disabled = false;
|
| 398 |
+
}, 1500);
|
| 399 |
+
});
|
| 400 |
+
copyBtn.addEventListener('click', () => {
|
| 401 |
+
const textToCopy = jsonOutput.innerText;
|
| 402 |
+
navigator.clipboard.writeText(textToCopy).then(() => {
|
| 403 |
+
const originalIcon = copyIcon.innerText;
|
| 404 |
+
copyIcon.style.opacity = '0';
|
| 405 |
+
copyIcon.style.transform = 'scale(0.8)';
|
| 406 |
+
setTimeout(() => {
|
| 407 |
+
copyIcon.innerText = 'check';
|
| 408 |
+
copyIcon.classList.remove('text-neutral-500');
|
| 409 |
+
copyIcon.classList.add('text-amber-500');
|
| 410 |
+
copyIcon.classList.add('animate-copy-success');
|
| 411 |
+
copyIcon.style.opacity = '1';
|
| 412 |
+
copyIcon.style.transform = 'scale(1)';
|
| 413 |
+
copyBtn.classList.add('copy-active');
|
| 414 |
+
}, 150);
|
| 415 |
+
setTimeout(() => {
|
| 416 |
+
copyIcon.style.opacity = '0';
|
| 417 |
+
copyIcon.style.transform = 'scale(0.8)';
|
| 418 |
+
setTimeout(() => {
|
| 419 |
+
copyIcon.innerText = originalIcon;
|
| 420 |
+
copyIcon.classList.remove('text-amber-500', 'animate-copy-success');
|
| 421 |
+
copyIcon.classList.add('text-neutral-500');
|
| 422 |
+
copyIcon.style.opacity = '1';
|
| 423 |
+
copyIcon.style.transform = 'scale(1)';
|
| 424 |
+
copyBtn.classList.remove('copy-active');
|
| 425 |
+
}, 150);
|
| 426 |
+
}, 2000);
|
| 427 |
+
});
|
| 428 |
+
});
|
| 429 |
+
downloadBtn.addEventListener('click', () => {
|
| 430 |
+
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(jsonOutput.innerText);
|
| 431 |
+
const downloadAnchorNode = document.createElement('a');
|
| 432 |
+
downloadAnchorNode.setAttribute("href", dataStr);
|
| 433 |
+
downloadAnchorNode.setAttribute("download", "stylesync_output.json");
|
| 434 |
+
document.body.appendChild(downloadAnchorNode);
|
| 435 |
+
downloadAnchorNode.click();
|
| 436 |
+
downloadAnchorNode.remove();
|
| 437 |
+
});
|
| 438 |
+
startBtn.addEventListener('click', async () => {
|
| 439 |
+
if (!selectedFile) {
|
| 440 |
+
alert("Please select a file first.");
|
| 441 |
+
return;
|
| 442 |
+
}
|
| 443 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3"><svg class="animate-spin h-4 w-4 lg:h-5 lg:w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg><span class="text-white text-base lg:text-lg font-bold tracking-wide">Synthesizing...</span></div>';
|
| 444 |
+
startBtn.disabled = true;
|
| 445 |
+
startBtn.classList.remove('animate-pulse-slow', 'animate-glow-pulse');
|
| 446 |
+
setTimeout(() => {
|
| 447 |
+
isCatalogGenerated = true;
|
| 448 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3 relative z-10"><span class="text-white text-base lg:text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span><span class="material-symbols-outlined text-white text-lg lg:text-xl group-hover:translate-x-1 transition-transform">arrow_forward</span></div>';
|
| 449 |
+
startBtn.disabled = false;
|
| 450 |
+
startBtn.classList.add('animate-pulse-slow', 'animate-glow-pulse');
|
| 451 |
+
}, 1500);
|
| 452 |
+
});
|
| 453 |
+
</script>
|
| 454 |
+
|
| 455 |
+
</body></html>
|
install_gh.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import shutil
|
| 2 |
+
import subprocess
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
def main():
|
| 6 |
+
# Check Status
|
| 7 |
+
gh_path = shutil.which('gh')
|
| 8 |
+
|
| 9 |
+
if not gh_path:
|
| 10 |
+
# Install
|
| 11 |
+
print("GitHub CLI not found. Installing via winget...")
|
| 12 |
+
try:
|
| 13 |
+
subprocess.run(['winget', 'install', '--id', 'GitHub.cli', '-e'], check=True)
|
| 14 |
+
except subprocess.CalledProcessError as e:
|
| 15 |
+
print(f"Error installing GitHub CLI: {e}")
|
| 16 |
+
return
|
| 17 |
+
except FileNotFoundError:
|
| 18 |
+
print("Error: 'winget' command not found. Please ensure App Installer is installed.")
|
| 19 |
+
return
|
| 20 |
+
|
| 21 |
+
# Post-Install Instructions (Runs if installed or if installation succeeded)
|
| 22 |
+
print("\n" + "="*40)
|
| 23 |
+
try:
|
| 24 |
+
# Attempt to use ANSI codes for bold, may not work in all Windows terminals but works in VS Code / modern Windows Terminal
|
| 25 |
+
print("✅ \033[1mGitHub CLI is ready!\033[0m")
|
| 26 |
+
except:
|
| 27 |
+
print("✅ GitHub CLI is ready!")
|
| 28 |
+
print("="*40)
|
| 29 |
+
print("⚠️ IMPORTANT: You must now restart your terminal to reload your PATH.")
|
| 30 |
+
print("👉 After restarting, run this command to log in: gh auth login")
|
| 31 |
+
|
| 32 |
+
if __name__ == "__main__":
|
| 33 |
+
main()
|
landingpage.html
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
|
| 3 |
+
<html class="dark" lang="en"><head>
|
| 4 |
+
<meta charset="utf-8"/>
|
| 5 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 6 |
+
<title>StyleSync AI | Autonomous E-Commerce Catalog Intelligence</title>
|
| 7 |
+
<!-- BEGIN: External Resources -->
|
| 8 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 9 |
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
| 10 |
+
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
| 11 |
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"/>
|
| 12 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 13 |
+
<!-- END: External Resources -->
|
| 14 |
+
<!-- BEGIN: Tailwind Configuration -->
|
| 15 |
+
<script id="tailwind-config">
|
| 16 |
+
tailwind.config = {
|
| 17 |
+
darkMode: "class",
|
| 18 |
+
theme: {
|
| 19 |
+
extend: {
|
| 20 |
+
colors: {
|
| 21 |
+
"primary": "#fbbf24", // Amber-400
|
| 22 |
+
"primary-dark": "#d97706", // Amber-600
|
| 23 |
+
"charcoal": "#0a0a0a",
|
| 24 |
+
"glass-white": "rgba(255, 255, 255, 0.08)",
|
| 25 |
+
"glass-amber": "rgba(251, 191, 36, 0.05)",
|
| 26 |
+
},
|
| 27 |
+
fontFamily: {
|
| 28 |
+
"sans": ["Plus Jakarta Sans", "sans-serif"],
|
| 29 |
+
},
|
| 30 |
+
animation: {
|
| 31 |
+
'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
| 32 |
+
'shine': 'shine 2s infinite',
|
| 33 |
+
'float': 'float 6s ease-in-out infinite',
|
| 34 |
+
},
|
| 35 |
+
keyframes: {
|
| 36 |
+
shine: {
|
| 37 |
+
'0%': { transform: 'translateX(-100%)' },
|
| 38 |
+
'100%': { transform: 'translateX(100%)' }
|
| 39 |
+
},
|
| 40 |
+
float: {
|
| 41 |
+
'0%, 100%': { transform: 'translateY(0)' },
|
| 42 |
+
'50%': { transform: 'translateY(-20px)' }
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
},
|
| 47 |
+
}
|
| 48 |
+
</script>
|
| 49 |
+
<!-- END: Tailwind Configuration -->
|
| 50 |
+
<!-- BEGIN: Custom Styles -->
|
| 51 |
+
<style data-purpose="layout-and-glassmorphism">
|
| 52 |
+
body {
|
| 53 |
+
background-color: #0a0a0a;
|
| 54 |
+
background-image:
|
| 55 |
+
radial-gradient(circle at 20% 30%, rgba(217, 119, 6, 0.08) 0%, transparent 40%),
|
| 56 |
+
radial-gradient(circle at 80% 70%, rgba(251, 191, 36, 0.05) 0%, transparent 40%);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.glass-panel {
|
| 60 |
+
background: rgba(255, 255, 255, 0.03);
|
| 61 |
+
backdrop-filter: blur(20px);
|
| 62 |
+
-webkit-backdrop-filter: blur(20px);
|
| 63 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.glass-card-hover:hover {
|
| 67 |
+
background: rgba(251, 191, 36, 0.05);
|
| 68 |
+
border-color: rgba(251, 191, 36, 0.3);
|
| 69 |
+
transform: translateY(-4px);
|
| 70 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.text-glow {
|
| 74 |
+
text-shadow: 0 0 20px rgba(251, 191, 36, 0.3);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 78 |
+
width: 6px;
|
| 79 |
+
}
|
| 80 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 81 |
+
background: transparent;
|
| 82 |
+
}
|
| 83 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 84 |
+
background: rgba(255, 255, 255, 0.1);
|
| 85 |
+
border-radius: 10px;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
/* Scroll Reveal Animation Styles */
|
| 89 |
+
.reveal-card {
|
| 90 |
+
opacity: 0;
|
| 91 |
+
transform: translateY(30px);
|
| 92 |
+
transition: all 0.8s cubic-bezier(0.22, 1, 0.36, 1);
|
| 93 |
+
will-change: transform, opacity;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.reveal-card.is-visible {
|
| 97 |
+
opacity: 1;
|
| 98 |
+
transform: translateY(0);
|
| 99 |
+
}
|
| 100 |
+
</style>
|
| 101 |
+
<!-- END: Custom Styles -->
|
| 102 |
+
</head>
|
| 103 |
+
<body class="text-slate-200 antialiased font-sans">
|
| 104 |
+
<!-- BEGIN: NavHeader -->
|
| 105 |
+
<header class="fixed top-0 w-full z-50 px-6 py-4 flex items-center justify-between glass-panel border-t-0 border-x-0">
|
| 106 |
+
<div class="flex items-center gap-3">
|
| 107 |
+
<div class="flex items-center justify-center size-10 rounded-lg bg-gradient-to-br from-primary to-primary-dark text-charcoal">
|
| 108 |
+
<span class="material-symbols-outlined font-bold">all_inclusive</span>
|
| 109 |
+
</div>
|
| 110 |
+
<div>
|
| 111 |
+
<h1 class="text-white font-extrabold text-lg leading-none">StyleSync AI</h1>
|
| 112 |
+
<p class="text-[10px] text-primary uppercase tracking-[0.2em] font-semibold">Enterprise Edition</p>
|
| 113 |
+
</div>
|
| 114 |
+
</div>
|
| 115 |
+
<nav class="hidden md:flex items-center gap-8">
|
| 116 |
+
<a class="text-sm font-medium hover:text-primary transition-colors" href="#features">Features</a>
|
| 117 |
+
<a class="text-sm font-medium hover:text-primary transition-colors" href="#architecture">Architecture</a>
|
| 118 |
+
<a class="text-sm font-medium hover:text-primary transition-colors" href="#tech-stack">Tech Stack</a>
|
| 119 |
+
<a class="text-sm font-medium hover:text-primary transition-colors" href="#legal">Legal</a>
|
| 120 |
+
</nav>
|
| 121 |
+
<div class="flex items-center gap-4">
|
| 122 |
+
<button class="px-5 py-2.5 rounded-full text-sm font-bold bg-white/5 hover:bg-white/10 border border-white/10 transition-all">
|
| 123 |
+
Sign In
|
| 124 |
+
</button>
|
| 125 |
+
</div>
|
| 126 |
+
</header>
|
| 127 |
+
<!-- END: NavHeader -->
|
| 128 |
+
<main class="pt-24">
|
| 129 |
+
<!-- BEGIN: Hero Section -->
|
| 130 |
+
<section class="relative min-h-[90vh] flex items-center px-6 lg:px-24 overflow-hidden" id="hero">
|
| 131 |
+
<div class="grid lg:grid-cols-2 gap-12 items-center w-full">
|
| 132 |
+
<div class="z-10">
|
| 133 |
+
<span class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-widest mb-6">
|
| 134 |
+
<span class="relative flex h-2 w-2">
|
| 135 |
+
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
| 136 |
+
<span class="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
|
| 137 |
+
</span>
|
| 138 |
+
Now in Private Beta
|
| 139 |
+
</span>
|
| 140 |
+
<h2 class="text-5xl lg:text-7xl font-extrabold text-white leading-[1.1] mb-6 tracking-tight">
|
| 141 |
+
Autonomous <br/>
|
| 142 |
+
<span class="text-transparent bg-clip-text bg-gradient-to-r from-primary to-primary-dark">E-Commerce</span> <br/>
|
| 143 |
+
Intelligence
|
| 144 |
+
</h2>
|
| 145 |
+
<p class="text-lg text-slate-400 mb-10 max-w-xl leading-relaxed">
|
| 146 |
+
A multi-agent AI pipeline that converts raw product imagery into enterprise-grade, SEO-optimized e-commerce catalogs in seconds.
|
| 147 |
+
</p>
|
| 148 |
+
<div class="flex flex-wrap gap-4">
|
| 149 |
+
<a class="flex items-center gap-3 px-8 py-4 bg-primary hover:bg-primary-dark text-charcoal rounded-xl font-bold transition-all transform hover:scale-105 shadow-[0_0_30px_rgba(251,191,36,0.2)]" href="/dashboard">
|
| 150 |
+
<span class="material-symbols-outlined">rocket_launch</span>
|
| 151 |
+
Enter Dashboard
|
| 152 |
+
</a>
|
| 153 |
+
<button class="flex items-center gap-3 px-8 py-4 bg-white/5 hover:bg-white/10 border border-white/10 rounded-xl font-bold transition-all">
|
| 154 |
+
Watch Demo
|
| 155 |
+
</button>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
<!-- Visual: Floating Glassmorphic Product Conversion -->
|
| 159 |
+
<div class="relative flex justify-center items-center">
|
| 160 |
+
<div class="absolute w-[120%] h-[120%] bg-primary/5 rounded-full blur-[100px] -z-10"></div>
|
| 161 |
+
<div class="relative animate-float">
|
| 162 |
+
<!-- Glass Mockup for Conversion -->
|
| 163 |
+
<div class="w-72 h-96 glass-panel rounded-[2rem] p-6 shadow-2xl relative">
|
| 164 |
+
<div class="w-full h-48 rounded-2xl bg-white/5 border border-white/10 overflow-hidden mb-4 flex items-center justify-center">
|
| 165 |
+
<span class="material-symbols-outlined text-6xl text-white/20">image</span>
|
| 166 |
+
</div>
|
| 167 |
+
<div class="space-y-3">
|
| 168 |
+
<div class="h-4 w-3/4 bg-white/10 rounded"></div>
|
| 169 |
+
<div class="h-4 w-1/2 bg-white/10 rounded"></div>
|
| 170 |
+
<div class="pt-4 grid grid-cols-3 gap-2">
|
| 171 |
+
<div class="h-8 rounded bg-primary/20 border border-primary/30"></div>
|
| 172 |
+
<div class="h-8 rounded bg-white/5 border border-white/10"></div>
|
| 173 |
+
<div class="h-8 rounded bg-white/5 border border-white/10"></div>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
<!-- JSON Output Popover -->
|
| 177 |
+
<div class="absolute -right-12 -bottom-10 w-64 glass-panel rounded-2xl p-4 border-primary/30 shadow-2xl scale-90 lg:scale-100">
|
| 178 |
+
<div class="flex items-center gap-2 mb-2">
|
| 179 |
+
<div class="size-2 rounded-full bg-green-500"></div>
|
| 180 |
+
<span class="text-[10px] font-mono text-slate-400">output.json</span>
|
| 181 |
+
</div>
|
| 182 |
+
<pre class="text-[10px] font-mono text-primary/80"><code>{
|
| 183 |
+
"sku": "MF-9902",
|
| 184 |
+
"name": "AeroShell X",
|
| 185 |
+
"tags": ["SEO", "Hiker"]
|
| 186 |
+
}</code></pre>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
</section>
|
| 193 |
+
<!-- END: Hero Section -->
|
| 194 |
+
<!-- BEGIN: Core Value Prop -->
|
| 195 |
+
<section class="py-24 px-6 lg:px-24" id="features">
|
| 196 |
+
<div class="max-w-4xl mx-auto text-center mb-16">
|
| 197 |
+
<h3 class="text-3xl lg:text-5xl font-bold text-white mb-6">Zero Manual Bottlenecks</h3>
|
| 198 |
+
<p class="text-slate-400 text-lg">Shift from human-in-the-loop to autonomous excellence. Our pipeline ensures your inventory is ready for global distribution instantly.</p>
|
| 199 |
+
</div>
|
| 200 |
+
<div class="grid md:grid-cols-3 gap-8">
|
| 201 |
+
<div class="p-8 rounded-3xl glass-panel glass-card-hover text-center">
|
| 202 |
+
<div class="size-14 mx-auto mb-6 rounded-2xl bg-primary/10 flex items-center justify-center">
|
| 203 |
+
<span class="material-symbols-outlined text-primary text-3xl">bolt</span>
|
| 204 |
+
</div>
|
| 205 |
+
<h4 class="text-xl font-bold text-white mb-3">Instant Delivery</h4>
|
| 206 |
+
<p class="text-slate-400 text-sm leading-relaxed">Go from product photo to live Shopify entry in under 15 seconds with zero latency agents.</p>
|
| 207 |
+
</div>
|
| 208 |
+
<div class="p-8 rounded-3xl glass-panel glass-card-hover text-center border-primary/20 bg-glass-amber">
|
| 209 |
+
<div class="size-14 mx-auto mb-6 rounded-2xl bg-primary flex items-center justify-center">
|
| 210 |
+
<span class="material-symbols-outlined text-charcoal text-3xl">verified</span>
|
| 211 |
+
</div>
|
| 212 |
+
<h4 class="text-xl font-bold text-white mb-3">0% Human Error</h4>
|
| 213 |
+
<p class="text-slate-400 text-sm leading-relaxed">Automated cross-referencing against technical specs ensures 100% attribute accuracy.</p>
|
| 214 |
+
</div>
|
| 215 |
+
<div class="p-8 rounded-3xl glass-panel glass-card-hover text-center">
|
| 216 |
+
<div class="size-14 mx-auto mb-6 rounded-2xl bg-primary/10 flex items-center justify-center">
|
| 217 |
+
<span class="material-symbols-outlined text-primary text-3xl">trending_up</span>
|
| 218 |
+
</div>
|
| 219 |
+
<h4 class="text-xl font-bold text-white mb-3">SEO Native</h4>
|
| 220 |
+
<p class="text-slate-400 text-sm leading-relaxed">Every description is engineered for search algorithms using real-time market sentiment data.</p>
|
| 221 |
+
</div>
|
| 222 |
+
</div>
|
| 223 |
+
</section>
|
| 224 |
+
<!-- END: Core Value Prop -->
|
| 225 |
+
<!-- BEGIN: Multi-Agent Pipeline -->
|
| 226 |
+
<section class="py-24 px-6 lg:px-24 bg-white/2" id="architecture">
|
| 227 |
+
<div class="flex flex-col lg:flex-row gap-16 items-start">
|
| 228 |
+
<div class="lg:w-1/3 sticky top-32">
|
| 229 |
+
<h3 class="text-4xl font-bold text-white mb-6">Multi-Agent <br/>Pipeline</h3>
|
| 230 |
+
<p class="text-slate-400 leading-relaxed mb-8">A sophisticated orchestration of specialized AI agents working in harmony to deliver high-fidelity catalog data.</p>
|
| 231 |
+
<div class="p-6 rounded-2xl border border-white/5 bg-white/2">
|
| 232 |
+
<div class="flex items-center gap-4 text-sm text-slate-300">
|
| 233 |
+
<span class="material-symbols-outlined text-green-500">memory</span>
|
| 234 |
+
Orchestration Layer: FastAPI + LangGraph
|
| 235 |
+
</div>
|
| 236 |
+
</div>
|
| 237 |
+
</div>
|
| 238 |
+
<div class="lg:w-2/3 grid gap-6">
|
| 239 |
+
<!-- Card 1 -->
|
| 240 |
+
<div class="reveal-card group p-8 rounded-3xl glass-panel flex gap-6 items-start hover:border-primary/40 transition-all">
|
| 241 |
+
<div class="size-16 rounded-2xl bg-indigo-500/20 flex-shrink-0 flex items-center justify-center">
|
| 242 |
+
<span class="material-symbols-outlined text-indigo-400 text-3xl">visibility</span>
|
| 243 |
+
</div>
|
| 244 |
+
<div>
|
| 245 |
+
<div class="flex items-center justify-between mb-2">
|
| 246 |
+
<h4 class="text-xl font-bold text-white">Visual Analyst</h4>
|
| 247 |
+
<span class="text-[10px] font-mono px-2 py-1 rounded bg-indigo-500/10 text-indigo-400 border border-indigo-500/20">GEMINI 2.5 FLASH</span>
|
| 248 |
+
</div>
|
| 249 |
+
<p class="text-slate-400 text-sm leading-relaxed">Performs deep pixel analysis to extract material textures, silhouettes, and hardware details directly from raw imagery.</p>
|
| 250 |
+
</div>
|
| 251 |
+
</div>
|
| 252 |
+
<!-- Card 2 -->
|
| 253 |
+
<div class="reveal-card group p-8 rounded-3xl glass-panel flex gap-6 items-start hover:border-primary/40 transition-all">
|
| 254 |
+
<div class="size-16 rounded-2xl bg-cyan-500/20 flex-shrink-0 flex items-center justify-center">
|
| 255 |
+
<span class="material-symbols-outlined text-cyan-400 text-3xl">database</span>
|
| 256 |
+
</div>
|
| 257 |
+
<div>
|
| 258 |
+
<div class="flex items-center justify-between mb-2">
|
| 259 |
+
<h4 class="text-xl font-bold text-white">Semantic Memory</h4>
|
| 260 |
+
<span class="text-[10px] font-mono px-2 py-1 rounded bg-cyan-500/10 text-cyan-400 border border-cyan-500/20">PINECONE RAG</span>
|
| 261 |
+
</div>
|
| 262 |
+
<p class="text-slate-400 text-sm leading-relaxed">Queries vector databases for historical product context, brand voice guidelines, and competitive SEO keywords.</p>
|
| 263 |
+
</div>
|
| 264 |
+
</div>
|
| 265 |
+
<!-- Card 3 -->
|
| 266 |
+
<div class="reveal-card group p-8 rounded-3xl glass-panel flex gap-6 items-start hover:border-primary/40 transition-all">
|
| 267 |
+
<div class="size-16 rounded-2xl bg-primary/20 flex-shrink-0 flex items-center justify-center">
|
| 268 |
+
<span class="material-symbols-outlined text-primary text-3xl">edit_note</span>
|
| 269 |
+
</div>
|
| 270 |
+
<div>
|
| 271 |
+
<div class="flex items-center justify-between mb-2">
|
| 272 |
+
<h4 class="text-xl font-bold text-white">Writer Agent</h4>
|
| 273 |
+
<span class="text-[10px] font-mono px-2 py-1 rounded bg-primary/10 text-primary border border-primary/20">LLAMA 3.3 70B (GROQ)</span>
|
| 274 |
+
</div>
|
| 275 |
+
<p class="text-slate-400 text-sm leading-relaxed">Synthesizes visual and semantic data into high-converting product descriptions, bullet points, and metadata.</p>
|
| 276 |
+
</div>
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
</section>
|
| 281 |
+
<!-- END: Multi-Agent Pipeline -->
|
| 282 |
+
<!-- BEGIN: Tech Stack Table -->
|
| 283 |
+
<section class="py-24 px-6 lg:px-24" id="tech-stack">
|
| 284 |
+
<div class="glass-panel rounded-3xl overflow-hidden border-white/5 shadow-2xl">
|
| 285 |
+
<div class="px-8 py-6 border-b border-white/5 bg-white/2 flex items-center justify-between">
|
| 286 |
+
<h3 class="text-xl font-bold text-white">Technical Infrastructure</h3>
|
| 287 |
+
<span class="text-xs text-slate-500 font-mono tracking-widest">SYSTEM_MANIFEST.V2</span>
|
| 288 |
+
</div>
|
| 289 |
+
<div class="overflow-x-auto">
|
| 290 |
+
<table class="w-full text-left">
|
| 291 |
+
<thead>
|
| 292 |
+
<tr class="bg-white/2 text-[10px] uppercase tracking-widest text-slate-500 border-b border-white/5">
|
| 293 |
+
<th class="px-8 py-4 font-bold">Component</th>
|
| 294 |
+
<th class="px-8 py-4 font-bold">Technology</th>
|
| 295 |
+
<th class="px-8 py-4 font-bold">Purpose</th>
|
| 296 |
+
<th class="px-8 py-4 font-bold">Status</th>
|
| 297 |
+
</tr>
|
| 298 |
+
</thead>
|
| 299 |
+
<tbody class="text-sm">
|
| 300 |
+
<tr class="border-b border-white/5 hover:bg-white/2 transition-colors">
|
| 301 |
+
<td class="px-8 py-6 font-bold text-white">Core Backend</td>
|
| 302 |
+
<td class="px-8 py-6 text-slate-400">Python 3.10+ / FastAPI</td>
|
| 303 |
+
<td class="px-8 py-6 text-slate-400">Asynchronous high-throughput processing</td>
|
| 304 |
+
<td class="px-8 py-6"><span class="px-2 py-1 rounded-full bg-green-500/10 text-green-500 text-[10px] font-bold">STABLE</span></td>
|
| 305 |
+
</tr>
|
| 306 |
+
<tr class="border-b border-white/5 hover:bg-white/2 transition-colors">
|
| 307 |
+
<td class="px-8 py-6 font-bold text-white">Agent Framework</td>
|
| 308 |
+
<td class="px-8 py-6 text-slate-400">Google GenAI / Groq SDK</td>
|
| 309 |
+
<td class="px-8 py-6 text-slate-400">Multi-LLM orchestration & execution</td>
|
| 310 |
+
<td class="px-8 py-6"><span class="px-2 py-1 rounded-full bg-green-500/10 text-green-500 text-[10px] font-bold">STABLE</span></td>
|
| 311 |
+
</tr>
|
| 312 |
+
<tr class="border-b border-white/5 hover:bg-white/2 transition-colors">
|
| 313 |
+
<td class="px-8 py-6 font-bold text-white">Knowledge Base</td>
|
| 314 |
+
<td class="px-8 py-6 text-slate-400">Pinecone Vector DB</td>
|
| 315 |
+
<td class="px-8 py-6 text-slate-400">Retrieval Augmented Generation (RAG)</td>
|
| 316 |
+
<td class="px-8 py-6"><span class="px-2 py-1 rounded-full bg-green-500/10 text-green-500 text-[10px] font-bold">STABLE</span></td>
|
| 317 |
+
</tr>
|
| 318 |
+
<tr class="hover:bg-white/2 transition-colors">
|
| 319 |
+
<td class="px-8 py-6 font-bold text-white">Deployment</td>
|
| 320 |
+
<td class="px-8 py-6 text-slate-400">Docker / Kubernetes</td>
|
| 321 |
+
<td class="px-8 py-6 text-slate-400">Containerized auto-scaling architecture</td>
|
| 322 |
+
<td class="px-8 py-6"><span class="px-2 py-1 rounded-full bg-green-500/10 text-green-500 text-[10px] font-bold">STABLE</span></td>
|
| 323 |
+
</tr>
|
| 324 |
+
</tbody>
|
| 325 |
+
</table>
|
| 326 |
+
</div>
|
| 327 |
+
</div>
|
| 328 |
+
</section>
|
| 329 |
+
<!-- END: Tech Stack Table -->
|
| 330 |
+
<!-- BEGIN: Final CTA -->
|
| 331 |
+
<section class="py-24 px-6 text-center">
|
| 332 |
+
<div class="max-w-3xl mx-auto glass-panel p-12 lg:p-20 rounded-[3rem] border-primary/20 relative overflow-hidden">
|
| 333 |
+
<div class="absolute -top-24 -right-24 size-64 bg-primary/10 rounded-full blur-3xl"></div>
|
| 334 |
+
<h3 class="text-4xl lg:text-6xl font-extrabold text-white mb-8 tracking-tight">Scale Your Catalog To <br/>Infinite.</h3>
|
| 335 |
+
<p class="text-slate-400 text-lg mb-10 leading-relaxed">Join enterprise retail leaders who are automating their supply chain intelligence with StyleSync AI.</p>
|
| 336 |
+
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
| 337 |
+
<input class="w-full sm:w-auto min-w-[300px] bg-charcoal border border-white/10 rounded-xl px-6 py-4 focus:ring-2 focus:ring-primary/50 outline-none transition-all text-white" placeholder="Enter corporate email" type="email"/>
|
| 338 |
+
<button class="w-full sm:w-auto px-8 py-4 bg-primary hover:bg-primary-dark text-charcoal rounded-xl font-bold transition-all transform hover:scale-105">
|
| 339 |
+
Request Access
|
| 340 |
+
</button>
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
</section>
|
| 344 |
+
<!-- END: Final CTA -->
|
| 345 |
+
</main>
|
| 346 |
+
<!-- BEGIN: Footer -->
|
| 347 |
+
<footer class="mt-12 border-t border-white/5 bg-charcoal py-12 px-6 lg:px-24" id="legal">
|
| 348 |
+
<div class="flex flex-col md:flex-row justify-between items-start gap-12 mb-12">
|
| 349 |
+
<div class="max-w-xs">
|
| 350 |
+
<div class="flex items-center gap-3 mb-6">
|
| 351 |
+
<div class="flex items-center justify-center size-8 rounded-lg bg-primary text-charcoal">
|
| 352 |
+
<span class="material-symbols-outlined text-sm font-bold">all_inclusive</span>
|
| 353 |
+
</div>
|
| 354 |
+
<span class="text-white font-bold tracking-tight">StyleSync AI</span>
|
| 355 |
+
</div>
|
| 356 |
+
<p class="text-xs text-slate-500 leading-relaxed">
|
| 357 |
+
Designed and engineered for high-performance e-commerce logistics.
|
| 358 |
+
</p>
|
| 359 |
+
</div>
|
| 360 |
+
<div class="grid grid-cols-2 md:grid-cols-3 gap-12">
|
| 361 |
+
<div>
|
| 362 |
+
<h5 class="text-white text-xs font-bold uppercase tracking-widest mb-6">Platform</h5>
|
| 363 |
+
<ul class="space-y-4 text-xs text-slate-400">
|
| 364 |
+
<li><a class="hover:text-primary" href="#">Agent Dashboard</a></li>
|
| 365 |
+
<li><a class="hover:text-primary" href="#">API Documentation</a></li>
|
| 366 |
+
<li><a class="hover:text-primary" href="#">Pricing Plans</a></li>
|
| 367 |
+
</ul>
|
| 368 |
+
</div>
|
| 369 |
+
<div>
|
| 370 |
+
<h5 class="text-white text-xs font-bold uppercase tracking-widest mb-6">Company</h5>
|
| 371 |
+
<ul class="space-y-4 text-xs text-slate-400">
|
| 372 |
+
<li><a class="hover:text-primary" href="#">About Us</a></li>
|
| 373 |
+
<li><a class="hover:text-primary" href="#">Careers</a></li>
|
| 374 |
+
<li><a class="hover:text-primary" href="#">Contact</a></li>
|
| 375 |
+
</ul>
|
| 376 |
+
</div>
|
| 377 |
+
<div>
|
| 378 |
+
<h5 class="text-white text-xs font-bold uppercase tracking-widest mb-6">Security</h5>
|
| 379 |
+
<ul class="space-y-4 text-xs text-slate-400">
|
| 380 |
+
<li><a class="hover:text-primary" href="#">Privacy Policy</a></li>
|
| 381 |
+
<li><a class="hover:text-primary" href="#">Terms of Service</a></li>
|
| 382 |
+
<li><a class="hover:text-primary" href="#">SLA</a></li>
|
| 383 |
+
</ul>
|
| 384 |
+
</div>
|
| 385 |
+
</div>
|
| 386 |
+
</div>
|
| 387 |
+
<div class="pt-8 border-t border-white/5 flex flex-col md:flex-row justify-between items-center gap-4">
|
| 388 |
+
<p class="text-[10px] text-slate-600">
|
| 389 |
+
© 2026 StyleSync AI. All Rights Reserved.
|
| 390 |
+
</p>
|
| 391 |
+
<div class="flex gap-6">
|
| 392 |
+
<span class="material-symbols-outlined text-slate-600 hover:text-primary cursor-pointer">public</span>
|
| 393 |
+
<span class="material-symbols-outlined text-slate-600 hover:text-primary cursor-pointer">lock</span>
|
| 394 |
+
<span class="material-symbols-outlined text-slate-600 hover:text-primary cursor-pointer">hub</span>
|
| 395 |
+
</div>
|
| 396 |
+
</div>
|
| 397 |
+
</footer>
|
| 398 |
+
<!-- END: Footer -->
|
| 399 |
+
<!-- BEGIN: Animation Script -->
|
| 400 |
+
<script id="scroll-reveal-logic">
|
| 401 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 402 |
+
const observerOptions = {
|
| 403 |
+
root: null,
|
| 404 |
+
threshold: 0.15,
|
| 405 |
+
rootMargin: '0px 0px -50px 0px'
|
| 406 |
+
};
|
| 407 |
+
|
| 408 |
+
const observer = new IntersectionObserver((entries) => {
|
| 409 |
+
entries.forEach((entry, index) => {
|
| 410 |
+
if (entry.isIntersecting) {
|
| 411 |
+
// Stagger the animation of the cards
|
| 412 |
+
setTimeout(() => {
|
| 413 |
+
entry.target.classList.add('is-visible');
|
| 414 |
+
}, index * 100);
|
| 415 |
+
// Once visible, stop observing this specific element
|
| 416 |
+
observer.unobserve(entry.target);
|
| 417 |
+
}
|
| 418 |
+
});
|
| 419 |
+
}, observerOptions);
|
| 420 |
+
|
| 421 |
+
const revealCards = document.querySelectorAll('.reveal-card');
|
| 422 |
+
revealCards.forEach(card => observer.observe(card));
|
| 423 |
+
});
|
| 424 |
+
</script>
|
| 425 |
+
<!-- END: Animation Script -->
|
| 426 |
+
</body></html>
|
launcher.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import time
|
| 3 |
+
import webbrowser
|
| 4 |
+
import os
|
| 5 |
+
import urllib.request
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
def is_server_ready(url):
|
| 9 |
+
try:
|
| 10 |
+
with urllib.request.urlopen(url) as response:
|
| 11 |
+
return response.getcode() == 200
|
| 12 |
+
except Exception:
|
| 13 |
+
return False
|
| 14 |
+
|
| 15 |
+
def main():
|
| 16 |
+
print("🚀 Starting Engine...")
|
| 17 |
+
|
| 18 |
+
# Definition of the server command
|
| 19 |
+
# Using sys.executable to ensure we use the same python interpreter
|
| 20 |
+
server_command = [sys.executable, "-m", "uvicorn", "main:app", "--reload"]
|
| 21 |
+
|
| 22 |
+
# Start the server as a subprocess
|
| 23 |
+
process = subprocess.Popen(server_command, cwd=os.getcwd())
|
| 24 |
+
|
| 25 |
+
server_url = "http://localhost:8000"
|
| 26 |
+
|
| 27 |
+
# Poll for server availability
|
| 28 |
+
try:
|
| 29 |
+
while not is_server_ready(server_url):
|
| 30 |
+
time.sleep(1)
|
| 31 |
+
|
| 32 |
+
print("✅ Dashboard Launched")
|
| 33 |
+
|
| 34 |
+
# Open the dashboard in the default web browser
|
| 35 |
+
dashboard_path = os.path.abspath("dashboard.html")
|
| 36 |
+
webbrowser.open(f"file:///{dashboard_path}")
|
| 37 |
+
|
| 38 |
+
# Keep the script running to maintain the server process
|
| 39 |
+
process.wait()
|
| 40 |
+
|
| 41 |
+
except KeyboardInterrupt:
|
| 42 |
+
print("\n🛑 Shutting down...")
|
| 43 |
+
process.terminate()
|
| 44 |
+
process.wait()
|
| 45 |
+
|
| 46 |
+
if __name__ == "__main__":
|
| 47 |
+
main()
|
legacy/trend_spotter.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import google.generativeai as genai
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
class TrendSpotter:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.api_key = os.getenv("GEMINI_API_KEY")
|
| 11 |
+
if self.api_key:
|
| 12 |
+
genai.configure(api_key=self.api_key)
|
| 13 |
+
self.model = genai.GenerativeModel('gemini-flash-latest')
|
| 14 |
+
self.has_key = True
|
| 15 |
+
else:
|
| 16 |
+
self.model = None
|
| 17 |
+
self.has_key = False
|
| 18 |
+
|
| 19 |
+
def get_trends(self, niche: str):
|
| 20 |
+
if not self.has_key:
|
| 21 |
+
print("⚠️ No API Key found, using mock data")
|
| 22 |
+
return ['Retro Cat Mom', 'Pixel Art Kitty', 'Cattitude']
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
prompt = f"Generate 5 short, witty, and viral t-shirt text concepts for the niche: {niche}. Return strictly a JSON list of strings."
|
| 26 |
+
response = self.model.generate_content(prompt)
|
| 27 |
+
|
| 28 |
+
content = response.text
|
| 29 |
+
# Clean up markdown formatting if present
|
| 30 |
+
if "```json" in content:
|
| 31 |
+
content = content.replace("```json", "").replace("```", "")
|
| 32 |
+
elif "```" in content:
|
| 33 |
+
content = content.replace("```", "")
|
| 34 |
+
|
| 35 |
+
try:
|
| 36 |
+
trends = json.loads(content)
|
| 37 |
+
if isinstance(trends, list):
|
| 38 |
+
return trends
|
| 39 |
+
else:
|
| 40 |
+
return [content]
|
| 41 |
+
except json.JSONDecodeError:
|
| 42 |
+
return [content]
|
| 43 |
+
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print(f"❌ Error calling Gemini: {e}")
|
| 46 |
+
return ['Retro Cat Mom', 'Pixel Art Kitty', 'Cattitude']
|