Spaces:
Sleeping
Sleeping
GitHub Actions Bot commited on
Commit ·
eaa0562
1
Parent(s): d59f4dd
Deploy: c6cdf9e43825d598e4e3b553c1069ab77675c9fb
Browse files- .env +0 -2
- .env.example +0 -2
- README.md +34 -15
- app/__pycache__/main.cpython-314.pyc +0 -0
- app/main.py +72 -76
- app/services/__pycache__/model_service.cpython-314.pyc +0 -0
- app/services/llm_service.py +46 -0
- app/services/model_service.py +0 -64
- app/services/prompt_logic.py +27 -0
- app/services/storage_service.py +51 -0
- app/services/vision_service.py +41 -0
- requirements.txt +10 -6
.env
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
SUPABASE_URL=https://muftiyckgkedprgjdbhj.supabase.co
|
| 2 |
-
SUPABASE_KEY=sb_publishable_zlFaPiRisg0rA_-4NGG31g_8VrLFxJu
|
|
|
|
|
|
|
|
|
.env.example
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
SUPABASE_URL=your_supabase_url_here
|
| 2 |
-
SUPABASE_KEY=your_supabase_anon_key_here
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -8,23 +8,42 @@ pinned: false
|
|
| 8 |
app_port: 7860
|
| 9 |
---
|
| 10 |
|
| 11 |
-
# Prism Enhancer -
|
| 12 |
|
| 13 |
-
This
|
| 14 |
-
|
|
|
|
| 15 |
|
| 16 |
-
##
|
| 17 |
-
- **Offline Inference**: No external APIs.
|
| 18 |
-
- **MLOps Flywheel**: Synchronizes training data to Supabase (if configured).
|
| 19 |
-
- **FastAPI**: High-performance async API.
|
| 20 |
|
| 21 |
-
|
| 22 |
-
POST `/api/v1/enhance`
|
| 23 |
|
| 24 |
-
```
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
app_port: 7860
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# Prism Enhancer v3 - Multimodal Engine
|
| 12 |
|
| 13 |
+
This backend runs a dual-model pipeline for text and image understanding.
|
| 14 |
+
- **Vision Brain**: `vikhyatk/moondream2` (CPU Optimized)
|
| 15 |
+
- **Logic Brain**: `Qwen2.5-1.5B-Instruct` (GGUF)
|
| 16 |
|
| 17 |
+
## 🛠️ Supabase SQL Setup
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
Run this in your Supabase SQL Editor:
|
|
|
|
| 20 |
|
| 21 |
+
```sql
|
| 22 |
+
-- 1. Create Storage Bucket for User Uploads
|
| 23 |
+
insert into storage.buckets (id, name, public)
|
| 24 |
+
values ('user_uploads', 'user_uploads', true);
|
| 25 |
+
|
| 26 |
+
-- 2. Create MLOps Training Logs Table
|
| 27 |
+
create table training_logs (
|
| 28 |
+
id bigint generated by default as identity primary key,
|
| 29 |
+
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
| 30 |
+
original_text text,
|
| 31 |
+
enhanced_text text,
|
| 32 |
+
vision_description text,
|
| 33 |
+
image_path text,
|
| 34 |
+
category text
|
| 35 |
+
);
|
| 36 |
+
|
| 37 |
+
-- 3. Set up Storage Policies (Allow Public Read)
|
| 38 |
+
create policy "Public Access" on storage.objects for select using ( bucket_id = 'user_uploads' );
|
| 39 |
+
create policy "Allow Upload" on storage.objects for insert with check ( bucket_id = 'user_uploads' );
|
| 40 |
```
|
| 41 |
+
|
| 42 |
+
## 🚀 API Deployment
|
| 43 |
+
|
| 44 |
+
The backend is configured for Hugging Face Spaces (Docker). It uses `llama-cpp-python` for text logic and `transformers` for vision.
|
| 45 |
+
|
| 46 |
+
**Endpoint**: `POST /api/v1/enhance` (Multipart/FormData)
|
| 47 |
+
- `prompt`: Text input
|
| 48 |
+
- `category`: [General, 3D Logo, Cartoon Logo, Future Avatar, Video]
|
| 49 |
+
- `file`: Optional image upload for visual reference
|
app/__pycache__/main.cpython-314.pyc
DELETED
|
Binary file (6.14 kB)
|
|
|
app/main.py
CHANGED
|
@@ -1,99 +1,95 @@
|
|
| 1 |
import os
|
| 2 |
-
|
|
|
|
|
|
|
| 3 |
from pydantic import BaseModel
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
-
from supabase import create_client, Client
|
| 6 |
|
| 7 |
-
from app.services.
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
# Load environment variables
|
| 10 |
load_dotenv()
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
-
supabase: Client = None
|
| 18 |
-
if SUPABASE_URL and SUPABASE_KEY:
|
| 19 |
-
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 20 |
-
else:
|
| 21 |
-
print("WARNING: Supabase URL/KEY not found. Logging will be disabled.")
|
| 22 |
-
|
| 23 |
-
app = FastAPI(title="Prism v2 - Offline AI & MLOps", version="2.0.0")
|
| 24 |
-
|
| 25 |
-
# Data Models
|
| 26 |
-
class EnhanceRequest(BaseModel):
|
| 27 |
-
text: str
|
| 28 |
-
mode: str
|
| 29 |
-
platform: str # chatgpt | midjourney
|
| 30 |
|
|
|
|
| 31 |
class EnhanceResponse(BaseModel):
|
| 32 |
-
original_text: str
|
| 33 |
enhanced_prompt: str
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
log_id: int | None = None
|
| 37 |
|
| 38 |
-
|
| 39 |
-
log_id: int
|
| 40 |
-
liked: bool
|
| 41 |
-
|
| 42 |
-
# Helper for logging interaction to Supabase and getting the ID
|
| 43 |
-
def log_interaction(original_text: str, enhanced_text: str, mode: str, platform: str) -> int | None:
|
| 44 |
-
if not supabase:
|
| 45 |
-
return None
|
| 46 |
try:
|
| 47 |
data = {
|
| 48 |
-
"original_text":
|
| 49 |
-
"enhanced_text":
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
}
|
| 54 |
-
|
| 55 |
-
res = supabase.table("training_logs").insert(data).execute()
|
| 56 |
-
if res.data and len(res.data) > 0:
|
| 57 |
-
return res.data[0]['id']
|
| 58 |
except Exception as e:
|
| 59 |
-
print(f"Log Error: {e}")
|
| 60 |
-
return None
|
| 61 |
|
| 62 |
-
@app.
|
| 63 |
-
def
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
log_id=log_id
|
| 82 |
)
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
try:
|
| 90 |
-
# Update the log entry
|
| 91 |
-
supabase.table("training_logs").update({"liked": body.liked}).eq("id", body.log_id).execute()
|
| 92 |
-
return {"status": "success", "message": "Feedback recorded"}
|
| 93 |
-
except Exception as e:
|
| 94 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 95 |
|
| 96 |
@app.get("/")
|
| 97 |
-
def
|
| 98 |
-
return {"status": "running", "
|
| 99 |
-
|
|
|
|
| 1 |
import os
|
| 2 |
+
import io
|
| 3 |
+
from contextlib import asynccontextmanager
|
| 4 |
+
from fastapi import FastAPI, HTTPException, UploadFile, File, Form, BackgroundTasks
|
| 5 |
from pydantic import BaseModel
|
| 6 |
from dotenv import load_dotenv
|
|
|
|
| 7 |
|
| 8 |
+
from app.services.vision_service import get_vision_service
|
| 9 |
+
from app.services.storage_service import storage_service
|
| 10 |
+
from app.services.llm_service import llm_service
|
| 11 |
+
from app.services.prompt_logic import prompt_logic
|
| 12 |
|
|
|
|
| 13 |
load_dotenv()
|
| 14 |
|
| 15 |
+
# Lifecycle Manager
|
| 16 |
+
@asynccontextmanager
|
| 17 |
+
async def lifespan(app: FastAPI):
|
| 18 |
+
# Pre-load models once at startup
|
| 19 |
+
print("Initializing Dual-Brain Engine...")
|
| 20 |
+
get_vision_service()
|
| 21 |
+
llm_service.get_model()
|
| 22 |
+
yield
|
| 23 |
+
print("Shutting down Dual-Brain Engine...")
|
| 24 |
|
| 25 |
+
app = FastAPI(title="Prism v3 - Multimodal AI Engine", version="3.0.0", lifespan=lifespan)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
+
# Response Model
|
| 28 |
class EnhanceResponse(BaseModel):
|
|
|
|
| 29 |
enhanced_prompt: str
|
| 30 |
+
vision_analysis: str | None = None
|
| 31 |
+
image_url: str | None = None
|
|
|
|
| 32 |
|
| 33 |
+
def log_to_supabase(original, enhanced, vision_desc, img_url, category):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
try:
|
| 35 |
data = {
|
| 36 |
+
"original_text": original,
|
| 37 |
+
"enhanced_text": enhanced,
|
| 38 |
+
"vision_description": vision_desc,
|
| 39 |
+
"image_path": img_url,
|
| 40 |
+
"category": category
|
| 41 |
}
|
| 42 |
+
storage_service.supabase.table("training_logs").insert(data).execute()
|
|
|
|
|
|
|
|
|
|
| 43 |
except Exception as e:
|
| 44 |
+
print(f"MLOps Log Error: {e}")
|
|
|
|
| 45 |
|
| 46 |
+
@app.post("/api/v1/enhance")
|
| 47 |
+
async def enhance_pipeline(
|
| 48 |
+
background_tasks: BackgroundTasks,
|
| 49 |
+
prompt: str = Form(...),
|
| 50 |
+
category: str = Form("general"),
|
| 51 |
+
file: UploadFile | None = File(None)
|
| 52 |
+
):
|
| 53 |
+
vision_desc = None
|
| 54 |
+
img_url = None
|
| 55 |
+
opt_bytes = None
|
| 56 |
|
| 57 |
+
# Step 1 & 2: Process Image if exists
|
| 58 |
+
if file:
|
| 59 |
+
file_bytes = await file.read()
|
| 60 |
+
|
| 61 |
+
# 1. Optimize
|
| 62 |
+
opt_bytes = storage_service.optimize_image(file_bytes)
|
| 63 |
+
|
| 64 |
+
# 2. Analyze (Vision Brain)
|
| 65 |
+
vision_desc = get_vision_service().analyze_image(opt_bytes)
|
| 66 |
+
|
| 67 |
+
# 3. Upload (Vault) - Background Task to keep API fast
|
| 68 |
+
# Note: We actually need the URL for the response if possible,
|
| 69 |
+
# but let's upload and return the path/url as requested.
|
| 70 |
+
img_url = storage_service.upload_image(opt_bytes)
|
| 71 |
+
|
| 72 |
+
# Step 3: Enhance (Logic Brain)
|
| 73 |
+
# Construct combined instruction
|
| 74 |
+
master_prompt = prompt_logic.construct_master_prompt(
|
| 75 |
+
user_text=prompt,
|
| 76 |
+
vision_desc=vision_desc,
|
| 77 |
+
category=category
|
| 78 |
+
)
|
| 79 |
|
| 80 |
+
final_prompt = llm_service.generate(master_prompt)
|
| 81 |
+
|
| 82 |
+
# Step 4: Data Flywheel Logging
|
| 83 |
+
background_tasks.add_task(
|
| 84 |
+
log_to_supabase, prompt, final_prompt, vision_desc, img_url, category
|
|
|
|
| 85 |
)
|
| 86 |
|
| 87 |
+
return EnhanceResponse(
|
| 88 |
+
enhanced_prompt=final_prompt,
|
| 89 |
+
vision_analysis=vision_desc,
|
| 90 |
+
image_url=img_url
|
| 91 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
@app.get("/")
|
| 94 |
+
def health():
|
| 95 |
+
return {"status": "running", "engine": "Prism-v3-Multimodal"}
|
|
|
app/services/__pycache__/model_service.cpython-314.pyc
DELETED
|
Binary file (3.18 kB)
|
|
|
app/services/llm_service.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from huggingface_hub import hf_hub_download
|
| 3 |
+
from llama_cpp import Llama
|
| 4 |
+
|
| 5 |
+
class LLMService:
|
| 6 |
+
_llm = None
|
| 7 |
+
|
| 8 |
+
# Qwen2.5-1.5B is great for CPU and logic
|
| 9 |
+
MODEL_REPO = "bartowski/Qwen2.5-1.5B-Instruct-GGUF"
|
| 10 |
+
MODEL_FILE = "Qwen2.5-1.5B-Instruct-Q4_K_M.gguf"
|
| 11 |
+
|
| 12 |
+
@classmethod
|
| 13 |
+
def get_model(cls):
|
| 14 |
+
if cls._llm is None:
|
| 15 |
+
print(f"Loading LLM: {cls.MODEL_REPO}...")
|
| 16 |
+
try:
|
| 17 |
+
model_path = hf_hub_download(repo_id=cls.MODEL_REPO, filename=cls.MODEL_FILE)
|
| 18 |
+
cls._llm = Llama(
|
| 19 |
+
model_path=model_path,
|
| 20 |
+
n_ctx=4096,
|
| 21 |
+
verbose=False
|
| 22 |
+
)
|
| 23 |
+
print("LLM loaded successfully.")
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f"Error loading LLM: {e}")
|
| 26 |
+
raise e
|
| 27 |
+
return cls._llm
|
| 28 |
+
|
| 29 |
+
@classmethod
|
| 30 |
+
def generate(cls, master_prompt: str) -> str:
|
| 31 |
+
llm = cls.get_model()
|
| 32 |
+
|
| 33 |
+
# Qwen2.5 chat template
|
| 34 |
+
prompt = f"<|im_start|>system\nYou are a professional Prompt Architect. Refine and enhance input prompts based on category and visual evidence.<|im_end|>\n<|im_start|>user\n{master_prompt}<|im_end|>\n<|im_start|>assistant\n"
|
| 35 |
+
|
| 36 |
+
output = llm.create_completion(
|
| 37 |
+
prompt,
|
| 38 |
+
max_tokens=600,
|
| 39 |
+
temperature=0.7,
|
| 40 |
+
stop=["<|im_end|>"]
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
return output['choices'][0]['text'].strip()
|
| 44 |
+
|
| 45 |
+
# Singleton helper
|
| 46 |
+
llm_service = LLMService()
|
app/services/model_service.py
DELETED
|
@@ -1,64 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
from huggingface_hub import hf_hub_download
|
| 3 |
-
from llama_cpp import Llama
|
| 4 |
-
|
| 5 |
-
class ModelService:
|
| 6 |
-
_instance = None
|
| 7 |
-
_llm = None
|
| 8 |
-
|
| 9 |
-
# Configuration
|
| 10 |
-
# Using a stable quantized GGUF of Gemma 2 2B
|
| 11 |
-
MODEL_REPO = "bartowski/gemma-2-2b-it-GGUF"
|
| 12 |
-
MODEL_FILE = "gemma-2-2b-it-Q4_K_M.gguf"
|
| 13 |
-
|
| 14 |
-
@classmethod
|
| 15 |
-
def get_model(cls):
|
| 16 |
-
if cls._llm is None:
|
| 17 |
-
print(f"Loading model... {cls.MODEL_REPO}/{cls.MODEL_FILE}")
|
| 18 |
-
|
| 19 |
-
# Download/Load Model
|
| 20 |
-
try:
|
| 21 |
-
model_path = hf_hub_download(
|
| 22 |
-
repo_id=cls.MODEL_REPO,
|
| 23 |
-
filename=cls.MODEL_FILE,
|
| 24 |
-
)
|
| 25 |
-
print(f"Model path: {model_path}")
|
| 26 |
-
|
| 27 |
-
# Initialize Llama
|
| 28 |
-
cls._llm = Llama(
|
| 29 |
-
model_path=model_path,
|
| 30 |
-
n_ctx=2048,
|
| 31 |
-
verbose=False
|
| 32 |
-
)
|
| 33 |
-
print("Model loaded successfully.")
|
| 34 |
-
except Exception as e:
|
| 35 |
-
print(f"Error loading model: {e}")
|
| 36 |
-
raise e
|
| 37 |
-
|
| 38 |
-
return cls._llm
|
| 39 |
-
|
| 40 |
-
@staticmethod
|
| 41 |
-
def construct_system_prompt(mode: str, platform: str) -> str:
|
| 42 |
-
return (
|
| 43 |
-
f"You are an expert Prompt Engineer. Rewrite the user's input to be clear, structured, "
|
| 44 |
-
f"and optimized for {platform}. Use a {mode} tone. "
|
| 45 |
-
f"Return ONLY the enhanced prompt, no conversational filler."
|
| 46 |
-
)
|
| 47 |
-
|
| 48 |
-
@classmethod
|
| 49 |
-
def enhance(cls, text: str, mode: str, platform: str) -> str:
|
| 50 |
-
llm = cls.get_model()
|
| 51 |
-
|
| 52 |
-
system_prompt = cls.construct_system_prompt(mode, platform)
|
| 53 |
-
|
| 54 |
-
# Gemma 2 formatting
|
| 55 |
-
full_prompt = f"<start_of_turn>user\nSystem: {system_prompt}\nUser Input: {text}<end_of_turn>\n<start_of_turn>model\n"
|
| 56 |
-
|
| 57 |
-
output = llm.create_completion(
|
| 58 |
-
full_prompt,
|
| 59 |
-
max_tokens=512,
|
| 60 |
-
stop=["<end_of_turn>"],
|
| 61 |
-
temperature=0.7
|
| 62 |
-
)
|
| 63 |
-
|
| 64 |
-
return output['choices'][0]['text'].strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/services/prompt_logic.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class PromptLogic:
|
| 2 |
+
SYSTEM_INSTRUCTIONS = {
|
| 3 |
+
"general": "Enhance the user's prompt to be more descriptive and professional while maintaining the core intent.",
|
| 4 |
+
"3d logo": "Professional 3D logo design, high resolution, isometric view, Octane Render, Unreal Engine 5, minimalist, clean lines, professional color palette.",
|
| 5 |
+
"cartoon logo": "Playful cartoon logo, vibrant colors, vector style, bold outlines, 2D minimalist, sticker aesthetic.",
|
| 6 |
+
"future avatar": "Cyberpunk aesthetic, neon lighting, hyper-realistic, 8k resolution, ray tracing textures, metallic and glowing accents, futuristic clothing.",
|
| 7 |
+
"video": "Cinematic video prompt, 4k ultra HD, 60fps, professional lighting, dolly zoom effect, anamorphic lens, ISO 400, golden hour lighting, hyper-detailed textures.",
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
@staticmethod
|
| 11 |
+
def construct_master_prompt(user_text: str, vision_desc: str = None, category: str = "general") -> str:
|
| 12 |
+
category = category.lower()
|
| 13 |
+
instruction = PromptLogic.SYSTEM_INSTRUCTIONS.get(category, PromptLogic.SYSTEM_INSTRUCTIONS["general"])
|
| 14 |
+
|
| 15 |
+
master_prompt = f"System Instruction: {instruction}\n\n"
|
| 16 |
+
|
| 17 |
+
if vision_desc:
|
| 18 |
+
master_prompt += f"Visual Reference Analysis: {vision_desc}\n\n"
|
| 19 |
+
master_prompt += "Ensure the final prompt incorporates the visual details (face, hair, clothing, lighting) to maintain exact character consistency.\n\n"
|
| 20 |
+
|
| 21 |
+
master_prompt += f"User Input: {user_text}\n\n"
|
| 22 |
+
master_prompt += "Modified Output Prompt:"
|
| 23 |
+
|
| 24 |
+
return master_prompt
|
| 25 |
+
|
| 26 |
+
# Helper
|
| 27 |
+
prompt_logic = PromptLogic()
|
app/services/storage_service.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
import uuid
|
| 3 |
+
import os
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from supabase import create_client, Client
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
class StorageService:
|
| 11 |
+
def __init__(self):
|
| 12 |
+
url = os.getenv("SUPABASE_URL")
|
| 13 |
+
key = os.getenv("SUPABASE_KEY")
|
| 14 |
+
self.supabase: Client = create_client(url, key)
|
| 15 |
+
self.bucket_name = "user_uploads"
|
| 16 |
+
|
| 17 |
+
def optimize_image(self, file_bytes: bytes) -> bytes:
|
| 18 |
+
img = Image.open(io.BytesIO(file_bytes)).convert("RGB")
|
| 19 |
+
|
| 20 |
+
# Resize if > 1024px
|
| 21 |
+
max_size = 1024
|
| 22 |
+
if max(img.size) > max_size:
|
| 23 |
+
ratio = max_size / max(img.size)
|
| 24 |
+
new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
|
| 25 |
+
img = img.resize(new_size, Image.LANCZOS)
|
| 26 |
+
|
| 27 |
+
# Compress and save as WebP
|
| 28 |
+
output = io.BytesIO()
|
| 29 |
+
img.save(output, format="WEBP", quality=85)
|
| 30 |
+
return output.getvalue()
|
| 31 |
+
|
| 32 |
+
def upload_image(self, file_bytes: bytes) -> str:
|
| 33 |
+
try:
|
| 34 |
+
filename = f"{uuid.uuid4()}.webp"
|
| 35 |
+
|
| 36 |
+
# Binary upload to Supabase Storage
|
| 37 |
+
self.supabase.storage.from_(self.bucket_name).upload(
|
| 38 |
+
path=filename,
|
| 39 |
+
file=file_bytes,
|
| 40 |
+
file_options={"content-type": "image/webp"}
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
# Get Public URL
|
| 44 |
+
res = self.supabase.storage.from_(self.bucket_name).get_public_url(filename)
|
| 45 |
+
return res
|
| 46 |
+
except Exception as e:
|
| 47 |
+
print(f"Upload Error: {e}")
|
| 48 |
+
return ""
|
| 49 |
+
|
| 50 |
+
# Singleton
|
| 51 |
+
storage_service = StorageService()
|
app/services/vision_service.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import io
|
| 5 |
+
|
| 6 |
+
class VisionService:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.model_id = "vikhyatk/moondream2"
|
| 9 |
+
self.revision = "2024-08-26" # Use a stable revision
|
| 10 |
+
self.device = "cpu"
|
| 11 |
+
|
| 12 |
+
print(f"Loading Vision Model: {self.model_id}...")
|
| 13 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_id, revision=self.revision)
|
| 14 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 15 |
+
self.model_id,
|
| 16 |
+
trust_remote_code=True,
|
| 17 |
+
revision=self.revision
|
| 18 |
+
).to(self.device)
|
| 19 |
+
self.model.eval()
|
| 20 |
+
|
| 21 |
+
def analyze_image(self, image_bytes: bytes) -> str:
|
| 22 |
+
try:
|
| 23 |
+
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 24 |
+
enc_image = self.model.encode_image(image)
|
| 25 |
+
|
| 26 |
+
prompt = "Describe this person's face, hair, body type, lighting, and clothing in extreme detail for a 3D character reference."
|
| 27 |
+
|
| 28 |
+
description = self.model.answer_question(enc_image, prompt, self.tokenizer)
|
| 29 |
+
return description
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print(f"Vision Analysis Error: {e}")
|
| 32 |
+
return "No image description available."
|
| 33 |
+
|
| 34 |
+
# Singleton instance
|
| 35 |
+
vision_service = None
|
| 36 |
+
|
| 37 |
+
def get_vision_service():
|
| 38 |
+
global vision_service
|
| 39 |
+
if vision_service is None:
|
| 40 |
+
vision_service = VisionService()
|
| 41 |
+
return vision_service
|
requirements.txt
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
|
|
| 1 |
fastapi==0.109.0
|
| 2 |
uvicorn==0.27.0
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--index-url https://download.pytorch.org/whl/cpu
|
| 2 |
fastapi==0.109.0
|
| 3 |
uvicorn==0.27.0
|
| 4 |
+
python-multipart
|
| 5 |
+
torch
|
| 6 |
+
transformers
|
| 7 |
+
einops
|
| 8 |
+
pillow
|
| 9 |
+
supabase
|
| 10 |
+
huggingface-hub
|
| 11 |
+
llama-cpp-python
|
| 12 |
+
python-dotenv
|