Spaces:
Sleeping
Sleeping
Gaurav vashistha commited on
Commit ·
c7dc4b9
0
Parent(s):
UI Update: Glassmorphism Design & Binary Cleanup
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +30 -0
- .vite/deps/_metadata.json +8 -0
- .vite/deps/package.json +3 -0
- Dockerfile +10 -0
- LICENSE +6 -0
- README.md +75 -0
- add_license.py +64 -0
- agents/__init__.py +0 -0
- agents/manager.py +46 -0
- agents/memory_agent.py +104 -0
- agents/visual_analyst.py +60 -0
- agents/writer_agent.py +48 -0
- apply_proprietary_license.py +57 -0
- apply_qa_fixes.py +111 -0
- args.json +4 -0
- check_basic.py +20 -0
- check_gemini.py +26 -0
- check_gemini_clean.py +24 -0
- check_groq.py +11 -0
- check_groq_models.py +14 -0
- check_groq_vision.py +36 -0
- check_idefics.py +33 -0
- check_idefics_raw.py +29 -0
- check_idefics_v2.py +31 -0
- check_idefics_v3.py +30 -0
- check_llama.py +33 -0
- check_llava.py +33 -0
- check_models.py +15 -0
- check_models_list.py +15 -0
- check_qwen.py +48 -0
- check_qwen_raw.py +52 -0
- clean_binary_deploy.py +80 -0
- code.html +203 -0
- connect_n8n.py +126 -0
- create_dockerfile.py +50 -0
- dashboard.html +455 -0
- deploy_new_ui.py +60 -0
- final_deploy_push.py +20 -0
- final_upload.py +61 -0
- fix_dashboard_routing.py +87 -0
- fix_google_key.py +48 -0
- fix_readme.py +42 -0
- fix_vision_core.py +90 -0
- force_deploy.py +62 -0
- force_push.py +14 -0
- glassui.html +455 -0
- install_gh.py +33 -0
- launcher.py +47 -0
- legacy/trend_spotter.py +46 -0
- legacy/visionary.py +33 -0
.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Credentials and Secrets
|
| 2 |
+
.env
|
| 3 |
+
|
| 4 |
+
# Python Compiled Files
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.py[cod]
|
| 7 |
+
|
| 8 |
+
# Virtual Environments
|
| 9 |
+
venv/
|
| 10 |
+
.venv/
|
| 11 |
+
|
| 12 |
+
# System Files
|
| 13 |
+
.DS_Store
|
| 14 |
+
Thumbs.db
|
| 15 |
+
|
| 16 |
+
# Logs
|
| 17 |
+
*.log
|
| 18 |
+
|
| 19 |
+
# Editor Directories
|
| 20 |
+
.vscode/
|
| 21 |
+
.idea/
|
| 22 |
+
|
| 23 |
+
*.jpg
|
| 24 |
+
|
| 25 |
+
stitch_merchflow_ai_dashboard.zip
|
| 26 |
+
screen.jpg
|
| 27 |
+
test_image.jpg
|
| 28 |
+
*.zip
|
| 29 |
+
*.jpeg
|
| 30 |
+
*.png
|
.vite/deps/_metadata.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"hash": "7678463b",
|
| 3 |
+
"configHash": "aaeefbd1",
|
| 4 |
+
"lockfileHash": "e3b0c442",
|
| 5 |
+
"browserHash": "7b5c57bd",
|
| 6 |
+
"optimized": {},
|
| 7 |
+
"chunks": {}
|
| 8 |
+
}
|
.vite/deps/package.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"type": "module"
|
| 3 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9
|
| 2 |
+
WORKDIR /code
|
| 3 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 4 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 5 |
+
COPY . /code
|
| 6 |
+
# Fix permissions for libraries that write to home
|
| 7 |
+
RUN mkdir -p /tmp/home
|
| 8 |
+
ENV HOME=/tmp/home
|
| 9 |
+
# Start the FastAPI server on port 7860 (required by Hugging Face)
|
| 10 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
LICENSE
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright © 2026 Bhishaj Technologies.
|
| 2 |
+
This software and its documentation are the confidential and proprietary
|
| 3 |
+
information of Bhishaj Technologies (URN: UDYAM-UP-02-0108589).
|
| 4 |
+
Unauthorized copying, distribution, or use of this source code
|
| 5 |
+
is strictly prohibited. Use is governed by the signed Master Service Agreement.
|
| 6 |
+
All Rights Reserved.
|
README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: MerchFlow AI
|
| 3 |
+
emoji: 👟
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# MerchFlow AI
|
| 11 |
+
|
| 12 |
+

|
| 13 |
+

|
| 14 |
+

|
| 15 |
+
|
| 16 |
+
MerchFlow AI is a high-performance, multi-agent orchestration system designed to automate the generation of premium e-commerce product listings. By synergizing Computer Vision, Retrieval Augmented Generation (RAG), and Large Language Models (LLMs), it transforms raw product images into SEO-optimized market-ready content.
|
| 17 |
+
|
| 18 |
+
## 🏗️ Architecture Flow
|
| 19 |
+
|
| 20 |
+
The system employs a sophisticated event-driven architecture orchestrated by **FastAPI**:
|
| 21 |
+
|
| 22 |
+
1. **👁️ Visual Agent (Gemini 1.5)**
|
| 23 |
+
* **Function**: Zero-shot image analysis.
|
| 24 |
+
* **Process**: Extracts high-fidelity visual attributes including dominant color palettes, stylistic classifications, and granular item types.
|
| 25 |
+
* **Engine**: Google Gemini 1.5 Flash (Vision).
|
| 26 |
+
|
| 27 |
+
2. **🧠 Memory Agent (Pinecone)**
|
| 28 |
+
* **Function**: Semantic Search & RAG.
|
| 29 |
+
* **Process**: Vectorizes visual tags to query a high-dimensional index, retrieving historically high-performing SEO keywords and market trends relevant to the product.
|
| 30 |
+
* **Engine**: Pinecone Vector Database.
|
| 31 |
+
|
| 32 |
+
3. **✍️ Writer Agent (Llama 3)**
|
| 33 |
+
* **Function**: Creative Synthesis.
|
| 34 |
+
* **Process**: Fuses visual data with retrieved market intelligence to generate persuasive, conversion-focused title, description, and feature bullets.
|
| 35 |
+
* **Engine**: Meta Llama 3 (via Groq Cloud).
|
| 36 |
+
|
| 37 |
+
4. **⚙️ Orchestrator (FastAPI)**
|
| 38 |
+
* **Function**: Async Pipeline Management.
|
| 39 |
+
* **Process**: Handles non-blocking agent execution, error propagation, and API lifecycle management.
|
| 40 |
+
|
| 41 |
+
5. **🔗 Post-Processing (n8n)**
|
| 42 |
+
* **Function**: Automation Webhook.
|
| 43 |
+
* **Process**: Triggers downstream workflows (database storage, Shopify API integration) via secure webhooks upon successful generation.
|
| 44 |
+
|
| 45 |
+
## 🚀 Complete Setup
|
| 46 |
+
|
| 47 |
+
To run this system locally, ensure you have the following environment variables configured in your `.env` file:
|
| 48 |
+
|
| 49 |
+
```env
|
| 50 |
+
GEMINI_API_KEY=your_gemini_key
|
| 51 |
+
GROQ_API_KEY=your_groq_key
|
| 52 |
+
PINECONE_API_KEY=your_pinecone_key
|
| 53 |
+
N8N_WEBHOOK_URL=your_n8n_webhook_url
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## ⚡ Quick Start
|
| 57 |
+
|
| 58 |
+
### 1. Installation
|
| 59 |
+
Install the required dependencies using pip:
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
pip install -r requirements.txt
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### 2. Execution
|
| 66 |
+
Launch the FastAPI server:
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
python main.py
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
The API will be available at `http://localhost:7860`.
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
**🛡️ Maintained by Bhishaj Technologies (UDYAM-UP-02-0108589). All Rights Reserved.**
|
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,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
# Configure Gemini
|
| 12 |
+
self.gemini_api_key = os.getenv("GEMINI_API_KEY")
|
| 13 |
+
if not self.gemini_api_key:
|
| 14 |
+
raise ValueError("GEMINI_API_KEY not found")
|
| 15 |
+
genai.configure(api_key=self.gemini_api_key)
|
| 16 |
+
|
| 17 |
+
# Configure Pinecone
|
| 18 |
+
self.pinecone_api_key = os.getenv("PINECONE_API_KEY")
|
| 19 |
+
if not self.pinecone_api_key:
|
| 20 |
+
raise ValueError("PINECONE_API_KEY not found")
|
| 21 |
+
|
| 22 |
+
self.pc = Pinecone(api_key=self.pinecone_api_key)
|
| 23 |
+
self.index_name = "merchflow-index"
|
| 24 |
+
|
| 25 |
+
# Check and create index
|
| 26 |
+
existing_indexes = [i.name for i in self.pc.list_indexes()]
|
| 27 |
+
if self.index_name not in existing_indexes:
|
| 28 |
+
print(f"Creating index {self.index_name}...")
|
| 29 |
+
self.pc.create_index(
|
| 30 |
+
name=self.index_name,
|
| 31 |
+
dimension=768, # models/text-embedding-004 dimension
|
| 32 |
+
metric='cosine',
|
| 33 |
+
spec=ServerlessSpec(
|
| 34 |
+
cloud='aws',
|
| 35 |
+
region='us-east-1'
|
| 36 |
+
)
|
| 37 |
+
)
|
| 38 |
+
# Wait for index to be ready
|
| 39 |
+
while not self.pc.describe_index(self.index_name).status['ready']:
|
| 40 |
+
time.sleep(1)
|
| 41 |
+
print("Index created.")
|
| 42 |
+
|
| 43 |
+
self.index = self.pc.Index(self.index_name)
|
| 44 |
+
|
| 45 |
+
def _get_embedding(self, text):
|
| 46 |
+
# Using models/text-embedding-004
|
| 47 |
+
result = genai.embed_content(
|
| 48 |
+
model="models/text-embedding-004",
|
| 49 |
+
content=text,
|
| 50 |
+
task_type="retrieval_document"
|
| 51 |
+
)
|
| 52 |
+
return result['embedding']
|
| 53 |
+
|
| 54 |
+
def seed_database(self):
|
| 55 |
+
# Check if empty
|
| 56 |
+
stats = self.index.describe_index_stats()
|
| 57 |
+
if stats.total_vector_count > 0:
|
| 58 |
+
print("Database already seeded.")
|
| 59 |
+
return
|
| 60 |
+
|
| 61 |
+
print("Seeding database...")
|
| 62 |
+
items = [
|
| 63 |
+
{
|
| 64 |
+
"id": "item1",
|
| 65 |
+
"text": "Running Shoe",
|
| 66 |
+
"keywords": "breathable, shock absorption, marathon training, lightweight"
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"id": "item2",
|
| 70 |
+
"text": "Graphic T-Shirt",
|
| 71 |
+
"keywords": "100% cotton, vintage wash, pre-shrunk, soft feel"
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"id": "item3",
|
| 75 |
+
"text": "Leather Wallet",
|
| 76 |
+
"keywords": "genuine leather, RFID blocking, minimalist, bifold"
|
| 77 |
+
}
|
| 78 |
+
]
|
| 79 |
+
|
| 80 |
+
vectors = []
|
| 81 |
+
for item in items:
|
| 82 |
+
embedding = self._get_embedding(item['text'])
|
| 83 |
+
vectors.append({
|
| 84 |
+
"id": item['id'],
|
| 85 |
+
"values": embedding,
|
| 86 |
+
"metadata": {"keywords": item['keywords'], "text": item['text']}
|
| 87 |
+
})
|
| 88 |
+
|
| 89 |
+
self.index.upsert(vectors=vectors)
|
| 90 |
+
print(f"Seeded {len(vectors)} items.")
|
| 91 |
+
|
| 92 |
+
def retrieve_keywords(self, query_text: str):
|
| 93 |
+
try:
|
| 94 |
+
query_embedding = self._get_embedding(query_text)
|
| 95 |
+
|
| 96 |
+
results = self.index.query(
|
| 97 |
+
vector=query_embedding,
|
| 98 |
+
top_k=5,
|
| 99 |
+
include_metadata=True
|
| 100 |
+
)
|
| 101 |
+
return [m.metadata['keywords'] for m in results.matches if m.metadata and 'keywords' in m.metadata]
|
| 102 |
+
except Exception as e:
|
| 103 |
+
print(f"❌ Keyword Retrieval Failed: {e}")
|
| 104 |
+
return []
|
agents/visual_analyst.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import re
|
| 4 |
+
import google.generativeai as genai
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
class VisualAnalyst:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.api_key = os.getenv("GEMINI_API_KEY")
|
| 12 |
+
if not self.api_key:
|
| 13 |
+
raise ValueError("GEMINI_API_KEY not found")
|
| 14 |
+
|
| 15 |
+
genai.configure(api_key=self.api_key)
|
| 16 |
+
self.model_name = "models/gemini-flash-latest"
|
| 17 |
+
self.model = genai.GenerativeModel(self.model_name)
|
| 18 |
+
print(f"✅ VisualAnalyst stored Gemini model: {self.model_name}")
|
| 19 |
+
|
| 20 |
+
async def analyze_image(self, image_path: str):
|
| 21 |
+
try:
|
| 22 |
+
# Upload the file to Gemini
|
| 23 |
+
# Note: For efficiency in production, files should be managed (uploads/deletes)
|
| 24 |
+
# but for this agentic flow, we'll upload per request or assume local path usage helper if needed.
|
| 25 |
+
# However, the standard `model.generate_content` can take PIL images or file objects directly for some sdk versions,
|
| 26 |
+
# but using the File API is cleaner for 1.5 Flash multi-modal.
|
| 27 |
+
# Let's use the simpler PIL integration if available, or just path if the SDK supports it.
|
| 28 |
+
# actually, standard genai usage for images usually involves PIL or uploading.
|
| 29 |
+
# Let's try the PIL approach first as it's often more direct for local scripts.
|
| 30 |
+
import PIL.Image
|
| 31 |
+
img = PIL.Image.open(image_path)
|
| 32 |
+
|
| 33 |
+
user_prompt = (
|
| 34 |
+
"Analyze this product image. "
|
| 35 |
+
"Return ONLY valid JSON with keys: main_color, product_type, design_style, visual_features."
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
# Gemini 1.5 Flash supports JSON response schema, but simple prompting often works well too.
|
| 39 |
+
# We'll stick to prompt engineering for now to match the "Return ONLY valid JSON" instruction.
|
| 40 |
+
response = self.model.generate_content([user_prompt, img], request_options={'timeout': 15.0})
|
| 41 |
+
|
| 42 |
+
response_text = response.text
|
| 43 |
+
|
| 44 |
+
# Use regex to find the JSON block robustly
|
| 45 |
+
match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 46 |
+
if match:
|
| 47 |
+
cleaned_content = match.group(0)
|
| 48 |
+
else:
|
| 49 |
+
cleaned_content = response_text
|
| 50 |
+
|
| 51 |
+
return json.loads(cleaned_content.strip())
|
| 52 |
+
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f"❌ Analysis Failed: {e}")
|
| 55 |
+
return {
|
| 56 |
+
"main_color": "Unknown",
|
| 57 |
+
"product_type": "Unknown",
|
| 58 |
+
"design_style": "Unknown",
|
| 59 |
+
"visual_features": [f"Error: {str(e)}"]
|
| 60 |
+
}
|
agents/writer_agent.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
from groq import Groq
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
class WriterAgent:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.api_key = os.getenv("GROQ_API_KEY")
|
| 11 |
+
if not self.api_key:
|
| 12 |
+
raise ValueError("GROQ_API_KEY not found in environment variables")
|
| 13 |
+
self.client = Groq(api_key=self.api_key)
|
| 14 |
+
self.model = "llama-3.3-70b-versatile"
|
| 15 |
+
|
| 16 |
+
def write_listing(self, visual_data: dict, seo_keywords: list) -> dict:
|
| 17 |
+
system_prompt = (
|
| 18 |
+
"You are an expert e-commerce copywriter. "
|
| 19 |
+
"Write a persuasive product listing based on these visual attributes and SEO keywords. "
|
| 20 |
+
"Return JSON with keys: title, description, bullet_points."
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
user_content = f"""
|
| 24 |
+
Visual Attributes: {json.dumps(visual_data, indent=2)}
|
| 25 |
+
|
| 26 |
+
SEO Keywords: {', '.join(seo_keywords)}
|
| 27 |
+
|
| 28 |
+
Please generate the listing in JSON format.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
try:
|
| 32 |
+
completion = self.client.chat.completions.create(
|
| 33 |
+
model=self.model,
|
| 34 |
+
messages=[
|
| 35 |
+
{"role": "system", "content": system_prompt},
|
| 36 |
+
{"role": "user", "content": user_content}
|
| 37 |
+
],
|
| 38 |
+
temperature=0.7,
|
| 39 |
+
response_format={"type": "json_object"},
|
| 40 |
+
timeout=15.0
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
response_text = completion.choices[0].message.content
|
| 44 |
+
return json.loads(response_text)
|
| 45 |
+
|
| 46 |
+
except Exception as e:
|
| 47 |
+
print(f"Error generating listing: {e}")
|
| 48 |
+
return {"error": str(e)}
|
apply_proprietary_license.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Apply proprietary licensing updates and deploy git changes."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import subprocess
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
LICENSE_TEXT = """Copyright © 2026 Bhishaj Technologies.
|
| 10 |
+
This software and its documentation are the confidential and proprietary
|
| 11 |
+
information of Bhishaj Technologies (URN: UDYAM-UP-02-0108589).
|
| 12 |
+
Unauthorized copying, distribution, or use of this source code
|
| 13 |
+
is strictly prohibited. Use is governed by the signed Master Service Agreement.
|
| 14 |
+
All Rights Reserved.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
MIT_BADGE_LINE = ""
|
| 18 |
+
FOOTER = "---\n**🛡️ Maintained by Bhishaj Technologies (UDYAM-UP-02-0108589). All Rights Reserved.**\n"
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def overwrite_license_file() -> None:
|
| 22 |
+
Path("LICENSE").write_text(LICENSE_TEXT, encoding="utf-8")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def sanitize_readme() -> None:
|
| 26 |
+
readme_path = Path("README.md")
|
| 27 |
+
readme = readme_path.read_text(encoding="utf-8")
|
| 28 |
+
|
| 29 |
+
filtered_lines = [line for line in readme.splitlines() if line.strip() != MIT_BADGE_LINE]
|
| 30 |
+
sanitized = "\n".join(filtered_lines).rstrip("\n")
|
| 31 |
+
|
| 32 |
+
if FOOTER.strip() not in sanitized:
|
| 33 |
+
sanitized = f"{sanitized}\n\n{FOOTER}"
|
| 34 |
+
else:
|
| 35 |
+
sanitized = f"{sanitized}\n"
|
| 36 |
+
|
| 37 |
+
readme_path.write_text(sanitized, encoding="utf-8")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def run_git_commands() -> None:
|
| 41 |
+
commands = [
|
| 42 |
+
["git", "add", "LICENSE", "README.md"],
|
| 43 |
+
["git", "commit", "-m", "Switch to Proprietary License (Bhishaj Technologies)"],
|
| 44 |
+
["git", "push", "space", "clean_deploy:main"],
|
| 45 |
+
]
|
| 46 |
+
for command in commands:
|
| 47 |
+
subprocess.run(command, check=True)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def main() -> None:
|
| 51 |
+
overwrite_license_file()
|
| 52 |
+
sanitize_readme()
|
| 53 |
+
run_git_commands()
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
if __name__ == "__main__":
|
| 57 |
+
main()
|
apply_qa_fixes.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
|
| 4 |
+
def patch_main():
|
| 5 |
+
with open("main.py", "r", encoding="utf-8") as f:
|
| 6 |
+
content = f.read()
|
| 7 |
+
|
| 8 |
+
old_pattern = r'return JSONResponse\(\s*content=\{\s*"error": str\(e\),\s*"type": type\(e\)\.__name__,\s*"details": error_details\s*\},\s*status_code=500\s*\)'
|
| 9 |
+
new_text = '''return JSONResponse(
|
| 10 |
+
content={
|
| 11 |
+
"error": "An internal server error occurred.",
|
| 12 |
+
"type": type(e).__name__
|
| 13 |
+
},
|
| 14 |
+
status_code=500
|
| 15 |
+
)'''
|
| 16 |
+
content = re.sub(old_pattern, new_text, content[1:] if content.startswith('\ufeff') else content)
|
| 17 |
+
|
| 18 |
+
with open("main.py", "w", encoding="utf-8") as f:
|
| 19 |
+
f.write(content)
|
| 20 |
+
print("Patched main.py")
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def patch_memory_agent():
|
| 24 |
+
path = "agents/memory_agent.py"
|
| 25 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 26 |
+
content = f.read()
|
| 27 |
+
|
| 28 |
+
old_func = r'def retrieve_keywords\(.*?: str\):.*?return \[m\.metadata\[\'keywords\'\] for m in results\.matches if m\.metadata and \'keywords\' in m\.metadata\]'
|
| 29 |
+
new_func = '''def retrieve_keywords(self, query_text: str):
|
| 30 |
+
try:
|
| 31 |
+
query_embedding = self._get_embedding(query_text)
|
| 32 |
+
|
| 33 |
+
results = self.index.query(
|
| 34 |
+
vector=query_embedding,
|
| 35 |
+
top_k=5,
|
| 36 |
+
include_metadata=True
|
| 37 |
+
)
|
| 38 |
+
return [m.metadata['keywords'] for m in results.matches if m.metadata and 'keywords' in m.metadata]
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"❌ Keyword Retrieval Failed: {e}")
|
| 41 |
+
return []'''
|
| 42 |
+
|
| 43 |
+
content = re.sub(old_func, new_func, content, flags=re.DOTALL)
|
| 44 |
+
|
| 45 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 46 |
+
f.write(content)
|
| 47 |
+
print("Patched agents/memory_agent.py")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def patch_visual_analyst():
|
| 51 |
+
path = "agents/visual_analyst.py"
|
| 52 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 53 |
+
content = f.read()
|
| 54 |
+
|
| 55 |
+
# Add timeout
|
| 56 |
+
content = content.replace(
|
| 57 |
+
"response = self.model.generate_content([user_prompt, img])",
|
| 58 |
+
"response = self.model.generate_content([user_prompt, img], request_options={'timeout': 15.0})"
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
if "import re" not in content:
|
| 62 |
+
content = content.replace("import json", "import json\nimport re")
|
| 63 |
+
|
| 64 |
+
# Rewrite JSON parsing
|
| 65 |
+
old_parsing = r'# Clean up potential markdown code fences.*?return json\.loads\(cleaned_content\.strip\(\)\)'
|
| 66 |
+
new_parsing = '''# Use regex to find the JSON block robustly
|
| 67 |
+
match = re.search(r'\\{.*\\}', response_text, re.DOTALL)
|
| 68 |
+
if match:
|
| 69 |
+
cleaned_content = match.group(0)
|
| 70 |
+
else:
|
| 71 |
+
cleaned_content = response_text
|
| 72 |
+
|
| 73 |
+
return json.loads(cleaned_content.strip())'''
|
| 74 |
+
|
| 75 |
+
content = re.sub(old_parsing, new_parsing, content, flags=re.DOTALL)
|
| 76 |
+
|
| 77 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 78 |
+
f.write(content)
|
| 79 |
+
print("Patched agents/visual_analyst.py")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def patch_writer_agent():
|
| 83 |
+
path = "agents/writer_agent.py"
|
| 84 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 85 |
+
content = f.read()
|
| 86 |
+
|
| 87 |
+
old_completion = r'completion = self\.client\.chat\.completions\.create\(\s*model=self\.model,\s*messages=\[\s*\{"role": "system", "content": system_prompt\},\s*\{"role": "user", "content": user_content\}\s*\],\s*temperature=0\.7,\s*response_format=\{"type": "json_object"\}\s*\)'
|
| 88 |
+
|
| 89 |
+
new_completion = '''completion = self.client.chat.completions.create(
|
| 90 |
+
model=self.model,
|
| 91 |
+
messages=[
|
| 92 |
+
{"role": "system", "content": system_prompt},
|
| 93 |
+
{"role": "user", "content": user_content}
|
| 94 |
+
],
|
| 95 |
+
temperature=0.7,
|
| 96 |
+
response_format={"type": "json_object"},
|
| 97 |
+
timeout=15.0
|
| 98 |
+
)'''
|
| 99 |
+
|
| 100 |
+
content = re.sub(old_completion, new_completion, content)
|
| 101 |
+
|
| 102 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 103 |
+
f.write(content)
|
| 104 |
+
print("Patched agents/writer_agent.py")
|
| 105 |
+
|
| 106 |
+
if __name__ == "__main__":
|
| 107 |
+
patch_main()
|
| 108 |
+
patch_memory_agent()
|
| 109 |
+
patch_visual_analyst()
|
| 110 |
+
patch_writer_agent()
|
| 111 |
+
print("All file patching completed successfully.")
|
args.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"projectId": "13453765122851154258",
|
| 3 |
+
"screenId": "cf3af7f8a9e74daf85096241ed88c75c"
|
| 4 |
+
}
|
check_basic.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
|
| 10 |
+
print(f"Testing token with microsoft/resnet-50")
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
# Pass the URL directly as the input (InferenceClient handles URLs for image tasks)
|
| 14 |
+
result = client.image_classification(
|
| 15 |
+
model="microsoft/resnet-50",
|
| 16 |
+
image="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
)
|
| 18 |
+
print("Success:", result)
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print("Failed:", e)
|
check_gemini.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
| 8 |
+
genai.configure(api_key=api_key)
|
| 9 |
+
|
| 10 |
+
print("Listing available Gemini models...")
|
| 11 |
+
try:
|
| 12 |
+
for m in genai.list_models():
|
| 13 |
+
if 'generateContent' in m.supported_generation_methods:
|
| 14 |
+
print(m.name)
|
| 15 |
+
except Exception as e:
|
| 16 |
+
print(f"List models failed: {e}")
|
| 17 |
+
|
| 18 |
+
model_name = "gemini-1.5-flash"
|
| 19 |
+
print(f"\nTesting model: {model_name}")
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
model = genai.GenerativeModel(model_name)
|
| 23 |
+
response = model.generate_content("Hello, can you see this?")
|
| 24 |
+
print("Response:", response.text)
|
| 25 |
+
except Exception as e:
|
| 26 |
+
print(f"Test failed: {e}")
|
check_gemini_clean.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
| 8 |
+
genai.configure(api_key=api_key)
|
| 9 |
+
|
| 10 |
+
candidates = [
|
| 11 |
+
"gemini-2.0-flash",
|
| 12 |
+
"gemini-2.0-flash-exp",
|
| 13 |
+
"models/gemini-2.0-flash"
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
for model_name in candidates:
|
| 17 |
+
print(f"\nTesting model: {model_name}")
|
| 18 |
+
try:
|
| 19 |
+
model = genai.GenerativeModel(model_name)
|
| 20 |
+
response = model.generate_content("Hello")
|
| 21 |
+
print(f"✅ Success with {model_name}: {response.text}")
|
| 22 |
+
break
|
| 23 |
+
except Exception as e:
|
| 24 |
+
print(f"❌ Failed with {model_name}: {e}")
|
check_groq.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from groq import Groq
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
client = Groq(api_key=os.getenv("GROQ_API_KEY"))
|
| 7 |
+
|
| 8 |
+
print("Listing Groq models...")
|
| 9 |
+
models = client.models.list()
|
| 10 |
+
for m in models.data:
|
| 11 |
+
print(m.id)
|
check_groq_models.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from groq import Groq
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
try:
|
| 8 |
+
client = Groq(api_key=os.getenv("GROQ_API_KEY"))
|
| 9 |
+
models = client.models.list()
|
| 10 |
+
print("Available Models:")
|
| 11 |
+
for model in models.data:
|
| 12 |
+
print(f"- {model.id}")
|
| 13 |
+
except Exception as e:
|
| 14 |
+
print(f"Error listing models: {e}")
|
check_groq_vision.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from groq import Groq
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import base64
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
client = Groq(api_key=os.getenv("GROQ_API_KEY"))
|
| 9 |
+
model = "llama-3.2-11b-vision-preview"
|
| 10 |
+
|
| 11 |
+
print(f"Testing Groq Vision model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Image URL
|
| 14 |
+
print("\n--- Test 1: Image URL ---")
|
| 15 |
+
try:
|
| 16 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
completion = client.chat.completions.create(
|
| 18 |
+
model=model,
|
| 19 |
+
messages=[
|
| 20 |
+
{
|
| 21 |
+
"role": "user",
|
| 22 |
+
"content": [
|
| 23 |
+
{"type": "text", "text": "What's in this image?"},
|
| 24 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 25 |
+
],
|
| 26 |
+
}
|
| 27 |
+
],
|
| 28 |
+
temperature=1,
|
| 29 |
+
max_tokens=1024,
|
| 30 |
+
top_p=1,
|
| 31 |
+
stream=False,
|
| 32 |
+
stop=None,
|
| 33 |
+
)
|
| 34 |
+
print("Response:", completion.choices[0].message.content)
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print("Groq Vision failed:", e)
|
check_idefics.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "HuggingFaceM4/idefics2-8b"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Image URL
|
| 14 |
+
print("\n--- Test 1: Image URL ---")
|
| 15 |
+
try:
|
| 16 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
messages = [
|
| 18 |
+
{
|
| 19 |
+
"role": "user",
|
| 20 |
+
"content": [
|
| 21 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 22 |
+
{"type": "text", "text": "What is in this image?"}
|
| 23 |
+
]
|
| 24 |
+
}
|
| 25 |
+
]
|
| 26 |
+
completion = client.chat.completions.create(
|
| 27 |
+
model=model,
|
| 28 |
+
messages=messages,
|
| 29 |
+
max_tokens=100
|
| 30 |
+
)
|
| 31 |
+
print("Response:", completion.choices[0].message.content)
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print("Image URL failed:", e)
|
check_idefics_raw.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
api_key = os.getenv("HF_TOKEN")
|
| 9 |
+
model = "HuggingFaceM4/idefics2-8b"
|
| 10 |
+
url = f"https://router.huggingface.co/models/{model}"
|
| 11 |
+
|
| 12 |
+
headers = {"Authorization": f"Bearer {api_key}"}
|
| 13 |
+
|
| 14 |
+
print(f"Testing URL: {url}")
|
| 15 |
+
|
| 16 |
+
# Test A: Simple text inputs
|
| 17 |
+
print("\n--- Test A: Simple Text ---")
|
| 18 |
+
response = requests.post(url, headers=headers, json={"inputs": "Hello"})
|
| 19 |
+
print(f"Status: {response.status_code}")
|
| 20 |
+
print("Response:", response.text)
|
| 21 |
+
|
| 22 |
+
# Test B: Formatted inputs (Standard for some VLM APIs)
|
| 23 |
+
# Often they accept { "inputs": "User: ...", "parameters": ... }
|
| 24 |
+
print("\n--- Test B: Formatted Prompt ---")
|
| 25 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 26 |
+
prompt = f"User:  Describe this image.<end_of_utterance>\nAssistant:"
|
| 27 |
+
response = requests.post(url, headers=headers, json={"inputs": prompt, "parameters": {"max_new_tokens": 50}})
|
| 28 |
+
print(f"Status: {response.status_code}")
|
| 29 |
+
print("Response:", response.text)
|
check_idefics_v2.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "HuggingFaceM4/idefics2-8b"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 14 |
+
|
| 15 |
+
# Format for Idefics2:
|
| 16 |
+
# User:  <text><end_of_utterance>\nAssistant:
|
| 17 |
+
prompt = f"User:  Describe this image.<end_of_utterance>\nAssistant:"
|
| 18 |
+
|
| 19 |
+
print(f"\n--- Testing with text_generation and specific prompt ---")
|
| 20 |
+
print(f"Prompt: {prompt}")
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
# Use text_generation for models that don't support chat
|
| 24 |
+
response = client.text_generation(
|
| 25 |
+
prompt=prompt,
|
| 26 |
+
model=model,
|
| 27 |
+
max_new_tokens=100
|
| 28 |
+
)
|
| 29 |
+
print("Response:", response)
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print("Failed:", e)
|
check_idefics_v3.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import traceback
|
| 3 |
+
from huggingface_hub import InferenceClient
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
api_key = os.getenv("HF_TOKEN")
|
| 9 |
+
client = InferenceClient(api_key=api_key)
|
| 10 |
+
model = "HuggingFaceM4/idefics2-8b"
|
| 11 |
+
|
| 12 |
+
print(f"Testing model: {model}")
|
| 13 |
+
|
| 14 |
+
print("\n--- Test 1: Image to Text (Captioning) ---")
|
| 15 |
+
try:
|
| 16 |
+
# This might work if the API treats it as captioning
|
| 17 |
+
res = client.image_to_text(
|
| 18 |
+
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true",
|
| 19 |
+
model=model
|
| 20 |
+
)
|
| 21 |
+
print("Response:", res)
|
| 22 |
+
except Exception:
|
| 23 |
+
traceback.print_exc()
|
| 24 |
+
|
| 25 |
+
print("\n--- Test 2: Text Generation (Simple) ---")
|
| 26 |
+
try:
|
| 27 |
+
res = client.text_generation("describe a car", model=model, max_new_tokens=50)
|
| 28 |
+
print("Response:", res)
|
| 29 |
+
except Exception:
|
| 30 |
+
traceback.print_exc()
|
check_llama.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "meta-llama/Llama-3.2-11B-Vision-Instruct"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Image URL (Llama Vision)
|
| 14 |
+
print("\n--- Test 1: Image URL ---")
|
| 15 |
+
try:
|
| 16 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
messages = [
|
| 18 |
+
{
|
| 19 |
+
"role": "user",
|
| 20 |
+
"content": [
|
| 21 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 22 |
+
{"type": "text", "text": "What is in this image?"}
|
| 23 |
+
]
|
| 24 |
+
}
|
| 25 |
+
]
|
| 26 |
+
completion = client.chat.completions.create(
|
| 27 |
+
model=model,
|
| 28 |
+
messages=messages,
|
| 29 |
+
max_tokens=100
|
| 30 |
+
)
|
| 31 |
+
print("Response:", completion.choices[0].message.content)
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print("Image URL failed:", e)
|
check_llava.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "llava-hf/llava-1.5-7b-hf"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Image URL
|
| 14 |
+
print("\n--- Test 1: Image URL ---")
|
| 15 |
+
try:
|
| 16 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 17 |
+
messages = [
|
| 18 |
+
{
|
| 19 |
+
"role": "user",
|
| 20 |
+
"content": [
|
| 21 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 22 |
+
{"type": "text", "text": "What is in this image?"}
|
| 23 |
+
]
|
| 24 |
+
}
|
| 25 |
+
]
|
| 26 |
+
completion = client.chat.completions.create(
|
| 27 |
+
model=model,
|
| 28 |
+
messages=messages,
|
| 29 |
+
max_tokens=100
|
| 30 |
+
)
|
| 31 |
+
print("Response:", completion.choices[0].message.content)
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print("Image URL failed:", e)
|
check_models.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import os
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 8 |
+
if not api_key:
|
| 9 |
+
print("No API key found")
|
| 10 |
+
else:
|
| 11 |
+
genai.configure(api_key=api_key)
|
| 12 |
+
print("Listing models...")
|
| 13 |
+
for m in genai.list_models():
|
| 14 |
+
if 'generateContent' in m.supported_generation_methods:
|
| 15 |
+
print(m.name)
|
check_models_list.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import os
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 8 |
+
if not api_key:
|
| 9 |
+
print("❌ API Key not found")
|
| 10 |
+
else:
|
| 11 |
+
genai.configure(api_key=api_key)
|
| 12 |
+
print("Listing available models...")
|
| 13 |
+
for m in genai.list_models():
|
| 14 |
+
if 'generateContent' in m.supported_generation_methods:
|
| 15 |
+
print(m.name)
|
check_qwen.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import InferenceClient
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
api_key = os.getenv("HF_TOKEN")
|
| 8 |
+
client = InferenceClient(api_key=api_key)
|
| 9 |
+
model = "Qwen/Qwen2-VL-7B-Instruct"
|
| 10 |
+
|
| 11 |
+
print(f"Testing model: {model}")
|
| 12 |
+
|
| 13 |
+
# Test 1: Text only
|
| 14 |
+
print("\n--- Test 1: Text Only ---")
|
| 15 |
+
try:
|
| 16 |
+
messages = [
|
| 17 |
+
{"role": "user", "content": "Hello, are you working?"}
|
| 18 |
+
]
|
| 19 |
+
completion = client.chat.completions.create(
|
| 20 |
+
model=model,
|
| 21 |
+
messages=messages,
|
| 22 |
+
max_tokens=100
|
| 23 |
+
)
|
| 24 |
+
print("Response:", completion.choices[0].message.content)
|
| 25 |
+
except Exception as e:
|
| 26 |
+
print("Text only failed:", e)
|
| 27 |
+
|
| 28 |
+
# Test 2: Image (using a public URL to avoid base64 issues first)
|
| 29 |
+
print("\n--- Test 2: Image URL ---")
|
| 30 |
+
try:
|
| 31 |
+
image_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
|
| 32 |
+
messages = [
|
| 33 |
+
{
|
| 34 |
+
"role": "user",
|
| 35 |
+
"content": [
|
| 36 |
+
{"type": "image_url", "image_url": {"url": image_url}},
|
| 37 |
+
{"type": "text", "text": "What is in this image?"}
|
| 38 |
+
]
|
| 39 |
+
}
|
| 40 |
+
]
|
| 41 |
+
completion = client.chat.completions.create(
|
| 42 |
+
model=model,
|
| 43 |
+
messages=messages,
|
| 44 |
+
max_tokens=100
|
| 45 |
+
)
|
| 46 |
+
print("Response:", completion.choices[0].message.content)
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print("Image URL failed:", e)
|
check_qwen_raw.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
api_key = os.getenv("HF_TOKEN")
|
| 9 |
+
model = "Qwen/Qwen2-VL-7B-Instruct"
|
| 10 |
+
# Update URL to router
|
| 11 |
+
url = f"https://router.huggingface.co/models/{model}"
|
| 12 |
+
|
| 13 |
+
headers = {"Authorization": f"Bearer {api_key}"}
|
| 14 |
+
|
| 15 |
+
print(f"Testing URL: {url}")
|
| 16 |
+
|
| 17 |
+
# Test 1: Simple text generation payload (inputs string)
|
| 18 |
+
data_text = {
|
| 19 |
+
"inputs": "Hello",
|
| 20 |
+
"parameters": {"max_new_tokens": 50}
|
| 21 |
+
}
|
| 22 |
+
print("\n--- Test 1: Text Generation (inputs string) ---")
|
| 23 |
+
response = requests.post(url, headers=headers, json=data_text)
|
| 24 |
+
print(f"Status: {response.status_code}")
|
| 25 |
+
print("Response:", response.text)
|
| 26 |
+
|
| 27 |
+
# Test 2: VQA format
|
| 28 |
+
data_vqa = {
|
| 29 |
+
"inputs": {
|
| 30 |
+
"image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true",
|
| 31 |
+
"question": "What is in this image?"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
print("\n--- Test 2: VQA Format ---")
|
| 35 |
+
response = requests.post(url, headers=headers, json=data_vqa)
|
| 36 |
+
print(f"Status: {response.status_code}")
|
| 37 |
+
print("Response:", response.text)
|
| 38 |
+
|
| 39 |
+
# Test 3: Chat Completions API (OpenAI style)
|
| 40 |
+
url_chat = f"https://router.huggingface.co/models/{model}/v1/chat/completions"
|
| 41 |
+
print(f"\nTesting URL: {url_chat}")
|
| 42 |
+
data_chat = {
|
| 43 |
+
"model": model, # Sometimes required in body
|
| 44 |
+
"messages": [
|
| 45 |
+
{"role": "user", "content": "Hello"}
|
| 46 |
+
],
|
| 47 |
+
"max_tokens": 50
|
| 48 |
+
}
|
| 49 |
+
print("\n--- Test 3: Chat Completion ---")
|
| 50 |
+
response = requests.post(url_chat, headers=headers, json=data_chat)
|
| 51 |
+
print(f"Status: {response.status_code}")
|
| 52 |
+
print("Response:", response.text)
|
clean_binary_deploy.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def main():
|
| 5 |
+
files_to_delete = [
|
| 6 |
+
"stitch_merchflow_ai_dashboard.zip",
|
| 7 |
+
"screen.jpg",
|
| 8 |
+
"test_image.jpg"
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
# Delete Local Binaries
|
| 12 |
+
print("Deleting local binaries...")
|
| 13 |
+
for filename in files_to_delete:
|
| 14 |
+
try:
|
| 15 |
+
os.remove(filename)
|
| 16 |
+
print(f"Deleted: {filename}")
|
| 17 |
+
except FileNotFoundError:
|
| 18 |
+
print(f"File not found (already deleted): {filename}")
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print(f"Error deleting {filename}: {e}")
|
| 21 |
+
|
| 22 |
+
# Update .gitignore
|
| 23 |
+
print("Updating .gitignore...")
|
| 24 |
+
gitignore_path = ".gitignore"
|
| 25 |
+
|
| 26 |
+
# Read existing content to avoid duplicate entries
|
| 27 |
+
existing_ignores = []
|
| 28 |
+
try:
|
| 29 |
+
if os.path.exists(gitignore_path):
|
| 30 |
+
with open(gitignore_path, "r", encoding="utf-8") as f:
|
| 31 |
+
existing_ignores = f.read().splitlines()
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print(f"Warning: Could not read .gitignore: {e}")
|
| 34 |
+
|
| 35 |
+
ignores_to_add = ["*.zip", "*.jpg"]
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
with open(gitignore_path, "a", encoding="utf-8") as f:
|
| 39 |
+
for ignore in ignores_to_add:
|
| 40 |
+
if ignore not in existing_ignores:
|
| 41 |
+
# add a newline if files doesn't end with one, but simplier to just write
|
| 42 |
+
f.write(f"\n{ignore}\n")
|
| 43 |
+
print(f"Added '{ignore}' to .gitignore")
|
| 44 |
+
else:
|
| 45 |
+
print(f"'{ignore}' already in .gitignore")
|
| 46 |
+
except Exception as e:
|
| 47 |
+
print(f"Error updating .gitignore: {e}")
|
| 48 |
+
|
| 49 |
+
# The Orphan Branch Strategy
|
| 50 |
+
print("\nExecuting Git Orphan Branch Strategy...")
|
| 51 |
+
|
| 52 |
+
commands = [
|
| 53 |
+
"git checkout --orphan hf_clean_deploy_v2",
|
| 54 |
+
"git add .",
|
| 55 |
+
'git commit -m "UI Update: Glassmorphism Design & Binary Cleanup"'
|
| 56 |
+
]
|
| 57 |
+
|
| 58 |
+
for cmd in commands:
|
| 59 |
+
print(f"Running: {cmd}")
|
| 60 |
+
subprocess.run(cmd, shell=True, check=False)
|
| 61 |
+
|
| 62 |
+
push_cmd = "git push --force space hf_clean_deploy_v2:main"
|
| 63 |
+
print(f"\nRunning final push command: {push_cmd}")
|
| 64 |
+
|
| 65 |
+
result = subprocess.run(push_cmd, shell=True, capture_output=True, text=True)
|
| 66 |
+
|
| 67 |
+
# Expected Output
|
| 68 |
+
print("\n--- Push Command STDOUT ---")
|
| 69 |
+
print(result.stdout)
|
| 70 |
+
print("--- Push Command STDERR ---")
|
| 71 |
+
print(result.stderr)
|
| 72 |
+
print("---------------------------\n")
|
| 73 |
+
|
| 74 |
+
if result.returncode == 0:
|
| 75 |
+
print("✅ Force push successful!")
|
| 76 |
+
else:
|
| 77 |
+
print("❌ Force push failed (see STDERR above).")
|
| 78 |
+
|
| 79 |
+
if __name__ == "__main__":
|
| 80 |
+
main()
|
code.html
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="dark" lang="en"><head>
|
| 3 |
+
<meta charset="utf-8"/>
|
| 4 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 5 |
+
<title>MerchFlow AI Dashboard</title>
|
| 6 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 7 |
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
| 8 |
+
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Spline+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
"primary": "#3b82f6", // Electric Blue
|
| 18 |
+
"primary-hover": "#2563eb",
|
| 19 |
+
"secondary": "#6366f1", // Indigo accent
|
| 20 |
+
"background-light": "#f8fafc",
|
| 21 |
+
"background-dark": "#0f172a", // Deep Slate
|
| 22 |
+
"surface-dark": "#1e293b", // Slate 800
|
| 23 |
+
"surface-darker": "#020617", // Slate 950
|
| 24 |
+
"border-dark": "#334155", // Slate 700
|
| 25 |
+
},
|
| 26 |
+
fontFamily: {
|
| 27 |
+
"display": ["Spline Sans", "sans-serif"],
|
| 28 |
+
"mono": ["monospace"]
|
| 29 |
+
},
|
| 30 |
+
borderRadius: {"DEFAULT": "1rem", "lg": "2rem", "xl": "3rem", "full": "9999px"},
|
| 31 |
+
},
|
| 32 |
+
},
|
| 33 |
+
}
|
| 34 |
+
</script>
|
| 35 |
+
<style>.custom-scrollbar::-webkit-scrollbar {
|
| 36 |
+
width: 8px;
|
| 37 |
+
height: 8px;
|
| 38 |
+
}
|
| 39 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 40 |
+
background: #020617;
|
| 41 |
+
}
|
| 42 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 43 |
+
background: #334155;
|
| 44 |
+
border-radius: 4px;
|
| 45 |
+
}
|
| 46 |
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
| 47 |
+
background: #3b82f6;
|
| 48 |
+
}
|
| 49 |
+
</style>
|
| 50 |
+
</head>
|
| 51 |
+
<body class="font-display bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased overflow-hidden h-screen flex flex-col">
|
| 52 |
+
<header class="flex-none flex items-center justify-between whitespace-nowrap border-b border-solid border-border-dark px-6 py-4 bg-background-dark z-10">
|
| 53 |
+
<div class="flex items-center gap-3 text-white">
|
| 54 |
+
<div class="flex items-center justify-center size-10 rounded-full bg-primary/10 text-primary">
|
| 55 |
+
<span class="material-symbols-outlined text-2xl">all_inclusive</span>
|
| 56 |
+
</div>
|
| 57 |
+
<div>
|
| 58 |
+
<h2 class="text-white text-xl font-bold leading-tight tracking-tight">MerchFlow AI</h2>
|
| 59 |
+
<span class="text-xs text-primary/60 font-medium uppercase tracking-wider">Enterprise Edition</span>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
<button class="flex min-w-[84px] cursor-pointer items-center justify-center overflow-hidden rounded-full h-10 px-6 bg-primary hover:bg-primary-hover transition-colors text-white text-sm font-bold leading-normal tracking-[0.015em]">
|
| 63 |
+
<span class="material-symbols-outlined mr-2 text-lg">rocket_launch</span>
|
| 64 |
+
<span class="truncate">Deploy</span>
|
| 65 |
+
</button>
|
| 66 |
+
</header>
|
| 67 |
+
<main class="flex-1 flex overflow-hidden">
|
| 68 |
+
<div class="flex-1 flex flex-col border-r border-border-dark min-w-[400px] overflow-y-auto custom-scrollbar p-8">
|
| 69 |
+
<div class="max-w-2xl w-full mx-auto flex flex-col gap-6 h-full">
|
| 70 |
+
<div class="flex items-center justify-between">
|
| 71 |
+
<h2 class="text-white tracking-light text-[28px] font-bold leading-tight">Input Data</h2>
|
| 72 |
+
<span class="bg-surface-dark text-white px-3 py-1 rounded-full text-xs font-medium border border-border-dark">Step 1 of 2</span>
|
| 73 |
+
</div>
|
| 74 |
+
<div class="flex flex-col flex-1 max-h-[300px] min-h-[200px]">
|
| 75 |
+
<div class="group relative flex flex-col items-center justify-center gap-4 rounded-xl border-2 border-dashed border-slate-600 hover:border-primary/50 hover:bg-surface-dark transition-all cursor-pointer h-full w-full px-6 py-8">
|
| 76 |
+
<div class="size-16 rounded-full bg-surface-dark group-hover:bg-primary/20 flex items-center justify-center transition-colors border border-border-dark group-hover:border-primary/30">
|
| 77 |
+
<span class="material-symbols-outlined text-3xl text-primary">cloud_upload</span>
|
| 78 |
+
</div>
|
| 79 |
+
<div class="flex flex-col items-center gap-1">
|
| 80 |
+
<p class="text-white text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 81 |
+
<p class="text-slate-400 text-sm font-normal text-center">Supports JPG, PNG, WEBP</p>
|
| 82 |
+
</div>
|
| 83 |
+
<button class="mt-2 flex items-center justify-center rounded-full h-9 px-4 bg-slate-700 hover:bg-slate-600 text-white text-xs font-bold transition-colors">
|
| 84 |
+
Browse Files
|
| 85 |
+
</button>
|
| 86 |
+
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/10 pointer-events-none rounded-xl"></div>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
<div class="flex flex-col gap-3 flex-1">
|
| 90 |
+
<label class="flex items-center justify-between">
|
| 91 |
+
<span class="text-white text-base font-medium">Raw Product Specs</span>
|
| 92 |
+
<span class="text-xs text-slate-400">JSON or Plain Text</span>
|
| 93 |
+
</label>
|
| 94 |
+
<div class="relative flex-1">
|
| 95 |
+
<textarea class="form-input w-full h-full resize-none rounded-xl text-white placeholder:text-slate-500 focus:outline-0 focus:ring-2 focus:ring-primary/50 border border-border-dark bg-surface-dark p-4 text-base font-normal leading-relaxed font-mono" placeholder="Enter fabric details, dimensions, and SKU...
|
| 96 |
+
Example:
|
| 97 |
+
Material: 100% Recycled Polyester
|
| 98 |
+
Fit: Regular
|
| 99 |
+
SKU: JK-2024-WTR"></textarea>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
<div class="pt-2">
|
| 103 |
+
<button class="group relative w-full cursor-pointer overflow-hidden rounded-xl h-16 bg-gradient-to-r from-primary to-blue-700 hover:from-blue-500 hover:to-primary transition-all shadow-[0_0_20px_rgba(59,130,246,0.3)]">
|
| 104 |
+
<div class="absolute inset-0 flex items-center justify-center gap-3">
|
| 105 |
+
<span class="text-white text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span>
|
| 106 |
+
<span class="material-symbols-outlined text-white group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
| 107 |
+
</div>
|
| 108 |
+
<div class="absolute top-0 -inset-full h-full w-1/2 z-5 block transform -skew-x-12 bg-gradient-to-r from-transparent to-white opacity-20 group-hover:animate-shine"></div>
|
| 109 |
+
</button>
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
<div class="flex-1 flex flex-col bg-surface-darker p-8 overflow-hidden relative">
|
| 114 |
+
<div class="absolute inset-0 opacity-5 pointer-events-none" style="background-image: radial-gradient(#64748b 1px, transparent 1px); background-size: 24px 24px;"></div>
|
| 115 |
+
<div class="max-w-3xl w-full mx-auto flex flex-col gap-6 h-full relative z-10">
|
| 116 |
+
<div class="flex items-center justify-between">
|
| 117 |
+
<h2 class="text-white tracking-light text-[28px] font-bold leading-tight">Generated Output</h2>
|
| 118 |
+
<div class="flex gap-2">
|
| 119 |
+
<button class="p-2 hover:bg-surface-dark rounded-lg text-slate-400 hover:text-white transition-colors">
|
| 120 |
+
<span class="material-symbols-outlined text-xl">content_copy</span>
|
| 121 |
+
</button>
|
| 122 |
+
<button class="p-2 hover:bg-surface-dark rounded-lg text-slate-400 hover:text-white transition-colors">
|
| 123 |
+
<span class="material-symbols-outlined text-xl">download</span>
|
| 124 |
+
</button>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
| 128 |
+
<div class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 129 |
+
<div class="relative size-3">
|
| 130 |
+
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-purple-500 opacity-75"></span>
|
| 131 |
+
<span class="relative inline-flex rounded-full size-3 bg-purple-500"></span>
|
| 132 |
+
</div>
|
| 133 |
+
<div class="flex flex-col overflow-hidden">
|
| 134 |
+
<span class="text-xs text-slate-400 truncate">Vision Agent</span>
|
| 135 |
+
<span class="text-sm font-bold text-white truncate">Gemini Pro 1.5</span>
|
| 136 |
+
</div>
|
| 137 |
+
<span class="material-symbols-outlined text-purple-500 ml-auto text-lg">visibility</span>
|
| 138 |
+
</div>
|
| 139 |
+
<div class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 140 |
+
<div class="relative size-3">
|
| 141 |
+
<span class="relative inline-flex rounded-full size-3 bg-primary"></span>
|
| 142 |
+
</div>
|
| 143 |
+
<div class="flex flex-col overflow-hidden">
|
| 144 |
+
<span class="text-xs text-slate-400 truncate">Reasoning Agent</span>
|
| 145 |
+
<span class="text-sm font-bold text-white truncate">Llama 3 70B</span>
|
| 146 |
+
</div>
|
| 147 |
+
<span class="material-symbols-outlined text-primary ml-auto text-lg">psychology</span>
|
| 148 |
+
</div>
|
| 149 |
+
<div class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm">
|
| 150 |
+
<div class="relative size-3">
|
| 151 |
+
<span class="relative inline-flex rounded-full size-3 bg-cyan-500"></span>
|
| 152 |
+
</div>
|
| 153 |
+
<div class="flex flex-col overflow-hidden">
|
| 154 |
+
<span class="text-xs text-slate-400 truncate">SEO Context</span>
|
| 155 |
+
<span class="text-sm font-bold text-white truncate">Pinecone DB</span>
|
| 156 |
+
</div>
|
| 157 |
+
<span class="material-symbols-outlined text-cyan-500 ml-auto text-lg">database</span>
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
<div class="flex-1 rounded-xl bg-[#0d1117] border border-border-dark flex flex-col overflow-hidden shadow-2xl">
|
| 161 |
+
<div class="flex items-center justify-between px-4 py-2 bg-surface-dark border-b border-border-dark">
|
| 162 |
+
<span class="text-xs font-mono text-slate-400">output.json</span>
|
| 163 |
+
<div class="flex gap-1.5">
|
| 164 |
+
<div class="size-2.5 rounded-full bg-red-500/20"></div>
|
| 165 |
+
<div class="size-2.5 rounded-full bg-yellow-500/20"></div>
|
| 166 |
+
<div class="size-2.5 rounded-full bg-green-500/20"></div>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="flex-1 p-4 overflow-auto custom-scrollbar font-mono text-sm leading-6">
|
| 170 |
+
<pre><code class="language-json"><span class="text-slate-500">1</span> <span class="text-yellow-500">{</span>
|
| 171 |
+
<span class="text-slate-500">2</span> <span class="text-primary">"product_analysis"</span><span class="text-white">:</span> <span class="text-yellow-500">{</span>
|
| 172 |
+
<span class="text-slate-500">3</span> <span class="text-primary">"title"</span><span class="text-white">:</span> <span class="text-sky-300">"Apex Terrain All-Weather Performance Jacket"</span><span class="text-white">,</span>
|
| 173 |
+
<span class="text-slate-500">4</span> <span class="text-primary">"category"</span><span class="text-white">:</span> <span class="text-sky-300">"Outerwear / Men's / Technical Shells"</span><span class="text-white">,</span>
|
| 174 |
+
<span class="text-slate-500">5</span> <span class="text-primary">"features"</span><span class="text-white">:</span> <span class="text-yellow-500">[</span>
|
| 175 |
+
<span class="text-slate-500">6</span> <span class="text-sky-300">"Gore-Tex Pro Membrane"</span><span class="text-white">,</span>
|
| 176 |
+
<span class="text-slate-500">7</span> <span class="text-sky-300">"Articulated Sleeves"</span><span class="text-white">,</span>
|
| 177 |
+
<span class="text-slate-500">8</span> <span class="text-sky-300">"Helmet-Compatible Hood"</span>
|
| 178 |
+
<span class="text-slate-500">9</span> <span class="text-yellow-500">]</span><span class="text-white">,</span>
|
| 179 |
+
<span class="text-slate-500">10</span> <span class="text-primary">"seo_tags"</span><span class="text-white">:</span> <span class="text-yellow-500">[</span>
|
| 180 |
+
<span class="text-slate-500">11</span> <span class="text-sky-300">"#hikinggear"</span><span class="text-white">,</span> <span class="text-sky-300">"#waterproof"</span><span class="text-white">,</span> <span class="text-sky-300">"#adventure"</span>
|
| 181 |
+
<span class="text-slate-500">12</span> <span class="text-yellow-500">]</span><span class="text-white">,</span>
|
| 182 |
+
<span class="text-slate-500">13</span> <span class="text-primary">"sentiment_score"</span><span class="text-white">:</span> <span class="text-purple-400">0.98</span><span class="text-white">,</span>
|
| 183 |
+
<span class="text-slate-500">14</span> <span class="text-primary">"market_fit"</span><span class="text-white">:</span> <span class="text-sky-300">"High Demand"</span>
|
| 184 |
+
<span class="text-slate-500">15</span> <span class="text-yellow-500">}</span><span class="text-white">,</span>
|
| 185 |
+
<span class="text-slate-500">16</span> <span class="text-primary">"deployment_status"</span><span class="text-white">:</span> <span class="text-sky-300">"Ready"</span>
|
| 186 |
+
<span class="text-slate-500">17</span> <span class="text-yellow-500">}</span></code></pre>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
</main>
|
| 192 |
+
<script>
|
| 193 |
+
tailwind.config.theme.extend.animation = {
|
| 194 |
+
shine: 'shine 1s',
|
| 195 |
+
}
|
| 196 |
+
tailwind.config.theme.extend.keyframes = {
|
| 197 |
+
shine: {
|
| 198 |
+
'100%': { left: '125%' },
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
</script>
|
| 202 |
+
|
| 203 |
+
</body></html>
|
connect_n8n.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
def update_requirements():
|
| 5 |
+
req_file = "requirements.txt"
|
| 6 |
+
if not os.path.exists(req_file):
|
| 7 |
+
with open(req_file, "w") as f:
|
| 8 |
+
f.write("httpx\n")
|
| 9 |
+
print(f"Created {req_file} with httpx.")
|
| 10 |
+
return
|
| 11 |
+
|
| 12 |
+
with open(req_file, "r") as f:
|
| 13 |
+
content = f.read()
|
| 14 |
+
|
| 15 |
+
if "httpx" not in content:
|
| 16 |
+
with open(req_file, "a") as f:
|
| 17 |
+
f.write("\nhttpx\n")
|
| 18 |
+
print("Appended httpx to requirements.txt.")
|
| 19 |
+
else:
|
| 20 |
+
print("httpx already in requirements.txt.")
|
| 21 |
+
|
| 22 |
+
def update_main():
|
| 23 |
+
main_content = r'''import os
|
| 24 |
+
import httpx
|
| 25 |
+
import asyncio
|
| 26 |
+
from fastapi import FastAPI, UploadFile, File
|
| 27 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 28 |
+
from dotenv import load_dotenv
|
| 29 |
+
# Import Agents
|
| 30 |
+
from agents.visual_analyst import VisualAnalyst
|
| 31 |
+
from agents.memory_agent import MemoryAgent
|
| 32 |
+
from agents.writer_agent import WriterAgent
|
| 33 |
+
load_dotenv()
|
| 34 |
+
app = FastAPI()
|
| 35 |
+
# Initialize Agents
|
| 36 |
+
try:
|
| 37 |
+
visual_agent = VisualAnalyst()
|
| 38 |
+
memory_agent = MemoryAgent()
|
| 39 |
+
writer_agent = WriterAgent()
|
| 40 |
+
memory_agent.seed_database()
|
| 41 |
+
print("✅ All Agents Online")
|
| 42 |
+
except Exception as e:
|
| 43 |
+
print(f"⚠️ Agent Startup Warning: {e}")
|
| 44 |
+
@app.get("/", response_class=HTMLResponse)
|
| 45 |
+
async def read_root():
|
| 46 |
+
try:
|
| 47 |
+
with open("dashboard.html", "r") as f:
|
| 48 |
+
return f.read()
|
| 49 |
+
except FileNotFoundError:
|
| 50 |
+
return "<h1>Error: dashboard.html not found</h1>"
|
| 51 |
+
@app.post("/generate-catalog")
|
| 52 |
+
async def generate_catalog(file: UploadFile = File(...)):
|
| 53 |
+
try:
|
| 54 |
+
# 1. Save Temp File
|
| 55 |
+
os.makedirs("uploads", exist_ok=True)
|
| 56 |
+
file_path = f"uploads/{file.filename}"
|
| 57 |
+
with open(file_path, "wb") as f:
|
| 58 |
+
f.write(await file.read())
|
| 59 |
+
# 2. Run AI Pipeline
|
| 60 |
+
visual_data = await visual_agent.analyze_image(file_path)
|
| 61 |
+
|
| 62 |
+
query = f"{visual_data.get('main_color', '')} {visual_data.get('product_type', 'product')}"
|
| 63 |
+
seo_keywords = memory_agent.retrieve_keywords(query)
|
| 64 |
+
|
| 65 |
+
listing = writer_agent.write_listing(visual_data, seo_keywords)
|
| 66 |
+
|
| 67 |
+
# 3. Construct Final Payload
|
| 68 |
+
final_data = {
|
| 69 |
+
"visual_data": visual_data,
|
| 70 |
+
"seo_keywords": seo_keywords,
|
| 71 |
+
"listing": listing
|
| 72 |
+
}
|
| 73 |
+
# 4. ⚡ N8N AUTOMATION TRIGGER ⚡
|
| 74 |
+
n8n_url = os.getenv("N8N_WEBHOOK_URL")
|
| 75 |
+
if n8n_url:
|
| 76 |
+
print(f"🚀 Sending data to N8N: {n8n_url}")
|
| 77 |
+
# Fire and forget (don't make the user wait for n8n)
|
| 78 |
+
asyncio.create_task(send_to_n8n(n8n_url, final_data))
|
| 79 |
+
|
| 80 |
+
# Cleanup
|
| 81 |
+
if os.path.exists(file_path):
|
| 82 |
+
os.remove(file_path)
|
| 83 |
+
|
| 84 |
+
return JSONResponse(content=final_data)
|
| 85 |
+
except Exception as e:
|
| 86 |
+
return JSONResponse(content={"error": str(e)}, status_code=500)
|
| 87 |
+
# Async Helper to send data without blocking
|
| 88 |
+
async def send_to_n8n(url, data):
|
| 89 |
+
try:
|
| 90 |
+
async with httpx.AsyncClient() as client:
|
| 91 |
+
await client.post(url, json=data, timeout=5.0)
|
| 92 |
+
print("✅ N8N Webhook Sent Successfully")
|
| 93 |
+
except Exception as e:
|
| 94 |
+
print(f"❌ N8N Webhook Failed: {e}")
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
import uvicorn
|
| 97 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 98 |
+
'''
|
| 99 |
+
with open("main.py", "w", encoding="utf-8") as f:
|
| 100 |
+
f.write(main_content)
|
| 101 |
+
print("Updated main.py with N8N integration logic.")
|
| 102 |
+
|
| 103 |
+
def deploy():
|
| 104 |
+
try:
|
| 105 |
+
subprocess.run(["git", "add", "."], check=True)
|
| 106 |
+
# Check if there are changes to commit
|
| 107 |
+
status = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True)
|
| 108 |
+
if status.stdout.strip():
|
| 109 |
+
subprocess.run(["git", "commit", "-m", "Add N8N Integration"], check=True)
|
| 110 |
+
print("Git commit successful.")
|
| 111 |
+
else:
|
| 112 |
+
print("No changes to commit.")
|
| 113 |
+
|
| 114 |
+
print("Pushing to space...")
|
| 115 |
+
subprocess.run(["git", "push", "space", "clean_deploy:main"], check=True)
|
| 116 |
+
print("✅ Successfully deployed to Hugging Face Space.")
|
| 117 |
+
|
| 118 |
+
except subprocess.CalledProcessError as e:
|
| 119 |
+
print(f"❌ Deployment failed: {e}")
|
| 120 |
+
|
| 121 |
+
if __name__ == "__main__":
|
| 122 |
+
print("Starting N8N Integration Setup...")
|
| 123 |
+
update_requirements()
|
| 124 |
+
update_main()
|
| 125 |
+
deploy()
|
| 126 |
+
print("✅ connect_n8n.py completed.")
|
create_dockerfile.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
def run_command(command):
|
| 5 |
+
print(f"Running: {command}")
|
| 6 |
+
try:
|
| 7 |
+
# shell=True allows us to run the command string exactly as provided
|
| 8 |
+
subprocess.run(command, shell=True, check=True)
|
| 9 |
+
except subprocess.CalledProcessError as e:
|
| 10 |
+
print(f"Error executing command '{command}': {e}")
|
| 11 |
+
sys.exit(1)
|
| 12 |
+
|
| 13 |
+
def main():
|
| 14 |
+
# 1. Create Dockerfile
|
| 15 |
+
dockerfile_content = """FROM python:3.9
|
| 16 |
+
WORKDIR /code
|
| 17 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 18 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 19 |
+
COPY . /code
|
| 20 |
+
# Fix permissions for libraries that write to home
|
| 21 |
+
RUN mkdir -p /tmp/home
|
| 22 |
+
ENV HOME=/tmp/home
|
| 23 |
+
# Start the FastAPI server on port 7860 (required by Hugging Face)
|
| 24 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
print("Creating Dockerfile...")
|
| 28 |
+
try:
|
| 29 |
+
with open("Dockerfile", "w", newline='\n') as f:
|
| 30 |
+
f.write(dockerfile_content)
|
| 31 |
+
print("Dockerfile created successfully.")
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print(f"Failed to create Dockerfile: {e}")
|
| 34 |
+
sys.exit(1)
|
| 35 |
+
|
| 36 |
+
# 2. Push to Space
|
| 37 |
+
print("Executing Git commands...")
|
| 38 |
+
commands = [
|
| 39 |
+
'git add Dockerfile',
|
| 40 |
+
'git commit -m "Add Dockerfile for Hugging Face deployment"',
|
| 41 |
+
'git push -f space clean_deploy:main'
|
| 42 |
+
]
|
| 43 |
+
|
| 44 |
+
for cmd in commands:
|
| 45 |
+
run_command(cmd)
|
| 46 |
+
|
| 47 |
+
print("\ncreate_dockerfile.py execution completed.")
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
main()
|
dashboard.html
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="dark" lang="en"><head>
|
| 3 |
+
<meta charset="utf-8"/>
|
| 4 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 5 |
+
<title>MerchFlow AI Dashboard</title>
|
| 6 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 7 |
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
| 8 |
+
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
"primary": "#f59e0b",
|
| 18 |
+
"primary-hover": "#d97706",
|
| 19 |
+
"glass-border": "rgba(255, 255, 255, 0.12)",
|
| 20 |
+
"glass-surface": "rgba(255, 255, 255, 0.03)",
|
| 21 |
+
"glass-surface-hover": "rgba(255, 255, 255, 0.07)",
|
| 22 |
+
"charcoal": "#0a0a0a",
|
| 23 |
+
"amber-accent": "#fbbf24"
|
| 24 |
+
},
|
| 25 |
+
fontFamily: {
|
| 26 |
+
"display": ["Plus Jakarta Sans", "sans-serif"],
|
| 27 |
+
"mono": ["monospace"]
|
| 28 |
+
},
|
| 29 |
+
borderRadius: { "DEFAULT": "1rem", "lg": "1.5rem", "xl": "2rem", "2xl": "3rem", "full": "9999px" },
|
| 30 |
+
backdropBlur: {
|
| 31 |
+
'xs': '2px',
|
| 32 |
+
},
|
| 33 |
+
screens: {
|
| 34 |
+
'xs': '480px',
|
| 35 |
+
},
|
| 36 |
+
animation: {
|
| 37 |
+
'pulse-slow': 'pulse-slow 3s infinite',
|
| 38 |
+
'glow-pulse': 'glow-pulse 3s infinite',
|
| 39 |
+
'copy-success': 'copy-success 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
|
| 40 |
+
},
|
| 41 |
+
keyframes: {
|
| 42 |
+
'pulse-slow': {
|
| 43 |
+
'0%, 100%': { transform: 'scale(1)' },
|
| 44 |
+
'50%': { transform: 'scale(1.02)' },
|
| 45 |
+
},
|
| 46 |
+
'glow-pulse': {
|
| 47 |
+
'0%, 100%': { boxShadow: '0 0 0 0 rgba(245, 158, 11, 0.4)' },
|
| 48 |
+
'50%': { boxShadow: '0 0 20px 5px rgba(251, 191, 36, 0.5)' },
|
| 49 |
+
},
|
| 50 |
+
'copy-success': {
|
| 51 |
+
'0%': { transform: 'scale(0.8)', opacity: '0' },
|
| 52 |
+
'100%': { transform: 'scale(1)', opacity: '1' },
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
},
|
| 57 |
+
}
|
| 58 |
+
</script>
|
| 59 |
+
<style type="text/tailwindcss">
|
| 60 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 61 |
+
width: 6px;
|
| 62 |
+
height: 6px;
|
| 63 |
+
}
|
| 64 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 65 |
+
background: transparent;
|
| 66 |
+
}
|
| 67 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 68 |
+
background: rgba(255, 255, 255, 0.1);
|
| 69 |
+
border-radius: 99px;
|
| 70 |
+
}
|
| 71 |
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
| 72 |
+
background: rgba(255, 255, 255, 0.2);
|
| 73 |
+
}
|
| 74 |
+
body {
|
| 75 |
+
background-color: #0a0a0a;
|
| 76 |
+
background-image:
|
| 77 |
+
radial-gradient(at 0% 0%, rgba(20, 20, 20, 1) 0, transparent 50%),
|
| 78 |
+
radial-gradient(at 50% -10%, rgba(217, 119, 6, 0.15) 0, transparent 60%),
|
| 79 |
+
radial-gradient(at 100% 0%, rgba(251, 191, 36, 0.1) 0, transparent 50%),
|
| 80 |
+
radial-gradient(at 80% 80%, rgba(217, 119, 6, 0.12) 0, transparent 50%);
|
| 81 |
+
background-attachment: fixed;
|
| 82 |
+
min-height: 100vh;
|
| 83 |
+
}
|
| 84 |
+
.glass-panel {
|
| 85 |
+
background: rgba(15, 15, 15, 0.75);
|
| 86 |
+
backdrop-filter: blur(20px);
|
| 87 |
+
-webkit-backdrop-filter: blur(20px);
|
| 88 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 89 |
+
}
|
| 90 |
+
@media (max-width: 768px) {
|
| 91 |
+
.glass-panel {
|
| 92 |
+
backdrop-filter: blur(14px);
|
| 93 |
+
-webkit-backdrop-filter: blur(14px);
|
| 94 |
+
background: rgba(10, 10, 10, 0.85);
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
.glass-card {
|
| 98 |
+
background: rgba(255, 255, 255, 0.02);
|
| 99 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 100 |
+
backdrop-filter: blur(12px);
|
| 101 |
+
transition: all 0.3s ease;
|
| 102 |
+
}
|
| 103 |
+
@media (hover: hover) {
|
| 104 |
+
.glass-card:hover {
|
| 105 |
+
border-color: rgba(251, 191, 36, 0.3);
|
| 106 |
+
box-shadow: 0 0 25px rgba(245, 158, 11, 0.15);
|
| 107 |
+
background: rgba(255, 255, 255, 0.04);
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
.neon-textarea {
|
| 111 |
+
background: rgba(255, 255, 255, 0.03);
|
| 112 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 113 |
+
backdrop-filter: blur(16px);
|
| 114 |
+
-webkit-backdrop-filter: blur(16px);
|
| 115 |
+
transition: all 0.3s ease;
|
| 116 |
+
}
|
| 117 |
+
.neon-textarea:focus {
|
| 118 |
+
outline: none;
|
| 119 |
+
border-color: rgba(251, 191, 36, 0.5);
|
| 120 |
+
box-shadow: 0 0 15px rgba(245, 158, 11, 0.25), inset 0 0 5px rgba(245, 158, 11, 0.05);
|
| 121 |
+
background: rgba(255, 255, 255, 0.06);
|
| 122 |
+
}
|
| 123 |
+
.copy-active {
|
| 124 |
+
box-shadow: 0 0 15px rgba(251, 191, 36, 0.6) !important;
|
| 125 |
+
border-color: rgba(251, 191, 36, 0.4) !important;
|
| 126 |
+
background: rgba(251, 191, 36, 0.1) !important;
|
| 127 |
+
}
|
| 128 |
+
</style>
|
| 129 |
+
<style>
|
| 130 |
+
body {
|
| 131 |
+
min-height: max(884px, 100dvh);
|
| 132 |
+
}
|
| 133 |
+
</style>
|
| 134 |
+
</head>
|
| 135 |
+
<body class="font-display text-white antialiased overflow-x-hidden min-h-screen flex flex-col selection:bg-amber-500 selection:text-black">
|
| 136 |
+
<header class="flex-none z-30 px-4 py-3 lg:px-6 lg:py-4 border-b border-white/5 glass-panel sticky top-0">
|
| 137 |
+
<div class="max-w-[1600px] mx-auto flex items-center justify-between gap-4">
|
| 138 |
+
<div class="flex items-center gap-3 lg:gap-4 flex-1 min-w-0">
|
| 139 |
+
<div class="flex-none flex items-center justify-center size-9 lg:size-10 rounded-xl bg-gradient-to-br from-amber-400 to-amber-700 shadow-lg shadow-amber-900/20 text-white">
|
| 140 |
+
<span class="material-symbols-outlined text-xl lg:text-2xl">all_inclusive</span>
|
| 141 |
+
</div>
|
| 142 |
+
<div class="min-w-0">
|
| 143 |
+
<h2 class="text-white text-base lg:text-xl font-bold leading-tight tracking-tight truncate">MerchFlow AI</h2>
|
| 144 |
+
<div class="flex items-center gap-2">
|
| 145 |
+
<span class="size-1.5 rounded-full bg-amber-500 animate-pulse flex-none"></span>
|
| 146 |
+
<span class="text-[10px] text-amber-200/60 font-bold uppercase tracking-wider truncate">Enterprise Edition</span>
|
| 147 |
+
</div>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
<button class="group relative flex-none flex cursor-pointer items-center justify-center overflow-hidden rounded-full h-9 lg:h-10 px-4 lg:px-6 bg-gradient-to-r from-amber-600 to-amber-800 hover:from-amber-500 hover:to-amber-700 border border-white/10 shadow-[0_0_15px_rgba(245,158,11,0.3)] transition-all" id="deployBtn">
|
| 151 |
+
<div class="absolute inset-0 bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
| 152 |
+
<span class="material-symbols-outlined lg:mr-2 text-lg relative z-10">rocket_launch</span>
|
| 153 |
+
<span class="hidden lg:inline truncate text-sm font-bold tracking-wide relative z-10">Deploy</span>
|
| 154 |
+
</button>
|
| 155 |
+
</div>
|
| 156 |
+
</header>
|
| 157 |
+
<main class="flex-1 flex flex-col lg:flex-row relative z-10">
|
| 158 |
+
<div class="fixed top-1/4 -left-20 w-64 h-64 lg:w-96 lg:h-96 bg-amber-900/10 rounded-full blur-[120px] pointer-events-none z-0"></div>
|
| 159 |
+
<div class="fixed bottom-1/4 -right-20 w-64 h-64 lg:w-96 lg:h-96 bg-orange-900/5 rounded-full blur-[120px] pointer-events-none z-0"></div>
|
| 160 |
+
<div class="flex-none lg:flex-1 flex flex-col lg:border-r border-white/5 p-4 lg:p-8 bg-black/40 lg:overflow-y-auto lg:h-[calc(100vh-73px)] relative z-10">
|
| 161 |
+
<div class="max-w-xl w-full mx-auto flex flex-col gap-4 lg:gap-6 h-full">
|
| 162 |
+
<div class="flex items-center justify-between mb-2">
|
| 163 |
+
<h2 class="text-white text-xl lg:text-3xl font-bold tracking-tight">Input Data</h2>
|
| 164 |
+
<span class="bg-white/5 text-amber-200/80 px-2.5 py-1 rounded-full text-[10px] lg:text-xs font-semibold border border-white/10 backdrop-blur-sm">Step 1 of 2</span>
|
| 165 |
+
</div>
|
| 166 |
+
<div class="flex flex-col flex-none">
|
| 167 |
+
<input accept=".jpg,.jpeg,.png,.webp" class="hidden" id="fileInput" type="file"/>
|
| 168 |
+
<div class="group relative flex flex-col items-center justify-center gap-3 lg:gap-4 rounded-2xl lg:rounded-3xl border-2 border-dashed border-white/10 hover:border-amber-500/50 hover:bg-white/5 transition-all cursor-pointer min-h-[200px] lg:min-h-[260px] px-4 py-6 lg:px-6 lg:py-8 glass-card" id="dropZone">
|
| 169 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 170 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 171 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 172 |
+
</div>
|
| 173 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 174 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 175 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 176 |
+
</div>
|
| 177 |
+
<button class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide" id="browseBtn">
|
| 178 |
+
Browse Files
|
| 179 |
+
</button>
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
<div class="flex flex-col gap-2 lg:gap-3 flex-1">
|
| 183 |
+
<label class="flex items-center justify-between px-1">
|
| 184 |
+
<span class="text-white text-sm lg:text-base font-semibold">Raw Product Specs</span>
|
| 185 |
+
<span class="text-[10px] lg:text-xs text-neutral-500 font-medium bg-black/40 px-2 py-0.5 rounded border border-white/5">JSON or Plain Text</span>
|
| 186 |
+
</label>
|
| 187 |
+
<div class="relative w-full">
|
| 188 |
+
<textarea class="form-input w-full min-h-[140px] lg:h-full lg:min-h-[180px] resize-y lg:resize-none rounded-xl lg:rounded-2xl text-neutral-100 placeholder:text-neutral-600 neon-textarea p-4 lg:p-5 text-sm leading-relaxed font-mono shadow-inner transition-all duration-300" id="productSpecs" placeholder="// Enter fabric details, dimensions, and SKU..."></textarea>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="pt-2 pb-6 lg:pb-0">
|
| 192 |
+
<button class="group relative w-full cursor-pointer overflow-hidden rounded-xl lg:rounded-2xl h-12 lg:h-14 bg-gradient-to-r from-amber-600 via-amber-700 to-amber-800 hover:from-amber-500 hover:to-amber-700 transition-all border border-white/10 animate-pulse-slow animate-glow-pulse" id="startBtn">
|
| 193 |
+
<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3 relative z-10">
|
| 194 |
+
<span class="text-white text-base lg:text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span>
|
| 195 |
+
<span class="material-symbols-outlined text-white text-lg lg:text-xl group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
| 196 |
+
</div>
|
| 197 |
+
</button>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
<div class="flex-none lg:flex-1 flex flex-col p-4 lg:p-8 bg-black/60 lg:overflow-y-auto lg:h-[calc(100vh-73px)] relative">
|
| 202 |
+
<div class="absolute inset-0 opacity-[0.02] pointer-events-none" style="background-image: linear-gradient(to right, #ffffff 1px, transparent 1px), linear-gradient(to bottom, #ffffff 1px, transparent 1px); background-size: 40px 40px;"></div>
|
| 203 |
+
<div class="max-w-3xl w-full mx-auto flex flex-col gap-4 lg:gap-6 h-full relative z-10 pb-8 lg:pb-0">
|
| 204 |
+
<div class="flex items-center justify-between mb-2">
|
| 205 |
+
<h2 class="text-white text-xl lg:text-3xl font-bold tracking-tight">Generated Output</h2>
|
| 206 |
+
<div class="flex items-center gap-2">
|
| 207 |
+
<span class="hidden xs:inline-block bg-white/5 text-amber-200 px-2.5 py-1 rounded-full text-[10px] lg:text-xs font-semibold border border-white/10 backdrop-blur-sm">Step 2 of 2</span>
|
| 208 |
+
<div class="hidden xs:block h-6 w-px bg-white/10 mx-1"></div>
|
| 209 |
+
<button class="size-8 lg:size-9 flex items-center justify-center hover:bg-white/10 rounded-lg text-neutral-500 hover:text-amber-400 transition-all duration-300 border border-transparent" id="copyBtn" title="Copy JSON">
|
| 210 |
+
<span class="material-symbols-outlined text-lg lg:text-xl transition-all duration-300" id="copyIcon">content_copy</span>
|
| 211 |
+
</button>
|
| 212 |
+
<button class="size-8 lg:size-9 flex items-center justify-center hover:bg-white/10 rounded-lg text-neutral-500 hover:text-amber-400 transition-colors" id="downloadBtn" title="Download JSON">
|
| 213 |
+
<span class="material-symbols-outlined text-lg lg:text-xl">download</span>
|
| 214 |
+
</button>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
<div class="grid grid-cols-1 xs:grid-cols-3 gap-3">
|
| 218 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 219 |
+
<div class="relative size-3 flex-none">
|
| 220 |
+
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-500 opacity-75"></span>
|
| 221 |
+
<span class="relative inline-flex rounded-full size-3 bg-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.5)]"></span>
|
| 222 |
+
</div>
|
| 223 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 224 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">Vision Agent</span>
|
| 225 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Gemini Pro 1.5</span>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 229 |
+
<div class="relative size-3 flex-none">
|
| 230 |
+
<span class="relative inline-flex rounded-full size-3 bg-amber-600 shadow-[0_0_10px_rgba(217,119,6,0.5)]"></span>
|
| 231 |
+
</div>
|
| 232 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 233 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">Reasoning Agent</span>
|
| 234 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Llama 3 70B</span>
|
| 235 |
+
</div>
|
| 236 |
+
</div>
|
| 237 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 238 |
+
<div class="relative size-3 flex-none">
|
| 239 |
+
<span class="relative inline-flex rounded-full size-3 bg-neutral-600 shadow-[0_0_10px_rgba(82,82,82,0.5)]"></span>
|
| 240 |
+
</div>
|
| 241 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 242 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">SEO Context</span>
|
| 243 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Pinecone DB</span>
|
| 244 |
+
</div>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
<div class="flex-none lg:flex-1 min-h-[400px] lg:min-h-0 rounded-xl lg:rounded-2xl bg-black/60 backdrop-blur-xl border border-white/5 flex flex-col overflow-hidden shadow-2xl relative glass-card group/output transition-all duration-500">
|
| 248 |
+
<div class="absolute top-0 right-0 w-64 h-64 bg-amber-500/5 rounded-full blur-[100px] pointer-events-none"></div>
|
| 249 |
+
<div class="flex items-center justify-between px-3 py-2.5 lg:px-4 lg:py-3 bg-white/5 border-b border-white/5">
|
| 250 |
+
<span class="text-[10px] lg:text-xs font-mono text-neutral-500 flex items-center gap-2">
|
| 251 |
+
<span class="material-symbols-outlined text-xs lg:text-sm">code</span>
|
| 252 |
+
output.json
|
| 253 |
+
</span>
|
| 254 |
+
<div class="flex gap-1.5">
|
| 255 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-neutral-800 border border-white/10"></div>
|
| 256 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-neutral-800 border border-white/10"></div>
|
| 257 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-amber-500/20 border border-amber-500/30"></div>
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
<div class="flex-1 p-4 lg:p-5 overflow-auto custom-scrollbar font-mono text-xs lg:text-sm leading-6 lg:leading-7">
|
| 261 |
+
<pre><code class="language-json block" id="jsonOutput"><span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">01</span><span class="text-amber-400">{</span>
|
| 262 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">02</span> <span class="text-amber-600">"product_analysis"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">{</span>
|
| 263 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">03</span> <span class="text-amber-600">"title"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Noir Elite Series Artisan Timepiece"</span><span class="text-neutral-400">,</span>
|
| 264 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">04</span> <span class="text-amber-600">"category"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Luxury / Accessories"</span><span class="text-neutral-400">,</span>
|
| 265 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">05</span> <span class="text-amber-600">"features"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">[</span>
|
| 266 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">06</span> <span class="text-amber-200">"Obsidian Finish"</span><span class="text-neutral-400">,</span>
|
| 267 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">07</span> <span class="text-amber-200">"Golden Accents"</span><span class="text-neutral-400">,</span>
|
| 268 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">08</span> <span class="text-amber-200">"Smart Haptic Interface"</span>
|
| 269 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">09</span> <span class="text-amber-400">]</span><span class="text-neutral-400">,</span>
|
| 270 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">10</span> <span class="text-amber-600">"seo_tags"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">[</span>
|
| 271 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">11</span> <span class="text-amber-200">"#luxurywear"</span><span class="text-neutral-400">,</span> <span class="text-amber-200">"#amberstyle"</span><span class="text-neutral-400">,</span> <span class="text-amber-200">"#premiumtech"</span>
|
| 272 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">12</span> <span class="text-amber-400">]</span><span class="text-neutral-400">,</span>
|
| 273 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">13</span> <span class="text-amber-600">"sentiment_score"</span><span class="text-neutral-400">:</span> <span class="text-amber-500">0.99</span><span class="text-neutral-400">,</span>
|
| 274 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">14</span> <span class="text-amber-600">"market_fit"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Exceptional"</span>
|
| 275 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">15</span> <span class="text-amber-400">}</span><span class="text-neutral-400">,</span>
|
| 276 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">16</span> <span class="text-amber-600">"deployment_status"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Authorized"</span>
|
| 277 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">17</span><span class="text-amber-400">}</span></code></pre>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
</main>
|
| 283 |
+
<footer class="flex-none flex items-center justify-center py-4 bg-black/80 border-t border-white/5 backdrop-blur-md z-10">
|
| 284 |
+
<span class="text-[10px] lg:text-xs text-neutral-600 font-medium tracking-wide">© 2026 Bhishaj Technologies — All Rights Reserved</span>
|
| 285 |
+
</footer>
|
| 286 |
+
<script>
|
| 287 |
+
tailwind.config.theme.extend.animation = {
|
| 288 |
+
shine: 'shine 1.5s infinite',
|
| 289 |
+
}
|
| 290 |
+
tailwind.config.theme.extend.keyframes = {
|
| 291 |
+
shine: {
|
| 292 |
+
'0%': { left: '-100%' },
|
| 293 |
+
'100%': { left: '200%' },
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
const dropZone = document.getElementById('dropZone');
|
| 297 |
+
const fileInput = document.getElementById('fileInput');
|
| 298 |
+
const startBtn = document.getElementById('startBtn');
|
| 299 |
+
const jsonOutput = document.getElementById('jsonOutput');
|
| 300 |
+
const deployBtn = document.getElementById('deployBtn');
|
| 301 |
+
const copyBtn = document.getElementById('copyBtn');
|
| 302 |
+
const copyIcon = document.getElementById('copyIcon');
|
| 303 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 304 |
+
let selectedFile = null;
|
| 305 |
+
let isCatalogGenerated = false;
|
| 306 |
+
const defaultDropZoneContent = `
|
| 307 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 308 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 309 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 310 |
+
</div>
|
| 311 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 312 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 313 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 314 |
+
</div>
|
| 315 |
+
<button id="browseBtn"
|
| 316 |
+
class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide">
|
| 317 |
+
Browse Files
|
| 318 |
+
</button>
|
| 319 |
+
`;
|
| 320 |
+
function initDropZone() {
|
| 321 |
+
const browseBtn = document.getElementById('browseBtn');
|
| 322 |
+
if (browseBtn) {
|
| 323 |
+
browseBtn.addEventListener('click', (e) => {
|
| 324 |
+
e.stopPropagation();
|
| 325 |
+
fileInput.click();
|
| 326 |
+
});
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
initDropZone();
|
| 330 |
+
fileInput.addEventListener('change', (e) => {
|
| 331 |
+
if (e.target.files.length > 0) {
|
| 332 |
+
handleFile(e.target.files[0]);
|
| 333 |
+
}
|
| 334 |
+
});
|
| 335 |
+
dropZone.addEventListener('dragover', (e) => {
|
| 336 |
+
e.preventDefault();
|
| 337 |
+
dropZone.classList.add('border-amber-500');
|
| 338 |
+
dropZone.classList.add('bg-amber-500/5');
|
| 339 |
+
});
|
| 340 |
+
dropZone.addEventListener('dragleave', (e) => {
|
| 341 |
+
e.preventDefault();
|
| 342 |
+
dropZone.classList.remove('border-amber-500');
|
| 343 |
+
dropZone.classList.remove('bg-amber-500/5');
|
| 344 |
+
});
|
| 345 |
+
dropZone.addEventListener('drop', (e) => {
|
| 346 |
+
e.preventDefault();
|
| 347 |
+
dropZone.classList.remove('border-amber-500');
|
| 348 |
+
dropZone.classList.remove('bg-amber-500/5');
|
| 349 |
+
if (e.dataTransfer.files.length > 0) {
|
| 350 |
+
handleFile(e.dataTransfer.files[0]);
|
| 351 |
+
}
|
| 352 |
+
});
|
| 353 |
+
function handleFile(file) {
|
| 354 |
+
selectedFile = file;
|
| 355 |
+
dropZone.innerHTML = `
|
| 356 |
+
<div class="flex flex-col items-center justify-center gap-4 z-10">
|
| 357 |
+
<div class="size-14 lg:size-16 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center">
|
| 358 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">check_circle</span>
|
| 359 |
+
</div>
|
| 360 |
+
<div class="flex flex-col items-center gap-1">
|
| 361 |
+
<p class="text-white text-base lg:text-lg font-bold text-center">${file.name}</p>
|
| 362 |
+
<p class="text-neutral-500 text-xs lg:text-sm text-center">${(file.size / 1024).toFixed(1)} KB</p>
|
| 363 |
+
</div>
|
| 364 |
+
<button id="removeFileBtn" class="mt-2 flex items-center justify-center gap-2 rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 transition-all text-[10px] lg:text-xs font-bold uppercase tracking-wide">
|
| 365 |
+
<span class="material-symbols-outlined text-sm lg:text-base">close</span>
|
| 366 |
+
<span>Remove File</span>
|
| 367 |
+
</button>
|
| 368 |
+
</div>
|
| 369 |
+
`;
|
| 370 |
+
document.getElementById('removeFileBtn').addEventListener('click', (e) => {
|
| 371 |
+
e.stopPropagation();
|
| 372 |
+
resetUploadUI();
|
| 373 |
+
});
|
| 374 |
+
}
|
| 375 |
+
function resetUploadUI() {
|
| 376 |
+
selectedFile = null;
|
| 377 |
+
fileInput.value = "";
|
| 378 |
+
dropZone.innerHTML = defaultDropZoneContent;
|
| 379 |
+
initDropZone();
|
| 380 |
+
}
|
| 381 |
+
deployBtn.addEventListener('click', () => {
|
| 382 |
+
if (!isCatalogGenerated) {
|
| 383 |
+
alert("Please generate a catalog first before deploying.");
|
| 384 |
+
return;
|
| 385 |
+
}
|
| 386 |
+
const originalContent = deployBtn.innerHTML;
|
| 387 |
+
deployBtn.disabled = true;
|
| 388 |
+
deployBtn.innerHTML = `
|
| 389 |
+
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
| 390 |
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
| 391 |
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
| 392 |
+
</svg>
|
| 393 |
+
`;
|
| 394 |
+
setTimeout(() => {
|
| 395 |
+
alert("Success: Catalog pushed to storefront!");
|
| 396 |
+
deployBtn.innerHTML = originalContent;
|
| 397 |
+
deployBtn.disabled = false;
|
| 398 |
+
}, 1500);
|
| 399 |
+
});
|
| 400 |
+
copyBtn.addEventListener('click', () => {
|
| 401 |
+
const textToCopy = jsonOutput.innerText;
|
| 402 |
+
navigator.clipboard.writeText(textToCopy).then(() => {
|
| 403 |
+
const originalIcon = copyIcon.innerText;
|
| 404 |
+
copyIcon.style.opacity = '0';
|
| 405 |
+
copyIcon.style.transform = 'scale(0.8)';
|
| 406 |
+
setTimeout(() => {
|
| 407 |
+
copyIcon.innerText = 'check';
|
| 408 |
+
copyIcon.classList.remove('text-neutral-500');
|
| 409 |
+
copyIcon.classList.add('text-amber-500');
|
| 410 |
+
copyIcon.classList.add('animate-copy-success');
|
| 411 |
+
copyIcon.style.opacity = '1';
|
| 412 |
+
copyIcon.style.transform = 'scale(1)';
|
| 413 |
+
copyBtn.classList.add('copy-active');
|
| 414 |
+
}, 150);
|
| 415 |
+
setTimeout(() => {
|
| 416 |
+
copyIcon.style.opacity = '0';
|
| 417 |
+
copyIcon.style.transform = 'scale(0.8)';
|
| 418 |
+
setTimeout(() => {
|
| 419 |
+
copyIcon.innerText = originalIcon;
|
| 420 |
+
copyIcon.classList.remove('text-amber-500', 'animate-copy-success');
|
| 421 |
+
copyIcon.classList.add('text-neutral-500');
|
| 422 |
+
copyIcon.style.opacity = '1';
|
| 423 |
+
copyIcon.style.transform = 'scale(1)';
|
| 424 |
+
copyBtn.classList.remove('copy-active');
|
| 425 |
+
}, 150);
|
| 426 |
+
}, 2000);
|
| 427 |
+
});
|
| 428 |
+
});
|
| 429 |
+
downloadBtn.addEventListener('click', () => {
|
| 430 |
+
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(jsonOutput.innerText);
|
| 431 |
+
const downloadAnchorNode = document.createElement('a');
|
| 432 |
+
downloadAnchorNode.setAttribute("href", dataStr);
|
| 433 |
+
downloadAnchorNode.setAttribute("download", "merchflow_output.json");
|
| 434 |
+
document.body.appendChild(downloadAnchorNode);
|
| 435 |
+
downloadAnchorNode.click();
|
| 436 |
+
downloadAnchorNode.remove();
|
| 437 |
+
});
|
| 438 |
+
startBtn.addEventListener('click', async () => {
|
| 439 |
+
if (!selectedFile) {
|
| 440 |
+
alert("Please select a file first.");
|
| 441 |
+
return;
|
| 442 |
+
}
|
| 443 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3"><svg class="animate-spin h-4 w-4 lg:h-5 lg:w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg><span class="text-white text-base lg:text-lg font-bold tracking-wide">Synthesizing...</span></div>';
|
| 444 |
+
startBtn.disabled = true;
|
| 445 |
+
startBtn.classList.remove('animate-pulse-slow', 'animate-glow-pulse');
|
| 446 |
+
setTimeout(() => {
|
| 447 |
+
isCatalogGenerated = true;
|
| 448 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3 relative z-10"><span class="text-white text-base lg:text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span><span class="material-symbols-outlined text-white text-lg lg:text-xl group-hover:translate-x-1 transition-transform">arrow_forward</span></div>';
|
| 449 |
+
startBtn.disabled = false;
|
| 450 |
+
startBtn.classList.add('animate-pulse-slow', 'animate-glow-pulse');
|
| 451 |
+
}, 1500);
|
| 452 |
+
});
|
| 453 |
+
</script>
|
| 454 |
+
|
| 455 |
+
</body></html>
|
deploy_new_ui.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import subprocess
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
def main():
|
| 6 |
+
glassui_path = "glassui.html"
|
| 7 |
+
dashboard_path = "dashboard.html"
|
| 8 |
+
|
| 9 |
+
# Read the New Design
|
| 10 |
+
try:
|
| 11 |
+
with open(glassui_path, 'r', encoding='utf-8') as f:
|
| 12 |
+
glassui_content = f.read()
|
| 13 |
+
except FileNotFoundError:
|
| 14 |
+
print(f"Error: Could not find '{glassui_path}'.")
|
| 15 |
+
sys.exit(1)
|
| 16 |
+
|
| 17 |
+
# The Safety Scan (Crucial)
|
| 18 |
+
required_ids = [
|
| 19 |
+
"dropZone",
|
| 20 |
+
"fileInput",
|
| 21 |
+
"browseBtn",
|
| 22 |
+
"startBtn",
|
| 23 |
+
"deployBtn",
|
| 24 |
+
"jsonOutput",
|
| 25 |
+
"copyBtn",
|
| 26 |
+
"downloadBtn"
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
for element_id in required_ids:
|
| 30 |
+
# Check if id="element_id" or id='element_id' exists
|
| 31 |
+
pattern = rf'id\s*=\s*[\'"]{element_id}[\'"]'
|
| 32 |
+
if not re.search(pattern, glassui_content):
|
| 33 |
+
print(f"⚠️ WARNING: Missing ID {element_id}")
|
| 34 |
+
|
| 35 |
+
# Overwrite
|
| 36 |
+
try:
|
| 37 |
+
with open(dashboard_path, 'w', encoding='utf-8') as f:
|
| 38 |
+
f.write(glassui_content)
|
| 39 |
+
print(f"Successfully copied '{glassui_path}' to '{dashboard_path}'.")
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"Error overwriting '{dashboard_path}': {e}")
|
| 42 |
+
sys.exit(1)
|
| 43 |
+
|
| 44 |
+
# Deploy
|
| 45 |
+
print("Deploying to Hugging Face...")
|
| 46 |
+
git_commands = [
|
| 47 |
+
["git", "add", "dashboard.html"],
|
| 48 |
+
["git", "commit", "-m", "UI Update: Apply responsive Glassmorphism design from Stitch"],
|
| 49 |
+
["git", "push", "space", "clean_deploy:main"]
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
for cmd in git_commands:
|
| 53 |
+
print(f"Running: {' '.join(cmd)}")
|
| 54 |
+
# Use encoding='utf-8' as strictly requested
|
| 55 |
+
result = subprocess.run(cmd, text=True, encoding='utf-8')
|
| 56 |
+
if result.returncode != 0:
|
| 57 |
+
print(f"Command failed: {' '.join(cmd)}")
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
main()
|
final_deploy_push.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
# Force UTF-8 output for Windows terminals
|
| 5 |
+
sys.stdout.reconfigure(encoding='utf-8')
|
| 6 |
+
|
| 7 |
+
def deploy():
|
| 8 |
+
print("⚠️ Ensure you are inside the D:\\Projects\\MerchFlow 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 - MerchFlow AI"', check=True)
|
| 38 |
+
except subprocess.CalledProcessError:
|
| 39 |
+
print("Commit failed (likely nothing to commit). Continuing...")
|
| 40 |
+
|
| 41 |
+
print("Renaming branch to main...")
|
| 42 |
+
run_command("git branch -M main")
|
| 43 |
+
|
| 44 |
+
print("Removing existing origin (if any)...")
|
| 45 |
+
# Don't check=True here because it fails if origin doesn't exist
|
| 46 |
+
run_command("git remote remove origin", check=False)
|
| 47 |
+
|
| 48 |
+
print(f"Adding remote origin: {github_url}")
|
| 49 |
+
run_command(f"git remote add origin {github_url}")
|
| 50 |
+
|
| 51 |
+
print("Pushing to GitHub...")
|
| 52 |
+
run_command("git push -u origin main")
|
| 53 |
+
|
| 54 |
+
# Success message
|
| 55 |
+
print('✅ Code is live on GitHub!')
|
| 56 |
+
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"\n❌ An error occurred: {e}")
|
| 59 |
+
|
| 60 |
+
if __name__ == "__main__":
|
| 61 |
+
main()
|
fix_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: MerchFlow AI
|
| 10 |
+
emoji: 🚀
|
| 11 |
+
colorFrom: blue
|
| 12 |
+
colorTo: indigo
|
| 13 |
+
sdk: docker
|
| 14 |
+
pinned: false
|
| 15 |
+
---
|
| 16 |
+
# MerchFlow 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()
|
force_deploy.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
force_deploy.py
|
| 3 |
+
Force-deploys the current codebase to the Hugging Face Space.
|
| 4 |
+
Handles HF binary file restrictions by untracking offending files first.
|
| 5 |
+
"""
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
def run(cmd, allow_fail=False):
|
| 10 |
+
print(f"\n>>> {cmd}")
|
| 11 |
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
| 12 |
+
if result.stdout:
|
| 13 |
+
print(result.stdout.strip())
|
| 14 |
+
if result.stderr:
|
| 15 |
+
print(result.stderr.strip())
|
| 16 |
+
if result.returncode != 0 and not allow_fail:
|
| 17 |
+
print(f"Command failed with exit code {result.returncode}")
|
| 18 |
+
sys.exit(1)
|
| 19 |
+
return result
|
| 20 |
+
|
| 21 |
+
if __name__ == "__main__":
|
| 22 |
+
print("=" * 60)
|
| 23 |
+
print(" MerchFlow AI — Force Deploy to Hugging Face Space")
|
| 24 |
+
print("=" * 60)
|
| 25 |
+
|
| 26 |
+
# 0. Remove binary files from Git tracking (keep local copies)
|
| 27 |
+
binaries = [
|
| 28 |
+
"stitch_merchflow_ai_dashboard.zip",
|
| 29 |
+
"screen.jpg",
|
| 30 |
+
"test_image.jpg",
|
| 31 |
+
]
|
| 32 |
+
for b in binaries:
|
| 33 |
+
run(f"git rm --cached {b}", allow_fail=True)
|
| 34 |
+
|
| 35 |
+
# Ensure .gitignore blocks them from being re-added
|
| 36 |
+
ignore_entries = binaries + ["*.zip", "*.jpg", "*.jpeg", "*.png"]
|
| 37 |
+
try:
|
| 38 |
+
existing = open(".gitignore", "r").read()
|
| 39 |
+
except FileNotFoundError:
|
| 40 |
+
existing = ""
|
| 41 |
+
with open(".gitignore", "a") as f:
|
| 42 |
+
for entry in ignore_entries:
|
| 43 |
+
if entry not in existing:
|
| 44 |
+
f.write(f"\n{entry}")
|
| 45 |
+
print("\nUpdated .gitignore with binary exclusions.")
|
| 46 |
+
|
| 47 |
+
# 1. Stage everything
|
| 48 |
+
run("git add .")
|
| 49 |
+
|
| 50 |
+
# 2. Commit (allow fail in case nothing changed)
|
| 51 |
+
run('git commit -m "Critical Deployment: Force update of License, Security Fixes, and Branding"', allow_fail=True)
|
| 52 |
+
|
| 53 |
+
# 3. Force push HEAD to space remote's main branch
|
| 54 |
+
run("git push --force space HEAD:main")
|
| 55 |
+
|
| 56 |
+
# 4. Print the latest commit for verification
|
| 57 |
+
print("\n" + "=" * 60)
|
| 58 |
+
print(" Deployed Commit:")
|
| 59 |
+
print("=" * 60)
|
| 60 |
+
run("git log -1")
|
| 61 |
+
|
| 62 |
+
print("\n✅ Force deploy complete.")
|
force_push.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
|
| 3 |
+
def main():
|
| 4 |
+
print("Executing force push...")
|
| 5 |
+
command = "git push --force space HEAD:main"
|
| 6 |
+
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
| 7 |
+
|
| 8 |
+
print("STDOUT:")
|
| 9 |
+
print(result.stdout)
|
| 10 |
+
print("STDERR:")
|
| 11 |
+
print(result.stderr)
|
| 12 |
+
|
| 13 |
+
if __name__ == "__main__":
|
| 14 |
+
main()
|
glassui.html
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="dark" lang="en"><head>
|
| 3 |
+
<meta charset="utf-8"/>
|
| 4 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 5 |
+
<title>MerchFlow AI Dashboard</title>
|
| 6 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 7 |
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
| 8 |
+
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
"primary": "#f59e0b",
|
| 18 |
+
"primary-hover": "#d97706",
|
| 19 |
+
"glass-border": "rgba(255, 255, 255, 0.12)",
|
| 20 |
+
"glass-surface": "rgba(255, 255, 255, 0.03)",
|
| 21 |
+
"glass-surface-hover": "rgba(255, 255, 255, 0.07)",
|
| 22 |
+
"charcoal": "#0a0a0a",
|
| 23 |
+
"amber-accent": "#fbbf24"
|
| 24 |
+
},
|
| 25 |
+
fontFamily: {
|
| 26 |
+
"display": ["Plus Jakarta Sans", "sans-serif"],
|
| 27 |
+
"mono": ["monospace"]
|
| 28 |
+
},
|
| 29 |
+
borderRadius: { "DEFAULT": "1rem", "lg": "1.5rem", "xl": "2rem", "2xl": "3rem", "full": "9999px" },
|
| 30 |
+
backdropBlur: {
|
| 31 |
+
'xs': '2px',
|
| 32 |
+
},
|
| 33 |
+
screens: {
|
| 34 |
+
'xs': '480px',
|
| 35 |
+
},
|
| 36 |
+
animation: {
|
| 37 |
+
'pulse-slow': 'pulse-slow 3s infinite',
|
| 38 |
+
'glow-pulse': 'glow-pulse 3s infinite',
|
| 39 |
+
'copy-success': 'copy-success 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
|
| 40 |
+
},
|
| 41 |
+
keyframes: {
|
| 42 |
+
'pulse-slow': {
|
| 43 |
+
'0%, 100%': { transform: 'scale(1)' },
|
| 44 |
+
'50%': { transform: 'scale(1.02)' },
|
| 45 |
+
},
|
| 46 |
+
'glow-pulse': {
|
| 47 |
+
'0%, 100%': { boxShadow: '0 0 0 0 rgba(245, 158, 11, 0.4)' },
|
| 48 |
+
'50%': { boxShadow: '0 0 20px 5px rgba(251, 191, 36, 0.5)' },
|
| 49 |
+
},
|
| 50 |
+
'copy-success': {
|
| 51 |
+
'0%': { transform: 'scale(0.8)', opacity: '0' },
|
| 52 |
+
'100%': { transform: 'scale(1)', opacity: '1' },
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
},
|
| 57 |
+
}
|
| 58 |
+
</script>
|
| 59 |
+
<style type="text/tailwindcss">
|
| 60 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 61 |
+
width: 6px;
|
| 62 |
+
height: 6px;
|
| 63 |
+
}
|
| 64 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 65 |
+
background: transparent;
|
| 66 |
+
}
|
| 67 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 68 |
+
background: rgba(255, 255, 255, 0.1);
|
| 69 |
+
border-radius: 99px;
|
| 70 |
+
}
|
| 71 |
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
| 72 |
+
background: rgba(255, 255, 255, 0.2);
|
| 73 |
+
}
|
| 74 |
+
body {
|
| 75 |
+
background-color: #0a0a0a;
|
| 76 |
+
background-image:
|
| 77 |
+
radial-gradient(at 0% 0%, rgba(20, 20, 20, 1) 0, transparent 50%),
|
| 78 |
+
radial-gradient(at 50% -10%, rgba(217, 119, 6, 0.15) 0, transparent 60%),
|
| 79 |
+
radial-gradient(at 100% 0%, rgba(251, 191, 36, 0.1) 0, transparent 50%),
|
| 80 |
+
radial-gradient(at 80% 80%, rgba(217, 119, 6, 0.12) 0, transparent 50%);
|
| 81 |
+
background-attachment: fixed;
|
| 82 |
+
min-height: 100vh;
|
| 83 |
+
}
|
| 84 |
+
.glass-panel {
|
| 85 |
+
background: rgba(15, 15, 15, 0.75);
|
| 86 |
+
backdrop-filter: blur(20px);
|
| 87 |
+
-webkit-backdrop-filter: blur(20px);
|
| 88 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 89 |
+
}
|
| 90 |
+
@media (max-width: 768px) {
|
| 91 |
+
.glass-panel {
|
| 92 |
+
backdrop-filter: blur(14px);
|
| 93 |
+
-webkit-backdrop-filter: blur(14px);
|
| 94 |
+
background: rgba(10, 10, 10, 0.85);
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
.glass-card {
|
| 98 |
+
background: rgba(255, 255, 255, 0.02);
|
| 99 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 100 |
+
backdrop-filter: blur(12px);
|
| 101 |
+
transition: all 0.3s ease;
|
| 102 |
+
}
|
| 103 |
+
@media (hover: hover) {
|
| 104 |
+
.glass-card:hover {
|
| 105 |
+
border-color: rgba(251, 191, 36, 0.3);
|
| 106 |
+
box-shadow: 0 0 25px rgba(245, 158, 11, 0.15);
|
| 107 |
+
background: rgba(255, 255, 255, 0.04);
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
.neon-textarea {
|
| 111 |
+
background: rgba(255, 255, 255, 0.03);
|
| 112 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 113 |
+
backdrop-filter: blur(16px);
|
| 114 |
+
-webkit-backdrop-filter: blur(16px);
|
| 115 |
+
transition: all 0.3s ease;
|
| 116 |
+
}
|
| 117 |
+
.neon-textarea:focus {
|
| 118 |
+
outline: none;
|
| 119 |
+
border-color: rgba(251, 191, 36, 0.5);
|
| 120 |
+
box-shadow: 0 0 15px rgba(245, 158, 11, 0.25), inset 0 0 5px rgba(245, 158, 11, 0.05);
|
| 121 |
+
background: rgba(255, 255, 255, 0.06);
|
| 122 |
+
}
|
| 123 |
+
.copy-active {
|
| 124 |
+
box-shadow: 0 0 15px rgba(251, 191, 36, 0.6) !important;
|
| 125 |
+
border-color: rgba(251, 191, 36, 0.4) !important;
|
| 126 |
+
background: rgba(251, 191, 36, 0.1) !important;
|
| 127 |
+
}
|
| 128 |
+
</style>
|
| 129 |
+
<style>
|
| 130 |
+
body {
|
| 131 |
+
min-height: max(884px, 100dvh);
|
| 132 |
+
}
|
| 133 |
+
</style>
|
| 134 |
+
</head>
|
| 135 |
+
<body class="font-display text-white antialiased overflow-x-hidden min-h-screen flex flex-col selection:bg-amber-500 selection:text-black">
|
| 136 |
+
<header class="flex-none z-30 px-4 py-3 lg:px-6 lg:py-4 border-b border-white/5 glass-panel sticky top-0">
|
| 137 |
+
<div class="max-w-[1600px] mx-auto flex items-center justify-between gap-4">
|
| 138 |
+
<div class="flex items-center gap-3 lg:gap-4 flex-1 min-w-0">
|
| 139 |
+
<div class="flex-none flex items-center justify-center size-9 lg:size-10 rounded-xl bg-gradient-to-br from-amber-400 to-amber-700 shadow-lg shadow-amber-900/20 text-white">
|
| 140 |
+
<span class="material-symbols-outlined text-xl lg:text-2xl">all_inclusive</span>
|
| 141 |
+
</div>
|
| 142 |
+
<div class="min-w-0">
|
| 143 |
+
<h2 class="text-white text-base lg:text-xl font-bold leading-tight tracking-tight truncate">MerchFlow AI</h2>
|
| 144 |
+
<div class="flex items-center gap-2">
|
| 145 |
+
<span class="size-1.5 rounded-full bg-amber-500 animate-pulse flex-none"></span>
|
| 146 |
+
<span class="text-[10px] text-amber-200/60 font-bold uppercase tracking-wider truncate">Enterprise Edition</span>
|
| 147 |
+
</div>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
<button class="group relative flex-none flex cursor-pointer items-center justify-center overflow-hidden rounded-full h-9 lg:h-10 px-4 lg:px-6 bg-gradient-to-r from-amber-600 to-amber-800 hover:from-amber-500 hover:to-amber-700 border border-white/10 shadow-[0_0_15px_rgba(245,158,11,0.3)] transition-all" id="deployBtn">
|
| 151 |
+
<div class="absolute inset-0 bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
| 152 |
+
<span class="material-symbols-outlined lg:mr-2 text-lg relative z-10">rocket_launch</span>
|
| 153 |
+
<span class="hidden lg:inline truncate text-sm font-bold tracking-wide relative z-10">Deploy</span>
|
| 154 |
+
</button>
|
| 155 |
+
</div>
|
| 156 |
+
</header>
|
| 157 |
+
<main class="flex-1 flex flex-col lg:flex-row relative z-10">
|
| 158 |
+
<div class="fixed top-1/4 -left-20 w-64 h-64 lg:w-96 lg:h-96 bg-amber-900/10 rounded-full blur-[120px] pointer-events-none z-0"></div>
|
| 159 |
+
<div class="fixed bottom-1/4 -right-20 w-64 h-64 lg:w-96 lg:h-96 bg-orange-900/5 rounded-full blur-[120px] pointer-events-none z-0"></div>
|
| 160 |
+
<div class="flex-none lg:flex-1 flex flex-col lg:border-r border-white/5 p-4 lg:p-8 bg-black/40 lg:overflow-y-auto lg:h-[calc(100vh-73px)] relative z-10">
|
| 161 |
+
<div class="max-w-xl w-full mx-auto flex flex-col gap-4 lg:gap-6 h-full">
|
| 162 |
+
<div class="flex items-center justify-between mb-2">
|
| 163 |
+
<h2 class="text-white text-xl lg:text-3xl font-bold tracking-tight">Input Data</h2>
|
| 164 |
+
<span class="bg-white/5 text-amber-200/80 px-2.5 py-1 rounded-full text-[10px] lg:text-xs font-semibold border border-white/10 backdrop-blur-sm">Step 1 of 2</span>
|
| 165 |
+
</div>
|
| 166 |
+
<div class="flex flex-col flex-none">
|
| 167 |
+
<input accept=".jpg,.jpeg,.png,.webp" class="hidden" id="fileInput" type="file"/>
|
| 168 |
+
<div class="group relative flex flex-col items-center justify-center gap-3 lg:gap-4 rounded-2xl lg:rounded-3xl border-2 border-dashed border-white/10 hover:border-amber-500/50 hover:bg-white/5 transition-all cursor-pointer min-h-[200px] lg:min-h-[260px] px-4 py-6 lg:px-6 lg:py-8 glass-card" id="dropZone">
|
| 169 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 170 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 171 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 172 |
+
</div>
|
| 173 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 174 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 175 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 176 |
+
</div>
|
| 177 |
+
<button class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide" id="browseBtn">
|
| 178 |
+
Browse Files
|
| 179 |
+
</button>
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
<div class="flex flex-col gap-2 lg:gap-3 flex-1">
|
| 183 |
+
<label class="flex items-center justify-between px-1">
|
| 184 |
+
<span class="text-white text-sm lg:text-base font-semibold">Raw Product Specs</span>
|
| 185 |
+
<span class="text-[10px] lg:text-xs text-neutral-500 font-medium bg-black/40 px-2 py-0.5 rounded border border-white/5">JSON or Plain Text</span>
|
| 186 |
+
</label>
|
| 187 |
+
<div class="relative w-full">
|
| 188 |
+
<textarea class="form-input w-full min-h-[140px] lg:h-full lg:min-h-[180px] resize-y lg:resize-none rounded-xl lg:rounded-2xl text-neutral-100 placeholder:text-neutral-600 neon-textarea p-4 lg:p-5 text-sm leading-relaxed font-mono shadow-inner transition-all duration-300" id="productSpecs" placeholder="// Enter fabric details, dimensions, and SKU..."></textarea>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="pt-2 pb-6 lg:pb-0">
|
| 192 |
+
<button class="group relative w-full cursor-pointer overflow-hidden rounded-xl lg:rounded-2xl h-12 lg:h-14 bg-gradient-to-r from-amber-600 via-amber-700 to-amber-800 hover:from-amber-500 hover:to-amber-700 transition-all border border-white/10 animate-pulse-slow animate-glow-pulse" id="startBtn">
|
| 193 |
+
<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3 relative z-10">
|
| 194 |
+
<span class="text-white text-base lg:text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span>
|
| 195 |
+
<span class="material-symbols-outlined text-white text-lg lg:text-xl group-hover:translate-x-1 transition-transform">arrow_forward</span>
|
| 196 |
+
</div>
|
| 197 |
+
</button>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
<div class="flex-none lg:flex-1 flex flex-col p-4 lg:p-8 bg-black/60 lg:overflow-y-auto lg:h-[calc(100vh-73px)] relative">
|
| 202 |
+
<div class="absolute inset-0 opacity-[0.02] pointer-events-none" style="background-image: linear-gradient(to right, #ffffff 1px, transparent 1px), linear-gradient(to bottom, #ffffff 1px, transparent 1px); background-size: 40px 40px;"></div>
|
| 203 |
+
<div class="max-w-3xl w-full mx-auto flex flex-col gap-4 lg:gap-6 h-full relative z-10 pb-8 lg:pb-0">
|
| 204 |
+
<div class="flex items-center justify-between mb-2">
|
| 205 |
+
<h2 class="text-white text-xl lg:text-3xl font-bold tracking-tight">Generated Output</h2>
|
| 206 |
+
<div class="flex items-center gap-2">
|
| 207 |
+
<span class="hidden xs:inline-block bg-white/5 text-amber-200 px-2.5 py-1 rounded-full text-[10px] lg:text-xs font-semibold border border-white/10 backdrop-blur-sm">Step 2 of 2</span>
|
| 208 |
+
<div class="hidden xs:block h-6 w-px bg-white/10 mx-1"></div>
|
| 209 |
+
<button class="size-8 lg:size-9 flex items-center justify-center hover:bg-white/10 rounded-lg text-neutral-500 hover:text-amber-400 transition-all duration-300 border border-transparent" id="copyBtn" title="Copy JSON">
|
| 210 |
+
<span class="material-symbols-outlined text-lg lg:text-xl transition-all duration-300" id="copyIcon">content_copy</span>
|
| 211 |
+
</button>
|
| 212 |
+
<button class="size-8 lg:size-9 flex items-center justify-center hover:bg-white/10 rounded-lg text-neutral-500 hover:text-amber-400 transition-colors" id="downloadBtn" title="Download JSON">
|
| 213 |
+
<span class="material-symbols-outlined text-lg lg:text-xl">download</span>
|
| 214 |
+
</button>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
<div class="grid grid-cols-1 xs:grid-cols-3 gap-3">
|
| 218 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 219 |
+
<div class="relative size-3 flex-none">
|
| 220 |
+
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-500 opacity-75"></span>
|
| 221 |
+
<span class="relative inline-flex rounded-full size-3 bg-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.5)]"></span>
|
| 222 |
+
</div>
|
| 223 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 224 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">Vision Agent</span>
|
| 225 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Gemini Pro 1.5</span>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 229 |
+
<div class="relative size-3 flex-none">
|
| 230 |
+
<span class="relative inline-flex rounded-full size-3 bg-amber-600 shadow-[0_0_10px_rgba(217,119,6,0.5)]"></span>
|
| 231 |
+
</div>
|
| 232 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 233 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">Reasoning Agent</span>
|
| 234 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Llama 3 70B</span>
|
| 235 |
+
</div>
|
| 236 |
+
</div>
|
| 237 |
+
<div class="flex items-center gap-3 p-3 lg:p-4 rounded-xl glass-card cursor-default group">
|
| 238 |
+
<div class="relative size-3 flex-none">
|
| 239 |
+
<span class="relative inline-flex rounded-full size-3 bg-neutral-600 shadow-[0_0_10px_rgba(82,82,82,0.5)]"></span>
|
| 240 |
+
</div>
|
| 241 |
+
<div class="flex flex-col overflow-hidden min-w-0">
|
| 242 |
+
<span class="text-[9px] lg:text-[10px] uppercase tracking-wider text-neutral-500 truncate group-hover:text-amber-300 transition-colors">SEO Context</span>
|
| 243 |
+
<span class="text-xs lg:text-sm font-bold text-white truncate">Pinecone DB</span>
|
| 244 |
+
</div>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
<div class="flex-none lg:flex-1 min-h-[400px] lg:min-h-0 rounded-xl lg:rounded-2xl bg-black/60 backdrop-blur-xl border border-white/5 flex flex-col overflow-hidden shadow-2xl relative glass-card group/output transition-all duration-500">
|
| 248 |
+
<div class="absolute top-0 right-0 w-64 h-64 bg-amber-500/5 rounded-full blur-[100px] pointer-events-none"></div>
|
| 249 |
+
<div class="flex items-center justify-between px-3 py-2.5 lg:px-4 lg:py-3 bg-white/5 border-b border-white/5">
|
| 250 |
+
<span class="text-[10px] lg:text-xs font-mono text-neutral-500 flex items-center gap-2">
|
| 251 |
+
<span class="material-symbols-outlined text-xs lg:text-sm">code</span>
|
| 252 |
+
output.json
|
| 253 |
+
</span>
|
| 254 |
+
<div class="flex gap-1.5">
|
| 255 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-neutral-800 border border-white/10"></div>
|
| 256 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-neutral-800 border border-white/10"></div>
|
| 257 |
+
<div class="size-2.5 lg:size-3 rounded-full bg-amber-500/20 border border-amber-500/30"></div>
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
<div class="flex-1 p-4 lg:p-5 overflow-auto custom-scrollbar font-mono text-xs lg:text-sm leading-6 lg:leading-7">
|
| 261 |
+
<pre><code class="language-json block" id="jsonOutput"><span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">01</span><span class="text-amber-400">{</span>
|
| 262 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">02</span> <span class="text-amber-600">"product_analysis"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">{</span>
|
| 263 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">03</span> <span class="text-amber-600">"title"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Noir Elite Series Artisan Timepiece"</span><span class="text-neutral-400">,</span>
|
| 264 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">04</span> <span class="text-amber-600">"category"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Luxury / Accessories"</span><span class="text-neutral-400">,</span>
|
| 265 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">05</span> <span class="text-amber-600">"features"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">[</span>
|
| 266 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">06</span> <span class="text-amber-200">"Obsidian Finish"</span><span class="text-neutral-400">,</span>
|
| 267 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">07</span> <span class="text-amber-200">"Golden Accents"</span><span class="text-neutral-400">,</span>
|
| 268 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">08</span> <span class="text-amber-200">"Smart Haptic Interface"</span>
|
| 269 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">09</span> <span class="text-amber-400">]</span><span class="text-neutral-400">,</span>
|
| 270 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">10</span> <span class="text-amber-600">"seo_tags"</span><span class="text-neutral-400">:</span> <span class="text-amber-400">[</span>
|
| 271 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">11</span> <span class="text-amber-200">"#luxurywear"</span><span class="text-neutral-400">,</span> <span class="text-amber-200">"#amberstyle"</span><span class="text-neutral-400">,</span> <span class="text-amber-200">"#premiumtech"</span>
|
| 272 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">12</span> <span class="text-amber-400">]</span><span class="text-neutral-400">,</span>
|
| 273 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">13</span> <span class="text-amber-600">"sentiment_score"</span><span class="text-neutral-400">:</span> <span class="text-amber-500">0.99</span><span class="text-neutral-400">,</span>
|
| 274 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">14</span> <span class="text-amber-600">"market_fit"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Exceptional"</span>
|
| 275 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">15</span> <span class="text-amber-400">}</span><span class="text-neutral-400">,</span>
|
| 276 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">16</span> <span class="text-amber-600">"deployment_status"</span><span class="text-neutral-400">:</span> <span class="text-amber-200">"Authorized"</span>
|
| 277 |
+
<span class="text-neutral-700 select-none mr-3 lg:mr-4 border-r border-neutral-800 pr-2">17</span><span class="text-amber-400">}</span></code></pre>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
</main>
|
| 283 |
+
<footer class="flex-none flex items-center justify-center py-4 bg-black/80 border-t border-white/5 backdrop-blur-md z-10">
|
| 284 |
+
<span class="text-[10px] lg:text-xs text-neutral-600 font-medium tracking-wide">© 2026 Bhishaj Technologies — All Rights Reserved</span>
|
| 285 |
+
</footer>
|
| 286 |
+
<script>
|
| 287 |
+
tailwind.config.theme.extend.animation = {
|
| 288 |
+
shine: 'shine 1.5s infinite',
|
| 289 |
+
}
|
| 290 |
+
tailwind.config.theme.extend.keyframes = {
|
| 291 |
+
shine: {
|
| 292 |
+
'0%': { left: '-100%' },
|
| 293 |
+
'100%': { left: '200%' },
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
const dropZone = document.getElementById('dropZone');
|
| 297 |
+
const fileInput = document.getElementById('fileInput');
|
| 298 |
+
const startBtn = document.getElementById('startBtn');
|
| 299 |
+
const jsonOutput = document.getElementById('jsonOutput');
|
| 300 |
+
const deployBtn = document.getElementById('deployBtn');
|
| 301 |
+
const copyBtn = document.getElementById('copyBtn');
|
| 302 |
+
const copyIcon = document.getElementById('copyIcon');
|
| 303 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 304 |
+
let selectedFile = null;
|
| 305 |
+
let isCatalogGenerated = false;
|
| 306 |
+
const defaultDropZoneContent = `
|
| 307 |
+
<div class="absolute w-16 h-16 lg:w-20 lg:h-20 bg-amber-500/10 rounded-full blur-xl group-hover:bg-amber-500/20 transition-all"></div>
|
| 308 |
+
<div class="size-14 lg:size-16 relative z-10 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center transition-transform group-hover:scale-110 duration-300">
|
| 309 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">cloud_upload</span>
|
| 310 |
+
</div>
|
| 311 |
+
<div class="flex flex-col items-center gap-1 relative z-10">
|
| 312 |
+
<p class="text-white text-base lg:text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p>
|
| 313 |
+
<p class="text-neutral-500 text-xs lg:text-sm font-medium text-center">Supports JPG, PNG, WEBP</p>
|
| 314 |
+
</div>
|
| 315 |
+
<button id="browseBtn"
|
| 316 |
+
class="mt-2 relative z-10 flex items-center justify-center rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-white/5 hover:bg-white/10 border border-white/10 text-white text-[10px] lg:text-xs font-bold transition-all uppercase tracking-wide">
|
| 317 |
+
Browse Files
|
| 318 |
+
</button>
|
| 319 |
+
`;
|
| 320 |
+
function initDropZone() {
|
| 321 |
+
const browseBtn = document.getElementById('browseBtn');
|
| 322 |
+
if (browseBtn) {
|
| 323 |
+
browseBtn.addEventListener('click', (e) => {
|
| 324 |
+
e.stopPropagation();
|
| 325 |
+
fileInput.click();
|
| 326 |
+
});
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
initDropZone();
|
| 330 |
+
fileInput.addEventListener('change', (e) => {
|
| 331 |
+
if (e.target.files.length > 0) {
|
| 332 |
+
handleFile(e.target.files[0]);
|
| 333 |
+
}
|
| 334 |
+
});
|
| 335 |
+
dropZone.addEventListener('dragover', (e) => {
|
| 336 |
+
e.preventDefault();
|
| 337 |
+
dropZone.classList.add('border-amber-500');
|
| 338 |
+
dropZone.classList.add('bg-amber-500/5');
|
| 339 |
+
});
|
| 340 |
+
dropZone.addEventListener('dragleave', (e) => {
|
| 341 |
+
e.preventDefault();
|
| 342 |
+
dropZone.classList.remove('border-amber-500');
|
| 343 |
+
dropZone.classList.remove('bg-amber-500/5');
|
| 344 |
+
});
|
| 345 |
+
dropZone.addEventListener('drop', (e) => {
|
| 346 |
+
e.preventDefault();
|
| 347 |
+
dropZone.classList.remove('border-amber-500');
|
| 348 |
+
dropZone.classList.remove('bg-amber-500/5');
|
| 349 |
+
if (e.dataTransfer.files.length > 0) {
|
| 350 |
+
handleFile(e.dataTransfer.files[0]);
|
| 351 |
+
}
|
| 352 |
+
});
|
| 353 |
+
function handleFile(file) {
|
| 354 |
+
selectedFile = file;
|
| 355 |
+
dropZone.innerHTML = `
|
| 356 |
+
<div class="flex flex-col items-center justify-center gap-4 z-10">
|
| 357 |
+
<div class="size-14 lg:size-16 rounded-2xl bg-gradient-to-br from-neutral-800 to-black border border-white/10 shadow-lg flex items-center justify-center">
|
| 358 |
+
<span class="material-symbols-outlined text-2xl lg:text-3xl text-amber-500">check_circle</span>
|
| 359 |
+
</div>
|
| 360 |
+
<div class="flex flex-col items-center gap-1">
|
| 361 |
+
<p class="text-white text-base lg:text-lg font-bold text-center">${file.name}</p>
|
| 362 |
+
<p class="text-neutral-500 text-xs lg:text-sm text-center">${(file.size / 1024).toFixed(1)} KB</p>
|
| 363 |
+
</div>
|
| 364 |
+
<button id="removeFileBtn" class="mt-2 flex items-center justify-center gap-2 rounded-full h-8 lg:h-9 px-4 lg:px-5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 transition-all text-[10px] lg:text-xs font-bold uppercase tracking-wide">
|
| 365 |
+
<span class="material-symbols-outlined text-sm lg:text-base">close</span>
|
| 366 |
+
<span>Remove File</span>
|
| 367 |
+
</button>
|
| 368 |
+
</div>
|
| 369 |
+
`;
|
| 370 |
+
document.getElementById('removeFileBtn').addEventListener('click', (e) => {
|
| 371 |
+
e.stopPropagation();
|
| 372 |
+
resetUploadUI();
|
| 373 |
+
});
|
| 374 |
+
}
|
| 375 |
+
function resetUploadUI() {
|
| 376 |
+
selectedFile = null;
|
| 377 |
+
fileInput.value = "";
|
| 378 |
+
dropZone.innerHTML = defaultDropZoneContent;
|
| 379 |
+
initDropZone();
|
| 380 |
+
}
|
| 381 |
+
deployBtn.addEventListener('click', () => {
|
| 382 |
+
if (!isCatalogGenerated) {
|
| 383 |
+
alert("Please generate a catalog first before deploying.");
|
| 384 |
+
return;
|
| 385 |
+
}
|
| 386 |
+
const originalContent = deployBtn.innerHTML;
|
| 387 |
+
deployBtn.disabled = true;
|
| 388 |
+
deployBtn.innerHTML = `
|
| 389 |
+
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
| 390 |
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
| 391 |
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
| 392 |
+
</svg>
|
| 393 |
+
`;
|
| 394 |
+
setTimeout(() => {
|
| 395 |
+
alert("Success: Catalog pushed to storefront!");
|
| 396 |
+
deployBtn.innerHTML = originalContent;
|
| 397 |
+
deployBtn.disabled = false;
|
| 398 |
+
}, 1500);
|
| 399 |
+
});
|
| 400 |
+
copyBtn.addEventListener('click', () => {
|
| 401 |
+
const textToCopy = jsonOutput.innerText;
|
| 402 |
+
navigator.clipboard.writeText(textToCopy).then(() => {
|
| 403 |
+
const originalIcon = copyIcon.innerText;
|
| 404 |
+
copyIcon.style.opacity = '0';
|
| 405 |
+
copyIcon.style.transform = 'scale(0.8)';
|
| 406 |
+
setTimeout(() => {
|
| 407 |
+
copyIcon.innerText = 'check';
|
| 408 |
+
copyIcon.classList.remove('text-neutral-500');
|
| 409 |
+
copyIcon.classList.add('text-amber-500');
|
| 410 |
+
copyIcon.classList.add('animate-copy-success');
|
| 411 |
+
copyIcon.style.opacity = '1';
|
| 412 |
+
copyIcon.style.transform = 'scale(1)';
|
| 413 |
+
copyBtn.classList.add('copy-active');
|
| 414 |
+
}, 150);
|
| 415 |
+
setTimeout(() => {
|
| 416 |
+
copyIcon.style.opacity = '0';
|
| 417 |
+
copyIcon.style.transform = 'scale(0.8)';
|
| 418 |
+
setTimeout(() => {
|
| 419 |
+
copyIcon.innerText = originalIcon;
|
| 420 |
+
copyIcon.classList.remove('text-amber-500', 'animate-copy-success');
|
| 421 |
+
copyIcon.classList.add('text-neutral-500');
|
| 422 |
+
copyIcon.style.opacity = '1';
|
| 423 |
+
copyIcon.style.transform = 'scale(1)';
|
| 424 |
+
copyBtn.classList.remove('copy-active');
|
| 425 |
+
}, 150);
|
| 426 |
+
}, 2000);
|
| 427 |
+
});
|
| 428 |
+
});
|
| 429 |
+
downloadBtn.addEventListener('click', () => {
|
| 430 |
+
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(jsonOutput.innerText);
|
| 431 |
+
const downloadAnchorNode = document.createElement('a');
|
| 432 |
+
downloadAnchorNode.setAttribute("href", dataStr);
|
| 433 |
+
downloadAnchorNode.setAttribute("download", "merchflow_output.json");
|
| 434 |
+
document.body.appendChild(downloadAnchorNode);
|
| 435 |
+
downloadAnchorNode.click();
|
| 436 |
+
downloadAnchorNode.remove();
|
| 437 |
+
});
|
| 438 |
+
startBtn.addEventListener('click', async () => {
|
| 439 |
+
if (!selectedFile) {
|
| 440 |
+
alert("Please select a file first.");
|
| 441 |
+
return;
|
| 442 |
+
}
|
| 443 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3"><svg class="animate-spin h-4 w-4 lg:h-5 lg:w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg><span class="text-white text-base lg:text-lg font-bold tracking-wide">Synthesizing...</span></div>';
|
| 444 |
+
startBtn.disabled = true;
|
| 445 |
+
startBtn.classList.remove('animate-pulse-slow', 'animate-glow-pulse');
|
| 446 |
+
setTimeout(() => {
|
| 447 |
+
isCatalogGenerated = true;
|
| 448 |
+
startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-2 lg:gap-3 relative z-10"><span class="text-white text-base lg:text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span><span class="material-symbols-outlined text-white text-lg lg:text-xl group-hover:translate-x-1 transition-transform">arrow_forward</span></div>';
|
| 449 |
+
startBtn.disabled = false;
|
| 450 |
+
startBtn.classList.add('animate-pulse-slow', 'animate-glow-pulse');
|
| 451 |
+
}, 1500);
|
| 452 |
+
});
|
| 453 |
+
</script>
|
| 454 |
+
|
| 455 |
+
</body></html>
|
install_gh.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import shutil
|
| 2 |
+
import subprocess
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
def main():
|
| 6 |
+
# Check Status
|
| 7 |
+
gh_path = shutil.which('gh')
|
| 8 |
+
|
| 9 |
+
if not gh_path:
|
| 10 |
+
# Install
|
| 11 |
+
print("GitHub CLI not found. Installing via winget...")
|
| 12 |
+
try:
|
| 13 |
+
subprocess.run(['winget', 'install', '--id', 'GitHub.cli', '-e'], check=True)
|
| 14 |
+
except subprocess.CalledProcessError as e:
|
| 15 |
+
print(f"Error installing GitHub CLI: {e}")
|
| 16 |
+
return
|
| 17 |
+
except FileNotFoundError:
|
| 18 |
+
print("Error: 'winget' command not found. Please ensure App Installer is installed.")
|
| 19 |
+
return
|
| 20 |
+
|
| 21 |
+
# Post-Install Instructions (Runs if installed or if installation succeeded)
|
| 22 |
+
print("\n" + "="*40)
|
| 23 |
+
try:
|
| 24 |
+
# Attempt to use ANSI codes for bold, may not work in all Windows terminals but works in VS Code / modern Windows Terminal
|
| 25 |
+
print("✅ \033[1mGitHub CLI is ready!\033[0m")
|
| 26 |
+
except:
|
| 27 |
+
print("✅ GitHub CLI is ready!")
|
| 28 |
+
print("="*40)
|
| 29 |
+
print("⚠️ IMPORTANT: You must now restart your terminal to reload your PATH.")
|
| 30 |
+
print("👉 After restarting, run this command to log in: gh auth login")
|
| 31 |
+
|
| 32 |
+
if __name__ == "__main__":
|
| 33 |
+
main()
|
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"
|