Spaces:
Running
Running
Commit
·
0fc3485
0
Parent(s):
Deploy: StyleSync AI Phase 5 (Fixes Applied)
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitignore +23 -0
- Dockerfile +21 -0
- LICENSE +21 -0
- README.md +23 -0
- add_license.py +64 -0
- agents/__init__.py +0 -0
- agents/manager.py +46 -0
- agents/memory_agent.py +83 -0
- agents/visual_analyst.py +59 -0
- agents/writer_agent.py +81 -0
- check_basic.py +20 -0
- check_gemini.py +26 -0
- check_gemini_clean.py +24 -0
- check_gemini_models.py +15 -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
- code.html +203 -0
- connect_n8n.py +126 -0
- create_dockerfile.py +50 -0
- dashboard.html +462 -0
- final_deploy_push.py +20 -0
- final_upload.py +61 -0
- finalize_ui.py +34 -0
- fix_dashboard_routing.py +87 -0
- fix_google_key.py +48 -0
- fix_readme.py +42 -0
- fix_vision_core.py +90 -0
- install_gh.py +33 -0
- launcher.py +47 -0
- legacy/trend_spotter.py +46 -0
- legacy/visionary.py +33 -0
- main.py +90 -0
- requirements.txt +14 -0
- restore_full_brain.py +122 -0
- results/merch_batch_Coffee_20251214_052033.csv +6 -0
- results/merch_batch_Coffee_20251214_052441.csv +6 -0
- results/merch_batch_Coffee_20251214_052609.csv +6 -0
- results/merch_batch_Coffee_20251214_052808.csv +4 -0
- scan_vision_models.py +44 -0
.gitignore
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
Dockerfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use official Python 3.10 image (Fixes 3.9 deprecation warnings)
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# Install git (required for some dependencies)
|
| 7 |
+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
| 8 |
+
|
| 9 |
+
# Copy requirements and install
|
| 10 |
+
COPY requirements.txt .
|
| 11 |
+
RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt
|
| 12 |
+
|
| 13 |
+
# Copy application files
|
| 14 |
+
COPY . .
|
| 15 |
+
|
| 16 |
+
# Fix permissions for Hugging Face Spaces
|
| 17 |
+
RUN mkdir -p /tmp/home
|
| 18 |
+
ENV HOME=/tmp/home
|
| 19 |
+
|
| 20 |
+
EXPOSE 7860
|
| 21 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Bhishaj
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: StyleSync AI
|
| 3 |
+
emoji: 🛍️
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# StyleSync AI
|
| 11 |
+
|
| 12 |
+
**StyleSync AI** is an autonomous design & merchandising agent built for the Antigravity IDE.
|
| 13 |
+
|
| 14 |
+
## ⚡ Stack
|
| 15 |
+
* **Core:** FastAPI & Python 3.9
|
| 16 |
+
* **Vision:** Gemini 1.5 Flash
|
| 17 |
+
* **Copy:** Llama 3 (Groq)
|
| 18 |
+
* **Memory:** Pinecone
|
| 19 |
+
|
| 20 |
+
## 🚀 Local Run
|
| 21 |
+
```bash
|
| 22 |
+
pip install -r requirements.txt
|
| 23 |
+
python main.py
|
add_license.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def run_command(command):
|
| 5 |
+
try:
|
| 6 |
+
# shell=True is often required on Windows for some commands/environments
|
| 7 |
+
print(f"Running: {command}")
|
| 8 |
+
result = subprocess.run(command, check=True, shell=True, capture_output=True, text=True)
|
| 9 |
+
print(result.stdout)
|
| 10 |
+
except subprocess.CalledProcessError as e:
|
| 11 |
+
print(f"Error running: {command}")
|
| 12 |
+
print(e.stderr)
|
| 13 |
+
# We don't exit here to allow attempting subsequent commands or user debugging if one fails,
|
| 14 |
+
# though for git flow it usually makes sense to stop.
|
| 15 |
+
# Given the instruction is a sequence, we should probably stop if add/commit fails.
|
| 16 |
+
exit(1)
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
license_text = """MIT License
|
| 20 |
+
|
| 21 |
+
Copyright (c) 2025 Bhishaj
|
| 22 |
+
|
| 23 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 24 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 25 |
+
in the Software without restriction, including without limitation the rights
|
| 26 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 27 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 28 |
+
furnished to do so, subject to the following conditions:
|
| 29 |
+
|
| 30 |
+
The above copyright notice and this permission notice shall be included in all
|
| 31 |
+
copies or substantial portions of the Software.
|
| 32 |
+
|
| 33 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 34 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 35 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 36 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 37 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 38 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 39 |
+
SOFTWARE.
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
file_path = "LICENSE"
|
| 43 |
+
|
| 44 |
+
print(f"Creating {file_path}...")
|
| 45 |
+
with open(file_path, "w", encoding="utf-8") as f:
|
| 46 |
+
f.write(license_text)
|
| 47 |
+
print(f"{file_path} created successfully.")
|
| 48 |
+
|
| 49 |
+
print("Running git commands...")
|
| 50 |
+
|
| 51 |
+
# 1. git add LICENSE
|
| 52 |
+
run_command("git add LICENSE")
|
| 53 |
+
|
| 54 |
+
# 2. git commit -m 'Add MIT License'
|
| 55 |
+
run_command("git commit -m \"Add MIT License\"")
|
| 56 |
+
|
| 57 |
+
# 3. git push space clean_deploy:main
|
| 58 |
+
print("Pushing to Hugging Face Space (this might take a few seconds)...")
|
| 59 |
+
run_command("git push space clean_deploy:main")
|
| 60 |
+
|
| 61 |
+
print("Done! License added and pushed to Hugging Face Space.")
|
| 62 |
+
|
| 63 |
+
if __name__ == "__main__":
|
| 64 |
+
main()
|
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,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
from pinecone import Pinecone, ServerlessSpec
|
| 5 |
+
import google.generativeai as genai
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
class MemoryAgent:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
# 1. Configure Gemini (for Embeddings)
|
| 12 |
+
self.gemini_api_key = os.getenv("GEMINI_API_KEY")
|
| 13 |
+
if not self.gemini_api_key:
|
| 14 |
+
print("⚠️ GEMINI_API_KEY missing. Memory Agent will fail.")
|
| 15 |
+
return
|
| 16 |
+
genai.configure(api_key=self.gemini_api_key)
|
| 17 |
+
|
| 18 |
+
# 2. Configure Pinecone (Vector DB)
|
| 19 |
+
self.pinecone_api_key = os.getenv("PINECONE_API_KEY")
|
| 20 |
+
if not self.pinecone_api_key:
|
| 21 |
+
print("⚠️ PINECONE_API_KEY missing. Memory Agent will fail.")
|
| 22 |
+
return
|
| 23 |
+
|
| 24 |
+
self.pc = Pinecone(api_key=self.pinecone_api_key)
|
| 25 |
+
self.index_name = "stylesync-index-v2" # Rebranded Index Name
|
| 26 |
+
|
| 27 |
+
# 3. Create Index if not exists
|
| 28 |
+
existing_indexes = [i.name for i in self.pc.list_indexes()]
|
| 29 |
+
if self.index_name not in existing_indexes:
|
| 30 |
+
print(f"🧠 Creating new memory index: {self.index_name}...")
|
| 31 |
+
try:
|
| 32 |
+
self.pc.create_index(
|
| 33 |
+
name=self.index_name,
|
| 34 |
+
dimension=3072, # Dimension for 'models/gemini-embedding-001'
|
| 35 |
+
metric='cosine',
|
| 36 |
+
spec=ServerlessSpec(cloud='aws', region='us-east-1')
|
| 37 |
+
)
|
| 38 |
+
while not self.pc.describe_index(self.index_name).status['ready']:
|
| 39 |
+
time.sleep(1)
|
| 40 |
+
print("✅ Index created successfully.")
|
| 41 |
+
except Exception as e:
|
| 42 |
+
print(f"❌ Failed to create index: {e}")
|
| 43 |
+
|
| 44 |
+
self.index = self.pc.Index(self.index_name)
|
| 45 |
+
|
| 46 |
+
def _get_embedding(self, text):
|
| 47 |
+
"""Generates vector embeddings using Gemini"""
|
| 48 |
+
try:
|
| 49 |
+
result = genai.embed_content(
|
| 50 |
+
model="models/gemini-embedding-001",
|
| 51 |
+
content=text,
|
| 52 |
+
task_type="retrieval_document"
|
| 53 |
+
)
|
| 54 |
+
return result['embedding']
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f"❌ Embedding Error: {e}")
|
| 57 |
+
return [0.0] * 3072 # Return empty vector on failure
|
| 58 |
+
|
| 59 |
+
def retrieve_keywords(self, query_text: str, top_k=5):
|
| 60 |
+
"""Searches memory for relevant keywords"""
|
| 61 |
+
if not hasattr(self, 'index'): return []
|
| 62 |
+
|
| 63 |
+
print(f"🧠 Searching memory for: '{query_text}'...")
|
| 64 |
+
embedding = self._get_embedding(query_text)
|
| 65 |
+
|
| 66 |
+
try:
|
| 67 |
+
results = self.index.query(
|
| 68 |
+
vector=embedding,
|
| 69 |
+
top_k=top_k,
|
| 70 |
+
include_metadata=True
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
# Extract unique keywords
|
| 74 |
+
keywords = []
|
| 75 |
+
for match in results.matches:
|
| 76 |
+
if match.score > 0.5: # Relevance threshold
|
| 77 |
+
kw_str = match.metadata.get('keywords', '')
|
| 78 |
+
keywords.extend([k.strip() for k in kw_str.split(',')])
|
| 79 |
+
|
| 80 |
+
return list(set(keywords))[:10] # Return top 10 unique
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"❌ Search Error: {e}")
|
| 83 |
+
return []
|
agents/visual_analyst.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 VisualAnalyst:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.api_key = os.getenv("GEMINI_API_KEY")
|
| 11 |
+
if not self.api_key:
|
| 12 |
+
raise ValueError("GEMINI_API_KEY not found")
|
| 13 |
+
|
| 14 |
+
genai.configure(api_key=self.api_key)
|
| 15 |
+
self.model_name = "models/gemini-flash-latest"
|
| 16 |
+
self.model = genai.GenerativeModel(self.model_name)
|
| 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 |
+
# Gemini 1.5 Flash supports JSON response schema, but simple prompting often works well too.
|
| 38 |
+
# We'll stick to prompt engineering for now to match the "Return ONLY valid JSON" instruction.
|
| 39 |
+
response = self.model.generate_content([user_prompt, img])
|
| 40 |
+
|
| 41 |
+
response_text = response.text
|
| 42 |
+
|
| 43 |
+
# Clean up potential markdown code fences
|
| 44 |
+
cleaned_content = response_text
|
| 45 |
+
if "```json" in cleaned_content:
|
| 46 |
+
cleaned_content = cleaned_content.replace("```json", "").replace("```", "")
|
| 47 |
+
elif "```" in cleaned_content:
|
| 48 |
+
cleaned_content = cleaned_content.replace("```", "")
|
| 49 |
+
|
| 50 |
+
return json.loads(cleaned_content.strip())
|
| 51 |
+
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f"❌ Analysis Failed: {e}")
|
| 54 |
+
return {
|
| 55 |
+
"main_color": "Unknown",
|
| 56 |
+
"product_type": "Unknown",
|
| 57 |
+
"design_style": "Unknown",
|
| 58 |
+
"visual_features": [f"Error: {str(e)}"]
|
| 59 |
+
}
|
agents/writer_agent.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import re
|
| 4 |
+
from groq import Groq
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
class WriterAgent:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.api_key = os.getenv("GROQ_API_KEY")
|
| 12 |
+
if not self.api_key:
|
| 13 |
+
print("⚠️ GROQ_API_KEY missing.")
|
| 14 |
+
self.client = None
|
| 15 |
+
else:
|
| 16 |
+
self.client = Groq(api_key=self.api_key)
|
| 17 |
+
self.model = "llama-3.3-70b-versatile"
|
| 18 |
+
|
| 19 |
+
def write_listing(self, visual_data: dict, seo_keywords: list) -> dict:
|
| 20 |
+
if not self.client:
|
| 21 |
+
return {"error": "No API Key"}
|
| 22 |
+
|
| 23 |
+
# Sanitization: Ensure data is clean string
|
| 24 |
+
style = str(visual_data.get('design_style', 'modern'))
|
| 25 |
+
|
| 26 |
+
system_prompt = """You are an expert e-commerce copywriter.
|
| 27 |
+
Your task is to write a high-converting product listing.
|
| 28 |
+
|
| 29 |
+
CRITICAL RULES:
|
| 30 |
+
1. Return ONLY valid JSON.
|
| 31 |
+
2. Do not include markdown formatting (like ```json).
|
| 32 |
+
3. Escape any double quotes inside the description string.
|
| 33 |
+
4. Keep the 'description' to 1-2 paragraphs max.
|
| 34 |
+
|
| 35 |
+
JSON Structure:
|
| 36 |
+
{
|
| 37 |
+
"title": "SEO Optimized Title",
|
| 38 |
+
"description": "Engaging product description...",
|
| 39 |
+
"features": ["Feature 1", "Feature 2", "Feature 3"],
|
| 40 |
+
"price_estimate": "$XX-$XX"
|
| 41 |
+
}
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
user_content = f"""
|
| 45 |
+
PRODUCT DATA:
|
| 46 |
+
{json.dumps(visual_data, indent=2)}
|
| 47 |
+
|
| 48 |
+
KEYWORDS:
|
| 49 |
+
{', '.join(seo_keywords)}
|
| 50 |
+
"""
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
completion = self.client.chat.completions.create(
|
| 54 |
+
model=self.model,
|
| 55 |
+
messages=[
|
| 56 |
+
{"role": "system", "content": system_prompt},
|
| 57 |
+
{"role": "user", "content": user_content}
|
| 58 |
+
],
|
| 59 |
+
# LOWER TEMPERATURE TO 0.3 TO PREVENT SYNTAX ERRORS
|
| 60 |
+
temperature=0.3,
|
| 61 |
+
response_format={"type": "json_object"}
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
content = completion.choices[0].message.content
|
| 65 |
+
|
| 66 |
+
# Extra Safety: Attempt to repair common JSON strings if needed
|
| 67 |
+
try:
|
| 68 |
+
return json.loads(content)
|
| 69 |
+
except json.JSONDecodeError:
|
| 70 |
+
# If strict parsing fails, try to strip markdown
|
| 71 |
+
content = content.replace("```json", "").replace("```", "").strip()
|
| 72 |
+
return json.loads(content)
|
| 73 |
+
|
| 74 |
+
except Exception as e:
|
| 75 |
+
print(f"❌ Writer Error: {e}")
|
| 76 |
+
return {
|
| 77 |
+
"title": "Error Generating Listing",
|
| 78 |
+
"description": "Please try again. The AI model output invalid data.",
|
| 79 |
+
"features": [],
|
| 80 |
+
"error": str(e)
|
| 81 |
+
}
|
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_gemini_models.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
import google.generativeai as genai
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 7 |
+
|
| 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 'embedContent' in m.supported_generation_methods:
|
| 15 |
+
print(f"- {m.name}")
|
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)
|
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 AI AI AI 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 |
+
"emerald-500": "#3b82f6", // Electric Blue
|
| 18 |
+
"emerald-500-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-emerald-500/10 text-emerald-500">
|
| 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 AI AI AI AI</h2>
|
| 59 |
+
<span class="text-xs text-emerald-500/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-emerald-500 hover:bg-emerald-500-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-emerald-500/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-emerald-500/20 flex items-center justify-center transition-colors border border-border-dark group-hover:border-emerald-500/30">
|
| 77 |
+
<span class="material-symbols-outlined text-3xl text-emerald-500">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-emerald-500/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-emerald-500 to-blue-700 hover:from-blue-500 hover:to-emerald-500 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-emerald-500"></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-emerald-500 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-emerald-500">"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-emerald-500">"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-emerald-500">"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-emerald-500">"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-emerald-500">"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-emerald-500">"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-emerald-500">"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-emerald-500">"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,462 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="dark" lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="utf-8" />
|
| 6 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
| 7 |
+
<title>StyleSync AI AI AI AI AI Dashboard</title>
|
| 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=Spline+Sans:wght@300;400;500;600;700&display=swap"
|
| 12 |
+
rel="stylesheet" />
|
| 13 |
+
<link
|
| 14 |
+
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
| 15 |
+
rel="stylesheet" />
|
| 16 |
+
<script id="tailwind-config">
|
| 17 |
+
tailwind.config = {
|
| 18 |
+
darkMode: "class",
|
| 19 |
+
theme: {
|
| 20 |
+
extend: {
|
| 21 |
+
colors: {
|
| 22 |
+
"emerald-500": "#3b82f6", // Electric Blue
|
| 23 |
+
"emerald-500-hover": "#2563eb",
|
| 24 |
+
"secondary": "#6366f1", // Indigo accent
|
| 25 |
+
"background-light": "#f8fafc",
|
| 26 |
+
"background-dark": "#0f172a", // Deep Slate
|
| 27 |
+
"surface-dark": "#1e293b", // Slate 800
|
| 28 |
+
"surface-darker": "#020617", // Slate 950
|
| 29 |
+
"border-dark": "#334155", // Slate 700
|
| 30 |
+
},
|
| 31 |
+
fontFamily: {
|
| 32 |
+
"display": ["Spline Sans", "sans-serif"],
|
| 33 |
+
"mono": ["monospace"]
|
| 34 |
+
},
|
| 35 |
+
borderRadius: { "DEFAULT": "1rem", "lg": "2rem", "xl": "3rem", "full": "9999px" },
|
| 36 |
+
},
|
| 37 |
+
},
|
| 38 |
+
}
|
| 39 |
+
</script>
|
| 40 |
+
<style>
|
| 41 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 42 |
+
width: 8px;
|
| 43 |
+
height: 8px;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 47 |
+
background: #020617;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 51 |
+
background: #334155;
|
| 52 |
+
border-radius: 4px;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
| 56 |
+
background: #3b82f6;
|
| 57 |
+
}
|
| 58 |
+
</style>
|
| 59 |
+
</head>
|
| 60 |
+
|
| 61 |
+
<body
|
| 62 |
+
class="font-display bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased overflow-hidden h-screen flex flex-col">
|
| 63 |
+
<header
|
| 64 |
+
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">
|
| 65 |
+
<div class="flex items-center gap-3 text-white">
|
| 66 |
+
<div class="flex items-center justify-center size-10 rounded-full bg-emerald-500/10 text-emerald-500">
|
| 67 |
+
<span class="material-symbols-outlined text-2xl">all_inclusive</span>
|
| 68 |
+
</div>
|
| 69 |
+
<div>
|
| 70 |
+
<h2 class="text-white text-xl font-bold leading-tight tracking-tight">StyleSync AI AI AI AI AI</h2>
|
| 71 |
+
<span class="text-xs text-emerald-500/60 font-medium uppercase tracking-wider">Enterprise Edition</span>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
<button id="deployBtn"
|
| 75 |
+
class="flex min-w-[84px] cursor-pointer items-center justify-center overflow-hidden rounded-full h-10 px-6 bg-emerald-500 hover:bg-emerald-500-hover transition-colors text-white text-sm font-bold leading-normal tracking-[0.015em]">
|
| 76 |
+
<span class="material-symbols-outlined mr-2 text-lg">rocket_launch</span>
|
| 77 |
+
<span class="truncate">Deploy</span>
|
| 78 |
+
</button>
|
| 79 |
+
</header>
|
| 80 |
+
<main class="flex-1 flex overflow-hidden">
|
| 81 |
+
<div
|
| 82 |
+
class="flex-1 flex flex-col border-r border-border-dark min-w-[400px] overflow-y-auto custom-scrollbar p-8">
|
| 83 |
+
<div class="max-w-2xl w-full mx-auto flex flex-col gap-6 h-full">
|
| 84 |
+
<div class="flex items-center justify-between">
|
| 85 |
+
<h2 class="text-white tracking-light text-[28px] font-bold leading-tight">Input Data</h2>
|
| 86 |
+
<span
|
| 87 |
+
class="bg-surface-dark text-white px-3 py-1 rounded-full text-xs font-medium border border-border-dark">Step
|
| 88 |
+
1 of 2</span>
|
| 89 |
+
</div>
|
| 90 |
+
<div class="flex flex-col flex-1 max-h-[300px] min-h-[200px]">
|
| 91 |
+
<input type="file" id="fileInput" class="hidden" accept=".jpg,.jpeg,.png,.webp" />
|
| 92 |
+
<div id="dropZone"
|
| 93 |
+
class="group relative flex flex-col items-center justify-center gap-4 rounded-xl border-2 border-dashed border-slate-600 hover:border-emerald-500/50 hover:bg-surface-dark transition-all cursor-pointer h-full w-full px-6 py-8">
|
| 94 |
+
<!-- Initial content populated by JS -->
|
| 95 |
+
<div
|
| 96 |
+
class="size-16 rounded-full bg-surface-dark group-hover:bg-emerald-500/20 flex items-center justify-center transition-colors border border-border-dark group-hover:border-emerald-500/30">
|
| 97 |
+
<span class="material-symbols-outlined text-3xl text-emerald-500">cloud_upload</span>
|
| 98 |
+
</div>
|
| 99 |
+
<div class="flex flex-col items-center gap-1">
|
| 100 |
+
<p class="text-white text-lg font-bold leading-tight tracking-tight text-center">Drop
|
| 101 |
+
Product Image Here</p>
|
| 102 |
+
<p class="text-slate-400 text-sm font-normal text-center">Supports JPG, PNG, WEBP</p>
|
| 103 |
+
</div>
|
| 104 |
+
<button id="browseBtn"
|
| 105 |
+
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">
|
| 106 |
+
Browse Files
|
| 107 |
+
</button>
|
| 108 |
+
<div
|
| 109 |
+
class="absolute inset-0 bg-gradient-to-b from-transparent to-black/10 pointer-events-none rounded-xl">
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
<div class="flex flex-col gap-3 flex-1">
|
| 114 |
+
<label class="flex items-center justify-between">
|
| 115 |
+
<span class="text-white text-base font-medium">Raw Product Specs</span>
|
| 116 |
+
<span class="text-xs text-slate-400">JSON or Plain Text</span>
|
| 117 |
+
</label>
|
| 118 |
+
<div class="relative flex-1">
|
| 119 |
+
<textarea
|
| 120 |
+
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-emerald-500/50 border border-border-dark bg-surface-dark p-4 text-base font-normal leading-relaxed font-mono"
|
| 121 |
+
placeholder="Enter fabric details, dimensions, and SKU...
|
| 122 |
+
Example:
|
| 123 |
+
Material: 100% Recycled Polyester
|
| 124 |
+
Fit: Regular
|
| 125 |
+
SKU: JK-2024-WTR"></textarea>
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
+
<div class="pt-2">
|
| 129 |
+
<button id="startBtn"
|
| 130 |
+
class="group relative w-full cursor-pointer overflow-hidden rounded-xl h-16 bg-gradient-to-r from-emerald-500 to-blue-700 hover:from-blue-500 hover:to-emerald-500 transition-all shadow-[0_0_20px_rgba(59,130,246,0.3)]">
|
| 131 |
+
<div class="absolute inset-0 flex items-center justify-center gap-3">
|
| 132 |
+
<span
|
| 133 |
+
class="text-white text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start
|
| 134 |
+
Agent Workflow</span>
|
| 135 |
+
<span
|
| 136 |
+
class="material-symbols-outlined text-white group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
| 137 |
+
</div>
|
| 138 |
+
<div
|
| 139 |
+
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">
|
| 140 |
+
</div>
|
| 141 |
+
</button>
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
<div class="flex-1 flex flex-col bg-surface-darker p-8 overflow-hidden relative">
|
| 146 |
+
<div class="absolute inset-0 opacity-5 pointer-events-none"
|
| 147 |
+
style="background-image: radial-gradient(#64748b 1px, transparent 1px); background-size: 24px 24px;">
|
| 148 |
+
</div>
|
| 149 |
+
<div class="max-w-3xl w-full mx-auto flex flex-col gap-6 h-full relative z-10">
|
| 150 |
+
<div class="flex items-center justify-between">
|
| 151 |
+
<h2 class="text-white tracking-light text-[28px] font-bold leading-tight">Generated Output</h2>
|
| 152 |
+
<div class="flex items-center gap-2">
|
| 153 |
+
<span
|
| 154 |
+
class="bg-surface-dark text-white px-3 py-1 rounded-full text-xs font-medium border border-border-dark">Step
|
| 155 |
+
2 of 2</span>
|
| 156 |
+
<button id="copyBtn"
|
| 157 |
+
class="p-2 hover:bg-surface-dark rounded-lg text-slate-400 hover:text-white transition-colors">
|
| 158 |
+
<span class="material-symbols-outlined text-xl">content_copy</span>
|
| 159 |
+
</button>
|
| 160 |
+
<button id="downloadBtn"
|
| 161 |
+
class="p-2 hover:bg-surface-dark rounded-lg text-slate-400 hover:text-white transition-colors">
|
| 162 |
+
<span class="material-symbols-outlined text-xl">download</span>
|
| 163 |
+
</button>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
| 167 |
+
<div
|
| 168 |
+
class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 169 |
+
<div class="relative size-3">
|
| 170 |
+
<span
|
| 171 |
+
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-purple-500 opacity-75"></span>
|
| 172 |
+
<span class="relative inline-flex rounded-full size-3 bg-purple-500"></span>
|
| 173 |
+
</div>
|
| 174 |
+
<div class="flex flex-col overflow-hidden">
|
| 175 |
+
<span class="text-xs text-slate-400 truncate">Vision Agent</span>
|
| 176 |
+
<span class="text-sm font-bold text-white truncate">Gemini Pro 1.5</span>
|
| 177 |
+
</div>
|
| 178 |
+
<span class="material-symbols-outlined text-purple-500 ml-auto text-lg">visibility</span>
|
| 179 |
+
</div>
|
| 180 |
+
<div
|
| 181 |
+
class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 182 |
+
<div class="relative size-3">
|
| 183 |
+
<span class="relative inline-flex rounded-full size-3 bg-emerald-500"></span>
|
| 184 |
+
</div>
|
| 185 |
+
<div class="flex flex-col overflow-hidden">
|
| 186 |
+
<span class="text-xs text-slate-400 truncate">Reasoning Agent</span>
|
| 187 |
+
<span class="text-sm font-bold text-white truncate">Llama 3 70B</span>
|
| 188 |
+
</div>
|
| 189 |
+
<span class="material-symbols-outlined text-emerald-500 ml-auto text-lg">psychology</span>
|
| 190 |
+
</div>
|
| 191 |
+
<div
|
| 192 |
+
class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 193 |
+
<div class="relative size-3">
|
| 194 |
+
<span class="relative inline-flex rounded-full size-3 bg-cyan-500"></span>
|
| 195 |
+
</div>
|
| 196 |
+
<div class="flex flex-col overflow-hidden">
|
| 197 |
+
<span class="text-xs text-slate-400 truncate">SEO Context</span>
|
| 198 |
+
<span class="text-sm font-bold text-white truncate">Pinecone DB</span>
|
| 199 |
+
</div>
|
| 200 |
+
<span class="material-symbols-outlined text-cyan-500 ml-auto text-lg">database</span>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
<div
|
| 204 |
+
class="flex-1 rounded-xl bg-[#0d1117] border border-border-dark flex flex-col overflow-hidden shadow-2xl">
|
| 205 |
+
<div
|
| 206 |
+
class="flex items-center justify-between px-4 py-2 bg-surface-dark border-b border-border-dark">
|
| 207 |
+
<span class="text-xs font-mono text-slate-400">output.json</span>
|
| 208 |
+
<div class="flex gap-1.5">
|
| 209 |
+
<div class="size-2.5 rounded-full bg-red-500/20"></div>
|
| 210 |
+
<div class="size-2.5 rounded-full bg-yellow-500/20"></div>
|
| 211 |
+
<div class="size-2.5 rounded-full bg-green-500/20"></div>
|
| 212 |
+
</div>
|
| 213 |
+
</div>
|
| 214 |
+
<div class="flex-1 p-4 overflow-auto custom-scrollbar font-mono text-sm leading-6">
|
| 215 |
+
<pre><code id="jsonOutput" class="language-json"><span class="text-slate-500">1</span> <span class="text-yellow-500">{</span>
|
| 216 |
+
<span class="text-slate-500">2</span> <span class="text-emerald-500">"product_analysis"</span><span class="text-white">:</span> <span class="text-yellow-500">{</span>
|
| 217 |
+
<span class="text-slate-500">3</span> <span class="text-emerald-500">"title"</span><span class="text-white">:</span> <span class="text-sky-300">"Apex Terrain All-Weather Performance Jacket"</span><span class="text-white">,</span>
|
| 218 |
+
<span class="text-slate-500">4</span> <span class="text-emerald-500">"category"</span><span class="text-white">:</span> <span class="text-sky-300">"Outerwear / Men's / Technical Shells"</span><span class="text-white">,</span>
|
| 219 |
+
<span class="text-slate-500">5</span> <span class="text-emerald-500">"features"</span><span class="text-white">:</span> <span class="text-yellow-500">[</span>
|
| 220 |
+
<span class="text-slate-500">6</span> <span class="text-sky-300">"Gore-Tex Pro Membrane"</span><span class="text-white">,</span>
|
| 221 |
+
<span class="text-slate-500">7</span> <span class="text-sky-300">"Articulated Sleeves"</span><span class="text-white">,</span>
|
| 222 |
+
<span class="text-slate-500">8</span> <span class="text-sky-300">"Helmet-Compatible Hood"</span>
|
| 223 |
+
<span class="text-slate-500">9</span> <span class="text-yellow-500">]</span><span class="text-white">,</span>
|
| 224 |
+
<span class="text-slate-500">10</span> <span class="text-emerald-500">"seo_tags"</span><span class="text-white">:</span> <span class="text-yellow-500">[</span>
|
| 225 |
+
<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>
|
| 226 |
+
<span class="text-slate-500">12</span> <span class="text-yellow-500">]</span><span class="text-white">,</span>
|
| 227 |
+
<span class="text-slate-500">13</span> <span class="text-emerald-500">"sentiment_score"</span><span class="text-white">:</span> <span class="text-purple-400">0.98</span><span class="text-white">,</span>
|
| 228 |
+
<span class="text-slate-500">14</span> <span class="text-emerald-500">"market_fit"</span><span class="text-white">:</span> <span class="text-sky-300">"High Demand"</span>
|
| 229 |
+
<span class="text-slate-500">15</span> <span class="text-yellow-500">}</span><span class="text-white">,</span>
|
| 230 |
+
<span class="text-slate-500">16</span> <span class="text-emerald-500">"deployment_status"</span><span class="text-white">:</span> <span class="text-sky-300">"Ready"</span>
|
| 231 |
+
<span class="text-slate-500">17</span> <span class="text-yellow-500">}</span></code></pre>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
</main>
|
| 237 |
+
<script>
|
| 238 |
+
tailwind.config.theme.extend.animation = {
|
| 239 |
+
shine: 'shine 1s',
|
| 240 |
+
}
|
| 241 |
+
tailwind.config.theme.extend.keyframes = {
|
| 242 |
+
shine: {
|
| 243 |
+
'100%': { left: '125%' },
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
const dropZone = document.getElementById('dropZone');
|
| 248 |
+
const fileInput = document.getElementById('fileInput');
|
| 249 |
+
const startBtn = document.getElementById('startBtn');
|
| 250 |
+
const jsonOutput = document.getElementById('jsonOutput');
|
| 251 |
+
const deployBtn = document.getElementById('deployBtn');
|
| 252 |
+
const copyBtn = document.getElementById('copyBtn');
|
| 253 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 254 |
+
|
| 255 |
+
let selectedFile = null;
|
| 256 |
+
let isCatalogGenerated = false;
|
| 257 |
+
|
| 258 |
+
// Default DropZone Content Template
|
| 259 |
+
const defaultDropZoneContent = `
|
| 260 |
+
<div class="size-16 rounded-full bg-surface-dark group-hover:bg-emerald-500/20 flex items-center justify-center transition-colors border border-border-dark group-hover:border-emerald-500/30">
|
| 261 |
+
<span class="material-symbols-outlined text-3xl text-emerald-500">cloud_upload</span>
|
| 262 |
+
</div>
|
| 263 |
+
<div class="flex flex-col items-center gap-1">
|
| 264 |
+
<p class="text-white text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 265 |
+
<p class="text-slate-400 text-sm font-normal text-center">Supports JPG, PNG, WEBP</p>
|
| 266 |
+
</div>
|
| 267 |
+
<button id="browseBtn" 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">
|
| 268 |
+
Browse Files
|
| 269 |
+
</button>
|
| 270 |
+
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/10 pointer-events-none rounded-xl"></div>
|
| 271 |
+
`;
|
| 272 |
+
|
| 273 |
+
// Initialize DropZone with listeners
|
| 274 |
+
function initDropZone() {
|
| 275 |
+
const browseBtn = document.getElementById('browseBtn');
|
| 276 |
+
if (browseBtn) {
|
| 277 |
+
browseBtn.addEventListener('click', (e) => {
|
| 278 |
+
e.stopPropagation();
|
| 279 |
+
fileInput.click();
|
| 280 |
+
});
|
| 281 |
+
}
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
// Initial setup
|
| 285 |
+
initDropZone();
|
| 286 |
+
|
| 287 |
+
// File Input Change
|
| 288 |
+
fileInput.addEventListener('change', (e) => {
|
| 289 |
+
if (e.target.files.length > 0) {
|
| 290 |
+
handleFile(e.target.files[0]);
|
| 291 |
+
}
|
| 292 |
+
});
|
| 293 |
+
|
| 294 |
+
// Drag & Drop
|
| 295 |
+
dropZone.addEventListener('dragover', (e) => {
|
| 296 |
+
e.preventDefault();
|
| 297 |
+
dropZone.classList.add('border-emerald-500');
|
| 298 |
+
});
|
| 299 |
+
|
| 300 |
+
dropZone.addEventListener('dragleave', (e) => {
|
| 301 |
+
e.preventDefault();
|
| 302 |
+
dropZone.classList.remove('border-emerald-500');
|
| 303 |
+
});
|
| 304 |
+
|
| 305 |
+
dropZone.addEventListener('drop', (e) => {
|
| 306 |
+
e.preventDefault();
|
| 307 |
+
dropZone.classList.remove('border-emerald-500');
|
| 308 |
+
if (e.dataTransfer.files.length > 0) {
|
| 309 |
+
handleFile(e.dataTransfer.files[0]);
|
| 310 |
+
}
|
| 311 |
+
});
|
| 312 |
+
|
| 313 |
+
function handleFile(file) {
|
| 314 |
+
selectedFile = file;
|
| 315 |
+
// Update UI to Selected State
|
| 316 |
+
dropZone.innerHTML = `
|
| 317 |
+
<div class="flex flex-col items-center justify-center gap-4 z-10">
|
| 318 |
+
<div class="size-16 rounded-full bg-surface-dark flex items-center justify-center border border-border-dark">
|
| 319 |
+
<span class="material-symbols-outlined text-3xl text-emerald-500">description</span>
|
| 320 |
+
</div>
|
| 321 |
+
<div class="flex flex-col items-center gap-1">
|
| 322 |
+
<p class="text-white text-lg font-bold text-center">${file.name}</p>
|
| 323 |
+
<p class="text-slate-400 text-sm text-center">${(file.size / 1024).toFixed(1)} KB</p>
|
| 324 |
+
</div>
|
| 325 |
+
<button id="removeFileBtn" class="mt-2 flex items-center justify-center gap-2 rounded-full h-9 px-4 bg-slate-700 hover:bg-red-500/20 hover:text-red-500 hover:border-red-500/50 border border-transparent transition-all text-white text-xs font-bold">
|
| 326 |
+
<span class="material-symbols-outlined text-base">close</span>
|
| 327 |
+
<span>Remove File</span>
|
| 328 |
+
</button>
|
| 329 |
+
</div>
|
| 330 |
+
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/10 pointer-events-none rounded-xl"></div>
|
| 331 |
+
`;
|
| 332 |
+
|
| 333 |
+
// Add listener to the new remove button
|
| 334 |
+
document.getElementById('removeFileBtn').addEventListener('click', (e) => {
|
| 335 |
+
e.stopPropagation();
|
| 336 |
+
resetUploadUI();
|
| 337 |
+
});
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
function resetUploadUI() {
|
| 341 |
+
selectedFile = null;
|
| 342 |
+
fileInput.value = ""; // Clear input
|
| 343 |
+
dropZone.innerHTML = defaultDropZoneContent;
|
| 344 |
+
initDropZone(); // Re-attach browse listener
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
// Deploy Button
|
| 348 |
+
deployBtn.addEventListener('click', () => {
|
| 349 |
+
if (!isCatalogGenerated) {
|
| 350 |
+
alert("Please generate a catalog first before deploying.");
|
| 351 |
+
return;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
const originalContent = `
|
| 355 |
+
<span class="material-symbols-outlined mr-2 text-lg">rocket_launch</span>
|
| 356 |
+
<span class="truncate">Deploy</span>
|
| 357 |
+
`;
|
| 358 |
+
|
| 359 |
+
deployBtn.disabled = true;
|
| 360 |
+
// Spinner
|
| 361 |
+
deployBtn.innerHTML = `
|
| 362 |
+
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
| 363 |
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
| 364 |
+
<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>
|
| 365 |
+
</svg>
|
| 366 |
+
Processing...
|
| 367 |
+
`;
|
| 368 |
+
|
| 369 |
+
setTimeout(() => {
|
| 370 |
+
alert("Success: Catalog pushed to Shopify!");
|
| 371 |
+
deployBtn.innerHTML = originalContent;
|
| 372 |
+
deployBtn.disabled = false;
|
| 373 |
+
}, 1500);
|
| 374 |
+
});
|
| 375 |
+
|
| 376 |
+
// Copy Button
|
| 377 |
+
copyBtn.addEventListener('click', () => {
|
| 378 |
+
const textToCopy = jsonOutput.innerText;
|
| 379 |
+
navigator.clipboard.writeText(textToCopy).then(() => {
|
| 380 |
+
const originalIcon = copyBtn.innerHTML;
|
| 381 |
+
copyBtn.innerHTML = '<span class="material-symbols-outlined text-xl text-green-500">check</span>';
|
| 382 |
+
setTimeout(() => {
|
| 383 |
+
copyBtn.innerHTML = originalIcon;
|
| 384 |
+
}, 2000);
|
| 385 |
+
});
|
| 386 |
+
});
|
| 387 |
+
|
| 388 |
+
// Download Button
|
| 389 |
+
downloadBtn.addEventListener('click', () => {
|
| 390 |
+
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(jsonOutput.innerText);
|
| 391 |
+
const downloadAnchorNode = document.createElement('a');
|
| 392 |
+
downloadAnchorNode.setAttribute("href", dataStr);
|
| 393 |
+
downloadAnchorNode.setAttribute("download", "merchflow_catalog.json");
|
| 394 |
+
document.body.appendChild(downloadAnchorNode);
|
| 395 |
+
downloadAnchorNode.click();
|
| 396 |
+
downloadAnchorNode.remove();
|
| 397 |
+
});
|
| 398 |
+
|
| 399 |
+
// Start Workflow
|
| 400 |
+
startBtn.addEventListener('click', async () => {
|
| 401 |
+
if (!selectedFile) {
|
| 402 |
+
alert("Please select a file first.");
|
| 403 |
+
return;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
// Show loading state
|
| 407 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-3"><span class="text-white text-lg font-bold tracking-wide">Processing...</span></div>';
|
| 408 |
+
startBtn.disabled = true;
|
| 409 |
+
|
| 410 |
+
const formData = new FormData();
|
| 411 |
+
formData.append('file', selectedFile);
|
| 412 |
+
|
| 413 |
+
try {
|
| 414 |
+
// Determine API URL - expecting localhost for this demo
|
| 415 |
+
const response = await fetch('/generate-catalog', {
|
| 416 |
+
method: 'POST',
|
| 417 |
+
body: formData
|
| 418 |
+
});
|
| 419 |
+
|
| 420 |
+
if (!response.ok) {
|
| 421 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
const data = await response.json();
|
| 425 |
+
|
| 426 |
+
// Format JSON for display
|
| 427 |
+
jsonOutput.textContent = JSON.stringify(data, null, 2);
|
| 428 |
+
jsonOutput.className = "language-json";
|
| 429 |
+
|
| 430 |
+
// Allow deployment
|
| 431 |
+
isCatalogGenerated = true;
|
| 432 |
+
|
| 433 |
+
} catch (error) {
|
| 434 |
+
console.error("Error:", error);
|
| 435 |
+
|
| 436 |
+
// Fallback simulation for demo purposes if backend isn't running
|
| 437 |
+
console.log("Backend failed, simulating success for demo.");
|
| 438 |
+
const demoData = {
|
| 439 |
+
"product_analysis": {
|
| 440 |
+
"title": "Simulated Product Title",
|
| 441 |
+
"category": "Men's Apparel",
|
| 442 |
+
"features": ["Feature 1", "Feature 2"],
|
| 443 |
+
"seo_tags": ["#demo", "#test"],
|
| 444 |
+
"sentiment_score": 0.95
|
| 445 |
+
},
|
| 446 |
+
"status": "Generated (Simulation)"
|
| 447 |
+
};
|
| 448 |
+
jsonOutput.textContent = JSON.stringify(demoData, null, 2);
|
| 449 |
+
isCatalogGenerated = true;
|
| 450 |
+
|
| 451 |
+
// Use this to show error if strictly required:
|
| 452 |
+
// jsonOutput.textContent = JSON.stringify({ error: error.message }, null, 2);
|
| 453 |
+
} finally {
|
| 454 |
+
// Reset button
|
| 455 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-3"><span class="text-white text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span><span class="material-symbols-outlined text-white group-hover:translate-x-1 transition-transform">arrow_forward</span></div><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>';
|
| 456 |
+
startBtn.disabled = false;
|
| 457 |
+
}
|
| 458 |
+
});
|
| 459 |
+
</script>
|
| 460 |
+
</body>
|
| 461 |
+
|
| 462 |
+
</html>
|
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 AI AI AI 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 AI AI AI 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()
|
finalize_ui.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
def polish_dashboard():
|
| 4 |
+
file_path = "dashboard.html"
|
| 5 |
+
if not os.path.exists(file_path):
|
| 6 |
+
print("❌ dashboard.html not found!")
|
| 7 |
+
return
|
| 8 |
+
|
| 9 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 10 |
+
html = f.read()
|
| 11 |
+
|
| 12 |
+
# Force Branding Updates
|
| 13 |
+
replacements = {
|
| 14 |
+
"MerchFlow AI": "StyleSync AI",
|
| 15 |
+
"MerchFlow": "StyleSync",
|
| 16 |
+
"bg-primary": "bg-emerald-600",
|
| 17 |
+
"text-primary": "text-emerald-500",
|
| 18 |
+
"border-primary": "border-emerald-500",
|
| 19 |
+
"from-primary": "from-emerald-500",
|
| 20 |
+
"to-primary": "to-emerald-500",
|
| 21 |
+
"hover:bg-primary": "hover:bg-emerald-500",
|
| 22 |
+
"hover:text-primary": "hover:text-emerald-400"
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
for old, new in replacements.items():
|
| 26 |
+
html = html.replace(old, new)
|
| 27 |
+
|
| 28 |
+
with open(file_path, "w", encoding="utf-8") as f:
|
| 29 |
+
f.write(html)
|
| 30 |
+
|
| 31 |
+
print("✅ Dashboard UI polished: Emerald Green Theme & StyleSync Branding applied.")
|
| 32 |
+
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
polish_dashboard()
|
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_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 AI AI AI AI
|
| 10 |
+
emoji: 🚀
|
| 11 |
+
colorFrom: blue
|
| 12 |
+
colorTo: indigo
|
| 13 |
+
sdk: docker
|
| 14 |
+
pinned: false
|
| 15 |
+
---
|
| 16 |
+
# StyleSync AI AI AI AI AI
|
| 17 |
+
An AI-powered merchandising 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()
|
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()
|
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']
|
legacy/visionary.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
class Visionary:
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.api_key = os.getenv("GEMINI_API_KEY")
|
| 10 |
+
if self.api_key:
|
| 11 |
+
genai.configure(api_key=self.api_key)
|
| 12 |
+
self.model = genai.GenerativeModel('gemini-flash-latest')
|
| 13 |
+
self.has_key = True
|
| 14 |
+
else:
|
| 15 |
+
self.model = None
|
| 16 |
+
self.has_key = False
|
| 17 |
+
|
| 18 |
+
def generate_art_prompt(self, slogan: str, niche: str) -> str:
|
| 19 |
+
if not self.has_key:
|
| 20 |
+
return "Mock visualization: A cute retro cat wearing sunglasses, vector art, pastel colors"
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
system_prompt = (
|
| 24 |
+
f'You are an expert T-shirt Designer. Create a high-quality AI art generation prompt '
|
| 25 |
+
f'for the slogan: "{slogan}" in the niche: "{niche}". '
|
| 26 |
+
f'Specify style (e.g., vector, retro, kawaii), colors, and composition. '
|
| 27 |
+
f'Keep it under 40 words.'
|
| 28 |
+
)
|
| 29 |
+
response = self.model.generate_content(system_prompt)
|
| 30 |
+
return response.text.strip()
|
| 31 |
+
except Exception as e:
|
| 32 |
+
print(f"❌ Error calling Gemini: {e}")
|
| 33 |
+
return "Error generating prompt"
|
main.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import httpx
|
| 3 |
+
import asyncio
|
| 4 |
+
from fastapi import FastAPI, UploadFile, File
|
| 5 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 6 |
+
from fastapi.staticfiles import StaticFiles
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
# Import Phase 2 & 3 Agents
|
| 10 |
+
from agents.visual_analyst import VisualAnalyst
|
| 11 |
+
from agents.memory_agent import MemoryAgent
|
| 12 |
+
from agents.writer_agent import WriterAgent
|
| 13 |
+
|
| 14 |
+
load_dotenv()
|
| 15 |
+
app = FastAPI()
|
| 16 |
+
|
| 17 |
+
# --- Global Agent Initialization ---
|
| 18 |
+
print("🚀 StyleSync AI: Initializing Agents...")
|
| 19 |
+
try:
|
| 20 |
+
visual_agent = VisualAnalyst()
|
| 21 |
+
memory_agent = MemoryAgent() # Connects to 'stylesync-index-v2'
|
| 22 |
+
writer_agent = WriterAgent()
|
| 23 |
+
print("✅ All Agents Online & Ready.")
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f"❌ Critical Startup Error: {e}")
|
| 26 |
+
|
| 27 |
+
@app.get("/", response_class=HTMLResponse)
|
| 28 |
+
async def read_root():
|
| 29 |
+
try:
|
| 30 |
+
with open("dashboard.html", "r", encoding="utf-8") as f:
|
| 31 |
+
return f.read()
|
| 32 |
+
except FileNotFoundError:
|
| 33 |
+
return "<h1>Error: dashboard.html not found. Run setup scripts first.</h1>"
|
| 34 |
+
|
| 35 |
+
@app.post("/generate-catalog")
|
| 36 |
+
async def generate_catalog(file: UploadFile = File(...)):
|
| 37 |
+
file_path = f"temp_{file.filename}"
|
| 38 |
+
try:
|
| 39 |
+
# 1. Save File Temporarily
|
| 40 |
+
with open(file_path, "wb") as f:
|
| 41 |
+
f.write(await file.read())
|
| 42 |
+
|
| 43 |
+
# 2. Vision (The Eyes)
|
| 44 |
+
print(f"👁️ Analyzing: {file.filename}")
|
| 45 |
+
visual_data = await visual_agent.analyze_image(file_path)
|
| 46 |
+
|
| 47 |
+
# 3. Memory (The Context)
|
| 48 |
+
# Create a search query from visual tags
|
| 49 |
+
search_query = f"{visual_data.get('design_style', '')} {visual_data.get('product_type', '')}"
|
| 50 |
+
print(f"🧠 Recalling trends for: {search_query}")
|
| 51 |
+
seo_keywords = memory_agent.retrieve_keywords(search_query)
|
| 52 |
+
|
| 53 |
+
# 4. Writer (The Brain)
|
| 54 |
+
print("✍️ Drafting copy...")
|
| 55 |
+
listing = writer_agent.write_listing(visual_data, seo_keywords)
|
| 56 |
+
|
| 57 |
+
# 5. Construct Payload
|
| 58 |
+
response_data = {
|
| 59 |
+
"status": "success",
|
| 60 |
+
"visual_analysis": visual_data,
|
| 61 |
+
"market_trends": seo_keywords,
|
| 62 |
+
"final_listing": listing
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
# 6. Automation Trigger (n8n)
|
| 66 |
+
n8n_url = os.getenv("N8N_WEBHOOK_URL")
|
| 67 |
+
if n8n_url:
|
| 68 |
+
asyncio.create_task(trigger_webhook(n8n_url, response_data))
|
| 69 |
+
|
| 70 |
+
return JSONResponse(content=response_data)
|
| 71 |
+
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"❌ Pipeline Error: {e}")
|
| 74 |
+
return JSONResponse(content={"error": str(e)}, status_code=500)
|
| 75 |
+
finally:
|
| 76 |
+
if os.path.exists(file_path):
|
| 77 |
+
os.remove(file_path)
|
| 78 |
+
|
| 79 |
+
async def trigger_webhook(url, data):
|
| 80 |
+
"""Fire-and-forget webhook to n8n"""
|
| 81 |
+
try:
|
| 82 |
+
async with httpx.AsyncClient() as client:
|
| 83 |
+
await client.post(url, json=data, timeout=5.0)
|
| 84 |
+
print(f"🚀 Webhook sent to n8n")
|
| 85 |
+
except Exception as e:
|
| 86 |
+
print(f"⚠️ Webhook failed: {e}")
|
| 87 |
+
|
| 88 |
+
if __name__ == "__main__":
|
| 89 |
+
import uvicorn
|
| 90 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.109.0
|
| 2 |
+
uvicorn==0.27.0
|
| 3 |
+
python-multipart
|
| 4 |
+
python-dotenv
|
| 5 |
+
google-generativeai>=0.8.3
|
| 6 |
+
groq
|
| 7 |
+
pinecone>=3.0.0
|
| 8 |
+
langchain
|
| 9 |
+
langchain-community
|
| 10 |
+
langchain-google-genai
|
| 11 |
+
langchain-groq
|
| 12 |
+
pillow
|
| 13 |
+
huggingface_hub[cli]
|
| 14 |
+
httpx
|
restore_full_brain.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def restore_main():
|
| 5 |
+
content = """import os
|
| 6 |
+
from fastapi import FastAPI, UploadFile, File
|
| 7 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
|
| 10 |
+
# Import Agents
|
| 11 |
+
from agents.visual_analyst import VisualAnalyst
|
| 12 |
+
from agents.memory_agent import MemoryAgent
|
| 13 |
+
from agents.writer_agent import WriterAgent
|
| 14 |
+
|
| 15 |
+
load_dotenv()
|
| 16 |
+
app = FastAPI()
|
| 17 |
+
|
| 18 |
+
# Initialize All Agents
|
| 19 |
+
try:
|
| 20 |
+
visual_agent = VisualAnalyst()
|
| 21 |
+
memory_agent = MemoryAgent()
|
| 22 |
+
writer_agent = WriterAgent()
|
| 23 |
+
|
| 24 |
+
# Seed memory on startup
|
| 25 |
+
memory_agent.seed_database()
|
| 26 |
+
print("✅ All Agents Online")
|
| 27 |
+
except Exception as e:
|
| 28 |
+
print(f"⚠️ Warning: Some agents failed to load: {e}")
|
| 29 |
+
|
| 30 |
+
# 1. SERVE DASHBOARD AT ROOT
|
| 31 |
+
@app.get("/", response_class=HTMLResponse)
|
| 32 |
+
async def read_root():
|
| 33 |
+
try:
|
| 34 |
+
with open("dashboard.html", "r") as f:
|
| 35 |
+
return f.read()
|
| 36 |
+
except FileNotFoundError:
|
| 37 |
+
return "Error: dashboard.html not found"
|
| 38 |
+
|
| 39 |
+
# 2. THE MAIN ORCHESTRATOR ENDPOINT
|
| 40 |
+
@app.post("/generate-catalog")
|
| 41 |
+
async def generate_catalog(file: UploadFile = File(...)):
|
| 42 |
+
try:
|
| 43 |
+
# A. Save Temp File
|
| 44 |
+
os.makedirs("uploads", exist_ok=True)
|
| 45 |
+
file_path = f"uploads/{file.filename}"
|
| 46 |
+
with open(file_path, "wb") as f:
|
| 47 |
+
f.write(await file.read())
|
| 48 |
+
|
| 49 |
+
# B. Visual Analysis (The Eyes)
|
| 50 |
+
visual_data = await visual_agent.analyze_image(file_path)
|
| 51 |
+
|
| 52 |
+
# C. Memory Search (The Context)
|
| 53 |
+
# Create a search query from visual data
|
| 54 |
+
query = f"{visual_data.get('main_color', '')} {visual_data.get('product_type', 'product')}"
|
| 55 |
+
seo_keywords = memory_agent.retrieve_keywords(query)
|
| 56 |
+
|
| 57 |
+
# D. Write Copy (The Brain)
|
| 58 |
+
listing = writer_agent.write_listing(visual_data, seo_keywords)
|
| 59 |
+
|
| 60 |
+
# Cleanup
|
| 61 |
+
if os.path.exists(file_path):
|
| 62 |
+
os.remove(file_path)
|
| 63 |
+
|
| 64 |
+
# Return Full Data Structure
|
| 65 |
+
return JSONResponse(content={
|
| 66 |
+
"visual_data": visual_data,
|
| 67 |
+
"seo_keywords": seo_keywords,
|
| 68 |
+
"listing": listing
|
| 69 |
+
})
|
| 70 |
+
except Exception as e:
|
| 71 |
+
print(f"Error: {e}")
|
| 72 |
+
return JSONResponse(content={"error": str(e)}, status_code=500)
|
| 73 |
+
|
| 74 |
+
if __name__ == "__main__":
|
| 75 |
+
import uvicorn
|
| 76 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 77 |
+
"""
|
| 78 |
+
with open("main.py", "w", encoding="utf-8") as f:
|
| 79 |
+
f.write(content)
|
| 80 |
+
print("✅ main.py restored with full agent logic.")
|
| 81 |
+
|
| 82 |
+
def update_dashboard():
|
| 83 |
+
try:
|
| 84 |
+
with open("dashboard.html", "r", encoding="utf-8") as f:
|
| 85 |
+
content = f.read()
|
| 86 |
+
|
| 87 |
+
# Replace localhost URL with relative path
|
| 88 |
+
new_content = content.replace("http://localhost:8000/generate-catalog", "/generate-catalog")
|
| 89 |
+
|
| 90 |
+
with open("dashboard.html", "w", encoding="utf-8") as f:
|
| 91 |
+
f.write(new_content)
|
| 92 |
+
print("✅ dashboard.html updated for cloud deployment.")
|
| 93 |
+
except Exception as e:
|
| 94 |
+
print(f"❌ Error updating dashboard.html: {e}")
|
| 95 |
+
|
| 96 |
+
def deploy():
|
| 97 |
+
print("🚀 Starting Deployment...")
|
| 98 |
+
commands = [
|
| 99 |
+
["git", "add", "main.py", "dashboard.html"],
|
| 100 |
+
["git", "commit", "-m", "Restore full brain logic and fix dashboard URL"],
|
| 101 |
+
["git", "push", "space", "clean_deploy:main"]
|
| 102 |
+
]
|
| 103 |
+
|
| 104 |
+
for cmd in commands:
|
| 105 |
+
try:
|
| 106 |
+
print(f"Running: {' '.join(cmd)}")
|
| 107 |
+
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
| 108 |
+
print(result.stdout)
|
| 109 |
+
except subprocess.CalledProcessError as e:
|
| 110 |
+
print(f"❌ Error running command: {' '.join(cmd)}")
|
| 111 |
+
print(e.stderr)
|
| 112 |
+
# Don't break on commit error as it might be empty
|
| 113 |
+
if "nothing to commit" in e.stderr:
|
| 114 |
+
continue
|
| 115 |
+
# For other errors we might want to continue or stop, but let's try to proceed
|
| 116 |
+
print("✅ Deployment script finished.")
|
| 117 |
+
|
| 118 |
+
if __name__ == "__main__":
|
| 119 |
+
print("🔧 Restoring Full Brain...")
|
| 120 |
+
restore_main()
|
| 121 |
+
update_dashboard()
|
| 122 |
+
deploy()
|
results/merch_batch_Coffee_20251214_052033.csv
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Niche,Slogan,Art Prompt
|
| 2 |
+
Coffee,Espresso Patronum.,"Neo-traditional digital illustration: Coffee portafilter emitting a glowing blue steam Patronus. Rich brown, teal, and gold colors. Centered, detailed T-shirt graphic. (23 words)"
|
| 3 |
+
Coffee,This Is My Resting Coffee Face.,"**Retro 1970s cartoon T-shirt design, centered.** Grumpy, steaming mug face integrated with bold text. Colors: Espresso, Cream, Burnt Orange. **Vector.**"
|
| 4 |
+
Coffee,My Blood Type Is Dark Roast.,"Retro screen print vector. Dripping anatomical heart as a coffee bean. Deep espresso, charcoal, and cream colors. Bold, centered T-shirt design."
|
| 5 |
+
Coffee,Humaning Is Hard.,Error generating prompt
|
| 6 |
+
Coffee,Powered By Anxiety & Arabica.,Error generating prompt
|
results/merch_batch_Coffee_20251214_052441.csv
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Niche,Slogan,Art Prompt
|
| 2 |
+
Coffee,Thou Shalt Not Decaf.,"High-contrast woodcut engraving of sacred coffee tablets. Espresso, cream, gold palette. Centered, dramatic lighting. Vector-ready."
|
| 3 |
+
Coffee,Pre-Coffee: Danger Zone.,"Mid-Century modern warning sign, centered. Bold text on distressed yellow. Black/crimson palette. Vector, high contrast, coffee bomb symbol."
|
| 4 |
+
Coffee,My Blood Type Is Coffee.,"Vintage distressed screen print. IV blood bag filled with coffee, centered. Espresso, cream, and black palette. Typography reads: ""My Blood Type Is Coffee."""
|
| 5 |
+
Coffee,"I'm Not Addicted, I'm Committed.","Retro-vector graphic: Coffee cup with stylized steam waves. Burnt orange, espresso, and cream palette. Centered, bold T-shirt design."
|
| 6 |
+
Coffee,Warning: May Talk About Coffee Too Much.,Error generating prompt
|
results/merch_batch_Coffee_20251214_052609.csv
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Niche,Slogan,Art Prompt
|
| 2 |
+
Coffee,Must Caffeinate to Human.,"Vintage tattoo flash style. Skull morphing into steaming coffee cup, centered. Warm brown, cream, black palette. T-shirt ready graphic."
|
| 3 |
+
Coffee,My Blood Type Is Arabica.,Error generating prompt
|
| 4 |
+
Coffee,Decaf Is A Conspiracy.,Error generating prompt
|
| 5 |
+
Coffee,Powered by Espresso and Anxiety.,Error generating prompt
|
| 6 |
+
Coffee,I'll Sleep When The Coffee Runs Out.,Error generating prompt
|
results/merch_batch_Coffee_20251214_052808.csv
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Niche,Slogan,Art Prompt
|
| 2 |
+
Coffee,Retro Cat Mom,Error generating prompt
|
| 3 |
+
Coffee,Pixel Art Kitty,Error generating prompt
|
| 4 |
+
Coffee,Cattitude,Error generating prompt
|
scan_vision_models.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
headers = {"Authorization": f"Bearer {api_key}"}
|
| 10 |
+
|
| 11 |
+
candidates = [
|
| 12 |
+
"HuggingFaceM4/idefics2-8b",
|
| 13 |
+
"HuggingFaceM4/idefics2-8b-chatty",
|
| 14 |
+
"llava-hf/llava-1.5-7b-hf",
|
| 15 |
+
"llava-hf/llava-v1.6-mistral-7b-hf",
|
| 16 |
+
"microsoft/Phi-3-vision-128k-instruct",
|
| 17 |
+
"NousResearch/Nous-Hermes-2-Vision-Alpha",
|
| 18 |
+
"OpenGVLab/InternVL-Chat-V1-5",
|
| 19 |
+
"Qwen/Qwen2.5-VL-7B-Instruct",
|
| 20 |
+
"google/paligemma-3b-mix-224"
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
print("Scanning for working Serverless Vision Models...\n")
|
| 24 |
+
|
| 25 |
+
for model in candidates:
|
| 26 |
+
url = f"https://router.huggingface.co/models/{model}"
|
| 27 |
+
print(f"Testing: {model}")
|
| 28 |
+
try:
|
| 29 |
+
# Simple probe payload
|
| 30 |
+
response = requests.post(url, headers=headers, json={"inputs": "Hello"})
|
| 31 |
+
if response.status_code == 200:
|
| 32 |
+
print(f"✅ WORKS! {model} (Status: 200)")
|
| 33 |
+
print(f"Response: {response.text[:100]}...")
|
| 34 |
+
elif response.status_code == 400:
|
| 35 |
+
# 400 might mean it Exists but input format is wrong (which is good!)
|
| 36 |
+
print(f"⚠️ EXISTS but 400 (Bad Request): {model}")
|
| 37 |
+
print(f"Response: {response.text[:100]}...")
|
| 38 |
+
elif response.status_code == 404:
|
| 39 |
+
print(f"❌ 404 Not Found: {model}")
|
| 40 |
+
else:
|
| 41 |
+
print(f"❌ Error {response.status_code}: {model}")
|
| 42 |
+
except Exception as e:
|
| 43 |
+
print(f"❌ Exception: {e}")
|
| 44 |
+
print("-" * 30)
|