Spaces:
Sleeping
Sleeping
Commit
·
d75fbc8
0
Parent(s):
Initial commit with LFS models
Browse files- .gitattributes +36 -0
- Dockerfile +37 -0
- README.md +12 -0
- app.py +249 -0
- models/EDSR_x4.pb +3 -0
- models/colorization_release_v2.caffemodel +3 -0
- models/pts_in_hull.npy +3 -0
- models/u2net.onnx +3 -0
- models/yolov8n-pose.pt +3 -0
- models/yolov8n.pt +3 -0
- requirements.txt +23 -0
- services/__init__.py +0 -0
- services/__pycache__/__init__.cpython-311.pyc +0 -0
- services/__pycache__/ai_studio.cpython-311.pyc +0 -0
- services/__pycache__/studio.cpython-311.pyc +0 -0
- services/__pycache__/utilities.cpython-311.pyc +0 -0
- services/__pycache__/vision.cpython-311.pyc +0 -0
- services/ai_studio.py +113 -0
- services/studio.py +111 -0
- services/utilities.py +129 -0
- services/vision.py +179 -0
- static/css/style.css +558 -0
- static/js/main.js +419 -0
- templates/about.html +147 -0
- templates/ai_studio.html +102 -0
- templates/base.html +145 -0
- templates/docs.html +174 -0
- templates/download.html +389 -0
- templates/onboarding.html +135 -0
- templates/products.html +23 -0
- templates/studio.html +141 -0
- templates/utilities.html +205 -0
- templates/vision.html +89 -0
.gitattributes
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.caffemodel filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 1. Base image
|
| 2 |
+
FROM python:3.9-slim
|
| 3 |
+
|
| 4 |
+
# 2. Create non-root user (HF requirement)
|
| 5 |
+
RUN useradd -m -u 1000 user
|
| 6 |
+
USER user
|
| 7 |
+
|
| 8 |
+
# 3. Environment
|
| 9 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
# 4. System dependencies (OpenCV + git)
|
| 13 |
+
USER root
|
| 14 |
+
RUN apt-get update && apt-get install -y \
|
| 15 |
+
libgl1-mesa-glx \
|
| 16 |
+
libglib2.0-0 \
|
| 17 |
+
git \
|
| 18 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 19 |
+
USER user
|
| 20 |
+
|
| 21 |
+
# 5. Copy requirements first (cache-friendly)
|
| 22 |
+
COPY --chown=user requirements.txt .
|
| 23 |
+
|
| 24 |
+
# 6. Install Python dependencies
|
| 25 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 26 |
+
|
| 27 |
+
# 7. Copy application code
|
| 28 |
+
COPY --chown=user . .
|
| 29 |
+
|
| 30 |
+
# 8. Create upload directory safely
|
| 31 |
+
RUN mkdir -p static/uploads
|
| 32 |
+
|
| 33 |
+
# 9. Hugging Face port
|
| 34 |
+
EXPOSE 7860
|
| 35 |
+
|
| 36 |
+
# 10. Production server
|
| 37 |
+
CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
|
README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Radiant Ai
|
| 3 |
+
emoji: ⚡
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: openrail
|
| 9 |
+
short_description: Advance AI Image Editor and Processor
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
from flask import Flask, render_template, request, jsonify, send_from_directory
|
| 4 |
+
from werkzeug.utils import secure_filename
|
| 5 |
+
|
| 6 |
+
# Import Services (We will create these next)
|
| 7 |
+
# Note: Ensure empty __init__.py exists in services/ folder
|
| 8 |
+
from services.studio import StudioService
|
| 9 |
+
from services.vision import VisionService
|
| 10 |
+
from services.ai_studio import AIStudioService
|
| 11 |
+
from services.utilities import UtilitiesService
|
| 12 |
+
|
| 13 |
+
# Initialize Flask App
|
| 14 |
+
app = Flask(__name__)
|
| 15 |
+
|
| 16 |
+
# --- Configuration ---
|
| 17 |
+
UPLOAD_FOLDER = 'static/uploads'
|
| 18 |
+
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'webp', 'bmp'}
|
| 19 |
+
|
| 20 |
+
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
| 21 |
+
app.config['MAX_CONTENT_LENGTH'] = 32 * 1024 * 1024 # Increased to 32MB for high-res images
|
| 22 |
+
app.secret_key = 'radiant_secret_key_dev'
|
| 23 |
+
|
| 24 |
+
# Ensure upload directory exists
|
| 25 |
+
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
| 26 |
+
|
| 27 |
+
# Initialize Service Instances
|
| 28 |
+
studio_service = StudioService()
|
| 29 |
+
vision_service = VisionService()
|
| 30 |
+
ai_service = AIStudioService()
|
| 31 |
+
utilities_service = UtilitiesService()
|
| 32 |
+
|
| 33 |
+
# --- Helpers ---
|
| 34 |
+
def allowed_file(filename):
|
| 35 |
+
return '.' in filename and \
|
| 36 |
+
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
| 37 |
+
|
| 38 |
+
def get_file_path(filename):
|
| 39 |
+
return os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(filename))
|
| 40 |
+
|
| 41 |
+
def generate_output_filename(original_filename, prefix="processed"):
|
| 42 |
+
timestamp = int(time.time())
|
| 43 |
+
name, ext = os.path.splitext(original_filename)
|
| 44 |
+
return f"{prefix}_{timestamp}_{name}{ext}"
|
| 45 |
+
|
| 46 |
+
# --- Page Routes (Frontend) ---
|
| 47 |
+
|
| 48 |
+
@app.route('/')
|
| 49 |
+
def index():
|
| 50 |
+
return render_template('onboarding.html')
|
| 51 |
+
|
| 52 |
+
@app.route('/studio')
|
| 53 |
+
def studio():
|
| 54 |
+
return render_template('studio.html')
|
| 55 |
+
|
| 56 |
+
@app.route('/vision')
|
| 57 |
+
def vision():
|
| 58 |
+
return render_template('vision.html')
|
| 59 |
+
|
| 60 |
+
@app.route('/ai-studio')
|
| 61 |
+
def ai_studio():
|
| 62 |
+
return render_template('ai_studio.html')
|
| 63 |
+
|
| 64 |
+
@app.route('/utilities')
|
| 65 |
+
def utilities():
|
| 66 |
+
return render_template('utilities.html')
|
| 67 |
+
|
| 68 |
+
@app.route('/about')
|
| 69 |
+
def about():
|
| 70 |
+
return render_template('about.html')
|
| 71 |
+
|
| 72 |
+
#@app.route('/products')
|
| 73 |
+
#def products():
|
| 74 |
+
# return render_template('products.html')
|
| 75 |
+
|
| 76 |
+
@app.route('/docs')
|
| 77 |
+
def docs():
|
| 78 |
+
return render_template('docs.html')
|
| 79 |
+
|
| 80 |
+
@app.route('/download')
|
| 81 |
+
def download():
|
| 82 |
+
return render_template('download.html')
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# --- API: Core Upload ---
|
| 89 |
+
|
| 90 |
+
@app.route('/api/upload', methods=['POST'])
|
| 91 |
+
def upload_file():
|
| 92 |
+
if 'file' not in request.files:
|
| 93 |
+
return jsonify({'success': False, 'error': 'No file part'}), 400
|
| 94 |
+
|
| 95 |
+
file = request.files['file']
|
| 96 |
+
if file.filename == '':
|
| 97 |
+
return jsonify({'success': False, 'error': 'No selected file'}), 400
|
| 98 |
+
|
| 99 |
+
if file and allowed_file(file.filename):
|
| 100 |
+
# Create unique filename
|
| 101 |
+
filename = secure_filename(f"{int(time.time())}_{file.filename}")
|
| 102 |
+
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
| 103 |
+
file.save(filepath)
|
| 104 |
+
|
| 105 |
+
return jsonify({
|
| 106 |
+
'success': True,
|
| 107 |
+
'filename': filename,
|
| 108 |
+
'url': f"/static/uploads/{filename}"
|
| 109 |
+
})
|
| 110 |
+
|
| 111 |
+
return jsonify({'success': False, 'error': 'Invalid file type'}), 400
|
| 112 |
+
|
| 113 |
+
# --- API: Studio (Image Editing) ---
|
| 114 |
+
|
| 115 |
+
@app.route('/api/process-studio', methods=['POST'])
|
| 116 |
+
def process_studio():
|
| 117 |
+
try:
|
| 118 |
+
data = request.json
|
| 119 |
+
filename = data.get('filename')
|
| 120 |
+
options = data.get('options') # Contains brightness, contrast, filter, etc.
|
| 121 |
+
|
| 122 |
+
if not filename or not options:
|
| 123 |
+
return jsonify({'success': False, 'error': 'Missing parameters'}), 400
|
| 124 |
+
|
| 125 |
+
input_path = get_file_path(filename)
|
| 126 |
+
output_filename = generate_output_filename(filename, "edit")
|
| 127 |
+
output_path = os.path.join(app.config['UPLOAD_FOLDER'], output_filename)
|
| 128 |
+
|
| 129 |
+
# Call Service
|
| 130 |
+
success, result = studio_service.process_request(input_path, output_path, options)
|
| 131 |
+
|
| 132 |
+
if success:
|
| 133 |
+
return jsonify({
|
| 134 |
+
'success': True,
|
| 135 |
+
'url': f"/static/uploads/{output_filename}",
|
| 136 |
+
'filename': output_filename
|
| 137 |
+
})
|
| 138 |
+
else:
|
| 139 |
+
return jsonify({'success': False, 'error': str(result)}), 500
|
| 140 |
+
|
| 141 |
+
except Exception as e:
|
| 142 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 143 |
+
|
| 144 |
+
# --- API: Vision (YOLO, BG Remove) ---
|
| 145 |
+
|
| 146 |
+
@app.route('/api/process-vision', methods=['POST'])
|
| 147 |
+
def process_vision():
|
| 148 |
+
try:
|
| 149 |
+
data = request.json
|
| 150 |
+
filename = data.get('filename')
|
| 151 |
+
task = data.get('task')
|
| 152 |
+
# extra options like 'color' for bg replacement or 'value' for blur intensity
|
| 153 |
+
options = data
|
| 154 |
+
|
| 155 |
+
if not filename or not task:
|
| 156 |
+
return jsonify({'success': False, 'error': 'Missing parameters'}), 400
|
| 157 |
+
|
| 158 |
+
input_path = get_file_path(filename)
|
| 159 |
+
output_filename = generate_output_filename(filename, f"vision_{task}")
|
| 160 |
+
output_path = os.path.join(app.config['UPLOAD_FOLDER'], output_filename)
|
| 161 |
+
|
| 162 |
+
success, result_meta = vision_service.process_request(input_path, output_path, task, options)
|
| 163 |
+
|
| 164 |
+
if success:
|
| 165 |
+
response = {
|
| 166 |
+
'success': True,
|
| 167 |
+
'url': f"/static/uploads/{output_filename}",
|
| 168 |
+
'filename': output_filename
|
| 169 |
+
}
|
| 170 |
+
# Add detections if they exist (for YOLO)
|
| 171 |
+
if isinstance(result_meta, dict) and 'detections' in result_meta:
|
| 172 |
+
response['detections'] = result_meta['detections']
|
| 173 |
+
return jsonify(response)
|
| 174 |
+
else:
|
| 175 |
+
return jsonify({'success': False, 'error': str(result_meta)}), 500
|
| 176 |
+
|
| 177 |
+
except Exception as e:
|
| 178 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 179 |
+
|
| 180 |
+
# --- API: AI Studio (Colorize, Upscale) ---
|
| 181 |
+
|
| 182 |
+
@app.route('/api/process-ai', methods=['POST'])
|
| 183 |
+
def process_ai():
|
| 184 |
+
try:
|
| 185 |
+
data = request.json
|
| 186 |
+
filename = data.get('filename')
|
| 187 |
+
task = data.get('task')
|
| 188 |
+
|
| 189 |
+
if not filename or not task:
|
| 190 |
+
return jsonify({'success': False, 'error': 'Missing parameters'}), 400
|
| 191 |
+
|
| 192 |
+
input_path = get_file_path(filename)
|
| 193 |
+
output_filename = generate_output_filename(filename, f"ai_{task}")
|
| 194 |
+
output_path = os.path.join(app.config['UPLOAD_FOLDER'], output_filename)
|
| 195 |
+
|
| 196 |
+
success, result = ai_service.process_request(input_path, output_path, task)
|
| 197 |
+
|
| 198 |
+
if success:
|
| 199 |
+
return jsonify({
|
| 200 |
+
'success': True,
|
| 201 |
+
'url': f"/static/uploads/{output_filename}",
|
| 202 |
+
'filename': output_filename
|
| 203 |
+
})
|
| 204 |
+
else:
|
| 205 |
+
return jsonify({'success': False, 'error': str(result)}), 500
|
| 206 |
+
|
| 207 |
+
except Exception as e:
|
| 208 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 209 |
+
|
| 210 |
+
# --- API: Utilities (Convert, Resize) ---
|
| 211 |
+
|
| 212 |
+
@app.route('/api/process-utility', methods=['POST'])
|
| 213 |
+
def process_utility():
|
| 214 |
+
try:
|
| 215 |
+
data = request.json
|
| 216 |
+
filename = data.get('filename')
|
| 217 |
+
# Service handles specific logic internally
|
| 218 |
+
|
| 219 |
+
input_path = get_file_path(filename)
|
| 220 |
+
# Output directory is passed so service can generate name based on format
|
| 221 |
+
output_dir = app.config['UPLOAD_FOLDER']
|
| 222 |
+
|
| 223 |
+
success, result = utilities_service.process_request(input_path, output_dir, data)
|
| 224 |
+
|
| 225 |
+
if success:
|
| 226 |
+
if data.get('action') == 'metadata':
|
| 227 |
+
return jsonify({'success': True, 'metadata': result})
|
| 228 |
+
else:
|
| 229 |
+
return jsonify({
|
| 230 |
+
'success': True,
|
| 231 |
+
'url': f"/static/uploads/{result}",
|
| 232 |
+
'filename': result
|
| 233 |
+
})
|
| 234 |
+
else:
|
| 235 |
+
return jsonify({'success': False, 'error': str(result)}), 500
|
| 236 |
+
|
| 237 |
+
except Exception as e:
|
| 238 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 239 |
+
|
| 240 |
+
# --- Serve Static Files (Images) ---
|
| 241 |
+
@app.route('/static/uploads/<filename>')
|
| 242 |
+
def uploaded_file(filename):
|
| 243 |
+
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
| 244 |
+
|
| 245 |
+
if __name__ == '__main__':
|
| 246 |
+
return "Radiant AI running on Hugging Face 🚀"
|
| 247 |
+
#print("🚀 Radiant AI Server Starting...")
|
| 248 |
+
#app.run(debug=True, port=5000)
|
| 249 |
+
#app.run(host='0.0.0.0', port=7860)
|
models/EDSR_x4.pb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dd35ce3cae53ecee2d16045e08a932c3e7242d641bb65cb971d123e06904347f
|
| 3 |
+
size 38573255
|
models/colorization_release_v2.caffemodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f5af1e602646328c792e1094f9876fe9cd4c09ac46fa886e5708a1abc89137b1
|
| 3 |
+
size 128946764
|
models/pts_in_hull.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b5dec01315c34f43f1c8c089e84c45ae35d1838d8e77ed0e7ca930f79ffa450e
|
| 3 |
+
size 5088
|
models/u2net.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8d10d2f3bb75ae3b6d527c77944fc5e7dcd94b29809d47a739a7a728a912b491
|
| 3 |
+
size 175997641
|
models/yolov8n-pose.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c6fa93dd1ee4a2c18c900a45c1d864a1c6f7aba75d84f91648a30b7fb641d212
|
| 3 |
+
size 6832633
|
models/yolov8n.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:31e20dde3def09e2cf938c7be6fe23d9150bbbe503982af13345706515f2ef95
|
| 3 |
+
size 6534387
|
requirements.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Flask==3.0.0
|
| 2 |
+
opencv-python-headless==4.9.0.80
|
| 3 |
+
numpy==1.26.2
|
| 4 |
+
Pillow==10.1.0
|
| 5 |
+
rembg==2.0.50
|
| 6 |
+
ultralytics==8.0.227
|
| 7 |
+
werkzeug==3.0.1
|
| 8 |
+
onnxruntime==1.16.3
|
| 9 |
+
scikit-image==0.22.0
|
| 10 |
+
gunicorn
|
| 11 |
+
# Note: TensorFlow removed to save space (OpenCV handles the .pb models)
|
| 12 |
+
# Note: Torch is installed automatically as a dependency of Ultralytics
|
| 13 |
+
|
| 14 |
+
#flask
|
| 15 |
+
#opencv-python-headless
|
| 16 |
+
#numpy
|
| 17 |
+
#pillow
|
| 18 |
+
#torch
|
| 19 |
+
#ultralytics
|
| 20 |
+
#rembg
|
| 21 |
+
#onnxruntime
|
| 22 |
+
#scikit-image
|
| 23 |
+
#tensorflow
|
services/__init__.py
ADDED
|
File without changes
|
services/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (197 Bytes). View file
|
|
|
services/__pycache__/ai_studio.cpython-311.pyc
ADDED
|
Binary file (7.2 kB). View file
|
|
|
services/__pycache__/studio.cpython-311.pyc
ADDED
|
Binary file (7.41 kB). View file
|
|
|
services/__pycache__/utilities.cpython-311.pyc
ADDED
|
Binary file (6.27 kB). View file
|
|
|
services/__pycache__/vision.cpython-311.pyc
ADDED
|
Binary file (10.1 kB). View file
|
|
|
services/ai_studio.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
from cv2 import dnn_superres
|
| 5 |
+
|
| 6 |
+
class AIStudioService:
|
| 7 |
+
"""
|
| 8 |
+
Handles Deep Learning tasks:
|
| 9 |
+
- Colorization (Caffe)
|
| 10 |
+
- Super Resolution (EDSR x4)
|
| 11 |
+
- Restoration (Denoising)
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
def __init__(self):
|
| 15 |
+
self.models_dir = 'models'
|
| 16 |
+
|
| 17 |
+
# Colorization Paths
|
| 18 |
+
self.proto_path = os.path.join(self.models_dir, 'colorization_deploy_v2.prototxt')
|
| 19 |
+
self.model_path = os.path.join(self.models_dir, 'colorization_release_v2.caffemodel')
|
| 20 |
+
self.points_path = os.path.join(self.models_dir, 'pts_in_hull.npy')
|
| 21 |
+
|
| 22 |
+
# Super Resolution Path
|
| 23 |
+
self.edsr_path = os.path.join(self.models_dir, 'EDSR_x4.pb')
|
| 24 |
+
|
| 25 |
+
def process_request(self, image_path, output_path, task):
|
| 26 |
+
try:
|
| 27 |
+
if not os.path.exists(image_path):
|
| 28 |
+
raise FileNotFoundError("Input file not found")
|
| 29 |
+
|
| 30 |
+
if task == 'colorize':
|
| 31 |
+
return self._colorize_image(image_path, output_path)
|
| 32 |
+
elif task == 'upscale':
|
| 33 |
+
return self._upscale_image(image_path, output_path)
|
| 34 |
+
elif task == 'restore':
|
| 35 |
+
return self._restore_image(image_path, output_path)
|
| 36 |
+
else:
|
| 37 |
+
raise ValueError(f"Unknown AI task: {task}")
|
| 38 |
+
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"AI Service Error: {e}")
|
| 41 |
+
return False, str(e)
|
| 42 |
+
|
| 43 |
+
def _colorize_image(self, input_path, output_path):
|
| 44 |
+
if not os.path.exists(self.model_path):
|
| 45 |
+
raise FileNotFoundError("Colorization models missing")
|
| 46 |
+
|
| 47 |
+
net = cv2.dnn.readNetFromCaffe(self.proto_path, self.model_path)
|
| 48 |
+
pts = np.load(self.points_path)
|
| 49 |
+
|
| 50 |
+
class8 = net.getLayerId("class8_ab")
|
| 51 |
+
conv8 = net.getLayerId("conv8_313_rh")
|
| 52 |
+
pts = pts.transpose().reshape(2, 313, 1, 1)
|
| 53 |
+
|
| 54 |
+
net.getLayer(class8).blobs = [pts.astype("float32")]
|
| 55 |
+
net.getLayer(conv8).blobs = [np.full([1, 313], 2.606, dtype="float32")]
|
| 56 |
+
|
| 57 |
+
img = cv2.imread(input_path)
|
| 58 |
+
normalized = img.astype("float32") / 255.0
|
| 59 |
+
lab = cv2.cvtColor(normalized, cv2.COLOR_BGR2LAB)
|
| 60 |
+
|
| 61 |
+
resized = cv2.resize(lab, (224, 224))
|
| 62 |
+
L = cv2.split(resized)[0]
|
| 63 |
+
L -= 50
|
| 64 |
+
|
| 65 |
+
net.setInput(cv2.dnn.blobFromImage(L))
|
| 66 |
+
ab = net.forward()[0, :, :, :].transpose((1, 2, 0))
|
| 67 |
+
ab = cv2.resize(ab, (img.shape[1], img.shape[0]))
|
| 68 |
+
|
| 69 |
+
L_orig = cv2.split(lab)[0]
|
| 70 |
+
colorized = np.concatenate((L_orig[:, :, np.newaxis], ab), axis=2)
|
| 71 |
+
colorized = cv2.cvtColor(colorized, cv2.COLOR_LAB2BGR)
|
| 72 |
+
colorized = np.clip(colorized, 0, 1)
|
| 73 |
+
|
| 74 |
+
colorized = (255 * colorized).astype("uint8")
|
| 75 |
+
cv2.imwrite(output_path, colorized)
|
| 76 |
+
|
| 77 |
+
return True, output_path
|
| 78 |
+
|
| 79 |
+
def _upscale_image(self, input_path, output_path):
|
| 80 |
+
if not os.path.exists(self.edsr_path):
|
| 81 |
+
raise FileNotFoundError("EDSR_x4.pb model missing")
|
| 82 |
+
|
| 83 |
+
img = cv2.imread(input_path)
|
| 84 |
+
h, w = img.shape[:2]
|
| 85 |
+
|
| 86 |
+
# Safety Check: Resize if too big before upscaling
|
| 87 |
+
max_dim = 1024
|
| 88 |
+
if max(h, w) > max_dim:
|
| 89 |
+
scale = max_dim / max(h, w)
|
| 90 |
+
new_w, new_h = int(w * scale), int(h * scale)
|
| 91 |
+
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
| 92 |
+
|
| 93 |
+
sr = dnn_superres.DnnSuperResImpl_create()
|
| 94 |
+
sr.readModel(self.edsr_path)
|
| 95 |
+
sr.setModel("edsr", 4)
|
| 96 |
+
|
| 97 |
+
result = sr.upsample(img)
|
| 98 |
+
cv2.imwrite(output_path, result)
|
| 99 |
+
|
| 100 |
+
return True, output_path
|
| 101 |
+
|
| 102 |
+
def _restore_image(self, input_path, output_path):
|
| 103 |
+
"""
|
| 104 |
+
Removes noise from image (Denoising).
|
| 105 |
+
"""
|
| 106 |
+
img = cv2.imread(input_path)
|
| 107 |
+
|
| 108 |
+
# h = strength of filter. Big h = perfectly smooth but blurred details.
|
| 109 |
+
# 10 is a good balance.
|
| 110 |
+
result = cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)
|
| 111 |
+
|
| 112 |
+
cv2.imwrite(output_path, result)
|
| 113 |
+
return True, output_path
|
services/studio.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
class StudioService:
|
| 5 |
+
"""
|
| 6 |
+
Handles standard image editing operations.
|
| 7 |
+
CRITICAL: Always expects the ORIGINAL image as input to prevent double-processing.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
def process_request(self, image_path, output_path, options):
|
| 11 |
+
try:
|
| 12 |
+
img = cv2.imread(image_path)
|
| 13 |
+
if img is None:
|
| 14 |
+
raise ValueError("Could not load image.")
|
| 15 |
+
|
| 16 |
+
# 1. Geometry (Rotate/Flip)
|
| 17 |
+
# Note: We apply this first so filters apply to the correct orientation
|
| 18 |
+
img = self._apply_geometry(img, options)
|
| 19 |
+
|
| 20 |
+
# 2. Adjustments (Brightness/Contrast/Sharpness)
|
| 21 |
+
img = self._apply_adjustments(img, options)
|
| 22 |
+
|
| 23 |
+
# 3. Filters
|
| 24 |
+
filter_type = options.get('filter', 'none')
|
| 25 |
+
if filter_type != 'none':
|
| 26 |
+
img = self._apply_filter(img, filter_type)
|
| 27 |
+
|
| 28 |
+
cv2.imwrite(output_path, img)
|
| 29 |
+
return True, output_path
|
| 30 |
+
|
| 31 |
+
except Exception as e:
|
| 32 |
+
print(f"Studio Error: {e}")
|
| 33 |
+
return False, str(e)
|
| 34 |
+
|
| 35 |
+
def _apply_geometry(self, img, options):
|
| 36 |
+
rotation = int(options.get('rotation', 0)) % 360
|
| 37 |
+
if rotation == 90: img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
|
| 38 |
+
elif rotation == 180: img = cv2.rotate(img, cv2.ROTATE_180)
|
| 39 |
+
elif rotation == 270: img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
| 40 |
+
|
| 41 |
+
if options.get('flip_h'): img = cv2.flip(img, 1)
|
| 42 |
+
if options.get('flip_v'): img = cv2.flip(img, 0)
|
| 43 |
+
return img
|
| 44 |
+
|
| 45 |
+
def _apply_adjustments(self, img, options):
|
| 46 |
+
# Brightness & Contrast
|
| 47 |
+
bright = int(options.get('brightness', 0))
|
| 48 |
+
contrast = int(options.get('contrast', 0))
|
| 49 |
+
|
| 50 |
+
if bright != 0 or contrast != 0:
|
| 51 |
+
if contrast == -100: alpha = 0.0
|
| 52 |
+
else: alpha = 131 * (contrast + 127) / (127 * (131 - contrast))
|
| 53 |
+
beta = bright
|
| 54 |
+
img = cv2.convertScaleAbs(img, alpha=alpha, beta=beta)
|
| 55 |
+
|
| 56 |
+
# Saturation
|
| 57 |
+
sat = int(options.get('saturation', 0))
|
| 58 |
+
if sat != 0:
|
| 59 |
+
# Convert to HSV, handling potential grayscale images
|
| 60 |
+
if len(img.shape) == 2: img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
|
| 61 |
+
|
| 62 |
+
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV).astype("float32")
|
| 63 |
+
(h, s, v) = cv2.split(hsv)
|
| 64 |
+
s = s * (1 + sat / 100.0)
|
| 65 |
+
s = np.clip(s, 0, 255)
|
| 66 |
+
hsv = cv2.merge([h, s, v])
|
| 67 |
+
img = cv2.cvtColor(hsv.astype("uint8"), cv2.COLOR_HSV2BGR)
|
| 68 |
+
|
| 69 |
+
# Sharpness (Unsharp Masking)
|
| 70 |
+
sharp = int(options.get('sharpness', 0))
|
| 71 |
+
if sharp != 0:
|
| 72 |
+
# Sigma=3.0, Threshold=0
|
| 73 |
+
gaussian = cv2.GaussianBlur(img, (0, 0), 3.0)
|
| 74 |
+
amount = sharp / 50.0 # Scale 0-100 to 0-2.0 strength
|
| 75 |
+
img = cv2.addWeighted(img, 1 + amount, gaussian, -amount, 0)
|
| 76 |
+
|
| 77 |
+
return img
|
| 78 |
+
|
| 79 |
+
def _apply_filter(self, img, f_type):
|
| 80 |
+
# Ensure image is 3-channel for filters
|
| 81 |
+
if len(img.shape) == 2: img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
|
| 82 |
+
|
| 83 |
+
if f_type == 'grayscale':
|
| 84 |
+
return cv2.cvtColor(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), cv2.COLOR_GRAY2BGR)
|
| 85 |
+
elif f_type == 'sepia':
|
| 86 |
+
kernel = np.array([[0.272, 0.534, 0.131], [0.349, 0.686, 0.168], [0.393, 0.769, 0.189]])
|
| 87 |
+
return cv2.transform(img, kernel)
|
| 88 |
+
elif f_type == 'blur':
|
| 89 |
+
return cv2.GaussianBlur(img, (15, 15), 0)
|
| 90 |
+
elif f_type == 'negative':
|
| 91 |
+
return cv2.bitwise_not(img)
|
| 92 |
+
elif f_type == 'cyberpunk':
|
| 93 |
+
img = img.astype(np.float32)
|
| 94 |
+
b, g, r = cv2.split(img)
|
| 95 |
+
b = np.clip(b * 1.2 + 10, 0, 255)
|
| 96 |
+
r = np.clip(r * 1.1 + 10, 0, 255)
|
| 97 |
+
return cv2.merge([b, g, r]).astype(np.uint8)
|
| 98 |
+
elif f_type == 'sketch':
|
| 99 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 100 |
+
inv = cv2.bitwise_not(gray)
|
| 101 |
+
blur = cv2.GaussianBlur(inv, (21, 21), 0)
|
| 102 |
+
return cv2.cvtColor(cv2.divide(gray, 255 - blur, scale=256), cv2.COLOR_GRAY2BGR)
|
| 103 |
+
elif f_type == 'cartoon':
|
| 104 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 105 |
+
gray = cv2.medianBlur(gray, 5)
|
| 106 |
+
edges = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 9)
|
| 107 |
+
color = cv2.bilateralFilter(img, 9, 300, 300)
|
| 108 |
+
return cv2.bitwise_and(color, cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR))
|
| 109 |
+
|
| 110 |
+
# Fallback for others (summer, winter, etc) or return original
|
| 111 |
+
return img
|
services/utilities.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from PIL import Image, ExifTags
|
| 3 |
+
|
| 4 |
+
class UtilitiesService:
|
| 5 |
+
"""
|
| 6 |
+
Handles general image utilities:
|
| 7 |
+
- Format Conversion (JPG, PNG, WEBP, BMP)
|
| 8 |
+
- Resizing & Compression
|
| 9 |
+
- Metadata (EXIF) Extraction
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
def process_request(self, image_path, output_dir, options):
|
| 13 |
+
"""
|
| 14 |
+
Main entry point.
|
| 15 |
+
options: {'action': 'convert'|'resize'|'metadata', ...params}
|
| 16 |
+
"""
|
| 17 |
+
try:
|
| 18 |
+
# check file exists
|
| 19 |
+
if not os.path.exists(image_path):
|
| 20 |
+
raise FileNotFoundError("Input file missing")
|
| 21 |
+
|
| 22 |
+
action = options.get('action')
|
| 23 |
+
|
| 24 |
+
if action == 'metadata':
|
| 25 |
+
return True, self._get_metadata(image_path)
|
| 26 |
+
|
| 27 |
+
elif action == 'convert':
|
| 28 |
+
return self._convert_format(image_path, output_dir, options)
|
| 29 |
+
|
| 30 |
+
elif action == 'resize':
|
| 31 |
+
return self._resize_image(image_path, output_dir, options)
|
| 32 |
+
|
| 33 |
+
else:
|
| 34 |
+
raise ValueError(f"Unknown utility action: {action}")
|
| 35 |
+
|
| 36 |
+
except Exception as e:
|
| 37 |
+
print(f"Utility Error: {e}")
|
| 38 |
+
return False, str(e)
|
| 39 |
+
|
| 40 |
+
def _convert_format(self, image_path, output_dir, options):
|
| 41 |
+
img = Image.open(image_path)
|
| 42 |
+
|
| 43 |
+
# Target format
|
| 44 |
+
target_format = options.get('format', 'png').lower()
|
| 45 |
+
|
| 46 |
+
# Handle RGBA to RGB conversion for JPG/BMP which don't support transparency
|
| 47 |
+
if target_format in ['jpg', 'jpeg', 'bmp'] and img.mode == 'RGBA':
|
| 48 |
+
img = img.convert('RGB')
|
| 49 |
+
|
| 50 |
+
# Generate new filename
|
| 51 |
+
base_name = os.path.splitext(os.path.basename(image_path))[0]
|
| 52 |
+
# Clean timestamp prefix if it exists to avoid double timestamps
|
| 53 |
+
if '_' in base_name and base_name[0].isdigit():
|
| 54 |
+
# rough heuristic to keep name clean
|
| 55 |
+
pass
|
| 56 |
+
|
| 57 |
+
new_filename = f"converted_{base_name}.{target_format}"
|
| 58 |
+
output_path = os.path.join(output_dir, new_filename)
|
| 59 |
+
|
| 60 |
+
# Save
|
| 61 |
+
if target_format in ['jpg', 'jpeg']:
|
| 62 |
+
img.save(output_path, quality=95)
|
| 63 |
+
else:
|
| 64 |
+
img.save(output_path)
|
| 65 |
+
|
| 66 |
+
return True, new_filename
|
| 67 |
+
|
| 68 |
+
def _resize_image(self, image_path, output_dir, options):
|
| 69 |
+
img = Image.open(image_path)
|
| 70 |
+
|
| 71 |
+
# Get dimensions
|
| 72 |
+
try:
|
| 73 |
+
# If input is empty string, use original dim
|
| 74 |
+
w_opt = options.get('width')
|
| 75 |
+
h_opt = options.get('height')
|
| 76 |
+
|
| 77 |
+
w = int(w_opt) if w_opt else img.width
|
| 78 |
+
h = int(h_opt) if h_opt else img.height
|
| 79 |
+
except ValueError:
|
| 80 |
+
w, h = img.width, img.height
|
| 81 |
+
|
| 82 |
+
quality = int(options.get('quality', 90))
|
| 83 |
+
|
| 84 |
+
# Resize (LANCZOS is high quality downsampling filter)
|
| 85 |
+
img = img.resize((w, h), Image.Resampling.LANCZOS)
|
| 86 |
+
|
| 87 |
+
# Generate filename
|
| 88 |
+
base_name = os.path.basename(image_path)
|
| 89 |
+
output_path = os.path.join(output_dir, f"resized_{base_name}")
|
| 90 |
+
|
| 91 |
+
# Save with compression if JPEG/WEBP
|
| 92 |
+
save_kwargs = {}
|
| 93 |
+
if output_path.lower().endswith(('.jpg', '.jpeg', '.webp')):
|
| 94 |
+
# Ensure RGB for JPEG
|
| 95 |
+
if img.mode == 'RGBA' and output_path.lower().endswith(('.jpg', '.jpeg')):
|
| 96 |
+
img = img.convert('RGB')
|
| 97 |
+
save_kwargs['quality'] = quality
|
| 98 |
+
|
| 99 |
+
img.save(output_path, **save_kwargs)
|
| 100 |
+
|
| 101 |
+
return True, os.path.basename(output_path)
|
| 102 |
+
|
| 103 |
+
def _get_metadata(self, image_path):
|
| 104 |
+
img = Image.open(image_path)
|
| 105 |
+
exif_data = {}
|
| 106 |
+
|
| 107 |
+
# Extract basic info
|
| 108 |
+
exif_data['Format'] = img.format
|
| 109 |
+
exif_data['Mode'] = img.mode
|
| 110 |
+
exif_data['Size'] = f"{img.width} x {img.height}"
|
| 111 |
+
|
| 112 |
+
# Extract EXIF if available
|
| 113 |
+
if hasattr(img, '_getexif') and img._getexif():
|
| 114 |
+
for tag, value in img._getexif().items():
|
| 115 |
+
if tag in ExifTags.TAGS:
|
| 116 |
+
tag_name = ExifTags.TAGS[tag]
|
| 117 |
+
# Filter out binary data which isn't JSON serializable
|
| 118 |
+
if isinstance(value, bytes):
|
| 119 |
+
value = "<Binary Data>"
|
| 120 |
+
# Limit long strings
|
| 121 |
+
if isinstance(value, str) and len(value) > 100:
|
| 122 |
+
value = value[:100] + "..."
|
| 123 |
+
|
| 124 |
+
exif_data[tag_name] = str(value)
|
| 125 |
+
|
| 126 |
+
if len(exif_data) <= 3: # Only basic info found
|
| 127 |
+
exif_data['Note'] = "No advanced EXIF data found in this image."
|
| 128 |
+
|
| 129 |
+
return exif_data
|
services/vision.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
from rembg import remove
|
| 5 |
+
from ultralytics import YOLO
|
| 6 |
+
from PIL import Image
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
|
| 9 |
+
class VisionService:
|
| 10 |
+
"""
|
| 11 |
+
Handles Computer Vision tasks:
|
| 12 |
+
- Object Detection (YOLOv8)
|
| 13 |
+
- Pose Estimation (YOLOv8-Pose)
|
| 14 |
+
- Background Removal (rembg)
|
| 15 |
+
- Background Replacement
|
| 16 |
+
- Privacy Face Blur
|
| 17 |
+
- Document Scanner (Thresholding)
|
| 18 |
+
- Edge Detection
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(self):
|
| 22 |
+
# Lazy loading models
|
| 23 |
+
self.yolo_model = None
|
| 24 |
+
self.pose_model = None
|
| 25 |
+
self.face_cascade = None
|
| 26 |
+
|
| 27 |
+
def process_request(self, image_path, output_path, task, options=None):
|
| 28 |
+
try:
|
| 29 |
+
if not os.path.exists(image_path):
|
| 30 |
+
raise FileNotFoundError("Input file not found")
|
| 31 |
+
|
| 32 |
+
# Initialize result metadata
|
| 33 |
+
result_meta = {}
|
| 34 |
+
options = options or {}
|
| 35 |
+
|
| 36 |
+
# --- Task Dispatcher ---
|
| 37 |
+
|
| 38 |
+
if task == 'yolo':
|
| 39 |
+
img = cv2.imread(image_path)
|
| 40 |
+
img, detections = self._detect_objects(img)
|
| 41 |
+
result_meta['detections'] = detections
|
| 42 |
+
cv2.imwrite(output_path, img)
|
| 43 |
+
|
| 44 |
+
elif task == 'pose':
|
| 45 |
+
img = cv2.imread(image_path)
|
| 46 |
+
img = self._estimate_pose(img)
|
| 47 |
+
cv2.imwrite(output_path, img)
|
| 48 |
+
|
| 49 |
+
elif task == 'remove_bg':
|
| 50 |
+
img = cv2.imread(image_path)
|
| 51 |
+
img = self._remove_background(img)
|
| 52 |
+
cv2.imwrite(output_path, img)
|
| 53 |
+
|
| 54 |
+
elif task == 'replace_bg':
|
| 55 |
+
color = options.get('color', '#ffffff')
|
| 56 |
+
self._replace_background_color(image_path, output_path, color)
|
| 57 |
+
|
| 58 |
+
elif task == 'face_blur':
|
| 59 |
+
img = cv2.imread(image_path)
|
| 60 |
+
intensity = int(options.get('value', 30))
|
| 61 |
+
img = self._blur_faces(img, intensity)
|
| 62 |
+
cv2.imwrite(output_path, img)
|
| 63 |
+
|
| 64 |
+
elif task == 'doc_scanner':
|
| 65 |
+
img = cv2.imread(image_path)
|
| 66 |
+
img = self._document_threshold(img)
|
| 67 |
+
cv2.imwrite(output_path, img)
|
| 68 |
+
|
| 69 |
+
elif task == 'canny_edge':
|
| 70 |
+
img = cv2.imread(image_path)
|
| 71 |
+
img = self._detect_edges(img)
|
| 72 |
+
cv2.imwrite(output_path, img)
|
| 73 |
+
|
| 74 |
+
else:
|
| 75 |
+
raise ValueError(f"Unknown task: {task}")
|
| 76 |
+
|
| 77 |
+
return True, result_meta
|
| 78 |
+
|
| 79 |
+
except Exception as e:
|
| 80 |
+
print(f"Vision Error: {e}")
|
| 81 |
+
return False, str(e)
|
| 82 |
+
|
| 83 |
+
# --- Feature Implementations ---
|
| 84 |
+
|
| 85 |
+
def _detect_objects(self, img):
|
| 86 |
+
if self.yolo_model is None:
|
| 87 |
+
self.yolo_model = YOLO('yolov8n.pt') # Auto-downloads if missing
|
| 88 |
+
|
| 89 |
+
results = self.yolo_model(img)
|
| 90 |
+
detections = []
|
| 91 |
+
|
| 92 |
+
for r in results:
|
| 93 |
+
for box in r.boxes:
|
| 94 |
+
x1, y1, x2, y2 = map(int, box.xyxy[0])
|
| 95 |
+
conf = float(box.conf[0])
|
| 96 |
+
cls = int(box.cls[0])
|
| 97 |
+
|
| 98 |
+
if conf < 0.4: continue
|
| 99 |
+
|
| 100 |
+
label = self.yolo_model.names[cls]
|
| 101 |
+
detections.append({'label': label, 'conf': conf})
|
| 102 |
+
|
| 103 |
+
# Draw Fancy Box
|
| 104 |
+
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 242, 255), 2)
|
| 105 |
+
cv2.putText(img, f"{label} {int(conf*100)}%", (x1, y1 - 10),
|
| 106 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 242, 255), 2)
|
| 107 |
+
|
| 108 |
+
return img, detections
|
| 109 |
+
|
| 110 |
+
def _estimate_pose(self, img):
|
| 111 |
+
if self.pose_model is None:
|
| 112 |
+
# Use the Pose model version
|
| 113 |
+
self.pose_model = YOLO('yolov8n-pose.pt')
|
| 114 |
+
|
| 115 |
+
results = self.pose_model(img)
|
| 116 |
+
|
| 117 |
+
# Plot results directly onto the image
|
| 118 |
+
# YOLOv8 has a built-in plotter that draws the skeletons beautifully
|
| 119 |
+
for r in results:
|
| 120 |
+
img = r.plot()
|
| 121 |
+
|
| 122 |
+
return img
|
| 123 |
+
|
| 124 |
+
def _remove_background(self, img):
|
| 125 |
+
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 126 |
+
output = remove(img_rgb)
|
| 127 |
+
return cv2.cvtColor(output, cv2.COLOR_RGBA2BGRA)
|
| 128 |
+
|
| 129 |
+
def _replace_background_color(self, input_path, output_path, hex_color):
|
| 130 |
+
with open(input_path, 'rb') as i:
|
| 131 |
+
subject_bytes = remove(i.read())
|
| 132 |
+
|
| 133 |
+
subject = Image.open(BytesIO(subject_bytes)).convert("RGBA")
|
| 134 |
+
|
| 135 |
+
# Parse Color
|
| 136 |
+
if hex_color.startswith('#'): hex_color = hex_color.lstrip('#')
|
| 137 |
+
rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
| 138 |
+
|
| 139 |
+
background = Image.new("RGBA", subject.size, rgb + (255,))
|
| 140 |
+
combined = Image.alpha_composite(background, subject)
|
| 141 |
+
combined.convert("RGB").save(output_path, quality=95)
|
| 142 |
+
|
| 143 |
+
def _blur_faces(self, img, intensity):
|
| 144 |
+
if self.face_cascade is None:
|
| 145 |
+
self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
| 146 |
+
|
| 147 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 148 |
+
faces = self.face_cascade.detectMultiScale(gray, 1.1, 4)
|
| 149 |
+
|
| 150 |
+
# Map intensity 0-100 to kernel size 3-99
|
| 151 |
+
k = int(intensity) * 2 + 1
|
| 152 |
+
|
| 153 |
+
for (x, y, w, h) in faces:
|
| 154 |
+
roi = img[y:y+h, x:x+w]
|
| 155 |
+
roi = cv2.GaussianBlur(roi, (k, k), 30)
|
| 156 |
+
img[y:y+h, x:x+w] = roi
|
| 157 |
+
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 1)
|
| 158 |
+
|
| 159 |
+
return img
|
| 160 |
+
|
| 161 |
+
def _document_threshold(self, img):
|
| 162 |
+
# Convert to grayscale
|
| 163 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 164 |
+
# Apply Adaptive Thresholding (Great for documents with uneven lighting)
|
| 165 |
+
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
| 166 |
+
cv2.THRESH_BINARY, 11, 2)
|
| 167 |
+
return cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
|
| 168 |
+
|
| 169 |
+
def _detect_edges(self, img):
|
| 170 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 171 |
+
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
| 172 |
+
# Auto-Canny: median based thresholds
|
| 173 |
+
v = np.median(blurred)
|
| 174 |
+
sigma = 0.33
|
| 175 |
+
lower = int(max(0, (1.0 - sigma) * v))
|
| 176 |
+
upper = int(min(255, (1.0 + sigma) * v))
|
| 177 |
+
|
| 178 |
+
edges = cv2.Canny(blurred, lower, upper)
|
| 179 |
+
return cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
|
static/css/style.css
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* --- CSS VARIABLES & THEME --- */
|
| 2 |
+
:root {
|
| 3 |
+
/* Colors */
|
| 4 |
+
--bg-dark: #0a0a0f;
|
| 5 |
+
--bg-panel: #14141e;
|
| 6 |
+
|
| 7 |
+
/* Neon Accents */
|
| 8 |
+
--primary-cyan: #00f2ff;
|
| 9 |
+
--primary-purple: #bd00ff;
|
| 10 |
+
--primary-pink: #ff0055;
|
| 11 |
+
--gradient-main: linear-gradient(135deg, #00f2ff 0%, #bd00ff 100%);
|
| 12 |
+
|
| 13 |
+
/* Glassmorphism settings */
|
| 14 |
+
--glass-bg: rgba(255, 255, 255, 0.03);
|
| 15 |
+
--glass-border: rgba(255, 255, 255, 0.08);
|
| 16 |
+
--glass-blur: 20px;
|
| 17 |
+
|
| 18 |
+
/* Text */
|
| 19 |
+
--text-white: #ffffff;
|
| 20 |
+
--text-gray: #a0a0a5;
|
| 21 |
+
|
| 22 |
+
/* Spacing */
|
| 23 |
+
--sidebar-width: 260px;
|
| 24 |
+
--header-height: 70px;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
/* --- RESET & BASE --- */
|
| 28 |
+
* {
|
| 29 |
+
box-sizing: border-box;
|
| 30 |
+
margin: 0;
|
| 31 |
+
padding: 0;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
body {
|
| 35 |
+
font-family: 'Inter', sans-serif;
|
| 36 |
+
color: var(--text-white);
|
| 37 |
+
background: var(--bg-dark);
|
| 38 |
+
font-size: 16px;
|
| 39 |
+
line-height: 1.5;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
a { text-decoration: none; color: inherit; }
|
| 43 |
+
ul { list-style: none; }
|
| 44 |
+
button, input, select { font-family: inherit; }
|
| 45 |
+
|
| 46 |
+
/* --- SIDEBAR STYLING --- */
|
| 47 |
+
.sidebar {
|
| 48 |
+
width: var(--sidebar-width);
|
| 49 |
+
height: 100vh;
|
| 50 |
+
background: rgba(10, 10, 15, 0.7);
|
| 51 |
+
backdrop-filter: blur(var(--glass-blur));
|
| 52 |
+
-webkit-backdrop-filter: blur(var(--glass-blur));
|
| 53 |
+
border-right: 1px solid var(--glass-border);
|
| 54 |
+
display: flex;
|
| 55 |
+
flex-direction: column;
|
| 56 |
+
padding: 25px;
|
| 57 |
+
z-index: 100;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.brand {
|
| 61 |
+
font-family: 'Orbitron', sans-serif;
|
| 62 |
+
font-size: 1.4rem;
|
| 63 |
+
font-weight: 700;
|
| 64 |
+
margin-bottom: 40px;
|
| 65 |
+
display: flex;
|
| 66 |
+
align-items: center;
|
| 67 |
+
gap: 12px;
|
| 68 |
+
color: var(--text-white);
|
| 69 |
+
letter-spacing: 1px;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.text-gradient {
|
| 73 |
+
background: var(--gradient-main);
|
| 74 |
+
-webkit-background-clip: text;
|
| 75 |
+
-webkit-text-fill-color: transparent;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
/* Top Bar Navigation Links */
|
| 79 |
+
.nav-link-btn {
|
| 80 |
+
display: inline-flex;
|
| 81 |
+
align-items: center;
|
| 82 |
+
gap: 6px;
|
| 83 |
+
padding: 8px 16px;
|
| 84 |
+
border-radius: 10px;
|
| 85 |
+
background: rgba(255, 255, 255, 0.03);
|
| 86 |
+
border: 1px solid var(--glass-border);
|
| 87 |
+
color: var(--text-gray);
|
| 88 |
+
font-size: 0.9rem;
|
| 89 |
+
font-weight: 500;
|
| 90 |
+
transition: all 0.3s ease;
|
| 91 |
+
text-decoration: none;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.nav-link-btn i {
|
| 95 |
+
font-size: 1.1rem;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.nav-link-btn:hover {
|
| 99 |
+
background: rgba(255, 255, 255, 0.08);
|
| 100 |
+
border-color: var(--primary-cyan);
|
| 101 |
+
color: var(--text-white);
|
| 102 |
+
transform: translateY(-2px);
|
| 103 |
+
box-shadow: 0 4px 12px rgba(0, 242, 255, 0.1);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
/* Responsive Navigation */
|
| 107 |
+
@media (max-width: 768px) {
|
| 108 |
+
.nav-link-btn span {
|
| 109 |
+
display: none;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.nav-link-btn {
|
| 113 |
+
padding: 10px;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.user-controls {
|
| 117 |
+
gap: 10px;
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
/* Section Styling for About/Docs Pages */
|
| 122 |
+
.page-container {
|
| 123 |
+
max-width: 1200px;
|
| 124 |
+
margin: 0 auto;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.section-block {
|
| 128 |
+
background: var(--glass-bg);
|
| 129 |
+
border: 1px solid var(--glass-border);
|
| 130 |
+
border-radius: 20px;
|
| 131 |
+
padding: 40px;
|
| 132 |
+
margin-bottom: 30px;
|
| 133 |
+
animation: fadeIn 0.6s ease-out;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.section-title {
|
| 137 |
+
font-family: 'Orbitron', sans-serif;
|
| 138 |
+
font-size: 2rem;
|
| 139 |
+
margin-bottom: 20px;
|
| 140 |
+
background: var(--gradient-main);
|
| 141 |
+
-webkit-background-clip: text;
|
| 142 |
+
-webkit-text-fill-color: transparent;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.section-subtitle {
|
| 146 |
+
font-size: 1.5rem;
|
| 147 |
+
margin-bottom: 15px;
|
| 148 |
+
color: var(--text-white);
|
| 149 |
+
font-weight: 600;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.info-grid {
|
| 153 |
+
display: grid;
|
| 154 |
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
| 155 |
+
gap: 20px;
|
| 156 |
+
margin-top: 30px;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.info-card {
|
| 160 |
+
background: rgba(255, 255, 255, 0.02);
|
| 161 |
+
border: 1px solid var(--glass-border);
|
| 162 |
+
border-radius: 15px;
|
| 163 |
+
padding: 25px;
|
| 164 |
+
transition: all 0.3s ease;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.info-card:hover {
|
| 168 |
+
background: rgba(255, 255, 255, 0.05);
|
| 169 |
+
border-color: var(--primary-purple);
|
| 170 |
+
transform: translateY(-3px);
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.info-card h4 {
|
| 174 |
+
color: var(--primary-cyan);
|
| 175 |
+
margin-bottom: 10px;
|
| 176 |
+
font-size: 1.1rem;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.info-card p {
|
| 180 |
+
color: var(--text-gray);
|
| 181 |
+
line-height: 1.6;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
.nav-menu {
|
| 186 |
+
display: flex;
|
| 187 |
+
flex-direction: column;
|
| 188 |
+
gap: 8px;
|
| 189 |
+
flex: 1;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.nav-item {
|
| 193 |
+
display: flex;
|
| 194 |
+
align-items: center;
|
| 195 |
+
gap: 12px;
|
| 196 |
+
padding: 12px 16px;
|
| 197 |
+
border-radius: 12px;
|
| 198 |
+
color: var(--text-gray);
|
| 199 |
+
font-weight: 500;
|
| 200 |
+
transition: all 0.3s ease;
|
| 201 |
+
border: 1px solid transparent;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.nav-item i { font-size: 1.3rem; }
|
| 205 |
+
|
| 206 |
+
.nav-item:hover {
|
| 207 |
+
background: rgba(255, 255, 255, 0.05);
|
| 208 |
+
color: var(--text-white);
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
.nav-item.active {
|
| 212 |
+
background: rgba(0, 242, 255, 0.08);
|
| 213 |
+
border-color: rgba(0, 242, 255, 0.2);
|
| 214 |
+
color: var(--primary-cyan);
|
| 215 |
+
box-shadow: 0 0 15px rgba(0, 242, 255, 0.05);
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.sidebar-footer {
|
| 219 |
+
font-size: 0.8rem;
|
| 220 |
+
color: var(--text-gray);
|
| 221 |
+
border-top: 1px solid var(--glass-border);
|
| 222 |
+
padding-top: 20px;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.status-indicator {
|
| 226 |
+
display: flex;
|
| 227 |
+
align-items: center;
|
| 228 |
+
gap: 8px;
|
| 229 |
+
margin-bottom: 5px;
|
| 230 |
+
font-size: 0.75rem;
|
| 231 |
+
color: #00ff88;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.status-dot {
|
| 235 |
+
width: 8px;
|
| 236 |
+
height: 8px;
|
| 237 |
+
background: #00ff88;
|
| 238 |
+
border-radius: 50%;
|
| 239 |
+
box-shadow: 0 0 8px #00ff88;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
/* --- MAIN CONTENT LAYOUT --- */
|
| 243 |
+
.main-content {
|
| 244 |
+
flex: 1;
|
| 245 |
+
display: flex;
|
| 246 |
+
flex-direction: column;
|
| 247 |
+
height: 100vh;
|
| 248 |
+
overflow: hidden;
|
| 249 |
+
position: relative;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.top-bar {
|
| 253 |
+
height: var(--header-height);
|
| 254 |
+
display: flex;
|
| 255 |
+
justify-content: space-between;
|
| 256 |
+
align-items: center;
|
| 257 |
+
padding: 0 30px;
|
| 258 |
+
border-bottom: 1px solid var(--glass-border);
|
| 259 |
+
background: rgba(10, 10, 15, 0.3);
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
.page-title {
|
| 263 |
+
font-family: 'Orbitron', sans-serif;
|
| 264 |
+
font-size: 1.4rem;
|
| 265 |
+
letter-spacing: 0.5px;
|
| 266 |
+
font-weight: 500;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.user-controls {
|
| 270 |
+
display: flex;
|
| 271 |
+
gap: 20px;
|
| 272 |
+
align-items: center;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
.icon-btn {
|
| 276 |
+
background: transparent;
|
| 277 |
+
border: none;
|
| 278 |
+
color: var(--text-gray);
|
| 279 |
+
font-size: 1.2rem;
|
| 280 |
+
cursor: pointer;
|
| 281 |
+
transition: color 0.2s;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
.icon-btn:hover { color: var(--text-white); }
|
| 285 |
+
|
| 286 |
+
.avatar {
|
| 287 |
+
width: 38px;
|
| 288 |
+
height: 38px;
|
| 289 |
+
border-radius: 50%;
|
| 290 |
+
background: var(--gradient-main);
|
| 291 |
+
display: flex;
|
| 292 |
+
align-items: center;
|
| 293 |
+
justify-content: center;
|
| 294 |
+
font-weight: 700;
|
| 295 |
+
font-size: 1rem;
|
| 296 |
+
color: #000;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.content-scrollable {
|
| 300 |
+
flex: 1;
|
| 301 |
+
overflow-y: auto;
|
| 302 |
+
padding: 30px;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
/* --- GLASSMORPHISM COMPONENTS --- */
|
| 306 |
+
.glass-panel {
|
| 307 |
+
background: var(--glass-bg);
|
| 308 |
+
border: 1px solid var(--glass-border);
|
| 309 |
+
backdrop-filter: blur(var(--glass-blur));
|
| 310 |
+
-webkit-backdrop-filter: blur(var(--glass-blur));
|
| 311 |
+
border-radius: 20px;
|
| 312 |
+
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.2);
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
.tool-header {
|
| 316 |
+
font-size: 0.8rem;
|
| 317 |
+
text-transform: uppercase;
|
| 318 |
+
letter-spacing: 1px;
|
| 319 |
+
color: var(--text-gray);
|
| 320 |
+
margin-bottom: 12px;
|
| 321 |
+
font-weight: 600;
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
/* --- BUTTONS --- */
|
| 325 |
+
.btn {
|
| 326 |
+
padding: 12px 24px;
|
| 327 |
+
border-radius: 10px;
|
| 328 |
+
font-weight: 600;
|
| 329 |
+
cursor: pointer;
|
| 330 |
+
border: none;
|
| 331 |
+
font-family: 'Inter', sans-serif;
|
| 332 |
+
transition: all 0.2s ease;
|
| 333 |
+
display: inline-flex;
|
| 334 |
+
align-items: center;
|
| 335 |
+
justify-content: center;
|
| 336 |
+
gap: 8px;
|
| 337 |
+
font-size: 0.95rem;
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
.btn:active { transform: scale(0.97); }
|
| 341 |
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
| 342 |
+
|
| 343 |
+
.btn-primary {
|
| 344 |
+
background: var(--gradient-main);
|
| 345 |
+
color: #0a0a0f;
|
| 346 |
+
box-shadow: 0 0 20px rgba(0, 242, 255, 0.2);
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
.btn-primary:hover:not(:disabled) {
|
| 350 |
+
box-shadow: 0 0 30px rgba(0, 242, 255, 0.4);
|
| 351 |
+
filter: brightness(1.1);
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
.btn-secondary {
|
| 355 |
+
background: rgba(255, 255, 255, 0.05);
|
| 356 |
+
border: 1px solid var(--glass-border);
|
| 357 |
+
color: var(--text-white);
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
.btn-secondary:hover:not(:disabled) {
|
| 361 |
+
background: rgba(255, 255, 255, 0.1);
|
| 362 |
+
border-color: rgba(255, 255, 255, 0.2);
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
/* --- FORMS & INPUTS --- */
|
| 366 |
+
input[type="range"] {
|
| 367 |
+
width: 100%;
|
| 368 |
+
height: 6px;
|
| 369 |
+
background: rgba(255, 255, 255, 0.1);
|
| 370 |
+
border-radius: 5px;
|
| 371 |
+
outline: none;
|
| 372 |
+
-webkit-appearance: none;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
input[type="range"]::-webkit-slider-thumb {
|
| 376 |
+
-webkit-appearance: none;
|
| 377 |
+
width: 18px;
|
| 378 |
+
height: 18px;
|
| 379 |
+
background: var(--primary-cyan);
|
| 380 |
+
border-radius: 50%;
|
| 381 |
+
cursor: pointer;
|
| 382 |
+
box-shadow: 0 0 10px rgba(0, 242, 255, 0.5);
|
| 383 |
+
margin-top: -6px; /* center thumb */
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
input[type="range"]::-webkit-slider-runnable-track {
|
| 387 |
+
width: 100%;
|
| 388 |
+
height: 6px;
|
| 389 |
+
cursor: pointer;
|
| 390 |
+
background: transparent;
|
| 391 |
+
border-radius: 5px;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.upload-placeholder {
|
| 395 |
+
text-align: center;
|
| 396 |
+
color: var(--text-gray);
|
| 397 |
+
padding: 40px;
|
| 398 |
+
border: 2px dashed var(--glass-border);
|
| 399 |
+
border-radius: 16px;
|
| 400 |
+
transition: all 0.3s;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
.upload-placeholder:hover {
|
| 404 |
+
border-color: var(--primary-cyan);
|
| 405 |
+
background: rgba(0, 242, 255, 0.02);
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
/* Download Button Highlight */
|
| 409 |
+
.download-highlight {
|
| 410 |
+
background: linear-gradient(135deg, rgba(0, 242, 255, 0.1) 0%, rgba(189, 0, 255, 0.1) 100%) !important;
|
| 411 |
+
border-color: var(--primary-cyan) !important;
|
| 412 |
+
color: var(--primary-cyan) !important;
|
| 413 |
+
position: relative;
|
| 414 |
+
overflow: hidden;
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
.download-highlight::before {
|
| 418 |
+
content: '';
|
| 419 |
+
position: absolute;
|
| 420 |
+
top: 0;
|
| 421 |
+
left: -100%;
|
| 422 |
+
width: 100%;
|
| 423 |
+
height: 100%;
|
| 424 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
| 425 |
+
transition: left 0.5s;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
.download-highlight:hover::before {
|
| 429 |
+
left: 100%;
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.download-highlight:hover {
|
| 433 |
+
box-shadow: 0 0 20px rgba(0, 242, 255, 0.3) !important;
|
| 434 |
+
transform: translateY(-2px);
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
/* Platform Download Cards */
|
| 438 |
+
.download-cards {
|
| 439 |
+
display: grid;
|
| 440 |
+
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
| 441 |
+
gap: 25px;
|
| 442 |
+
margin-top: 30px;
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
.platform-card {
|
| 446 |
+
background: var(--glass-bg);
|
| 447 |
+
border: 1px solid var(--glass-border);
|
| 448 |
+
border-radius: 20px;
|
| 449 |
+
padding: 35px;
|
| 450 |
+
text-align: center;
|
| 451 |
+
transition: all 0.4s ease;
|
| 452 |
+
position: relative;
|
| 453 |
+
overflow: hidden;
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
.platform-card::before {
|
| 457 |
+
content: '';
|
| 458 |
+
position: absolute;
|
| 459 |
+
top: -50%;
|
| 460 |
+
left: -50%;
|
| 461 |
+
width: 200%;
|
| 462 |
+
height: 200%;
|
| 463 |
+
background: radial-gradient(circle, rgba(0, 242, 255, 0.1) 0%, transparent 70%);
|
| 464 |
+
opacity: 0;
|
| 465 |
+
transition: opacity 0.4s;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
.platform-card:hover::before {
|
| 469 |
+
opacity: 1;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
.platform-card:hover {
|
| 473 |
+
transform: translateY(-8px);
|
| 474 |
+
border-color: var(--primary-cyan);
|
| 475 |
+
box-shadow: 0 15px 40px rgba(0, 242, 255, 0.15);
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
.platform-icon {
|
| 479 |
+
font-size: 4rem;
|
| 480 |
+
margin-bottom: 20px;
|
| 481 |
+
display: inline-block;
|
| 482 |
+
position: relative;
|
| 483 |
+
z-index: 1;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
.platform-name {
|
| 487 |
+
font-size: 1.6rem;
|
| 488 |
+
font-weight: 600;
|
| 489 |
+
margin-bottom: 10px;
|
| 490 |
+
color: var(--text-white);
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
.platform-details {
|
| 494 |
+
color: var(--text-gray);
|
| 495 |
+
font-size: 0.9rem;
|
| 496 |
+
margin-bottom: 25px;
|
| 497 |
+
line-height: 1.6;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
.version-badge {
|
| 501 |
+
display: inline-block;
|
| 502 |
+
background: rgba(0, 242, 255, 0.1);
|
| 503 |
+
border: 1px solid rgba(0, 242, 255, 0.3);
|
| 504 |
+
padding: 5px 15px;
|
| 505 |
+
border-radius: 20px;
|
| 506 |
+
font-size: 0.75rem;
|
| 507 |
+
color: var(--primary-cyan);
|
| 508 |
+
margin-bottom: 20px;
|
| 509 |
+
font-weight: 600;
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
.file-size {
|
| 513 |
+
font-size: 0.8rem;
|
| 514 |
+
color: var(--text-gray);
|
| 515 |
+
margin-top: 10px;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
/* Feature List */
|
| 519 |
+
.feature-list {
|
| 520 |
+
list-style: none;
|
| 521 |
+
padding: 0;
|
| 522 |
+
margin: 20px 0;
|
| 523 |
+
text-align: left;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.feature-list li {
|
| 527 |
+
color: var(--text-gray);
|
| 528 |
+
line-height: 2.2;
|
| 529 |
+
padding-left: 30px;
|
| 530 |
+
position: relative;
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
.feature-list li::before {
|
| 534 |
+
content: '✓';
|
| 535 |
+
position: absolute;
|
| 536 |
+
left: 0;
|
| 537 |
+
color: #00ff88;
|
| 538 |
+
font-weight: 700;
|
| 539 |
+
font-size: 1.2rem;
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
/* --- SCROLLBAR --- */
|
| 544 |
+
::-webkit-scrollbar { width: 8px; }
|
| 545 |
+
::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); }
|
| 546 |
+
::-webkit-scrollbar-thumb {
|
| 547 |
+
background: rgba(255,255,255,0.15);
|
| 548 |
+
border-radius: 4px;
|
| 549 |
+
}
|
| 550 |
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.25); }
|
| 551 |
+
|
| 552 |
+
/* --- ANIMATIONS --- */
|
| 553 |
+
@keyframes fadeIn {
|
| 554 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 555 |
+
to { opacity: 1; transform: translateY(0); }
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
.fade-in { animation: fadeIn 0.4s ease-out; }
|
static/js/main.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Radiant AI - Main Logic v3.3
|
| 3 |
+
* Features: Auto-Apply, Independent Tab Chaining, History, Robust Session
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
const AppState = {
|
| 7 |
+
// Session State
|
| 8 |
+
uploadFilename: localStorage.getItem('radiant_upload_filename') || null, // Clean Original
|
| 9 |
+
currentFilename: localStorage.getItem('radiant_filename') || null, // Latest Edited
|
| 10 |
+
currentUrl: localStorage.getItem('radiant_current_url') || null,
|
| 11 |
+
|
| 12 |
+
// AI Lab specific state
|
| 13 |
+
selectedAIModel: null,
|
| 14 |
+
|
| 15 |
+
history: [],
|
| 16 |
+
historyIndex: -1,
|
| 17 |
+
|
| 18 |
+
serverParams: {
|
| 19 |
+
brightness: 0, contrast: 0, saturation: 0, sharpness: 0,
|
| 20 |
+
rotation: 0, flip_h: false, flip_v: false, filter: 'none'
|
| 21 |
+
},
|
| 22 |
+
|
| 23 |
+
debounceTimer: null,
|
| 24 |
+
isProcessing: false
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 28 |
+
initSession();
|
| 29 |
+
setupGlobalListeners();
|
| 30 |
+
setupKeyboardShortcuts();
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
// --- 1. Helper: Determine Source File ---
|
| 34 |
+
|
| 35 |
+
/**
|
| 36 |
+
* Returns the correct filename to process based on the tab's checkbox state.
|
| 37 |
+
* @param {string} checkboxId - The ID of the checkbox in the active tab
|
| 38 |
+
*/
|
| 39 |
+
function getSourceFilename(checkboxId) {
|
| 40 |
+
const cb = document.getElementById(checkboxId);
|
| 41 |
+
|
| 42 |
+
// If checkbox exists AND is checked AND we have a previous result -> Chain it
|
| 43 |
+
if (cb && cb.checked && AppState.currentFilename) {
|
| 44 |
+
console.log(`🔗 Chaining Enabled: Using ${AppState.currentFilename}`);
|
| 45 |
+
return AppState.currentFilename;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
// Otherwise use the clean original
|
| 49 |
+
console.log(`✨ Using Original: ${AppState.uploadFilename}`);
|
| 50 |
+
return AppState.uploadFilename;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
// --- 2. Session & History ---
|
| 54 |
+
|
| 55 |
+
function initSession() {
|
| 56 |
+
const savedParams = localStorage.getItem('radiant_params');
|
| 57 |
+
if(savedParams) AppState.serverParams = JSON.parse(savedParams);
|
| 58 |
+
|
| 59 |
+
if (AppState.currentUrl && AppState.uploadFilename) {
|
| 60 |
+
restoreImageToUI(AppState.currentUrl);
|
| 61 |
+
updateSlidersUI();
|
| 62 |
+
if(AppState.history.length === 0) pushHistory(AppState.currentUrl, AppState.serverParams);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
function pushHistory(url, params) {
|
| 67 |
+
if (AppState.historyIndex < AppState.history.length - 1) {
|
| 68 |
+
AppState.history = AppState.history.slice(0, AppState.historyIndex + 1);
|
| 69 |
+
}
|
| 70 |
+
const paramsCopy = JSON.parse(JSON.stringify(params));
|
| 71 |
+
AppState.history.push({ url: url, params: paramsCopy });
|
| 72 |
+
AppState.historyIndex++;
|
| 73 |
+
|
| 74 |
+
localStorage.setItem('radiant_current_url', url);
|
| 75 |
+
localStorage.setItem('radiant_params', JSON.stringify(params));
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
function undoEdit() {
|
| 79 |
+
if (AppState.historyIndex > 0) {
|
| 80 |
+
AppState.historyIndex--;
|
| 81 |
+
restoreState(AppState.history[AppState.historyIndex]);
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
function redoEdit() {
|
| 86 |
+
if (AppState.historyIndex < AppState.history.length - 1) {
|
| 87 |
+
AppState.historyIndex++;
|
| 88 |
+
restoreState(AppState.history[AppState.historyIndex]);
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
function restoreState(state) {
|
| 93 |
+
restoreImageToUI(state.url);
|
| 94 |
+
AppState.serverParams = JSON.parse(JSON.stringify(state.params));
|
| 95 |
+
updateSlidersUI();
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// --- 3. Studio Logic (Auto-Apply + Chaining) ---
|
| 99 |
+
|
| 100 |
+
// Special Chaining Logic for Studio:
|
| 101 |
+
// If "Bake" is enabled, we commit the current state as the new "Original"
|
| 102 |
+
// and reset sliders to 0 so you can edit on top.
|
| 103 |
+
function toggleStudioChaining(checkbox) {
|
| 104 |
+
if(checkbox.checked) {
|
| 105 |
+
if(confirm("Bake current edits? This will reset sliders to 0 but keep the look.")) {
|
| 106 |
+
// "Bake" logic: Set current edited file as the new "Original"
|
| 107 |
+
AppState.uploadFilename = AppState.currentFilename;
|
| 108 |
+
localStorage.setItem('radiant_upload_filename', AppState.currentFilename);
|
| 109 |
+
|
| 110 |
+
// Reset params to 0 (visual reset)
|
| 111 |
+
AppState.serverParams = { brightness: 0, contrast: 0, saturation: 0, sharpness: 0, rotation: 0, flip_h: false, flip_v: false, filter: 'none' };
|
| 112 |
+
updateSlidersUI();
|
| 113 |
+
|
| 114 |
+
// Uncheck box automatically after baking (optional UX choice)
|
| 115 |
+
checkbox.checked = false;
|
| 116 |
+
} else {
|
| 117 |
+
checkbox.checked = false;
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
function handleSliderChange(param, value) {
|
| 123 |
+
if(!AppState.uploadFilename) return;
|
| 124 |
+
document.getElementById(`val-${param}`).innerText = value;
|
| 125 |
+
AppState.serverParams[param] = parseInt(value);
|
| 126 |
+
if (AppState.debounceTimer) clearTimeout(AppState.debounceTimer);
|
| 127 |
+
AppState.debounceTimer = setTimeout(() => processAutoEdit(), 500);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
function handleFilterClick(filterName) {
|
| 131 |
+
if(!AppState.uploadFilename) return alert("Upload image first");
|
| 132 |
+
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
| 133 |
+
if(event && event.target) event.target.classList.add('active');
|
| 134 |
+
AppState.serverParams.filter = filterName;
|
| 135 |
+
processAutoEdit();
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
function triggerAutoApply(action) {
|
| 139 |
+
if(!AppState.uploadFilename) return alert("Upload image first");
|
| 140 |
+
if (action === 'rotate_right') AppState.serverParams.rotation = (AppState.serverParams.rotation + 90) % 360;
|
| 141 |
+
else if (action === 'rotate_left') AppState.serverParams.rotation = (AppState.serverParams.rotation - 90) % 360;
|
| 142 |
+
else if (action === 'flip_h') AppState.serverParams.flip_h = !AppState.serverParams.flip_h;
|
| 143 |
+
else if (action === 'flip_v') AppState.serverParams.flip_v = !AppState.serverParams.flip_v;
|
| 144 |
+
processAutoEdit();
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
async function processAutoEdit() {
|
| 148 |
+
if (AppState.isProcessing) return;
|
| 149 |
+
const overlay = document.getElementById('processingOverlay');
|
| 150 |
+
if(overlay) overlay.style.display = 'flex';
|
| 151 |
+
AppState.isProcessing = true;
|
| 152 |
+
|
| 153 |
+
try {
|
| 154 |
+
// Studio always uses uploadFilename (the clean source)
|
| 155 |
+
// unless "Bake" was triggered (which updates uploadFilename)
|
| 156 |
+
const payload = { filename: AppState.uploadFilename, options: AppState.serverParams };
|
| 157 |
+
|
| 158 |
+
const res = await fetch('/api/process-studio', {
|
| 159 |
+
method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload)
|
| 160 |
+
});
|
| 161 |
+
const data = await res.json();
|
| 162 |
+
|
| 163 |
+
if (data.success) {
|
| 164 |
+
AppState.currentFilename = data.filename;
|
| 165 |
+
localStorage.setItem('radiant_filename', data.filename);
|
| 166 |
+
restoreImageToUI(data.url);
|
| 167 |
+
pushHistory(data.url, AppState.serverParams);
|
| 168 |
+
}
|
| 169 |
+
} catch (err) { console.error(err); }
|
| 170 |
+
finally {
|
| 171 |
+
if(overlay) overlay.style.display = 'none';
|
| 172 |
+
AppState.isProcessing = false;
|
| 173 |
+
}
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
// --- 4. VISION LOGIC ---
|
| 177 |
+
|
| 178 |
+
async function runVisionTask(taskType) {
|
| 179 |
+
if(!AppState.uploadFilename) return alert("Please upload an image first.");
|
| 180 |
+
|
| 181 |
+
// Determine Source File (Chain or Original)
|
| 182 |
+
const sourceFile = getSourceFilename('visionChainingCheckbox');
|
| 183 |
+
|
| 184 |
+
const loader = document.getElementById('visionLoading');
|
| 185 |
+
const img = document.getElementById('visionPreview');
|
| 186 |
+
const overlay = document.getElementById('visionOverlay');
|
| 187 |
+
const ph = document.getElementById('visionPlaceholder');
|
| 188 |
+
|
| 189 |
+
const payload = { filename: sourceFile, task: taskType };
|
| 190 |
+
if (taskType === 'replace_bg') payload.color = document.getElementById('bgColorPicker').value;
|
| 191 |
+
if (taskType === 'face_blur') payload.value = document.getElementById('blurSlider').value;
|
| 192 |
+
|
| 193 |
+
if(loader) loader.style.display = 'flex';
|
| 194 |
+
if(img) img.style.filter = 'blur(5px) grayscale(80%)';
|
| 195 |
+
if(overlay) overlay.style.display = 'none';
|
| 196 |
+
|
| 197 |
+
try {
|
| 198 |
+
const res = await fetch('/api/process-vision', {
|
| 199 |
+
method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload)
|
| 200 |
+
});
|
| 201 |
+
const data = await res.json();
|
| 202 |
+
|
| 203 |
+
if(data.success) {
|
| 204 |
+
// Update current filename so subsequent chains use this result
|
| 205 |
+
AppState.currentFilename = data.filename;
|
| 206 |
+
localStorage.setItem('radiant_filename', data.filename);
|
| 207 |
+
|
| 208 |
+
const freshUrl = `${data.url}?t=${new Date().getTime()}`;
|
| 209 |
+
if(img) { img.src = freshUrl; img.style.display = 'block'; img.style.filter = 'none'; }
|
| 210 |
+
if(ph) ph.style.display = 'none';
|
| 211 |
+
|
| 212 |
+
document.getElementById('visionDownload').href = data.url;
|
| 213 |
+
document.getElementById('visionActionBar').style.display = 'flex';
|
| 214 |
+
|
| 215 |
+
if(data.detections && data.detections.length > 0 && overlay) {
|
| 216 |
+
overlay.style.display = 'block';
|
| 217 |
+
document.getElementById('detectionList').innerHTML = data.detections.map(d =>
|
| 218 |
+
`<li>• <b>${d.label}</b> <small>${(d.conf*100).toFixed(0)}%</small></li>`
|
| 219 |
+
).join('');
|
| 220 |
+
}
|
| 221 |
+
} else { alert("Vision Error: " + data.error); }
|
| 222 |
+
} catch(err) { alert("Server Error"); }
|
| 223 |
+
finally {
|
| 224 |
+
if(loader) loader.style.display = 'none';
|
| 225 |
+
if(img) img.style.filter = 'none';
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
// --- 5. AI LABORATORY LOGIC ---
|
| 230 |
+
|
| 231 |
+
function selectAIModel(task, card) {
|
| 232 |
+
AppState.selectedAIModel = task;
|
| 233 |
+
document.querySelectorAll('.model-card').forEach(c => c.classList.remove('active'));
|
| 234 |
+
card.classList.add('active');
|
| 235 |
+
const btn = document.getElementById('btnRunAI');
|
| 236 |
+
if (AppState.uploadFilename) btn.disabled = false;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
async function runAITask() {
|
| 240 |
+
if(!AppState.uploadFilename || !AppState.selectedAIModel) return alert("Select an image and a model first.");
|
| 241 |
+
|
| 242 |
+
// Determine Source File (Chain or Original)
|
| 243 |
+
const sourceFile = getSourceFilename('aiChainingCheckbox');
|
| 244 |
+
|
| 245 |
+
const loader = document.getElementById('aiLoader');
|
| 246 |
+
const outImg = document.getElementById('aiOutputImg');
|
| 247 |
+
const inImg = document.getElementById('aiInputImg');
|
| 248 |
+
const actions = document.getElementById('aiActions');
|
| 249 |
+
|
| 250 |
+
// Update Input Preview to match what we are processing
|
| 251 |
+
if(sourceFile !== AppState.uploadFilename) {
|
| 252 |
+
// If chaining, show the previous result as the input
|
| 253 |
+
inImg.src = `${AppState.currentUrl}?t=${new Date().getTime()}`;
|
| 254 |
+
} else {
|
| 255 |
+
// Else show original
|
| 256 |
+
inImg.src = `${AppState.originalUrl}?t=${new Date().getTime()}`;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
outImg.style.display = 'none';
|
| 260 |
+
actions.style.display = 'none';
|
| 261 |
+
if(loader) loader.style.display = 'flex';
|
| 262 |
+
|
| 263 |
+
try {
|
| 264 |
+
const res = await fetch('/api/process-ai', {
|
| 265 |
+
method: 'POST',
|
| 266 |
+
headers: {'Content-Type': 'application/json'},
|
| 267 |
+
body: JSON.stringify({
|
| 268 |
+
filename: sourceFile,
|
| 269 |
+
task: AppState.selectedAIModel
|
| 270 |
+
})
|
| 271 |
+
});
|
| 272 |
+
|
| 273 |
+
const data = await res.json();
|
| 274 |
+
|
| 275 |
+
if(data.success) {
|
| 276 |
+
// Update current filename so next chain uses this
|
| 277 |
+
AppState.currentFilename = data.filename;
|
| 278 |
+
localStorage.setItem('radiant_filename', data.filename);
|
| 279 |
+
localStorage.setItem('radiant_current_url', data.url);
|
| 280 |
+
|
| 281 |
+
outImg.src = `${data.url}?t=${new Date().getTime()}`;
|
| 282 |
+
outImg.style.display = 'block';
|
| 283 |
+
document.getElementById('aiDownload').href = data.url;
|
| 284 |
+
actions.style.display = 'flex';
|
| 285 |
+
} else {
|
| 286 |
+
alert("AI Error: " + data.error);
|
| 287 |
+
}
|
| 288 |
+
} catch(err) {
|
| 289 |
+
alert("Server connection failed.");
|
| 290 |
+
} finally {
|
| 291 |
+
if(loader) loader.style.display = 'none';
|
| 292 |
+
}
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
// --- 6. Upload & Helpers ---
|
| 296 |
+
|
| 297 |
+
function setupGlobalListeners() {
|
| 298 |
+
['fileInput', 'visionFileInput', 'aiFileInput', 'utilFileInput'].forEach(id => {
|
| 299 |
+
const el = document.getElementById(id);
|
| 300 |
+
if(el) el.addEventListener('change', handleUpload);
|
| 301 |
+
});
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
async function handleUpload(e) {
|
| 305 |
+
const file = e.target.files[0];
|
| 306 |
+
if(!file) return;
|
| 307 |
+
|
| 308 |
+
const formData = new FormData();
|
| 309 |
+
formData.append('file', file);
|
| 310 |
+
|
| 311 |
+
// Show loading
|
| 312 |
+
const btn = e.target.nextElementSibling;
|
| 313 |
+
const oldText = btn ? btn.innerHTML : '';
|
| 314 |
+
if(btn) btn.innerHTML = 'Uploading...';
|
| 315 |
+
|
| 316 |
+
try {
|
| 317 |
+
const res = await fetch('/api/upload', { method: 'POST', body: formData });
|
| 318 |
+
const data = await res.json();
|
| 319 |
+
|
| 320 |
+
if(data.success) {
|
| 321 |
+
// New Session
|
| 322 |
+
AppState.uploadFilename = data.filename;
|
| 323 |
+
AppState.currentFilename = data.filename; // Init current as original
|
| 324 |
+
AppState.originalUrl = data.url;
|
| 325 |
+
AppState.currentUrl = data.url;
|
| 326 |
+
|
| 327 |
+
AppState.history = [];
|
| 328 |
+
AppState.historyIndex = -1;
|
| 329 |
+
AppState.serverParams = { brightness: 0, contrast: 0, saturation: 0, sharpness: 0, rotation: 0, flip_h: false, flip_v: false, filter: 'none' };
|
| 330 |
+
|
| 331 |
+
localStorage.setItem('radiant_upload_filename', data.filename);
|
| 332 |
+
localStorage.setItem('radiant_filename', data.filename);
|
| 333 |
+
localStorage.setItem('radiant_current_url', data.url);
|
| 334 |
+
localStorage.setItem('radiant_original_url', data.url);
|
| 335 |
+
|
| 336 |
+
pushHistory(data.url, AppState.serverParams);
|
| 337 |
+
restoreImageToUI(data.url);
|
| 338 |
+
updateSlidersUI();
|
| 339 |
+
|
| 340 |
+
// Enable AI button if model selected
|
| 341 |
+
if(AppState.selectedAIModel) document.getElementById('btnRunAI').disabled = false;
|
| 342 |
+
|
| 343 |
+
// Show comparison view in AI tab
|
| 344 |
+
const aiWork = document.getElementById('aiWorkArea');
|
| 345 |
+
const aiPlace = document.getElementById('aiPlaceholder');
|
| 346 |
+
if(aiWork) aiWork.style.display = 'grid';
|
| 347 |
+
if(aiPlace) aiPlace.style.display = 'none';
|
| 348 |
+
}
|
| 349 |
+
} catch(err) { alert("Upload error"); }
|
| 350 |
+
finally { if(btn) btn.innerHTML = oldText; e.target.value = ''; }
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
function restoreImageToUI(url) {
|
| 354 |
+
const fresh = `${url}?t=${new Date().getTime()}`;
|
| 355 |
+
['imagePreview', 'visionPreview', 'aiInputImg', 'utilPreview'].forEach(id => {
|
| 356 |
+
const el = document.getElementById(id);
|
| 357 |
+
if(el) { el.src = fresh; el.style.display = 'block'; }
|
| 358 |
+
});
|
| 359 |
+
|
| 360 |
+
['placeholderState', 'visionPlaceholder', 'aiPlaceholder', 'utilPlaceholder'].forEach(id => {
|
| 361 |
+
const el = document.getElementById(id);
|
| 362 |
+
if(el) el.style.display = 'none';
|
| 363 |
+
});
|
| 364 |
+
|
| 365 |
+
const aiWork = document.getElementById('aiWorkArea');
|
| 366 |
+
if(aiWork && url) aiWork.style.display = 'grid';
|
| 367 |
+
|
| 368 |
+
const dl = document.getElementById('downloadLink');
|
| 369 |
+
if(dl) dl.href = fresh;
|
| 370 |
+
const bar = document.getElementById('actionBar');
|
| 371 |
+
if(bar) bar.style.display = 'flex';
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
function updateSlidersUI() {
|
| 375 |
+
const p = AppState.serverParams;
|
| 376 |
+
setVal('brightness', p.brightness);
|
| 377 |
+
setVal('contrast', p.contrast);
|
| 378 |
+
setVal('saturation', p.saturation);
|
| 379 |
+
setVal('sharpness', p.sharpness);
|
| 380 |
+
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
| 381 |
+
const btns = document.querySelectorAll('.filter-btn');
|
| 382 |
+
btns.forEach(b => {
|
| 383 |
+
if(b.innerText.toLowerCase() === p.filter) b.classList.add('active');
|
| 384 |
+
else if (p.filter === 'none' && b.innerText === 'Normal') b.classList.add('active');
|
| 385 |
+
});
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
function setVal(id, val) {
|
| 389 |
+
const el = document.getElementById(id);
|
| 390 |
+
const label = document.getElementById(`val-${id}`);
|
| 391 |
+
if(el) el.value = val;
|
| 392 |
+
if(label) label.innerText = val;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
function resetOriginal() {
|
| 396 |
+
if(AppState.uploadFilename && confirm("Reset all edits?")) {
|
| 397 |
+
AppState.serverParams = { brightness: 0, contrast: 0, saturation: 0, sharpness: 0, rotation: 0, flip_h: false, flip_v: false, filter: 'none' };
|
| 398 |
+
|
| 399 |
+
// Reset state to original
|
| 400 |
+
AppState.currentFilename = AppState.uploadFilename;
|
| 401 |
+
localStorage.setItem('radiant_filename', AppState.uploadFilename);
|
| 402 |
+
|
| 403 |
+
processAutoEdit();
|
| 404 |
+
updateSlidersUI();
|
| 405 |
+
|
| 406 |
+
// Reset AI Output
|
| 407 |
+
const aiOut = document.getElementById('aiOutputImg');
|
| 408 |
+
const aiAct = document.getElementById('aiActions');
|
| 409 |
+
if(aiOut) aiOut.style.display = 'none';
|
| 410 |
+
if(aiAct) aiAct.style.display = 'none';
|
| 411 |
+
}
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
function setupKeyboardShortcuts() {
|
| 415 |
+
document.addEventListener('keydown', (e) => {
|
| 416 |
+
if ((e.ctrlKey || e.metaKey) && e.key === 'z') { e.preventDefault(); undoEdit(); }
|
| 417 |
+
if ((e.ctrlKey || e.metaKey) && e.key === 'y') { e.preventDefault(); redoEdit(); }
|
| 418 |
+
});
|
| 419 |
+
}
|
templates/about.html
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}About - Radiant AI{% endblock %}
|
| 4 |
+
{% block page_title %}About{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<div class="page-container">
|
| 8 |
+
<!-- Hero Section -->
|
| 9 |
+
<div class="section-block" style="text-align: center;">
|
| 10 |
+
<div style="font-size: 4rem; margin-bottom: 20px;">
|
| 11 |
+
<i class="ph-duotone ph-aperture" style="color: var(--primary-cyan);"></i>
|
| 12 |
+
</div>
|
| 13 |
+
<h1 class="section-title">About Radiant AI</h1>
|
| 14 |
+
<p style="font-size: 1.2rem; color: var(--text-gray); max-width: 700px; margin: 0 auto; line-height: 1.7;">
|
| 15 |
+
Radiant AI is a next-generation visual intelligence platform that combines professional image editing
|
| 16 |
+
tools with cutting-edge artificial intelligence to deliver powerful, intuitive solutions for creators,
|
| 17 |
+
developers, and businesses.
|
| 18 |
+
</p>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<!-- Mission Section -->
|
| 22 |
+
<div class="section-block">
|
| 23 |
+
<h2 class="section-subtitle">
|
| 24 |
+
<i class="ph ph-rocket-launch" style="color: var(--primary-purple);"></i> Our Mission
|
| 25 |
+
</h2>
|
| 26 |
+
<p style="color: var(--text-gray); line-height: 1.8; font-size: 1rem;">
|
| 27 |
+
We believe that advanced AI-powered image processing should be accessible to everyone. Our mission is to
|
| 28 |
+
democratize visual intelligence by providing professional-grade tools that are simple to use, fast, and
|
| 29 |
+
powered by state-of-the-art machine learning models. Whether you're enhancing photos, detecting objects,
|
| 30 |
+
or restoring memories, Radiant AI empowers you to create without limits.
|
| 31 |
+
</p>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<!-- Features Grid -->
|
| 35 |
+
<div class="section-block">
|
| 36 |
+
<h2 class="section-subtitle">
|
| 37 |
+
<i class="ph ph-lightning" style="color: var(--primary-cyan);"></i> What We Offer
|
| 38 |
+
</h2>
|
| 39 |
+
<div class="info-grid">
|
| 40 |
+
<div class="info-card">
|
| 41 |
+
<h4><i class="ph ph-sliders-horizontal"></i> Studio Editor</h4>
|
| 42 |
+
<p>Professional image editing with filters, adjustments, and real-time previews.</p>
|
| 43 |
+
</div>
|
| 44 |
+
<div class="info-card">
|
| 45 |
+
<h4><i class="ph ph-scan"></i> Backgrounds & Vision</h4>
|
| 46 |
+
<p>Object detection with YOLOv8, background removal, and face blurring technology.</p>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="info-card">
|
| 49 |
+
<h4><i class="ph ph-sparkle"></i> AI Laboratory</h4>
|
| 50 |
+
<p>Super resolution upscaling, image colorization, and photo restoration.</p>
|
| 51 |
+
</div>
|
| 52 |
+
<div class="info-card">
|
| 53 |
+
<h4><i class="ph ph-wrench"></i> Utilities</h4>
|
| 54 |
+
<p>Format conversion, compression, and batch processing tools.</p>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
<!-- Desktop Software Section -->
|
| 60 |
+
<div class="section-block" style="background: linear-gradient(135deg, rgba(0, 242, 255, 0.08) 0%, rgba(189, 0, 255, 0.08) 100%);">
|
| 61 |
+
<h2 class="section-subtitle">
|
| 62 |
+
<i class="ph ph-desktop-tower" style="color: var(--primary-cyan);"></i> Desktop Application
|
| 63 |
+
</h2>
|
| 64 |
+
<p style="color: var(--text-gray); line-height: 1.8; margin-bottom: 20px;">
|
| 65 |
+
Take Radiant AI with you offline. Our desktop application brings the full power of AI-driven
|
| 66 |
+
image processing directly to your computer with enhanced performance, complete privacy, and
|
| 67 |
+
advanced features not available in the web version [web:17][web:20].
|
| 68 |
+
</p>
|
| 69 |
+
|
| 70 |
+
<div class="info-grid" style="margin-top: 25px;">
|
| 71 |
+
<div class="info-card">
|
| 72 |
+
<h4><i class="ph ph-airplane-takeoff"></i> Work Offline</h4>
|
| 73 |
+
<p>All AI models run locally on your machine. No internet connection required for processing [web:17].</p>
|
| 74 |
+
</div>
|
| 75 |
+
<div class="info-card">
|
| 76 |
+
<h4><i class="ph ph-lightning-slash"></i> 3x Faster</h4>
|
| 77 |
+
<p>GPU acceleration with CUDA and Metal support for blazing-fast processing speeds [web:20].</p>
|
| 78 |
+
</div>
|
| 79 |
+
<div class="info-card">
|
| 80 |
+
<h4><i class="ph ph-lock-key"></i> Private & Secure</h4>
|
| 81 |
+
<p>Your images stay on your computer. Perfect for sensitive or confidential content [web:17].</p>
|
| 82 |
+
</div>
|
| 83 |
+
<div class="info-card">
|
| 84 |
+
<h4><i class="ph ph-stack"></i> Unlimited Processing</h4>
|
| 85 |
+
<p>No file size limits or batch restrictions. Process thousands of images simultaneously.</p>
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
+
|
| 89 |
+
<div style="text-align: center; margin-top: 30px;">
|
| 90 |
+
<p style="color: var(--text-gray); margin-bottom: 20px; font-size: 1.05rem;">
|
| 91 |
+
Available for Windows, macOS, and Linux
|
| 92 |
+
</p>
|
| 93 |
+
<a href="{{ url_for('download') }}" class="btn btn-primary" style="font-size: 1.1rem; padding: 15px 35px;">
|
| 94 |
+
<i class="ph ph-download-simple"></i> Download Desktop App
|
| 95 |
+
</a>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
<!-- Technology Stack -->
|
| 101 |
+
<div class="section-block">
|
| 102 |
+
<h2 class="section-subtitle">
|
| 103 |
+
<i class="ph ph-cube" style="color: var(--primary-purple);"></i> Powered By
|
| 104 |
+
</h2>
|
| 105 |
+
<p style="color: var(--text-gray); line-height: 1.8; margin-bottom: 20px;">
|
| 106 |
+
Radiant AI leverages modern web technologies and advanced machine learning frameworks to deliver
|
| 107 |
+
exceptional performance and reliability:
|
| 108 |
+
</p>
|
| 109 |
+
<ul style="color: var(--text-gray); line-height: 2; list-style: none; padding: 0;">
|
| 110 |
+
<li><i class="ph ph-check-circle" style="color: #00ff88;"></i> <strong>Flask & Python</strong> - Robust backend infrastructure</li>
|
| 111 |
+
<li><i class="ph ph-check-circle" style="color: #00ff88;"></i> <strong>TensorFlow & PyTorch</strong> - Deep learning models</li>
|
| 112 |
+
<li><i class="ph ph-check-circle" style="color: #00ff88;"></i> <strong>YOLOv8</strong> - Real-time object detection</li>
|
| 113 |
+
<li><i class="ph ph-check-circle" style="color: #00ff88;"></i> <strong>OpenCV</strong> - Computer vision processing</li>
|
| 114 |
+
<li><i class="ph ph-check-circle" style="color: #00ff88;"></i> <strong>Modern UI/UX</strong> - Glassmorphism design with smooth animations</li>
|
| 115 |
+
</ul>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
<!-- Developer Info -->
|
| 119 |
+
<div class="section-block" style="text-align: center; background: linear-gradient(135deg, rgba(0, 242, 255, 0.05) 0%, rgba(189, 0, 255, 0.05) 100%);">
|
| 120 |
+
<h2 class="section-subtitle">
|
| 121 |
+
<i class="ph ph-code" style="color: var(--primary-cyan);"></i> Developed By
|
| 122 |
+
</h2>
|
| 123 |
+
<div style="display: inline-flex; align-items: center; gap: 15px; margin-top: 20px; padding: 20px 40px; background: rgba(0, 0, 0, 0.3); border-radius: 15px; border: 1px solid var(--glass-border);">
|
| 124 |
+
<div style="width: 60px; height: 60px; border-radius: 50%; background: var(--gradient-main); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 1.5rem; color: #000;">
|
| 125 |
+
J
|
| 126 |
+
</div>
|
| 127 |
+
<div style="text-align: left;">
|
| 128 |
+
<h3 style="margin: 0; font-size: 1.3rem; color: var(--text-white);">Jaiho Labs</h3>
|
| 129 |
+
<p style="margin: 5px 0 0 0; color: var(--text-gray); font-size: 0.9rem;">
|
| 130 |
+
Building AI-powered solutions for the future
|
| 131 |
+
</p>
|
| 132 |
+
<a href="https://jaiho-labs.onrender.com" target="_blank" rel="noopener noreferrer"
|
| 133 |
+
class="btn btn-secondary" style="margin-top: 15px; font-size: 0.85rem;">
|
| 134 |
+
<i class="ph ph-arrow-square-out"></i> Visit Jaiho Labs
|
| 135 |
+
</a>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
</div>
|
| 139 |
+
|
| 140 |
+
</div>
|
| 141 |
+
|
| 142 |
+
<!-- Version Info -->
|
| 143 |
+
<div style="text-align: center; padding: 30px 0; color: var(--text-gray); font-size: 0.9rem;">
|
| 144 |
+
<p>Radiant AI Version 2.0 | Built with ❤️ by Jaiho Labs</p>
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
{% endblock %}
|
templates/ai_studio.html
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Radiant AI - Deep Learning Lab{% endblock %}
|
| 4 |
+
{% block page_title %}AI Laboratory{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<style>
|
| 8 |
+
.ai-layout { display: grid; grid-template-columns: 300px 1fr; gap: 25px; height: 100%; }
|
| 9 |
+
.model-card { background: linear-gradient(145deg, rgba(255,255,255,0.05) 0%, rgba(0,0,0,0.2) 100%); border: 1px solid var(--glass-border); padding: 20px; border-radius: 16px; margin-bottom: 20px; transition: all 0.2s ease; cursor: pointer; position: relative; overflow: hidden; }
|
| 10 |
+
.model-card:hover { transform: translateY(-2px); border-color: var(--primary-purple); background: rgba(255, 255, 255, 0.08); }
|
| 11 |
+
.model-card.active { border-color: var(--primary-purple); background: rgba(189, 0, 255, 0.15); box-shadow: 0 0 20px rgba(189, 0, 255, 0.2); }
|
| 12 |
+
.model-icon { font-size: 2rem; margin-bottom: 10px; color: var(--text-white); }
|
| 13 |
+
.model-name { font-weight: 700; font-size: 1.1rem; margin-bottom: 5px; }
|
| 14 |
+
.model-tech { font-size: 0.75rem; color: var(--primary-cyan); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; display: inline-block; background: rgba(0, 242, 255, 0.1); padding: 2px 6px; border-radius: 4px; font-weight: 600; }
|
| 15 |
+
.comparison-area { display: none; grid-template-columns: 1fr 1fr; gap: 15px; height: 100%; min-height: 400px; }
|
| 16 |
+
.img-box { background: rgba(0,0,0,0.3); border-radius: 16px; border: 1px solid var(--glass-border); display: flex; flex-direction: column; align-items: center; justify-content: center; overflow: hidden; position: relative; }
|
| 17 |
+
.img-box img { max-width: 100%; max-height: 90%; object-fit: contain; }
|
| 18 |
+
.img-label { position: absolute; top: 15px; left: 15px; background: rgba(0,0,0,0.7); padding: 5px 12px; border-radius: 20px; font-size: 0.8rem; color: var(--text-white); backdrop-filter: blur(5px); border: 1px solid var(--glass-border); }
|
| 19 |
+
.loader-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.8); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 10; backdrop-filter: blur(5px); }
|
| 20 |
+
|
| 21 |
+
/* Checkbox Style */
|
| 22 |
+
.chain-control { display: flex; align-items: center; gap: 10px; background: rgba(255, 0, 85, 0.1); padding: 10px; border-radius: 8px; border: 1px solid rgba(255, 0, 85, 0.3); margin-bottom: 15px; }
|
| 23 |
+
.chain-control input { width: 18px; height: 18px; accent-color: #ff0055; cursor: pointer; }
|
| 24 |
+
.chain-control label { font-size: 0.9rem; color: #ff0055; cursor: pointer; font-weight: 500; }
|
| 25 |
+
</style>
|
| 26 |
+
|
| 27 |
+
<div class="ai-layout">
|
| 28 |
+
<aside class="controls-panel">
|
| 29 |
+
<div class="glass-panel" style="padding: 20px; margin-bottom: 20px;">
|
| 30 |
+
<div class="tool-header">Source Image</div>
|
| 31 |
+
<input type="file" id="aiFileInput" accept="image/*" hidden>
|
| 32 |
+
<button class="btn btn-secondary" style="width: 100%;" onclick="document.getElementById('aiFileInput').click()">
|
| 33 |
+
<i class="ph ph-file-image"></i> Select Photo
|
| 34 |
+
</button>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<div class="chain-control">
|
| 38 |
+
<input type="checkbox" id="aiChainingCheckbox">
|
| 39 |
+
<label for="aiChainingCheckbox">Chain AI Models</label>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<div class="tool-header">Select AI Model</div>
|
| 43 |
+
|
| 44 |
+
<div class="model-card" id="card-colorize" onclick="selectAIModel('colorize', this)">
|
| 45 |
+
<div class="model-tech">Caffe / DNN</div>
|
| 46 |
+
<i class="ph-duotone ph-palette model-icon" style="color: #ff0055;"></i>
|
| 47 |
+
<div class="model-name">Auto Colorize</div>
|
| 48 |
+
<p style="font-size: 0.85rem; color: var(--text-gray);">Restores vibrant colors to B&W photos.</p>
|
| 49 |
+
</div>
|
| 50 |
+
|
| 51 |
+
<div class="model-card" id="card-upscale" onclick="selectAIModel('upscale', this)">
|
| 52 |
+
<div class="model-tech">EDSR / TensorFlow</div>
|
| 53 |
+
<i class="ph-duotone ph-arrows-out-simple model-icon" style="color: var(--primary-cyan);"></i>
|
| 54 |
+
<div class="model-name">Super Res (4x)</div>
|
| 55 |
+
<p style="font-size: 0.85rem; color: var(--text-gray);">Upscales low-res images by 400%.</p>
|
| 56 |
+
</div>
|
| 57 |
+
|
| 58 |
+
<div class="model-card" id="card-restore" onclick="selectAIModel('restore', this)">
|
| 59 |
+
<div class="model-tech">OpenCV / NLM</div>
|
| 60 |
+
<i class="ph-duotone ph-sparkle model-icon" style="color: #bd00ff;"></i>
|
| 61 |
+
<div class="model-name">AI Restoration</div>
|
| 62 |
+
<p style="font-size: 0.85rem; color: var(--text-gray);">Denoise and repair grainy images.</p>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<button class="btn btn-primary" id="btnRunAI" onclick="runAITask()" style="width: 100%; margin-top: 10px;" disabled>
|
| 66 |
+
<i class="ph ph-lightning"></i> Run Processor
|
| 67 |
+
</button>
|
| 68 |
+
</aside>
|
| 69 |
+
|
| 70 |
+
<main class="glass-panel" style="display: flex; flex-direction: column; padding: 20px; overflow: hidden;">
|
| 71 |
+
<div id="aiPlaceholder" class="upload-placeholder" style="flex: 1; display: flex; flex-direction: column; justify-content: center;">
|
| 72 |
+
<i class="ph ph-brain" style="font-size: 4rem; color: var(--primary-purple); margin-bottom: 20px;"></i>
|
| 73 |
+
<h3>AI Laboratory</h3>
|
| 74 |
+
<p>Select a model and upload an image to begin.</p>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<div id="aiWorkArea" class="comparison-area">
|
| 78 |
+
<div class="img-box">
|
| 79 |
+
<div class="img-label">Original / Previous</div>
|
| 80 |
+
<img id="aiInputImg" src="" alt="Original">
|
| 81 |
+
</div>
|
| 82 |
+
<div class="img-box">
|
| 83 |
+
<div class="img-label" style="color: var(--primary-cyan); border-color: var(--primary-cyan);">AI Output</div>
|
| 84 |
+
<div id="aiLoader" class="loader-overlay" style="display: none;">
|
| 85 |
+
<i class="ph ph-spinner ph-spin" style="font-size: 3rem; color: var(--primary-cyan);"></i>
|
| 86 |
+
<p style="margin-top: 15px; font-size: 1rem; color: white;">Neural Network Processing...</p>
|
| 87 |
+
</div>
|
| 88 |
+
<img id="aiOutputImg" src="" alt="Result" style="display: none;">
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
|
| 92 |
+
<div id="aiActions" style="margin-top: 20px; display: none; justify-content: flex-end; gap: 10px;">
|
| 93 |
+
<button class="btn btn-secondary" onclick="resetOriginal()">
|
| 94 |
+
<i class="ph ph-trash"></i> Reset All
|
| 95 |
+
</button>
|
| 96 |
+
<a id="aiDownload" href="#" download="radiant_ai_output.png" class="btn btn-primary">
|
| 97 |
+
<i class="ph ph-download-simple"></i> Download High-Res
|
| 98 |
+
</a>
|
| 99 |
+
</div>
|
| 100 |
+
</main>
|
| 101 |
+
</div>
|
| 102 |
+
{% endblock %}
|
templates/base.html
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{% block title %}Radiant AI{% endblock %}</title>
|
| 7 |
+
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
|
| 11 |
+
|
| 12 |
+
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
| 13 |
+
|
| 14 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
| 15 |
+
|
| 16 |
+
<style>
|
| 17 |
+
/* Critical Layout Styles placed here to prevent FOUC (Flash of Unstyled Content) */
|
| 18 |
+
:root {
|
| 19 |
+
--bg-dark: #0a0a0f;
|
| 20 |
+
--accent-cyan: #00f2ff;
|
| 21 |
+
--accent-purple: #bd00ff;
|
| 22 |
+
--text-main: #ffffff;
|
| 23 |
+
--sidebar-width: 260px;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
body {
|
| 27 |
+
margin: 0;
|
| 28 |
+
padding: 0;
|
| 29 |
+
font-family: 'Inter', sans-serif;
|
| 30 |
+
background-color: var(--bg-dark);
|
| 31 |
+
/* Deep Space Gradient Background */
|
| 32 |
+
background-image:
|
| 33 |
+
radial-gradient(circle at 15% 50%, rgba(189, 0, 255, 0.08) 0%, transparent 50%),
|
| 34 |
+
radial-gradient(circle at 85% 30%, rgba(0, 242, 255, 0.08) 0%, transparent 50%);
|
| 35 |
+
color: var(--text-main);
|
| 36 |
+
height: 100vh;
|
| 37 |
+
display: flex;
|
| 38 |
+
overflow: hidden;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.app-wrapper {
|
| 42 |
+
display: flex;
|
| 43 |
+
width: 100%;
|
| 44 |
+
height: 100%;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/* Top Bar for Mobile/Tablet context */
|
| 48 |
+
.mobile-header {
|
| 49 |
+
display: none;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
@media (max-width: 768px) {
|
| 53 |
+
.app-wrapper { flex-direction: column; }
|
| 54 |
+
.sidebar { display: none; } /* Hide sidebar on mobile for now */
|
| 55 |
+
.mobile-header { display: flex; padding: 15px; background: #1a1a2e; }
|
| 56 |
+
}
|
| 57 |
+
</style>
|
| 58 |
+
</head>
|
| 59 |
+
<body>
|
| 60 |
+
|
| 61 |
+
<div class="app-wrapper">
|
| 62 |
+
<aside class="sidebar">
|
| 63 |
+
<div class="brand">
|
| 64 |
+
<i class="ph-fill ph-aperture" style="color: var(--accent-cyan);"></i>
|
| 65 |
+
<span>RADIANT <span class="text-gradient">AI</span></span>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<nav class="nav-menu">
|
| 69 |
+
<a href="{{ url_for('index') }}" class="nav-item {% if request.endpoint == 'index' %}active{% endif %}">
|
| 70 |
+
<i class="ph ph-house"></i>
|
| 71 |
+
<span>Home</span>
|
| 72 |
+
</a>
|
| 73 |
+
<a href="{{ url_for('studio') }}" class="nav-item {% if request.endpoint == 'studio' %}active{% endif %}">
|
| 74 |
+
<i class="ph ph-paint-brush-broad"></i>
|
| 75 |
+
<span>Studio Editor</span>
|
| 76 |
+
</a>
|
| 77 |
+
<a href="{{ url_for('vision') }}" class="nav-item {% if request.endpoint == 'vision' %}active{% endif %}">
|
| 78 |
+
<i class="ph ph-eye"></i>
|
| 79 |
+
<span>Backgrounds & Vision</span>
|
| 80 |
+
</a>
|
| 81 |
+
<a href="{{ url_for('ai_studio') }}" class="nav-item {% if request.endpoint == 'ai_studio' %}active{% endif %}">
|
| 82 |
+
<i class="ph ph-magic-wand"></i>
|
| 83 |
+
<span>AI Laboratory</span>
|
| 84 |
+
</a>
|
| 85 |
+
<a href="{{ url_for('utilities') }}" class="nav-item {% if request.endpoint == 'utilities' %}active{% endif %}">
|
| 86 |
+
<i class="ph ph-wrench"></i>
|
| 87 |
+
<span>Utilities</span>
|
| 88 |
+
</a>
|
| 89 |
+
<!---<a href="https://yourdomain.com/help" target="_blank" class="nav-item">
|
| 90 |
+
<i class="ph ph-question"></i>
|
| 91 |
+
<span>Help</span>
|
| 92 |
+
</a>--->
|
| 93 |
+
</nav>
|
| 94 |
+
|
| 95 |
+
<div class="sidebar-footer">
|
| 96 |
+
<div class="status-indicator">
|
| 97 |
+
<span class="status-dot"></span>
|
| 98 |
+
<a href="https://jaiho-labs.onrender.com" target="_blank" rel="noopener noreferrer"> Developed by Jaiho Labs</a>
|
| 99 |
+
</div>
|
| 100 |
+
<p>Radiant AI — Version 2.0</p>
|
| 101 |
+
</div>
|
| 102 |
+
</aside>
|
| 103 |
+
|
| 104 |
+
<main class="main-content">
|
| 105 |
+
<header class="top-bar">
|
| 106 |
+
<h2 class="page-title">{% block page_title %}Dashboard{% endblock %}</h2>
|
| 107 |
+
<div class="user-controls">
|
| 108 |
+
<span id="sessionStatus" style="font-size: 0.8rem; color: #a0a0a0; margin-right: 15px;"></span>
|
| 109 |
+
|
| 110 |
+
<!-- Navigation Links -->
|
| 111 |
+
<a href="{{ url_for('about') }}" class="nav-link-btn" title="About">
|
| 112 |
+
<i class="ph ph-info-circle"></i>
|
| 113 |
+
<span>About</span>
|
| 114 |
+
</a>
|
| 115 |
+
|
| 116 |
+
<a href="https://jaiho-digital.onrender.com/hub.html" target="_blank" rel="noopener noreferrer" class="nav-link-btn" title="Explore More Products">
|
| 117 |
+
<i class="ph ph-app-window"></i>
|
| 118 |
+
<span>Explore Products</span>
|
| 119 |
+
</a>
|
| 120 |
+
|
| 121 |
+
<a href="{{ url_for('docs') }}" class="nav-link-btn" title="Documentation">
|
| 122 |
+
<i class="ph ph-book-open"></i>
|
| 123 |
+
<span>Docs</span>
|
| 124 |
+
</a>
|
| 125 |
+
|
| 126 |
+
<a href="{{ url_for('download') }}" class="nav-link-btn download-highlight" title="Download Software">
|
| 127 |
+
<i class="ph ph-download-simple"></i>
|
| 128 |
+
<span>Download</span>
|
| 129 |
+
</a>
|
| 130 |
+
</div>
|
| 131 |
+
</header>
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
<div class="content-scrollable">
|
| 136 |
+
{% block content %}{% endblock %}
|
| 137 |
+
</div>
|
| 138 |
+
</main>
|
| 139 |
+
</div>
|
| 140 |
+
|
| 141 |
+
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
| 142 |
+
|
| 143 |
+
{% block scripts %}{% endblock %}
|
| 144 |
+
</body>
|
| 145 |
+
</html>
|
templates/docs.html
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Documentation - Radiant AI{% endblock %}
|
| 4 |
+
{% block page_title %}Documentation{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<div class="page-container">
|
| 8 |
+
<!-- Header -->
|
| 9 |
+
<div class="section-block" style="text-align: center;">
|
| 10 |
+
<div style="font-size: 3.5rem; margin-bottom: 20px;">
|
| 11 |
+
<i class="ph-duotone ph-book-open" style="color: var(--primary-purple);"></i>
|
| 12 |
+
</div>
|
| 13 |
+
<h1 class="section-title">Documentation</h1>
|
| 14 |
+
<p style="font-size: 1.1rem; color: var(--text-gray); max-width: 650px; margin: 0 auto;">
|
| 15 |
+
Complete guide to using Radiant AI's powerful image processing and AI tools.
|
| 16 |
+
</p>
|
| 17 |
+
</div>
|
| 18 |
+
|
| 19 |
+
<!-- Getting Started -->
|
| 20 |
+
<div class="section-block">
|
| 21 |
+
<h2 class="section-subtitle">
|
| 22 |
+
<i class="ph ph-play-circle" style="color: var(--primary-cyan);"></i> Getting Started
|
| 23 |
+
</h2>
|
| 24 |
+
<p style="color: var(--text-gray); line-height: 1.8; margin-bottom: 20px;">
|
| 25 |
+
Welcome to Radiant AI! This platform provides a suite of AI-powered image processing tools
|
| 26 |
+
accessible through an intuitive web interface.
|
| 27 |
+
</p>
|
| 28 |
+
<ol style="color: var(--text-gray); line-height: 2; padding-left: 20px;">
|
| 29 |
+
<li><strong>Navigate</strong> using the sidebar to access different tools</li>
|
| 30 |
+
<li><strong>Upload</strong> your images using drag-and-drop or file selection</li>
|
| 31 |
+
<li><strong>Process</strong> images with your chosen tool or AI model</li>
|
| 32 |
+
<li><strong>Download</strong> your enhanced results instantly</li>
|
| 33 |
+
</ol>
|
| 34 |
+
</div>
|
| 35 |
+
|
| 36 |
+
<!-- Studio Editor -->
|
| 37 |
+
<div class="section-block">
|
| 38 |
+
<h2 class="section-subtitle">
|
| 39 |
+
<i class="ph ph-paint-brush-broad" style="color: var(--primary-purple);"></i> Studio Editor
|
| 40 |
+
</h2>
|
| 41 |
+
<p style="color: var(--text-gray); line-height: 1.8; margin-bottom: 15px;">
|
| 42 |
+
Professional image editing tools for creative enhancement.
|
| 43 |
+
</p>
|
| 44 |
+
<div class="info-grid">
|
| 45 |
+
<div class="info-card">
|
| 46 |
+
<h4>Basic Adjustments</h4>
|
| 47 |
+
<p>Brightness, contrast, saturation, and exposure controls with real-time preview.</p>
|
| 48 |
+
</div>
|
| 49 |
+
<div class="info-card">
|
| 50 |
+
<h4>Filters & Effects</h4>
|
| 51 |
+
<p>Apply cinematic filters, vintage effects, and artistic styles to your images.</p>
|
| 52 |
+
</div>
|
| 53 |
+
<div class="info-card">
|
| 54 |
+
<h4>Transform Tools</h4>
|
| 55 |
+
<p>Crop, rotate, flip, and resize images with precision.</p>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<!-- Computer Vision -->
|
| 61 |
+
<div class="section-block">
|
| 62 |
+
<h2 class="section-subtitle">
|
| 63 |
+
<i class="ph ph-eye" style="color: var(--primary-cyan);"></i> Backgrounds & Vision
|
| 64 |
+
</h2>
|
| 65 |
+
<p style="color: var(--text-gray); line-height: 1.8; margin-bottom: 15px;">
|
| 66 |
+
Advanced AI-powered visual analysis and processing.
|
| 67 |
+
</p>
|
| 68 |
+
<div class="info-grid">
|
| 69 |
+
<div class="info-card">
|
| 70 |
+
<h4>Object Detection</h4>
|
| 71 |
+
<p>YOLOv8-powered detection recognizes and labels objects in your images with bounding boxes.</p>
|
| 72 |
+
</div>
|
| 73 |
+
<div class="info-card">
|
| 74 |
+
<h4>Background Removal</h4>
|
| 75 |
+
<p>AI-powered background segmentation removes backgrounds with precision.</p>
|
| 76 |
+
</div>
|
| 77 |
+
<div class="info-card">
|
| 78 |
+
<h4>Face Blurring</h4>
|
| 79 |
+
<p>Automatic face detection and blurring for privacy protection in photos.</p>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
|
| 84 |
+
<!-- AI Laboratory -->
|
| 85 |
+
<div class="section-block">
|
| 86 |
+
<h2 class="section-subtitle">
|
| 87 |
+
<i class="ph ph-magic-wand" style="color: var(--primary-purple);"></i> AI Laboratory
|
| 88 |
+
</h2>
|
| 89 |
+
<p style="color: var(--text-gray); line-height: 1.8; margin-bottom: 15px;">
|
| 90 |
+
Deep learning models for image enhancement and restoration.
|
| 91 |
+
</p>
|
| 92 |
+
<div class="info-grid">
|
| 93 |
+
<div class="info-card">
|
| 94 |
+
<h4>Super Resolution</h4>
|
| 95 |
+
<p>Upscale low-resolution images up to 4x while preserving details using deep learning.</p>
|
| 96 |
+
</div>
|
| 97 |
+
<div class="info-card">
|
| 98 |
+
<h4>Image Colorization</h4>
|
| 99 |
+
<p>Transform black and white photos into vibrant color images using AI.</p>
|
| 100 |
+
</div>
|
| 101 |
+
<div class="info-card">
|
| 102 |
+
<h4>Photo Restoration</h4>
|
| 103 |
+
<p>Repair damaged or old photographs by removing scratches and artifacts.</p>
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
</div>
|
| 107 |
+
|
| 108 |
+
<!-- API & Technical -->
|
| 109 |
+
<div class="section-block">
|
| 110 |
+
<h2 class="section-subtitle">
|
| 111 |
+
<i class="ph ph-code" style="color: var(--primary-cyan);"></i> Technical Details
|
| 112 |
+
</h2>
|
| 113 |
+
<h4 style="color: var(--text-white); margin-top: 25px; margin-bottom: 10px;">Supported Formats</h4>
|
| 114 |
+
<p style="color: var(--text-gray); line-height: 1.8;">
|
| 115 |
+
<strong>Input:</strong> JPG, JPEG, PNG, WEBP, BMP<br>
|
| 116 |
+
<strong>Output:</strong> JPG, PNG, WEBP
|
| 117 |
+
</p>
|
| 118 |
+
|
| 119 |
+
<h4 style="color: var(--text-white); margin-top: 25px; margin-bottom: 10px;">Processing Limits</h4>
|
| 120 |
+
<p style="color: var(--text-gray); line-height: 1.8;">
|
| 121 |
+
<strong>Max File Size:</strong> 10MB per image<br>
|
| 122 |
+
<strong>Max Resolution:</strong> 4000x4000 pixels<br>
|
| 123 |
+
<strong>Batch Processing:</strong> Up to 5 images simultaneously
|
| 124 |
+
</p>
|
| 125 |
+
|
| 126 |
+
<h4 style="color: var(--text-white); margin-top: 25px; margin-bottom: 10px;">Performance</h4>
|
| 127 |
+
<p style="color: var(--text-gray); line-height: 1.8;">
|
| 128 |
+
Processing times vary based on image size and selected operation. Most operations complete within 2-10 seconds.
|
| 129 |
+
</p>
|
| 130 |
+
</div>
|
| 131 |
+
|
| 132 |
+
<!-- FAQ -->
|
| 133 |
+
<div class="section-block">
|
| 134 |
+
<h2 class="section-subtitle">
|
| 135 |
+
<i class="ph ph-question" style="color: var(--primary-purple);"></i> Frequently Asked Questions
|
| 136 |
+
</h2>
|
| 137 |
+
<div style="margin-top: 20px;">
|
| 138 |
+
<div style="margin-bottom: 25px;">
|
| 139 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 8px;">Is my data secure?</h4>
|
| 140 |
+
<p style="color: var(--text-gray); line-height: 1.7;">
|
| 141 |
+
Yes. All images are processed on our secure servers and automatically deleted after processing.
|
| 142 |
+
We do not store or share your images.
|
| 143 |
+
</p>
|
| 144 |
+
</div>
|
| 145 |
+
|
| 146 |
+
<div style="margin-bottom: 25px;">
|
| 147 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 8px;">Can I use this for commercial projects?</h4>
|
| 148 |
+
<p style="color: var(--text-gray); line-height: 1.7;">
|
| 149 |
+
Yes. All processed images belong to you and can be used for any purpose including commercial projects.
|
| 150 |
+
</p>
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
<div style="margin-bottom: 25px;">
|
| 154 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 8px;">What AI models do you use?</h4>
|
| 155 |
+
<p style="color: var(--text-gray); line-height: 1.7;">
|
| 156 |
+
We use YOLOv8 for object detection, custom CNN models for super resolution, and transformer-based
|
| 157 |
+
models for colorization and restoration.
|
| 158 |
+
</p>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
|
| 163 |
+
<!-- Support -->
|
| 164 |
+
<div class="section-block" style="text-align: center; background: linear-gradient(135deg, rgba(189, 0, 255, 0.05) 0%, rgba(0, 242, 255, 0.05) 100%);">
|
| 165 |
+
<h2 class="section-subtitle">Need Help?</h2>
|
| 166 |
+
<p style="color: var(--text-gray); margin-bottom: 20px;">
|
| 167 |
+
Can't find what you're looking for? Contact us for support.
|
| 168 |
+
</p>
|
| 169 |
+
<a href="mailto:jaihodigital@gmail.com" class="btn btn-primary">
|
| 170 |
+
<i class="ph ph-chats-circle"></i> Contact Support
|
| 171 |
+
</a>
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
{% endblock %}
|
templates/download.html
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Download - Radiant AI{% endblock %}
|
| 4 |
+
{% block page_title %}Download Software{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<div class="page-container">
|
| 8 |
+
<!-- Hero Section -->
|
| 9 |
+
<div class="section-block" style="text-align: center;">
|
| 10 |
+
<div style="font-size: 4rem; margin-bottom: 20px;">
|
| 11 |
+
<i class="ph-duotone ph-download-simple" style="color: var(--primary-cyan);"></i>
|
| 12 |
+
</div>
|
| 13 |
+
<h1 class="section-title">Download Radiant AI Desktop</h1>
|
| 14 |
+
<p style="font-size: 1.2rem; color: var(--text-gray); max-width: 700px; margin: 0 auto 20px auto; line-height: 1.7;">
|
| 15 |
+
One-click portable installation. No manual setup required. The installer automatically handles
|
| 16 |
+
Python, dependencies, AI models, and launches the application in your browser [web:21][web:27].
|
| 17 |
+
</p>
|
| 18 |
+
<div class="version-badge" style="font-size: 0.9rem; padding: 8px 20px;">
|
| 19 |
+
<i class="ph ph-star-four"></i> Latest Version: 2.0.1 | Released: December 2025
|
| 20 |
+
</div>
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
<!-- One-Click Installation Highlight -->
|
| 24 |
+
<div class="section-block" style="background: linear-gradient(135deg, rgba(0, 242, 255, 0.1) 0%, rgba(189, 0, 255, 0.1) 100%); border: 2px solid var(--primary-cyan);">
|
| 25 |
+
<div style="text-align: center;">
|
| 26 |
+
<h2 style="font-family: 'Orbitron', sans-serif; font-size: 1.8rem; margin-bottom: 20px;">
|
| 27 |
+
<i class="ph ph-magic-wand" style="color: var(--primary-cyan);"></i>
|
| 28 |
+
<span class="text-gradient">One-Click Installation</span>
|
| 29 |
+
</h2>
|
| 30 |
+
<p style="color: var(--text-gray); line-height: 1.8; max-width: 700px; margin: 0 auto;">
|
| 31 |
+
Simply download and run the <strong>.bat</strong> file. It automatically installs Python,
|
| 32 |
+
creates a virtual environment, downloads all required libraries and AI models, and launches
|
| 33 |
+
Radiant AI in your default browser. No technical knowledge required [web:21][web:27][web:29]!
|
| 34 |
+
</p>
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
|
| 38 |
+
<!-- Platform Downloads -->
|
| 39 |
+
<div class="section-block">
|
| 40 |
+
<h2 class="section-subtitle" style="text-align: center;">
|
| 41 |
+
<i class="ph ph-desktop-tower" style="color: var(--primary-purple);"></i> Download for Your Platform
|
| 42 |
+
</h2>
|
| 43 |
+
|
| 44 |
+
<div class="download-cards">
|
| 45 |
+
<!-- Windows -->
|
| 46 |
+
<div class="platform-card">
|
| 47 |
+
<div class="platform-icon" style="color: #00a4ef;">
|
| 48 |
+
<i class="ph-fill ph-windows-logo"></i>
|
| 49 |
+
</div>
|
| 50 |
+
<h3 class="platform-name">Windows</h3>
|
| 51 |
+
<div class="version-badge">Portable Installation</div>
|
| 52 |
+
<p class="platform-details">
|
| 53 |
+
For Windows 10 and Windows 11 (64-bit)<br>
|
| 54 |
+
Includes: Python 3.11 + Virtual Environment + All Dependencies
|
| 55 |
+
</p>
|
| 56 |
+
<a href="#" class="btn btn-primary" style="width: 100%; justify-content: center;">
|
| 57 |
+
<i class="ph ph-download-simple"></i> Download RadiantAI-Setup.bat
|
| 58 |
+
</a>
|
| 59 |
+
<p class="file-size">Installer Script • 8 KB<br>
|
| 60 |
+
<span style="font-size: 0.75rem;">(Downloads ~450 MB on first run)</span>
|
| 61 |
+
</p>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<!-- macOS -->
|
| 65 |
+
<div class="platform-card">
|
| 66 |
+
<div class="platform-icon" style="color: var(--text-white);">
|
| 67 |
+
<i class="ph-fill ph-apple-logo"></i>
|
| 68 |
+
</div>
|
| 69 |
+
<h3 class="platform-name">macOS</h3>
|
| 70 |
+
<div class="version-badge">Shell Script</div>
|
| 71 |
+
<p class="platform-details">
|
| 72 |
+
For macOS 11 Big Sur and later (Intel & Apple Silicon)<br>
|
| 73 |
+
Includes: Python 3.11 + Virtual Environment + All Dependencies
|
| 74 |
+
</p>
|
| 75 |
+
<a href="#" class="btn btn-primary" style="width: 100%; justify-content: center;">
|
| 76 |
+
<i class="ph ph-download-simple"></i> Download RadiantAI-Setup.sh
|
| 77 |
+
</a>
|
| 78 |
+
<p class="file-size">Installer Script • 7 KB<br>
|
| 79 |
+
<span style="font-size: 0.75rem;">(Downloads ~450 MB on first run)</span>
|
| 80 |
+
</p>
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
<!-- Linux -->
|
| 84 |
+
<div class="platform-card">
|
| 85 |
+
<div class="platform-icon" style="color: #fcc624;">
|
| 86 |
+
<i class="ph-fill ph-linux-logo"></i>
|
| 87 |
+
</div>
|
| 88 |
+
<h3 class="platform-name">Linux</h3>
|
| 89 |
+
<div class="version-badge">Shell Script</div>
|
| 90 |
+
<p class="platform-details">
|
| 91 |
+
For Ubuntu 20.04+, Debian, Fedora, Arch, and other distributions<br>
|
| 92 |
+
Includes: Python 3.11 + Virtual Environment + All Dependencies
|
| 93 |
+
</p>
|
| 94 |
+
<a href="#" class="btn btn-primary" style="width: 100%; justify-content: center;">
|
| 95 |
+
<i class="ph ph-download-simple"></i> Download RadiantAI-Setup.sh
|
| 96 |
+
</a>
|
| 97 |
+
<p class="file-size">Installer Script • 7 KB<br>
|
| 98 |
+
<span style="font-size: 0.75rem;">(Downloads ~450 MB on first run)</span>
|
| 99 |
+
</p>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<!-- How It Works -->
|
| 105 |
+
<div class="section-block">
|
| 106 |
+
<h2 class="section-subtitle">
|
| 107 |
+
<i class="ph ph-gear-six" style="color: var(--primary-cyan);"></i> How the One-Click Installer Works
|
| 108 |
+
</h2>
|
| 109 |
+
<p style="color: var(--text-gray); line-height: 1.8; margin-bottom: 25px;">
|
| 110 |
+
The automated installer script handles all the complex setup for you [web:21][web:27][web:29].
|
| 111 |
+
</p>
|
| 112 |
+
|
| 113 |
+
<div class="info-grid">
|
| 114 |
+
<div class="info-card">
|
| 115 |
+
<h4><i class="ph ph-number-circle-one"></i> Python Installation</h4>
|
| 116 |
+
<p>Automatically downloads and installs Python 3.11 (portable version) if not already installed [web:21][web:29].</p>
|
| 117 |
+
</div>
|
| 118 |
+
<div class="info-card">
|
| 119 |
+
<h4><i class="ph ph-number-circle-two"></i> Virtual Environment</h4>
|
| 120 |
+
<p>Creates an isolated Python virtual environment (venv) to avoid conflicts with your system [web:27].</p>
|
| 121 |
+
</div>
|
| 122 |
+
<div class="info-card">
|
| 123 |
+
<h4><i class="ph ph-number-circle-three"></i> Dependencies</h4>
|
| 124 |
+
<p>Installs all required libraries: Flask, TensorFlow, OpenCV, PIL, NumPy, and more via pip [web:28].</p>
|
| 125 |
+
</div>
|
| 126 |
+
<div class="info-card">
|
| 127 |
+
<h4><i class="ph ph-number-circle-four"></i> AI Models</h4>
|
| 128 |
+
<p>Downloads pre-trained models: YOLOv8, super-resolution, colorization, and face detection models.</p>
|
| 129 |
+
</div>
|
| 130 |
+
<div class="info-card">
|
| 131 |
+
<h4><i class="ph ph-number-circle-five"></i> Auto Launch</h4>
|
| 132 |
+
<p>Starts the Flask server and automatically opens Radiant AI in your default web browser [web:27].</p>
|
| 133 |
+
</div>
|
| 134 |
+
<div class="info-card">
|
| 135 |
+
<h4><i class="ph ph-number-circle-six"></i> Future Runs</h4>
|
| 136 |
+
<p>After first setup, double-click the .bat file anytime to instantly launch the application [web:27].</p>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
</div>
|
| 140 |
+
|
| 141 |
+
<!-- Installation Instructions -->
|
| 142 |
+
<div class="section-block">
|
| 143 |
+
<h2 class="section-subtitle">
|
| 144 |
+
<i class="ph ph-list-checks" style="color: var(--primary-purple);"></i> Installation Instructions
|
| 145 |
+
</h2>
|
| 146 |
+
|
| 147 |
+
<div style="margin-top: 25px;">
|
| 148 |
+
<!-- Windows Instructions -->
|
| 149 |
+
<div style="background: rgba(255, 255, 255, 0.02); padding: 25px; border-radius: 15px; border: 1px solid var(--glass-border); margin-bottom: 25px;">
|
| 150 |
+
<h4 style="color: var(--text-white); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; font-size: 1.2rem;">
|
| 151 |
+
<i class="ph-fill ph-windows-logo" style="color: #00a4ef;"></i> Windows Installation
|
| 152 |
+
</h4>
|
| 153 |
+
<ol style="color: var(--text-gray); line-height: 2.2; padding-left: 20px; font-size: 0.95rem;">
|
| 154 |
+
<li>Download the <code style="background: rgba(0, 242, 255, 0.1); padding: 3px 10px; border-radius: 5px; color: var(--primary-cyan);">RadiantAI-Setup.bat</code> file</li>
|
| 155 |
+
<li>Save it to a folder where you want Radiant AI installed (e.g., <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">C:\RadiantAI\</code>) [web:27]</li>
|
| 156 |
+
<li><strong>Double-click</strong> the <code style="background: rgba(0, 242, 255, 0.1); padding: 3px 10px; border-radius: 5px; color: var(--primary-cyan);">RadiantAI-Setup.bat</code> file [web:21][web:27]</li>
|
| 157 |
+
<li>A command prompt will open and begin the automatic installation (takes 3-5 minutes on first run) [web:21]</li>
|
| 158 |
+
<li>Once complete, Radiant AI will automatically open in your browser at <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">http://localhost:5000</code></li>
|
| 159 |
+
<li><strong>Next time:</strong> Just double-click the .bat file to launch instantly! [web:27]</li>
|
| 160 |
+
</ol>
|
| 161 |
+
<div style="margin-top: 20px; padding: 15px; background: rgba(0, 242, 255, 0.05); border-left: 3px solid var(--primary-cyan); border-radius: 5px;">
|
| 162 |
+
<p style="color: var(--text-gray); margin: 0; font-size: 0.9rem;">
|
| 163 |
+
<i class="ph ph-info" style="color: var(--primary-cyan);"></i>
|
| 164 |
+
<strong>Note:</strong> Windows may show a security warning for the .bat file. Click "More info" → "Run anyway" to proceed [web:21].
|
| 165 |
+
</p>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
|
| 169 |
+
<!-- macOS Instructions -->
|
| 170 |
+
<div style="background: rgba(255, 255, 255, 0.02); padding: 25px; border-radius: 15px; border: 1px solid var(--glass-border); margin-bottom: 25px;">
|
| 171 |
+
<h4 style="color: var(--text-white); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; font-size: 1.2rem;">
|
| 172 |
+
<i class="ph-fill ph-apple-logo" style="color: var(--text-white);"></i> macOS Installation
|
| 173 |
+
</h4>
|
| 174 |
+
<ol style="color: var(--text-gray); line-height: 2.2; padding-left: 20px; font-size: 0.95rem;">
|
| 175 |
+
<li>Download the <code style="background: rgba(0, 242, 255, 0.1); padding: 3px 10px; border-radius: 5px; color: var(--primary-cyan);">RadiantAI-Setup.sh</code> file</li>
|
| 176 |
+
<li>Open Terminal and navigate to the download location</li>
|
| 177 |
+
<li>Make the script executable: <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">chmod +x RadiantAI-Setup.sh</code> [web:26]</li>
|
| 178 |
+
<li>Run the installer: <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">./RadiantAI-Setup.sh</code></li>
|
| 179 |
+
<li>The script will handle all installation and launch the browser automatically</li>
|
| 180 |
+
<li><strong>Next time:</strong> Run <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">./RadiantAI-Launch.sh</code> to start instantly</li>
|
| 181 |
+
</ol>
|
| 182 |
+
</div>
|
| 183 |
+
|
| 184 |
+
<!-- Linux Instructions -->
|
| 185 |
+
<div style="background: rgba(255, 255, 255, 0.02); padding: 25px; border-radius: 15px; border: 1px solid var(--glass-border);">
|
| 186 |
+
<h4 style="color: var(--text-white); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; font-size: 1.2rem;">
|
| 187 |
+
<i class="ph-fill ph-linux-logo" style="color: #fcc624;"></i> Linux Installation
|
| 188 |
+
</h4>
|
| 189 |
+
<ol style="color: var(--text-gray); line-height: 2.2; padding-left: 20px; font-size: 0.95rem;">
|
| 190 |
+
<li>Download the <code style="background: rgba(0, 242, 255, 0.1); padding: 3px 10px; border-radius: 5px; color: var(--primary-cyan);">RadiantAI-Setup.sh</code> file</li>
|
| 191 |
+
<li>Open your terminal and navigate to the download location</li>
|
| 192 |
+
<li>Make it executable: <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">chmod +x RadiantAI-Setup.sh</code> [web:26]</li>
|
| 193 |
+
<li>Run the installer: <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">./RadiantAI-Setup.sh</code></li>
|
| 194 |
+
<li>The script automatically installs dependencies and launches in your browser</li>
|
| 195 |
+
<li><strong>Next time:</strong> Run <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">./RadiantAI-Launch.sh</code> for instant startup</li>
|
| 196 |
+
</ol>
|
| 197 |
+
</div>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
|
| 201 |
+
<!-- What Gets Installed -->
|
| 202 |
+
<div class="section-block">
|
| 203 |
+
<h2 class="section-subtitle">
|
| 204 |
+
<i class="ph ph-package" style="color: var(--primary-cyan);"></i> What Gets Installed
|
| 205 |
+
</h2>
|
| 206 |
+
<p style="color: var(--text-gray); line-height: 1.8; margin-bottom: 20px;">
|
| 207 |
+
The installer downloads and configures everything needed for Radiant AI [web:21][web:27][web:28].
|
| 208 |
+
</p>
|
| 209 |
+
|
| 210 |
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">
|
| 211 |
+
<div>
|
| 212 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 15px;">
|
| 213 |
+
<i class="ph ph-code"></i> Core Components
|
| 214 |
+
</h4>
|
| 215 |
+
<ul class="feature-list">
|
| 216 |
+
<li>Python 3.11 (Portable) [web:29]</li>
|
| 217 |
+
<li>Virtual Environment (venv) [web:27]</li>
|
| 218 |
+
<li>Flask Web Framework</li>
|
| 219 |
+
<li>Requirements.txt dependencies [web:28]</li>
|
| 220 |
+
</ul>
|
| 221 |
+
</div>
|
| 222 |
+
|
| 223 |
+
<div>
|
| 224 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 15px;">
|
| 225 |
+
<i class="ph ph-brain"></i> AI & ML Libraries
|
| 226 |
+
</h4>
|
| 227 |
+
<ul class="feature-list">
|
| 228 |
+
<li>TensorFlow 2.x</li>
|
| 229 |
+
<li>PyTorch</li>
|
| 230 |
+
<li>OpenCV (cv2)</li>
|
| 231 |
+
<li>Pillow (PIL)</li>
|
| 232 |
+
<li>NumPy & SciPy</li>
|
| 233 |
+
</ul>
|
| 234 |
+
</div>
|
| 235 |
+
|
| 236 |
+
<div>
|
| 237 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 15px;">
|
| 238 |
+
<i class="ph ph-robot"></i> Pre-trained Models
|
| 239 |
+
</h4>
|
| 240 |
+
<ul class="feature-list">
|
| 241 |
+
<li>YOLOv8 (Object Detection)</li>
|
| 242 |
+
<li>Super Resolution (ESRGAN)</li>
|
| 243 |
+
<li>Colorization Model</li>
|
| 244 |
+
<li>Face Detection (Haar Cascade)</li>
|
| 245 |
+
<li>Background Removal (U2-Net)</li>
|
| 246 |
+
</ul>
|
| 247 |
+
</div>
|
| 248 |
+
</div>
|
| 249 |
+
</div>
|
| 250 |
+
|
| 251 |
+
<!-- Desktop Features -->
|
| 252 |
+
<div class="section-block">
|
| 253 |
+
<h2 class="section-subtitle">
|
| 254 |
+
<i class="ph ph-lightning" style="color: var(--primary-purple);"></i> Desktop Version Benefits
|
| 255 |
+
</h2>
|
| 256 |
+
|
| 257 |
+
<div class="info-grid">
|
| 258 |
+
<div class="info-card">
|
| 259 |
+
<h4><i class="ph ph-airplane-takeoff"></i> Fully Offline</h4>
|
| 260 |
+
<p>Process images without internet. All models and processing happen locally on your machine [web:26].</p>
|
| 261 |
+
</div>
|
| 262 |
+
<div class="info-card">
|
| 263 |
+
<h4><i class="ph ph-rocket-launch"></i> Faster Performance</h4>
|
| 264 |
+
<p>Native Python execution is significantly faster than web-based processing.</p>
|
| 265 |
+
</div>
|
| 266 |
+
<div class="info-card">
|
| 267 |
+
<h4><i class="ph ph-lock-key"></i> Complete Privacy</h4>
|
| 268 |
+
<p>Your images never leave your computer. Perfect for sensitive or confidential content [web:26].</p>
|
| 269 |
+
</div>
|
| 270 |
+
<div class="info-card">
|
| 271 |
+
<h4><i class="ph ph-infinity"></i> No Limits</h4>
|
| 272 |
+
<p>Process unlimited images with no file size or batch quantity restrictions.</p>
|
| 273 |
+
</div>
|
| 274 |
+
<div class="info-card">
|
| 275 |
+
<h4><i class="ph ph-hard-drives"></i> Local Storage</h4>
|
| 276 |
+
<p>Save processed images directly to your local folders with full file system access.</p>
|
| 277 |
+
</div>
|
| 278 |
+
<div class="info-card">
|
| 279 |
+
<h4><i class="ph ph-plugs-connected"></i> Easy Updates</h4>
|
| 280 |
+
<p>Simply re-run the .bat file to check for and install updates automatically [web:27].</p>
|
| 281 |
+
</div>
|
| 282 |
+
</div>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
<!-- System Requirements -->
|
| 286 |
+
<div class="section-block">
|
| 287 |
+
<h2 class="section-subtitle">
|
| 288 |
+
<i class="ph ph-cpu" style="color: var(--primary-cyan);"></i> System Requirements
|
| 289 |
+
</h2>
|
| 290 |
+
|
| 291 |
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 30px; margin-top: 25px;">
|
| 292 |
+
<div>
|
| 293 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 15px; font-size: 1.1rem;">Minimum Requirements</h4>
|
| 294 |
+
<ul class="feature-list">
|
| 295 |
+
<li>Windows 10/11, macOS 11+, or Linux (Ubuntu 20.04+)</li>
|
| 296 |
+
<li>4GB RAM (8GB recommended)</li>
|
| 297 |
+
<li>2 GHz dual-core processor</li>
|
| 298 |
+
<li>2GB free disk space (for installation)</li>
|
| 299 |
+
<li>Active internet (first installation only) [web:21]</li>
|
| 300 |
+
</ul>
|
| 301 |
+
</div>
|
| 302 |
+
|
| 303 |
+
<div>
|
| 304 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 15px; font-size: 1.1rem;">Recommended for Best Performance</h4>
|
| 305 |
+
<ul class="feature-list">
|
| 306 |
+
<li>16GB RAM or higher</li>
|
| 307 |
+
<li>Multi-core processor (6+ cores)</li>
|
| 308 |
+
<li>NVIDIA GPU with CUDA support</li>
|
| 309 |
+
<li>5GB free disk space</li>
|
| 310 |
+
<li>SSD storage for faster model loading</li>
|
| 311 |
+
</ul>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
</div>
|
| 315 |
+
|
| 316 |
+
<!-- Troubleshooting -->
|
| 317 |
+
<div class="section-block">
|
| 318 |
+
<h2 class="section-subtitle">
|
| 319 |
+
<i class="ph ph-wrench" style="color: var(--primary-purple);"></i> Troubleshooting
|
| 320 |
+
</h2>
|
| 321 |
+
|
| 322 |
+
<div style="margin-top: 20px;">
|
| 323 |
+
<div style="margin-bottom: 25px;">
|
| 324 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 8px;">
|
| 325 |
+
<i class="ph ph-warning"></i> "Windows protected your PC" warning
|
| 326 |
+
</h4>
|
| 327 |
+
<p style="color: var(--text-gray); line-height: 1.7;">
|
| 328 |
+
This is normal for .bat files. Click "More info" → "Run anyway" to proceed [web:21].
|
| 329 |
+
</p>
|
| 330 |
+
</div>
|
| 331 |
+
|
| 332 |
+
<div style="margin-bottom: 25px;">
|
| 333 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 8px;">
|
| 334 |
+
<i class="ph ph-x-circle"></i> Installation fails or gets stuck
|
| 335 |
+
</h4>
|
| 336 |
+
<p style="color: var(--text-gray); line-height: 1.7;">
|
| 337 |
+
Ensure you have a stable internet connection. Delete the created folders and try again [web:27].
|
| 338 |
+
</p>
|
| 339 |
+
</div>
|
| 340 |
+
|
| 341 |
+
<div style="margin-bottom: 25px;">
|
| 342 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 8px;">
|
| 343 |
+
<i class="ph ph-browsers"></i> Browser doesn't open automatically
|
| 344 |
+
</h4>
|
| 345 |
+
<p style="color: var(--text-gray); line-height: 1.7;">
|
| 346 |
+
Manually open your browser and navigate to <code style="background: rgba(255,255,255,0.05); padding: 2px 8px; border-radius: 4px;">http://localhost:5000</code>
|
| 347 |
+
</p>
|
| 348 |
+
</div>
|
| 349 |
+
|
| 350 |
+
<div style="margin-bottom: 25px;">
|
| 351 |
+
<h4 style="color: var(--primary-cyan); margin-bottom: 8px;">
|
| 352 |
+
<i class="ph ph-stop-circle"></i> "Port 5000 already in use" error
|
| 353 |
+
</h4>
|
| 354 |
+
<p style="color: var(--text-gray); line-height: 1.7;">
|
| 355 |
+
Another application is using port 5000. Close other Flask apps or modify the port in the config file.
|
| 356 |
+
</p>
|
| 357 |
+
</div>
|
| 358 |
+
</div>
|
| 359 |
+
</div>
|
| 360 |
+
|
| 361 |
+
<!-- Support Section -->
|
| 362 |
+
<div class="section-block" style="text-align: center; background: linear-gradient(135deg, rgba(189, 0, 255, 0.05) 0%, rgba(0, 242, 255, 0.05) 100%);">
|
| 363 |
+
<h2 class="section-subtitle">Need Help?</h2>
|
| 364 |
+
<p style="color: var(--text-gray); margin-bottom: 25px; max-width: 600px; margin-left: auto; margin-right: auto;">
|
| 365 |
+
Check our documentation for detailed guides, or contact us for support.
|
| 366 |
+
</p>
|
| 367 |
+
<div style="display: flex; gap: 15px; justify-content: center; flex-wrap: wrap;">
|
| 368 |
+
<a href="{{ url_for('docs') }}" class="btn btn-secondary">
|
| 369 |
+
<i class="ph ph-book-open"></i> View Documentation
|
| 370 |
+
</a>
|
| 371 |
+
<a href="mailto:jaihodigital@gmail.com" class="btn btn-primary">
|
| 372 |
+
<i class="ph ph-chats-circle"></i> Contact Support
|
| 373 |
+
</a>
|
| 374 |
+
|
| 375 |
+
</div>
|
| 376 |
+
</div>
|
| 377 |
+
|
| 378 |
+
<!-- License Info -->
|
| 379 |
+
<div style="text-align: center; padding: 30px 0; color: var(--text-gray); font-size: 0.85rem; border-top: 1px solid var(--glass-border); margin-top: 20px;">
|
| 380 |
+
<p style="margin-bottom: 10px;">
|
| 381 |
+
<i class="ph ph-shield-check" style="color: #00ff88;"></i>
|
| 382 |
+
By downloading Radiant AI, you agree to our Terms of Service and Privacy Policy.
|
| 383 |
+
</p>
|
| 384 |
+
<p>
|
| 385 |
+
© 2025 Jaiho Labs. All rights reserved. | Free for personal and commercial use
|
| 386 |
+
</p>
|
| 387 |
+
</div>
|
| 388 |
+
</div>
|
| 389 |
+
{% endblock %}
|
templates/onboarding.html
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Radiant AI - Home{% endblock %}
|
| 4 |
+
{% block page_title %}Welcome{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<style>
|
| 8 |
+
/* Page specific animations */
|
| 9 |
+
.hero-section {
|
| 10 |
+
text-align: center;
|
| 11 |
+
padding: 60px 20px;
|
| 12 |
+
animation: fadeIn 0.8s ease-out;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.hero-title {
|
| 16 |
+
font-family: 'Orbitron', sans-serif;
|
| 17 |
+
font-size: 3rem;
|
| 18 |
+
margin-bottom: 20px;
|
| 19 |
+
line-height: 1.2;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.hero-subtitle {
|
| 23 |
+
font-size: 1.2rem;
|
| 24 |
+
color: var(--text-gray);
|
| 25 |
+
max-width: 600px;
|
| 26 |
+
margin: 0 auto 40px auto;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.features-grid {
|
| 30 |
+
display: grid;
|
| 31 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 32 |
+
gap: 25px;
|
| 33 |
+
margin-top: 50px;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.feature-card {
|
| 37 |
+
background: var(--glass-bg);
|
| 38 |
+
border: 1px solid var(--glass-border);
|
| 39 |
+
padding: 30px;
|
| 40 |
+
border-radius: 20px;
|
| 41 |
+
transition: transform 0.3s, border-color 0.3s;
|
| 42 |
+
text-align: left;
|
| 43 |
+
position: relative;
|
| 44 |
+
overflow: hidden;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.feature-card:hover {
|
| 48 |
+
transform: translateY(-5px);
|
| 49 |
+
border-color: var(--primary-cyan);
|
| 50 |
+
background: rgba(255, 255, 255, 0.05);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.card-icon {
|
| 54 |
+
font-size: 2.5rem;
|
| 55 |
+
color: var(--primary-purple);
|
| 56 |
+
margin-bottom: 20px;
|
| 57 |
+
display: inline-block;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.card-title {
|
| 61 |
+
font-size: 1.4rem;
|
| 62 |
+
font-weight: 600;
|
| 63 |
+
margin-bottom: 10px;
|
| 64 |
+
color: var(--text-white);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.card-desc {
|
| 68 |
+
color: var(--text-gray);
|
| 69 |
+
font-size: 0.95rem;
|
| 70 |
+
margin-bottom: 25px;
|
| 71 |
+
line-height: 1.5;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.glow-circle {
|
| 75 |
+
position: absolute;
|
| 76 |
+
width: 150px;
|
| 77 |
+
height: 150px;
|
| 78 |
+
background: var(--primary-cyan);
|
| 79 |
+
filter: blur(80px);
|
| 80 |
+
opacity: 0.1;
|
| 81 |
+
border-radius: 50%;
|
| 82 |
+
top: -50px;
|
| 83 |
+
right: -50px;
|
| 84 |
+
pointer-events: none;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
@keyframes fadeIn {
|
| 88 |
+
from { opacity: 0; transform: translateY(20px); }
|
| 89 |
+
to { opacity: 1; transform: translateY(0); }
|
| 90 |
+
}
|
| 91 |
+
</style>
|
| 92 |
+
|
| 93 |
+
<div class="hero-section">
|
| 94 |
+
<h1 class="hero-title">Unleash the Power of <br><span class="text-gradient">Visual Intelligence</span></h1>
|
| 95 |
+
<p class="hero-subtitle">
|
| 96 |
+
Transform your images with next-generation AI tools. Enhance, detect, restore, and create with the speed of light.
|
| 97 |
+
</p>
|
| 98 |
+
|
| 99 |
+
<a href="{{ url_for('studio') }}" class="btn btn-primary" style="font-size: 1.1rem; padding: 15px 35px;">
|
| 100 |
+
<i class="ph-fill ph-rocket-launch"></i> Start Creating Now
|
| 101 |
+
</a>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<div class="features-grid">
|
| 105 |
+
<div class="feature-card glass-panel">
|
| 106 |
+
<div class="glow-circle" style="background: var(--primary-cyan);"></div>
|
| 107 |
+
<i class="ph-duotone ph-sliders-horizontal card-icon" style="color: var(--primary-cyan);"></i>
|
| 108 |
+
<h3 class="card-title">Image Studio</h3>
|
| 109 |
+
<p class="card-desc">
|
| 110 |
+
Professional editing tools at your fingertips. Crop, rotate, adjust lighting, and apply cinematic filters in real-time.
|
| 111 |
+
</p>
|
| 112 |
+
<a href="{{ url_for('studio') }}" class="btn btn-secondary">Open Studio <i class="ph ph-arrow-right"></i></a>
|
| 113 |
+
</div>
|
| 114 |
+
|
| 115 |
+
<div class="feature-card glass-panel">
|
| 116 |
+
<div class="glow-circle" style="background: var(--primary-purple);"></div>
|
| 117 |
+
<i class="ph-duotone ph-scan card-icon" style="color: var(--primary-purple);"></i>
|
| 118 |
+
<h3 class="card-title">Backgrounds & Vision</h3>
|
| 119 |
+
<p class="card-desc">
|
| 120 |
+
Detect objects with YOLOv8, remove backgrounds instantly, and blur faces for privacy with advanced machine learning.
|
| 121 |
+
</p>
|
| 122 |
+
<a href="{{ url_for('vision') }}" class="btn btn-secondary">Open Vision <i class="ph ph-arrow-right"></i></a>
|
| 123 |
+
</div>
|
| 124 |
+
|
| 125 |
+
<div class="feature-card glass-panel">
|
| 126 |
+
<div class="glow-circle" style="background: #ff0055;"></div>
|
| 127 |
+
<i class="ph-duotone ph-sparkle card-icon" style="color: #ff0055;"></i>
|
| 128 |
+
<h3 class="card-title">AI Laboratory</h3>
|
| 129 |
+
<p class="card-desc">
|
| 130 |
+
Deep learning magic. Upscale low-res photos (Super Resolution), colorize black & white memories, and restore old photos.
|
| 131 |
+
</p>
|
| 132 |
+
<a href="{{ url_for('ai_studio') }}" class="btn btn-secondary">Open AI Lab <i class="ph ph-arrow-right"></i></a>
|
| 133 |
+
</div>
|
| 134 |
+
</div>
|
| 135 |
+
{% endblock %}
|
templates/products.html
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Explore Products{% endblock %}
|
| 4 |
+
{% block page_title %}Other Products{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<div class="features-grid">
|
| 8 |
+
<div class="feature-card">
|
| 9 |
+
<h3>Radiant Video AI</h3>
|
| 10 |
+
<p>AI-powered video enhancement & restoration.</p>
|
| 11 |
+
</div>
|
| 12 |
+
|
| 13 |
+
<div class="feature-card">
|
| 14 |
+
<h3>Radiant Docs AI</h3>
|
| 15 |
+
<p>Smart document analysis and OCR.</p>
|
| 16 |
+
</div>
|
| 17 |
+
|
| 18 |
+
<div class="feature-card">
|
| 19 |
+
<h3>Radiant Vision Pro</h3>
|
| 20 |
+
<p>Enterprise-grade computer vision APIs.</p>
|
| 21 |
+
</div>
|
| 22 |
+
</div>
|
| 23 |
+
{% endblock %}
|
templates/studio.html
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Radiant AI - Studio{% endblock %}
|
| 4 |
+
{% block page_title %}Image Studio{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<style>
|
| 8 |
+
.studio-layout { display: grid; grid-template-columns: 320px 1fr; gap: 25px; height: 100%; min-height: 0; }
|
| 9 |
+
.controls-panel { display: flex; flex-direction: column; gap: 20px; overflow-y: auto; padding-right: 5px; }
|
| 10 |
+
.icon-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
|
| 11 |
+
.range-wrap { margin-bottom: 15px; }
|
| 12 |
+
.range-header { display: flex; justify-content: space-between; font-size: 0.85rem; margin-bottom: 8px; color: var(--text-gray); }
|
| 13 |
+
.filter-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
|
| 14 |
+
|
| 15 |
+
.filter-btn { background: rgba(255, 255, 255, 0.05); border: 1px solid transparent; padding: 8px; border-radius: 8px; color: var(--text-gray); cursor: pointer; font-size: 0.85rem; transition: all 0.2s; text-align: center; }
|
| 16 |
+
.filter-btn:hover { background: rgba(255, 255, 255, 0.1); color: white; }
|
| 17 |
+
.filter-btn.active { background: rgba(0, 242, 255, 0.15); border-color: var(--primary-cyan); color: var(--primary-cyan); }
|
| 18 |
+
|
| 19 |
+
.canvas-area { position: relative; background: radial-gradient(circle at center, #1a1a24 0%, #0d0d12 100%); border-radius: 20px; border: 1px solid var(--glass-border); display: flex; align-items: center; justify-content: center; overflow: hidden; }
|
| 20 |
+
#imagePreview { max-width: 95%; max-height: 90%; object-fit: contain; border-radius: 4px; box-shadow: 0 0 40px rgba(0,0,0,0.5); }
|
| 21 |
+
.action-bar { position: absolute; bottom: 20px; right: 20px; display: flex; gap: 10px; background: rgba(0,0,0,0.6); padding: 10px; border-radius: 12px; backdrop-filter: blur(10px); }
|
| 22 |
+
|
| 23 |
+
/* Checkbox Style */
|
| 24 |
+
.chain-control { display: flex; align-items: center; gap: 10px; background: rgba(0, 242, 255, 0.1); padding: 10px; border-radius: 8px; border: 1px solid rgba(0, 242, 255, 0.3); margin-bottom: 15px; }
|
| 25 |
+
.chain-control input { width: 18px; height: 18px; accent-color: var(--primary-cyan); cursor: pointer; }
|
| 26 |
+
.chain-control label { font-size: 0.9rem; color: var(--primary-cyan); cursor: pointer; font-weight: 500; }
|
| 27 |
+
</style>
|
| 28 |
+
|
| 29 |
+
<div class="studio-layout">
|
| 30 |
+
|
| 31 |
+
<aside class="controls-panel">
|
| 32 |
+
|
| 33 |
+
<div class="glass-panel" style="padding: 20px;">
|
| 34 |
+
<div class="tool-header">Source</div>
|
| 35 |
+
<input type="file" id="fileInput" accept="image/*" hidden>
|
| 36 |
+
<button class="btn btn-secondary" style="width: 100%;" onclick="document.getElementById('fileInput').click()">
|
| 37 |
+
<i class="ph ph-upload-simple"></i> Upload Image
|
| 38 |
+
</button>
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<div class="chain-control">
|
| 42 |
+
<input type="checkbox" id="studioChainingCheckbox" onchange="toggleStudioChaining(this)">
|
| 43 |
+
<label for="studioChainingCheckbox">Bake Edits (Chain)</label>
|
| 44 |
+
</div>
|
| 45 |
+
|
| 46 |
+
<div class="glass-panel" style="padding: 20px;">
|
| 47 |
+
<div class="tool-header">Adjustments</div>
|
| 48 |
+
|
| 49 |
+
<div class="range-wrap">
|
| 50 |
+
<div class="range-header"><span>Brightness</span> <span id="val-brightness">0</span></div>
|
| 51 |
+
<input type="range" id="brightness" min="-100" max="100" value="0" oninput="handleSliderChange('brightness', this.value)">
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<div class="range-wrap">
|
| 55 |
+
<div class="range-header"><span>Contrast</span> <span id="val-contrast">0</span></div>
|
| 56 |
+
<input type="range" id="contrast" min="-100" max="100" value="0" oninput="handleSliderChange('contrast', this.value)">
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
<div class="range-wrap">
|
| 60 |
+
<div class="range-header"><span>Saturation</span> <span id="val-saturation">0</span></div>
|
| 61 |
+
<input type="range" id="saturation" min="-100" max="100" value="0" oninput="handleSliderChange('saturation', this.value)">
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div class="range-wrap">
|
| 65 |
+
<div class="range-header"><span>Sharpness</span> <span id="val-sharpness">0</span></div>
|
| 66 |
+
<input type="range" id="sharpness" min="0" max="100" value="0" oninput="handleSliderChange('sharpness', this.value)">
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
<div class="glass-panel" style="padding: 20px;">
|
| 71 |
+
<div class="tool-header">Geometry</div>
|
| 72 |
+
<div class="icon-grid">
|
| 73 |
+
<button class="filter-btn" onclick="triggerAutoApply('rotate_left')"><i class="ph ph-arrow-counter-clockwise"></i> -90°</button>
|
| 74 |
+
<button class="filter-btn" onclick="triggerAutoApply('rotate_right')"><i class="ph ph-arrow-clockwise"></i> +90°</button>
|
| 75 |
+
<button class="filter-btn" onclick="triggerAutoApply('flip_h')"><i class="ph ph-arrows-left-right"></i> Flip H</button>
|
| 76 |
+
<button class="filter-btn" onclick="triggerAutoApply('flip_v')"><i class="ph ph-arrows-down-up"></i> Flip V</button>
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
|
| 80 |
+
<div class="glass-panel" style="padding: 20px;">
|
| 81 |
+
<div class="tool-header">Filters</div>
|
| 82 |
+
<div class="filter-grid">
|
| 83 |
+
<button class="filter-btn active" onclick="handleFilterClick('none')">Normal</button>
|
| 84 |
+
<button class="filter-btn" onclick="handleFilterClick('grayscale')">B&W</button>
|
| 85 |
+
<button class="filter-btn" onclick="handleFilterClick('sepia')">Sepia</button>
|
| 86 |
+
|
| 87 |
+
<button class="filter-btn" onclick="handleFilterClick('sketch')">Sketch</button>
|
| 88 |
+
<button class="filter-btn" onclick="handleFilterClick('cartoon')">Cartoon</button>
|
| 89 |
+
<button class="filter-btn" onclick="handleFilterClick('hdr')">HDR</button>
|
| 90 |
+
|
| 91 |
+
<button class="filter-btn" onclick="handleFilterClick('cyberpunk')">Cyberpunk</button>
|
| 92 |
+
<button class="filter-btn" onclick="handleFilterClick('summer')">Summer</button>
|
| 93 |
+
<button class="filter-btn" onclick="handleFilterClick('winter')">Winter</button>
|
| 94 |
+
|
| 95 |
+
<button class="filter-btn" onclick="handleFilterClick('posterize')">Posterize</button>
|
| 96 |
+
<button class="filter-btn" onclick="handleFilterClick('negative')">Negative</button>
|
| 97 |
+
<button class="filter-btn" onclick="handleFilterClick('emboss')">Emboss</button>
|
| 98 |
+
|
| 99 |
+
<button class="filter-btn" onclick="handleFilterClick('blur')">Blur</button>
|
| 100 |
+
<button class="filter-btn" onclick="handleFilterClick('sharpen_filter')">X-Sharp</button>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
</aside>
|
| 105 |
+
|
| 106 |
+
<main class="canvas-area">
|
| 107 |
+
<div style="position: absolute; top: 20px; left: 20px; z-index: 10; display: flex; gap: 10px;">
|
| 108 |
+
<button class="btn btn-secondary" onclick="undoEdit()" title="Undo (Ctrl+Z)">
|
| 109 |
+
<i class="ph ph-arrow-u-up-left"></i> Undo
|
| 110 |
+
</button>
|
| 111 |
+
<button class="btn btn-secondary" onclick="redoEdit()" title="Redo (Ctrl+Y)">
|
| 112 |
+
<i class="ph ph-arrow-u-up-right"></i> Redo
|
| 113 |
+
</button>
|
| 114 |
+
</div>
|
| 115 |
+
|
| 116 |
+
<div id="placeholderState" class="upload-placeholder">
|
| 117 |
+
<i class="ph ph-image" style="font-size: 3rem;"></i>
|
| 118 |
+
<h3>No Image Selected</h3>
|
| 119 |
+
<p>Upload an image to start editing</p>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
<div style="flex: 1; display: flex; align-items: center; justify-content: center; width: 100%; overflow: hidden; position: relative;">
|
| 123 |
+
<div id="processingOverlay" style="position: absolute; inset: 0; background: rgba(0,0,0,0.5); backdrop-filter: blur(2px); display: none; align-items: center; justify-content: center; z-index: 20;">
|
| 124 |
+
<div style="background: #000; padding: 15px 25px; border-radius: 30px; color: var(--primary-cyan); display: flex; align-items: center; gap: 10px; border: 1px solid var(--primary-cyan);">
|
| 125 |
+
<i class="ph ph-spinner ph-spin" style="font-size: 1.2rem;"></i> Rendering...
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
+
<img id="imagePreview" src="" alt="Preview" style="display: none;">
|
| 129 |
+
</div>
|
| 130 |
+
|
| 131 |
+
<div class="action-bar" id="actionBar" style="display: none;">
|
| 132 |
+
<button class="btn btn-secondary" onclick="resetOriginal()">
|
| 133 |
+
<i class="ph ph-arrow-counter-clockwise"></i> Reset Original
|
| 134 |
+
</button>
|
| 135 |
+
<a id="downloadLink" href="#" download="radiant_edit.png" class="btn btn-primary" style="background: var(--primary-cyan); color: #000;">
|
| 136 |
+
<i class="ph ph-download-simple"></i> Download
|
| 137 |
+
</a>
|
| 138 |
+
</div>
|
| 139 |
+
</main>
|
| 140 |
+
</div>
|
| 141 |
+
{% endblock %}
|
templates/utilities.html
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Radiant AI - Utilities{% endblock %}
|
| 4 |
+
{% block page_title %}Image Utilities{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<style>
|
| 8 |
+
.utils-layout {
|
| 9 |
+
display: grid;
|
| 10 |
+
grid-template-columns: 300px 1fr;
|
| 11 |
+
gap: 25px;
|
| 12 |
+
height: 100%;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.util-section {
|
| 16 |
+
margin-bottom: 25px;
|
| 17 |
+
border-bottom: 1px solid var(--glass-border);
|
| 18 |
+
padding-bottom: 20px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.util-section:last-child { border-bottom: none; padding-bottom: 0; }
|
| 22 |
+
|
| 23 |
+
.input-group {
|
| 24 |
+
display: flex;
|
| 25 |
+
gap: 10px;
|
| 26 |
+
margin-bottom: 10px;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.input-group input, .input-group select {
|
| 30 |
+
width: 100%;
|
| 31 |
+
background: rgba(0,0,0,0.3);
|
| 32 |
+
border: 1px solid var(--glass-border);
|
| 33 |
+
padding: 10px;
|
| 34 |
+
border-radius: 8px;
|
| 35 |
+
color: white;
|
| 36 |
+
font-family: 'Inter', sans-serif;
|
| 37 |
+
font-size: 0.9rem;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.input-group input:focus, .input-group select:focus {
|
| 41 |
+
border-color: var(--primary-cyan);
|
| 42 |
+
outline: none;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/* Metadata Display Box */
|
| 46 |
+
.meta-data-box {
|
| 47 |
+
background: rgba(0,0,0,0.4);
|
| 48 |
+
border-radius: 8px;
|
| 49 |
+
padding: 15px;
|
| 50 |
+
font-family: 'Courier New', monospace;
|
| 51 |
+
font-size: 0.85rem;
|
| 52 |
+
color: var(--text-gray);
|
| 53 |
+
max-height: 250px;
|
| 54 |
+
overflow-y: auto;
|
| 55 |
+
white-space: pre-wrap;
|
| 56 |
+
border: 1px solid var(--glass-border);
|
| 57 |
+
margin-top: 10px;
|
| 58 |
+
}
|
| 59 |
+
</style>
|
| 60 |
+
|
| 61 |
+
<div class="utils-layout">
|
| 62 |
+
|
| 63 |
+
<aside class="controls-panel">
|
| 64 |
+
|
| 65 |
+
<div class="glass-panel" style="padding: 20px;">
|
| 66 |
+
<div class="tool-header">Source File</div>
|
| 67 |
+
<input type="file" id="utilFileInput" accept="image/*" hidden>
|
| 68 |
+
<button class="btn btn-secondary" style="width: 100%;" onclick="document.getElementById('utilFileInput').click()">
|
| 69 |
+
<i class="ph ph-upload-simple"></i> Upload Image
|
| 70 |
+
</button>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<div class="glass-panel" style="padding: 20px;">
|
| 74 |
+
<div class="tool-header">Format Converter</div>
|
| 75 |
+
<div class="input-group">
|
| 76 |
+
<select id="convertFormat">
|
| 77 |
+
<option value="png">PNG (Lossless)</option>
|
| 78 |
+
<option value="jpg">JPEG (Web Optimized)</option>
|
| 79 |
+
<option value="webp">WebP (Modern)</option>
|
| 80 |
+
<option value="bmp">BMP (Raw)</option>
|
| 81 |
+
</select>
|
| 82 |
+
</div>
|
| 83 |
+
<button class="btn btn-primary" onclick="runUtility('convert')" style="width: 100%; margin-top: 5px;">
|
| 84 |
+
Convert Format
|
| 85 |
+
</button>
|
| 86 |
+
</div>
|
| 87 |
+
|
| 88 |
+
<div class="glass-panel" style="padding: 20px;">
|
| 89 |
+
<div class="tool-header">Resize (Pixels)</div>
|
| 90 |
+
|
| 91 |
+
<div class="input-group">
|
| 92 |
+
<input type="number" id="resizeW" placeholder="Width">
|
| 93 |
+
<input type="number" id="resizeH" placeholder="Height">
|
| 94 |
+
</div>
|
| 95 |
+
|
| 96 |
+
<div class="tool-header" style="margin-top: 15px;">Compression Quality</div>
|
| 97 |
+
<div class="input-group" style="align-items: center;">
|
| 98 |
+
<input type="range" id="qualitySlider" min="10" max="100" value="90" oninput="document.getElementById('qVal').innerText=this.value">
|
| 99 |
+
<span id="qVal" style="width: 35px; text-align: center; font-weight: bold;">90</span>
|
| 100 |
+
</div>
|
| 101 |
+
|
| 102 |
+
<button class="btn btn-secondary" onclick="runUtility('resize')" style="width: 100%; margin-top: 10px;">
|
| 103 |
+
Apply Resize
|
| 104 |
+
</button>
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
<div class="glass-panel" style="padding: 20px;">
|
| 108 |
+
<div class="tool-header">Information</div>
|
| 109 |
+
<button class="btn btn-secondary" onclick="runUtility('metadata')" style="width: 100%;">
|
| 110 |
+
<i class="ph ph-list-magnifying-glass"></i> View Metadata
|
| 111 |
+
</button>
|
| 112 |
+
</div>
|
| 113 |
+
|
| 114 |
+
</aside>
|
| 115 |
+
|
| 116 |
+
<main class="glass-panel" style="display: flex; flex-direction: column; padding: 20px; overflow-y: auto;">
|
| 117 |
+
|
| 118 |
+
<div id="utilPlaceholder" class="upload-placeholder" style="flex: 1; display: flex; flex-direction: column; justify-content: center;">
|
| 119 |
+
<i class="ph ph-wrench" style="font-size: 3rem; margin-bottom: 15px; color: var(--text-gray);"></i>
|
| 120 |
+
<h3>Utility Tools</h3>
|
| 121 |
+
<p>Convert, Resize, or Inspect your images.</p>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<div id="utilWorkArea" style="display: none; height: 100%; flex-direction: column;">
|
| 125 |
+
|
| 126 |
+
<div style="flex: 1; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.2); border-radius: 12px; margin-bottom: 20px; min-height: 300px; padding: 20px;">
|
| 127 |
+
<img id="utilPreview" src="" style="max-width: 100%; max-height: 400px; box-shadow: 0 0 20px rgba(0,0,0,0.5); border-radius: 8px;">
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 10px; background: rgba(0,0,0,0.3); border-radius: 10px;">
|
| 131 |
+
<div id="fileInfo" style="font-size: 0.9rem; color: var(--primary-cyan); font-weight: 500;">
|
| 132 |
+
</div>
|
| 133 |
+
<a id="utilDownload" href="#" class="btn btn-primary" download>
|
| 134 |
+
<i class="ph ph-download-simple"></i> Download Result
|
| 135 |
+
</a>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
<div id="metaSection" style="display: none;">
|
| 139 |
+
<div class="tool-header">EXIF Data</div>
|
| 140 |
+
<div id="metaDataOutput" class="meta-data-box"></div>
|
| 141 |
+
</div>
|
| 142 |
+
|
| 143 |
+
</div>
|
| 144 |
+
</main>
|
| 145 |
+
|
| 146 |
+
</div>
|
| 147 |
+
|
| 148 |
+
{% endblock %}
|
| 149 |
+
|
| 150 |
+
{% block scripts %}
|
| 151 |
+
<script>
|
| 152 |
+
async function runUtility(action) {
|
| 153 |
+
const filename = localStorage.getItem('radiant_filename');
|
| 154 |
+
if(!filename) return alert("Please upload an image first.");
|
| 155 |
+
|
| 156 |
+
const payload = {
|
| 157 |
+
filename: filename,
|
| 158 |
+
action: action
|
| 159 |
+
};
|
| 160 |
+
|
| 161 |
+
// Gather params based on action
|
| 162 |
+
if(action === 'convert') {
|
| 163 |
+
payload.format = document.getElementById('convertFormat').value;
|
| 164 |
+
} else if (action === 'resize') {
|
| 165 |
+
payload.width = document.getElementById('resizeW').value;
|
| 166 |
+
payload.height = document.getElementById('resizeH').value;
|
| 167 |
+
payload.quality = document.getElementById('qualitySlider').value;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
try {
|
| 171 |
+
const res = await fetch('/api/process-utility', {
|
| 172 |
+
method: 'POST',
|
| 173 |
+
headers: {'Content-Type': 'application/json'},
|
| 174 |
+
body: JSON.stringify(payload)
|
| 175 |
+
});
|
| 176 |
+
|
| 177 |
+
const data = await res.json();
|
| 178 |
+
|
| 179 |
+
if(data.success) {
|
| 180 |
+
if(action === 'metadata') {
|
| 181 |
+
// Handle Metadata display
|
| 182 |
+
document.getElementById('metaSection').style.display = 'block';
|
| 183 |
+
document.getElementById('metaDataOutput').innerText = JSON.stringify(data.metadata, null, 2);
|
| 184 |
+
} else {
|
| 185 |
+
// Handle Image operations
|
| 186 |
+
const img = document.getElementById('utilPreview');
|
| 187 |
+
img.src = `${data.url}?t=${new Date().getTime()}`;
|
| 188 |
+
|
| 189 |
+
document.getElementById('utilDownload').href = data.url;
|
| 190 |
+
|
| 191 |
+
// Update Info Text
|
| 192 |
+
const format = (action === 'convert') ? payload.format.toUpperCase() : 'Image';
|
| 193 |
+
document.getElementById('fileInfo').innerText = `Success: ${data.filename}`;
|
| 194 |
+
}
|
| 195 |
+
} else {
|
| 196 |
+
alert("Error: " + data.error);
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
} catch(err) {
|
| 200 |
+
console.error(err);
|
| 201 |
+
alert("Processing Error");
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
</script>
|
| 205 |
+
{% endblock %}
|
templates/vision.html
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Radiant AI - Computer Vision{% endblock %}
|
| 4 |
+
{% block page_title %}Computer Vision Hub{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<style>
|
| 8 |
+
.vision-layout { display: grid; grid-template-columns: 300px 1fr; gap: 25px; height: 100%; }
|
| 9 |
+
.tool-card { background: var(--glass-bg); border: 1px solid var(--glass-border); padding: 15px; border-radius: 16px; margin-bottom: 20px; }
|
| 10 |
+
.tool-title { font-weight: 600; margin-bottom: 8px; display: flex; align-items: center; gap: 10px; font-size: 0.95rem; }
|
| 11 |
+
.tool-icon { font-size: 1.4rem; color: var(--primary-cyan); }
|
| 12 |
+
.tool-desc { font-size: 0.8rem; color: var(--text-gray); margin-bottom: 15px; }
|
| 13 |
+
.color-picker-row { display: flex; align-items: center; gap: 10px; background: rgba(0,0,0,0.2); padding: 8px; border-radius: 8px; margin-bottom: 10px; }
|
| 14 |
+
input[type="color"] { -webkit-appearance: none; border: none; width: 32px; height: 32px; cursor: pointer; background: none; }
|
| 15 |
+
.canvas-area { position: relative; background: radial-gradient(circle at center, #1a1a24 0%, #0d0d12 100%); border-radius: 20px; border: 1px solid var(--glass-border); display: flex; align-items: center; justify-content: center; overflow: hidden; }
|
| 16 |
+
#visionPreview { max-width: 95%; max-height: 95%; object-fit: contain; display: none; }
|
| 17 |
+
.results-overlay { position: absolute; top: 20px; left: 20px; background: rgba(0,0,0,0.8); padding: 15px; border-radius: 12px; color: white; display: none; max-width: 250px; }
|
| 18 |
+
.processing-badge { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.9); color: var(--primary-cyan); padding: 15px 30px; border-radius: 50px; border: 1px solid var(--primary-cyan); display: none; align-items: center; gap: 10px; z-index: 10; }
|
| 19 |
+
.action-bar { position: absolute; bottom: 20px; right: 20px; display: flex; gap: 10px; background: rgba(0,0,0,0.6); padding: 10px; border-radius: 12px; }
|
| 20 |
+
|
| 21 |
+
/* Checkbox Style */
|
| 22 |
+
.chain-control { display: flex; align-items: center; gap: 10px; background: rgba(189, 0, 255, 0.1); padding: 10px; border-radius: 8px; border: 1px solid rgba(189, 0, 255, 0.3); margin-bottom: 15px; }
|
| 23 |
+
.chain-control input { width: 18px; height: 18px; accent-color: #bd00ff; cursor: pointer; }
|
| 24 |
+
.chain-control label { font-size: 0.9rem; color: #bd00ff; cursor: pointer; font-weight: 500; }
|
| 25 |
+
</style>
|
| 26 |
+
|
| 27 |
+
<div class="vision-layout">
|
| 28 |
+
|
| 29 |
+
<aside class="controls-panel">
|
| 30 |
+
<div class="tool-card">
|
| 31 |
+
<input type="file" id="visionFileInput" accept="image/*" hidden>
|
| 32 |
+
<button class="btn btn-secondary" style="width: 100%;" onclick="document.getElementById('visionFileInput').click()">
|
| 33 |
+
<i class="ph ph-upload-simple"></i> Upload Source
|
| 34 |
+
</button>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<div class="chain-control">
|
| 38 |
+
<input type="checkbox" id="visionChainingCheckbox">
|
| 39 |
+
<label for="visionChainingCheckbox">Apply on Previous Result</label>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<div class="tool-card">
|
| 43 |
+
<div class="tool-title"><i class="ph-duotone ph-scan tool-icon"></i> Detection</div>
|
| 44 |
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
| 45 |
+
<button class="btn btn-secondary" onclick="runVisionTask('yolo')">Objects</button>
|
| 46 |
+
<button class="btn btn-secondary" onclick="runVisionTask('pose')">Pose (Body)</button>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div class="tool-card">
|
| 51 |
+
<div class="tool-title"><i class="ph-duotone ph-sliders-horizontal tool-icon" style="color: #bd00ff;"></i> Utilities</div>
|
| 52 |
+
<button class="btn btn-secondary" onclick="runVisionTask('remove_bg')" style="width: 100%; margin-bottom: 10px;">Remove BG</button>
|
| 53 |
+
|
| 54 |
+
<div class="color-picker-row">
|
| 55 |
+
<input type="color" id="bgColorPicker" value="#ffffff">
|
| 56 |
+
<button class="btn btn-secondary" onclick="runVisionTask('replace_bg')" style="flex:1; padding: 5px;">Replace BG</button>
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
<button class="btn btn-secondary" onclick="runVisionTask('doc_scanner')" style="width: 100%;">Doc Scanner</button>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<div class="tool-card">
|
| 63 |
+
<div class="tool-title"><i class="ph-duotone ph-eye-slash tool-icon"></i> Privacy</div>
|
| 64 |
+
<div style="display: flex; gap: 10px; align-items: center; margin-bottom: 10px;">
|
| 65 |
+
<input type="range" id="blurSlider" min="5" max="100" value="30" style="flex:1;">
|
| 66 |
+
</div>
|
| 67 |
+
<button class="btn btn-secondary" onclick="runVisionTask('face_blur')" style="width: 100%;">Blur Faces</button>
|
| 68 |
+
<button class="btn btn-secondary" onclick="runVisionTask('canny_edge')" style="width: 100%; margin-top: 10px;">Edge Detect</button>
|
| 69 |
+
</div>
|
| 70 |
+
</aside>
|
| 71 |
+
|
| 72 |
+
<main class="canvas-area">
|
| 73 |
+
<div id="visionLoading" class="processing-badge"><i class="ph ph-spinner ph-spin"></i> AI Processing...</div>
|
| 74 |
+
<div id="visionPlaceholder" class="upload-placeholder">
|
| 75 |
+
<i class="ph ph-eye" style="font-size: 3rem; opacity: 0.5; margin-bottom: 10px;"></i>
|
| 76 |
+
<h3>Computer Vision</h3>
|
| 77 |
+
<p>Select a tool to analyze the image</p>
|
| 78 |
+
</div>
|
| 79 |
+
<img id="visionPreview" src="" alt="AI Result">
|
| 80 |
+
<div id="visionOverlay" class="results-overlay">
|
| 81 |
+
<strong style="color: var(--primary-cyan); display: block; margin-bottom: 5px;">Detections:</strong>
|
| 82 |
+
<ul id="detectionList" style="list-style: none; padding-left: 0;"></ul>
|
| 83 |
+
</div>
|
| 84 |
+
<div class="action-bar" id="visionActionBar" style="display: none;">
|
| 85 |
+
<a id="visionDownload" href="#" download="radiant_vision.png" class="btn btn-primary"><i class="ph ph-download-simple"></i> Download</a>
|
| 86 |
+
</div>
|
| 87 |
+
</main>
|
| 88 |
+
</div>
|
| 89 |
+
{% endblock %}
|