arshvir commited on
Commit
d75fbc8
·
0 Parent(s):

Initial commit with LFS models

Browse files
.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 %}