kirikir13 commited on
Commit
dfd056d
·
verified ·
1 Parent(s): f57cbb0

Upload 10 files

Browse files
Files changed (9) hide show
  1. .env +19 -0
  2. SPACES_README.md +93 -0
  3. dev-windows.bat +4 -0
  4. dr_image_magic_FULL.py +728 -0
  5. gradio_app.py +265 -0
  6. package.json +115 -0
  7. simple_image_magic_gui.py +297 -0
  8. todo.md +44 -0
  9. vite.config.ts +193 -0
.env ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DR-Image-Magic Environment Configuration
2
+
3
+ # Database Configuration
4
+ # The app is configured for MySQL
5
+ #
6
+ # OPTION 1: Install MySQL locally (recommended)
7
+ # Download from: https://dev.mysql.com/downloads/installer/
8
+ # Then use: mysql://root:yourpassword@localhost:3306/dr_image_magic
9
+ #
10
+ # OPTION 2: Skip database for now (Gradio app works without it)
11
+ # Just leave this commented out and use the Gradio interface
12
+ #
13
+ # DATABASE_URL=mysql://root:password@localhost:3306/dr_image_magic
14
+
15
+ # Analytics (optional - can leave blank for local dev)
16
+ VITE_ANALYTICS_ENDPOINT=
17
+ VITE_ANALYTICS_WEBSITE_ID=
18
+
19
+ # Add any other environment variables needed below
SPACES_README.md ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DR-Image-Magic on Hugging Face Spaces
2
+
3
+ This is a Gradio web interface for managing the Artistic Photo Transform project.
4
+
5
+ ## Deployment Instructions
6
+
7
+ ### Option 1: Deploy to Hugging Face Spaces
8
+
9
+ 1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
10
+ 2. Create a new Space:
11
+ - Select **Gradio** as the SDK
12
+ - Choose your username/organization
13
+ - Name it: `DR-Image-Magic` (or your preferred name)
14
+ 3. In the Space settings:
15
+ - Clone this repository or upload these files
16
+ - The Space will automatically detect `gradio_app.py` and `requirements.txt`
17
+ 4. Your Space will automatically start running!
18
+
19
+ ### Option 2: Run Locally
20
+
21
+ ```bash
22
+ # Install dependencies
23
+ pip install -r requirements.txt
24
+
25
+ # Run the Gradio app
26
+ python gradio_app.py
27
+ ```
28
+
29
+ Then open your browser to `http://localhost:7860`
30
+
31
+ ## Features
32
+
33
+ The Gradio interface provides:
34
+
35
+ ### 📋 Project Info Tab
36
+
37
+ - View project details
38
+ - Tech stack information
39
+ - Setup instructions
40
+
41
+ ### ⚙️ Setup Tab
42
+
43
+ - Install dependencies with one click
44
+ - Push database schema
45
+
46
+ ### 🚀 Development Tab
47
+
48
+ - Start development server
49
+ - Run type checking
50
+ - Format code automatically
51
+ - Execute test suite
52
+
53
+ ### 📦 Production Tab
54
+
55
+ - Build optimized production bundle
56
+ - Deployment guidance
57
+
58
+ ## Environment Variables
59
+
60
+ To use this on Hugging Face Spaces with actual functionality, you'll need to:
61
+
62
+ 1. Set up environment variables in your Space settings:
63
+ - `NODE_ENV`
64
+ - Database credentials
65
+ - AWS S3 credentials
66
+ - API keys
67
+
68
+ 2. Navigate to Space Settings → Variables and secrets
69
+
70
+ ## Requirements
71
+
72
+ - Python 3.8+
73
+ - Node.js 18+ (for running pnpm commands)
74
+ - pnpm package manager
75
+
76
+ ## Source Code
77
+
78
+ Full project source: https://github.com/DR-Studios/DR-Image-Magic
79
+
80
+ ## Note
81
+
82
+ This is a management interface for the DR-Image-Magic project. It assumes:
83
+
84
+ - The project files are cloned/deployed
85
+ - Node.js and pnpm are installed on the Spaces environment
86
+ - Environment variables are properly configured
87
+
88
+ For actual image transformation features, you'll need to:
89
+
90
+ 1. Set up AWS S3 credentials
91
+ 2. Configure AI model access (Claude, etc.)
92
+ 3. Set up database connection
93
+ 4. Configure authentication
dev-windows.bat ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ @echo off
2
+ REM Windows-compatible dev server
3
+ set NODE_ENV=development
4
+ tsx watch server/_core/index.ts
dr_image_magic_FULL.py ADDED
@@ -0,0 +1,728 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ DR-Image-Magic - FULL FEATURED GUI
4
+ All features, no database needed!
5
+ """
6
+
7
+ import tkinter as tk
8
+ from tkinter import filedialog, messagebox, ttk, scrolledtext
9
+ from PIL import Image, ImageTk, ImageEnhance, ImageFilter, ImageOps, ImageDraw, ImageFont
10
+ import os
11
+ from pathlib import Path
12
+ import numpy as np
13
+ from datetime import datetime
14
+
15
+ class DrImageMagicFull:
16
+ def __init__(self, root):
17
+ self.root = root
18
+ self.root.title("DR-Image-Magic - FULL EDITION")
19
+ self.root.geometry("1200x900")
20
+ self.root.configure(bg='#0a0a0a')
21
+
22
+ self.current_image = None
23
+ self.original_image = None
24
+ self.image_path = None
25
+ self.history = [] # Undo history
26
+
27
+ self.setup_ui()
28
+
29
+ def setup_ui(self):
30
+ # Main container with scrollbar
31
+ main_canvas = tk.Canvas(self.root, bg='#0a0a0a', highlightthickness=0)
32
+ scrollbar = ttk.Scrollbar(self.root, orient="vertical", command=main_canvas.yview)
33
+ scrollable_frame = tk.Frame(main_canvas, bg='#0a0a0a')
34
+
35
+ scrollable_frame.bind(
36
+ "<Configure>",
37
+ lambda e: main_canvas.configure(scrollregion=main_canvas.bbox("all"))
38
+ )
39
+
40
+ main_canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
41
+ main_canvas.configure(yscrollcommand=scrollbar.set)
42
+
43
+ main_canvas.pack(side="left", fill="both", expand=True)
44
+ scrollbar.pack(side="right", fill="y")
45
+
46
+ # Enable mousewheel scrolling
47
+ def _on_mousewheel(event):
48
+ main_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
49
+ main_canvas.bind_all("<MouseWheel>", _on_mousewheel)
50
+
51
+ # Header
52
+ header = tk.Label(
53
+ scrollable_frame,
54
+ text="🎨 DR-IMAGE-MAGIC",
55
+ font=("Arial", 32, "bold"),
56
+ bg='#0a0a0a',
57
+ fg='#ff6600'
58
+ )
59
+ header.pack(pady=15)
60
+
61
+ subtitle = tk.Label(
62
+ scrollable_frame,
63
+ text="FULL FEATURED EDITION - All Tools, No Limits",
64
+ font=("Arial", 12),
65
+ bg='#0a0a0a',
66
+ fg='#888'
67
+ )
68
+ subtitle.pack()
69
+
70
+ # Upload Section
71
+ upload_frame = tk.Frame(scrollable_frame, bg='#0a0a0a')
72
+ upload_frame.pack(pady=15)
73
+
74
+ upload_btn = tk.Button(
75
+ upload_frame,
76
+ text="📁 UPLOAD IMAGE",
77
+ command=self.upload_image,
78
+ font=("Arial", 16, "bold"),
79
+ bg='#ff6600',
80
+ fg='white',
81
+ padx=30,
82
+ pady=12,
83
+ cursor='hand2',
84
+ relief=tk.FLAT
85
+ )
86
+ upload_btn.pack()
87
+
88
+ # Image Preview
89
+ preview_frame = tk.Frame(scrollable_frame, bg='#1a1a1a', relief=tk.SUNKEN, bd=2)
90
+ preview_frame.pack(pady=10, padx=20, fill=tk.BOTH)
91
+
92
+ self.image_label = tk.Label(
93
+ preview_frame,
94
+ bg='#1a1a1a',
95
+ text="No Image Loaded\n\nDrag & Drop or Click Upload",
96
+ fg='#666',
97
+ font=("Arial", 14)
98
+ )
99
+ self.image_label.pack(pady=40, padx=20)
100
+
101
+ # Quick Actions
102
+ quick_frame = tk.Frame(scrollable_frame, bg='#0a0a0a')
103
+ quick_frame.pack(pady=10)
104
+
105
+ quick_btns = [
106
+ ("🔄 UNDO", self.undo_last, '#ff9900'),
107
+ ("↺ RESET", self.reset_image, '#cc0000'),
108
+ ("💾 SAVE", self.save_image, '#00cc00'),
109
+ ("📦 BATCH SAVE", self.batch_save, '#0066cc'),
110
+ ]
111
+
112
+ for text, cmd, color in quick_btns:
113
+ btn = tk.Button(
114
+ quick_frame,
115
+ text=text,
116
+ command=cmd,
117
+ font=("Arial", 11, "bold"),
118
+ bg=color,
119
+ fg='white',
120
+ padx=15,
121
+ pady=8,
122
+ cursor='hand2',
123
+ relief=tk.FLAT
124
+ )
125
+ btn.pack(side=tk.LEFT, padx=5)
126
+
127
+ # EFFECTS SECTIONS
128
+ self.create_effects_section(scrollable_frame)
129
+
130
+ def create_effects_section(self, parent):
131
+ # Section 1: ESSENTIAL ENHANCEMENTS
132
+ self.create_section(parent, "⚡ ESSENTIAL ENHANCEMENTS", [
133
+ ("✨ Auto Enhance", self.auto_enhance, "Smart enhancement"),
134
+ ("💎 Super Sharpen", self.super_sharpen, "Crystal clear"),
135
+ ("🌈 Color Pop", self.color_pop, "Vibrant colors"),
136
+ ("🔥 HDR Effect", self.hdr_effect, "High dynamic range"),
137
+ ])
138
+
139
+ # Section 2: ARTISTIC STYLES
140
+ self.create_section(parent, "🎨 ARTISTIC STYLES", [
141
+ ("🖼️ Oil Painting", self.oil_painting, "Classic art"),
142
+ ("📸 Film Noir", self.film_noir, "Black & white drama"),
143
+ ("🌅 Golden Hour", self.golden_hour, "Warm sunset glow"),
144
+ ("❄️ Ice Cold", self.ice_cold, "Cool blue tones"),
145
+ ("🔴 Infrared", self.infrared, "IR photography"),
146
+ ("⚫ High Key B&W", self.high_key_bw, "Bright grayscale"),
147
+ ])
148
+
149
+ # Section 3: DRAMATIC EFFECTS
150
+ self.create_section(parent, "💥 DRAMATIC EFFECTS", [
151
+ ("🌙 Dark Fantasy", self.dark_fantasy, "Gothic mood"),
152
+ ("☀️ Sunburst", self.sunburst, "Intense brightness"),
153
+ ("🎭 Vignette Drama", self.vignette_drama, "Dark edges"),
154
+ ("✨ Glow", self.glow_effect, "Soft luminous"),
155
+ ("🔆 Cross Process", self.cross_process, "Film effect"),
156
+ ])
157
+
158
+ # Section 4: VINTAGE & RETRO
159
+ self.create_section(parent, "📷 VINTAGE & RETRO", [
160
+ ("📼 VHS Glitch", self.vhs_glitch, "80s video"),
161
+ ("📺 CRT Monitor", self.crt_effect, "Old screen"),
162
+ ("🎞️ 70s Film", self.seventies_film, "Retro warm"),
163
+ ("📟 Polaroid", self.polaroid, "Instant camera"),
164
+ ("🌄 Faded Memory", self.faded_memory, "Old photo"),
165
+ ])
166
+
167
+ # Section 5: MODERN & DIGITAL
168
+ self.create_section(parent, "🚀 MODERN & DIGITAL", [
169
+ ("💻 Cyberpunk", self.cyberpunk, "Neon future"),
170
+ ("🌐 Glitch Art", self.glitch_art, "Digital chaos"),
171
+ ("🎮 Pixel Art", self.pixel_art, "8-bit style"),
172
+ ("🌌 Vaporwave", self.vaporwave, "A E S T H E T I C"),
173
+ ("⚡ Neon Lights", self.neon_lights, "Bright neon"),
174
+ ])
175
+
176
+ # Section 6: PRO PRESETS
177
+ self.create_section(parent, "🏆 PRO PRESETS", [
178
+ ("📱 Instagram Pro", self.instagram_pro, "Social ready"),
179
+ ("🖼️ Gallery Print", self.gallery_print, "Museum quality"),
180
+ ("💼 Professional", self.professional, "Business look"),
181
+ ("🎪 Artistic Bold", self.artistic_bold, "Creative statement"),
182
+ ("🕯️ Candlelight Sketch", self.candlelight_sketch, "Pencil + warm glow"),
183
+ ])
184
+
185
+ # Section 7: IMAGE TOOLS
186
+ self.create_section(parent, "🔧 IMAGE TOOLS", [
187
+ ("🔲 Expand Canvas", self.expand_canvas, "Extend image"),
188
+ ("📐 Upscale 2x", self.upscale_image, "Make bigger"),
189
+ ("🎨 Style Transfer", self.style_transfer, "Apply style"),
190
+ ("🔄 Rotate 90°", self.rotate_90, "Quick rotate"),
191
+ ("↔️ Flip Horizontal", self.flip_horizontal, "Mirror"),
192
+ ("↕️ Flip Vertical", self.flip_vertical, "Flip"),
193
+ ])
194
+
195
+ def create_section(self, parent, title, effects):
196
+ section = tk.LabelFrame(
197
+ parent,
198
+ text=title,
199
+ font=("Arial", 14, "bold"),
200
+ bg='#0a0a0a',
201
+ fg='#ff6600',
202
+ relief=tk.GROOVE,
203
+ bd=2
204
+ )
205
+ section.pack(pady=15, padx=20, fill=tk.X)
206
+
207
+ grid_frame = tk.Frame(section, bg='#0a0a0a')
208
+ grid_frame.pack(pady=10, padx=10)
209
+
210
+ row, col = 0, 0
211
+ for name, cmd, desc in effects:
212
+ btn_frame = tk.Frame(grid_frame, bg='#1a1a1a', relief=tk.RAISED, bd=1)
213
+ btn_frame.grid(row=row, column=col, padx=5, pady=5, sticky='ew')
214
+
215
+ btn = tk.Button(
216
+ btn_frame,
217
+ text=name,
218
+ command=cmd,
219
+ font=("Arial", 10, "bold"),
220
+ bg='#1a1a1a',
221
+ fg='#ff6600',
222
+ padx=12,
223
+ pady=8,
224
+ cursor='hand2',
225
+ relief=tk.FLAT
226
+ )
227
+ btn.pack(fill=tk.X)
228
+
229
+ desc_label = tk.Label(
230
+ btn_frame,
231
+ text=desc,
232
+ font=("Arial", 8),
233
+ bg='#1a1a1a',
234
+ fg='#666'
235
+ )
236
+ desc_label.pack()
237
+
238
+ col += 1
239
+ if col > 3: # 4 columns
240
+ col = 0
241
+ row += 1
242
+
243
+ def upload_image(self):
244
+ file_path = filedialog.askopenfilename(
245
+ title="Select Image",
246
+ filetypes=[("Images", "*.png *.jpg *.jpeg *.webp *.bmp"), ("All", "*.*")]
247
+ )
248
+ if file_path:
249
+ try:
250
+ self.image_path = file_path
251
+ self.original_image = Image.open(file_path).convert('RGB')
252
+ self.current_image = self.original_image.copy()
253
+ self.history = [self.current_image.copy()]
254
+ self.display_image(self.current_image)
255
+ except Exception as e:
256
+ messagebox.showerror("Error", f"Failed to load: {e}")
257
+
258
+ def display_image(self, image):
259
+ display_img = image.copy()
260
+ display_img.thumbnail((600, 400), Image.Resampling.LANCZOS)
261
+ photo = ImageTk.PhotoImage(display_img)
262
+ self.image_label.configure(image=photo, text="")
263
+ self.image_label.image = photo
264
+
265
+ def save_to_history(self):
266
+ if self.current_image:
267
+ self.history.append(self.current_image.copy())
268
+ if len(self.history) > 20: # Keep last 20
269
+ self.history.pop(0)
270
+
271
+ def check_image(self):
272
+ if not self.current_image:
273
+ messagebox.showwarning("No Image", "Upload an image first!")
274
+ return False
275
+ return True
276
+
277
+ def apply_effect(self, effect_func):
278
+ if not self.check_image(): return
279
+ self.save_to_history()
280
+ try:
281
+ self.current_image = effect_func(self.current_image)
282
+ self.display_image(self.current_image)
283
+ except Exception as e:
284
+ messagebox.showerror("Effect Error", str(e))
285
+
286
+ # === ESSENTIAL ENHANCEMENTS ===
287
+ def auto_enhance(self):
288
+ if not self.check_image(): return
289
+ self.save_to_history()
290
+ img = self.current_image
291
+ img = ImageEnhance.Contrast(img).enhance(1.2)
292
+ img = ImageEnhance.Sharpness(img).enhance(1.3)
293
+ img = ImageEnhance.Color(img).enhance(1.1)
294
+ self.current_image = img
295
+ self.display_image(img)
296
+
297
+ def super_sharpen(self):
298
+ self.apply_effect(lambda img: img.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3)))
299
+
300
+ def color_pop(self):
301
+ self.apply_effect(lambda img: ImageEnhance.Color(img).enhance(1.8))
302
+
303
+ def hdr_effect(self):
304
+ if not self.check_image(): return
305
+ self.save_to_history()
306
+ img = self.current_image
307
+ img = ImageEnhance.Contrast(img).enhance(1.5)
308
+ img = ImageEnhance.Brightness(img).enhance(0.95)
309
+ img = ImageEnhance.Color(img).enhance(1.3)
310
+ self.current_image = img
311
+ self.display_image(img)
312
+
313
+ # === ARTISTIC STYLES ===
314
+ def oil_painting(self):
315
+ self.apply_effect(lambda img: img.filter(ImageFilter.SMOOTH_MORE).filter(ImageFilter.EDGE_ENHANCE))
316
+
317
+ def film_noir(self):
318
+ if not self.check_image(): return
319
+ self.save_to_history()
320
+ img = self.current_image.convert('L').convert('RGB')
321
+ img = ImageEnhance.Contrast(img).enhance(1.6)
322
+ self.current_image = img
323
+ self.display_image(img)
324
+
325
+ def golden_hour(self):
326
+ if not self.check_image(): return
327
+ self.save_to_history()
328
+ img = self.current_image
329
+ r, g, b = img.split()
330
+ r = r.point(lambda i: min(255, int(i * 1.15)))
331
+ g = g.point(lambda i: min(255, int(i * 1.08)))
332
+ b = b.point(lambda i: int(i * 0.88))
333
+ img = Image.merge('RGB', (r, g, b))
334
+ img = ImageEnhance.Brightness(img).enhance(1.05)
335
+ self.current_image = img
336
+ self.display_image(img)
337
+
338
+ def ice_cold(self):
339
+ if not self.check_image(): return
340
+ self.save_to_history()
341
+ img = self.current_image
342
+ r, g, b = img.split()
343
+ r = r.point(lambda i: int(i * 0.85))
344
+ g = g.point(lambda i: int(i * 0.92))
345
+ b = b.point(lambda i: min(255, int(i * 1.15)))
346
+ self.current_image = Image.merge('RGB', (r, g, b))
347
+ self.display_image(self.current_image)
348
+
349
+ def infrared(self):
350
+ if not self.check_image(): return
351
+ self.save_to_history()
352
+ img = self.current_image.convert('L')
353
+ img = ImageOps.invert(img)
354
+ img = ImageEnhance.Contrast(img).enhance(1.3)
355
+ r = img
356
+ g = img
357
+ b = img.point(lambda i: int(i * 0.7))
358
+ self.current_image = Image.merge('RGB', (r, g, b))
359
+ self.display_image(self.current_image)
360
+
361
+ def high_key_bw(self):
362
+ if not self.check_image(): return
363
+ self.save_to_history()
364
+ img = self.current_image.convert('L').convert('RGB')
365
+ img = ImageEnhance.Brightness(img).enhance(1.2)
366
+ img = ImageEnhance.Contrast(img).enhance(0.8)
367
+ self.current_image = img
368
+ self.display_image(img)
369
+
370
+ # === DRAMATIC EFFECTS ===
371
+ def dark_fantasy(self):
372
+ if not self.check_image(): return
373
+ self.save_to_history()
374
+ img = self.current_image
375
+ img = ImageEnhance.Brightness(img).enhance(0.6)
376
+ img = ImageEnhance.Contrast(img).enhance(1.6)
377
+ img = ImageEnhance.Color(img).enhance(0.8)
378
+ self.current_image = img
379
+ self.display_image(img)
380
+
381
+ def sunburst(self):
382
+ self.apply_effect(lambda img: ImageEnhance.Brightness(img).enhance(1.4))
383
+
384
+ def vignette_drama(self):
385
+ if not self.check_image(): return
386
+ self.save_to_history()
387
+ img = self.current_image
388
+ width, height = img.size
389
+ mask = Image.new('L', (width, height), 0)
390
+ draw = ImageDraw.Draw(mask)
391
+
392
+ for i in range(min(width, height) // 4):
393
+ alpha = int(255 * (i / (min(width, height) // 4)))
394
+ draw.ellipse(
395
+ [i, i, width-i, height-i],
396
+ fill=alpha
397
+ )
398
+
399
+ dark = Image.new('RGB', img.size, (0, 0, 0))
400
+ self.current_image = Image.composite(img, dark, mask)
401
+ self.display_image(self.current_image)
402
+
403
+ def glow_effect(self):
404
+ if not self.check_image(): return
405
+ self.save_to_history()
406
+ img = self.current_image
407
+ glow = img.filter(ImageFilter.GaussianBlur(15))
408
+ glow = ImageEnhance.Brightness(glow).enhance(1.5)
409
+ self.current_image = Image.blend(img, glow, 0.3)
410
+ self.display_image(self.current_image)
411
+
412
+ def cross_process(self):
413
+ if not self.check_image(): return
414
+ self.save_to_history()
415
+ img = self.current_image
416
+ r, g, b = img.split()
417
+ r = r.point(lambda i: min(255, int(i * 1.1)))
418
+ g = g.point(lambda i: int(i * 0.95))
419
+ b = b.point(lambda i: min(255, int(i * 1.15)))
420
+ img = Image.merge('RGB', (r, g, b))
421
+ img = ImageEnhance.Contrast(img).enhance(1.3)
422
+ self.current_image = img
423
+ self.display_image(img)
424
+
425
+ # === VINTAGE & RETRO ===
426
+ def vhs_glitch(self):
427
+ if not self.check_image(): return
428
+ self.save_to_history()
429
+ img = self.current_image
430
+ img = ImageEnhance.Contrast(img).enhance(1.2)
431
+ img = ImageEnhance.Color(img).enhance(0.8)
432
+ self.current_image = img
433
+ self.display_image(img)
434
+
435
+ def crt_effect(self):
436
+ self.apply_effect(lambda img: ImageEnhance.Brightness(img).enhance(1.1))
437
+
438
+ def seventies_film(self):
439
+ if not self.check_image(): return
440
+ self.save_to_history()
441
+ img = self.current_image
442
+ r, g, b = img.split()
443
+ r = r.point(lambda i: min(255, int(i * 1.12)))
444
+ g = g.point(lambda i: min(255, int(i * 1.05)))
445
+ b = b.point(lambda i: int(i * 0.9))
446
+ img = Image.merge('RGB', (r, g, b))
447
+ img = ImageEnhance.Contrast(img).enhance(0.9)
448
+ self.current_image = img
449
+ self.display_image(img)
450
+
451
+ def polaroid(self):
452
+ if not self.check_image(): return
453
+ self.save_to_history()
454
+ img = self.current_image
455
+ img = ImageEnhance.Brightness(img).enhance(1.1)
456
+ img = ImageEnhance.Color(img).enhance(0.85)
457
+ img = ImageEnhance.Contrast(img).enhance(0.95)
458
+ self.current_image = img
459
+ self.display_image(img)
460
+
461
+ def faded_memory(self):
462
+ if not self.check_image(): return
463
+ self.save_to_history()
464
+ img = self.current_image
465
+ img = ImageEnhance.Color(img).enhance(0.6)
466
+ img = ImageEnhance.Brightness(img).enhance(1.15)
467
+ img = ImageEnhance.Contrast(img).enhance(0.8)
468
+ self.current_image = img
469
+ self.display_image(img)
470
+
471
+ # === MODERN & DIGITAL ===
472
+ def cyberpunk(self):
473
+ if not self.check_image(): return
474
+ self.save_to_history()
475
+ img = self.current_image
476
+ r, g, b = img.split()
477
+ r = r.point(lambda i: min(255, int(i * 1.2)))
478
+ g = g.point(lambda i: int(i * 0.9))
479
+ b = b.point(lambda i: min(255, int(i * 1.3)))
480
+ img = Image.merge('RGB', (r, g, b))
481
+ img = ImageEnhance.Contrast(img).enhance(1.4)
482
+ self.current_image = img
483
+ self.display_image(img)
484
+
485
+ def glitch_art(self):
486
+ self.apply_effect(lambda img: img.filter(ImageFilter.EDGE_ENHANCE_MORE))
487
+
488
+ def pixel_art(self):
489
+ if not self.check_image(): return
490
+ self.save_to_history()
491
+ img = self.current_image
492
+ small = img.resize((img.width // 16, img.height // 16), Image.Resampling.NEAREST)
493
+ self.current_image = small.resize(img.size, Image.Resampling.NEAREST)
494
+ self.display_image(self.current_image)
495
+
496
+ def vaporwave(self):
497
+ if not self.check_image(): return
498
+ self.save_to_history()
499
+ img = self.current_image
500
+ r, g, b = img.split()
501
+ r = r.point(lambda i: min(255, int(i * 1.2)))
502
+ g = g.point(lambda i: min(255, int(i * 0.95)))
503
+ b = b.point(lambda i: min(255, int(i * 1.25)))
504
+ img = Image.merge('RGB', (r, g, b))
505
+ img = ImageEnhance.Color(img).enhance(1.5)
506
+ self.current_image = img
507
+ self.display_image(img)
508
+
509
+ def neon_lights(self):
510
+ if not self.check_image(): return
511
+ self.save_to_history()
512
+ img = self.current_image
513
+ img = ImageEnhance.Color(img).enhance(2.0)
514
+ img = ImageEnhance.Contrast(img).enhance(1.3)
515
+ img = ImageEnhance.Brightness(img).enhance(1.2)
516
+ self.current_image = img
517
+ self.display_image(img)
518
+
519
+ # === PRO PRESETS ===
520
+ def instagram_pro(self):
521
+ if not self.check_image(): return
522
+ self.save_to_history()
523
+ img = self.current_image
524
+ img = ImageEnhance.Contrast(img).enhance(1.15)
525
+ img = ImageEnhance.Color(img).enhance(1.2)
526
+ img = ImageEnhance.Sharpness(img).enhance(1.1)
527
+ self.current_image = img
528
+ self.display_image(img)
529
+
530
+ def gallery_print(self):
531
+ if not self.check_image(): return
532
+ self.save_to_history()
533
+ img = self.current_image
534
+ img = ImageEnhance.Contrast(img).enhance(1.1)
535
+ img = ImageEnhance.Sharpness(img).enhance(1.3)
536
+ img = ImageEnhance.Color(img).enhance(1.05)
537
+ self.current_image = img
538
+ self.display_image(img)
539
+
540
+ def professional(self):
541
+ self.apply_effect(lambda img: ImageEnhance.Contrast(img).enhance(1.1))
542
+
543
+ def artistic_bold(self):
544
+ if not self.check_image(): return
545
+ self.save_to_history()
546
+ img = self.current_image
547
+ img = ImageEnhance.Color(img).enhance(1.6)
548
+ img = ImageEnhance.Contrast(img).enhance(1.4)
549
+ self.current_image = img
550
+ self.display_image(img)
551
+
552
+ def candlelight_sketch(self):
553
+ """Pencil sketch with warm candlelight - mimics the style Dave showed"""
554
+ if not self.check_image(): return
555
+ self.save_to_history()
556
+
557
+ img = self.current_image
558
+
559
+ # Step 1: Reduce color saturation (pencil sketch base)
560
+ img = ImageEnhance.Color(img).enhance(0.3)
561
+
562
+ # Step 2: Add warm candlelight glow (orange/golden tones)
563
+ r, g, b = img.split()
564
+ # Boost reds/oranges (candlelight)
565
+ r = r.point(lambda i: min(255, int(i * 1.3)))
566
+ g = g.point(lambda i: min(255, int(i * 1.15)))
567
+ b = b.point(lambda i: int(i * 0.75)) # Reduce blue for warmth
568
+ img = Image.merge('RGB', (r, g, b))
569
+
570
+ # Step 3: High contrast (dramatic lighting)
571
+ img = ImageEnhance.Contrast(img).enhance(1.8)
572
+
573
+ # Step 4: Darken overall (dark shadows)
574
+ img = ImageEnhance.Brightness(img).enhance(0.7)
575
+
576
+ # Step 5: Add slight blur to mimic soft pencil texture
577
+ img = img.filter(ImageFilter.GaussianBlur(radius=0.5))
578
+
579
+ # Step 6: Edge enhance for sketch lines
580
+ img = img.filter(ImageFilter.EDGE_ENHANCE)
581
+
582
+ self.current_image = img
583
+ self.display_image(img)
584
+
585
+ # === ACTIONS ===
586
+ def undo_last(self):
587
+ if len(self.history) > 1:
588
+ self.history.pop()
589
+ self.current_image = self.history[-1].copy()
590
+ self.display_image(self.current_image)
591
+ else:
592
+ messagebox.showinfo("Undo", "No more undo history!")
593
+
594
+ def reset_image(self):
595
+ if self.original_image:
596
+ self.current_image = self.original_image.copy()
597
+ self.history = [self.current_image.copy()]
598
+ self.display_image(self.current_image)
599
+
600
+ def save_image(self):
601
+ if not self.check_image(): return
602
+ file_path = filedialog.asksaveasfilename(
603
+ defaultextension=".png",
604
+ filetypes=[("PNG", "*.png"), ("JPEG", "*.jpg"), ("WebP", "*.webp")]
605
+ )
606
+ if file_path:
607
+ try:
608
+ self.current_image.save(file_path, quality=95)
609
+ messagebox.showinfo("Saved!", f"Saved to:\n{file_path}")
610
+ except Exception as e:
611
+ messagebox.showerror("Error", f"Save failed: {e}")
612
+
613
+ def batch_save(self):
614
+ if not self.check_image(): return
615
+ folder = filedialog.askdirectory(title="Select Output Folder")
616
+ if folder:
617
+ try:
618
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
619
+ filename = f"dr_magic_{timestamp}.png"
620
+ path = os.path.join(folder, filename)
621
+ self.current_image.save(path, quality=95)
622
+ messagebox.showinfo("Batch Saved!", f"Saved to:\n{path}")
623
+ except Exception as e:
624
+ messagebox.showerror("Error", str(e))
625
+
626
+ # === IMAGE TOOLS ===
627
+ def expand_canvas(self):
628
+ if not self.check_image(): return
629
+ self.save_to_history()
630
+
631
+ # Ask for expansion amount
632
+ expansion = 100 # pixels on each side
633
+
634
+ img = self.current_image
635
+ width, height = img.size
636
+ new_width = width + (expansion * 2)
637
+ new_height = height + (expansion * 2)
638
+
639
+ # Create new canvas
640
+ expanded = Image.new('RGB', (new_width, new_height), (20, 20, 20))
641
+
642
+ # Paste original in center
643
+ expanded.paste(img, (expansion, expansion))
644
+
645
+ self.current_image = expanded
646
+ self.display_image(self.current_image)
647
+ messagebox.showinfo("Expanded!", f"Canvas expanded by {expansion}px on each side")
648
+
649
+ def upscale_image(self):
650
+ if not self.check_image(): return
651
+ self.save_to_history()
652
+
653
+ img = self.current_image
654
+ new_size = (img.width * 2, img.height * 2)
655
+
656
+ # Use LANCZOS for quality upscaling
657
+ upscaled = img.resize(new_size, Image.Resampling.LANCZOS)
658
+
659
+ self.current_image = upscaled
660
+ self.display_image(self.current_image)
661
+ messagebox.showinfo("Upscaled!", f"Image doubled to {new_size[0]}x{new_size[1]}")
662
+
663
+ def style_transfer(self):
664
+ if not self.check_image(): return
665
+
666
+ style_path = filedialog.askopenfilename(
667
+ title="Select Style Reference Image",
668
+ filetypes=[("Images", "*.png *.jpg *.jpeg"), ("All", "*.*")]
669
+ )
670
+
671
+ if not style_path:
672
+ return
673
+
674
+ self.save_to_history()
675
+
676
+ try:
677
+ style_img = Image.open(style_path).convert('RGB')
678
+
679
+ # Simple style transfer using color statistics
680
+ content = self.current_image
681
+
682
+ # Get color statistics from style image
683
+ style_array = np.array(style_img)
684
+ content_array = np.array(content)
685
+
686
+ # Match mean and std of each channel
687
+ for i in range(3): # RGB channels
688
+ content_mean = content_array[:,:,i].mean()
689
+ content_std = content_array[:,:,i].std()
690
+ style_mean = style_array[:,:,i].mean()
691
+ style_std = style_array[:,:,i].std()
692
+
693
+ # Transfer statistics
694
+ content_array[:,:,i] = (content_array[:,:,i] - content_mean) / (content_std + 1e-5)
695
+ content_array[:,:,i] = content_array[:,:,i] * style_std + style_mean
696
+
697
+ # Clip to valid range
698
+ content_array = np.clip(content_array, 0, 255).astype(np.uint8)
699
+
700
+ self.current_image = Image.fromarray(content_array)
701
+ self.display_image(self.current_image)
702
+ messagebox.showinfo("Style Applied!", "Style transfer complete!")
703
+
704
+ except Exception as e:
705
+ messagebox.showerror("Error", f"Style transfer failed: {e}")
706
+
707
+ def rotate_90(self):
708
+ if not self.check_image(): return
709
+ self.save_to_history()
710
+ self.current_image = self.current_image.rotate(-90, expand=True)
711
+ self.display_image(self.current_image)
712
+
713
+ def flip_horizontal(self):
714
+ if not self.check_image(): return
715
+ self.save_to_history()
716
+ self.current_image = self.current_image.transpose(Image.FLIP_LEFT_RIGHT)
717
+ self.display_image(self.current_image)
718
+
719
+ def flip_vertical(self):
720
+ if not self.check_image(): return
721
+ self.save_to_history()
722
+ self.current_image = self.current_image.transpose(Image.FLIP_TOP_BOTTOM)
723
+ self.display_image(self.current_image)
724
+
725
+ if __name__ == "__main__":
726
+ root = tk.Tk()
727
+ app = DrImageMagicFull(root)
728
+ root.mainloop()
gradio_app.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ DR-Image-Magic Gradio Interface
4
+ A web UI for the Artistic Photo Transform project on Hugging Face Spaces
5
+ """
6
+
7
+ import gradio as gr
8
+ import subprocess
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ # Get the project root directory
14
+ PROJECT_ROOT = Path(__file__).parent
15
+
16
+ def run_command(cmd, shell=True):
17
+ """Execute a command and return output"""
18
+ try:
19
+ result = subprocess.run(
20
+ cmd,
21
+ shell=shell,
22
+ capture_output=True,
23
+ text=True,
24
+ cwd=PROJECT_ROOT,
25
+ timeout=300
26
+ )
27
+ return result.stdout + result.stderr
28
+ except subprocess.TimeoutExpired:
29
+ return "⚠️ Command timed out (5 minutes)"
30
+ except Exception as e:
31
+ return f"❌ Error: {str(e)}"
32
+
33
+ def install_dependencies():
34
+ """Install pnpm dependencies"""
35
+ return run_command("pnpm install")
36
+
37
+ def start_dev_server():
38
+ """Start development server"""
39
+ return run_command("pnpm run dev", shell=True)
40
+
41
+ def type_check():
42
+ """Run TypeScript type checking"""
43
+ return run_command("pnpm run check")
44
+
45
+ def format_code():
46
+ """Format code with prettier"""
47
+ return run_command("pnpm run format")
48
+
49
+ def run_tests():
50
+ """Run test suite"""
51
+ return run_command("pnpm test")
52
+
53
+ def push_db_schema():
54
+ """Push database schema"""
55
+ return run_command("pnpm run db:push")
56
+
57
+ def build_production():
58
+ """Build for production"""
59
+ return run_command("pnpm run build")
60
+
61
+ def get_project_info():
62
+ """Get project information"""
63
+ info = """
64
+ # 🎨 Artistic Photo Transform - DR-Image-Magic
65
+
66
+ ## Project Overview
67
+ A full-stack application that uses AI to transform photos into artistic variations.
68
+
69
+ ## Tech Stack
70
+ - **Frontend**: React 19 + TypeScript + Tailwind CSS
71
+ - **Backend**: Express + tRPC + Node.js
72
+ - **Database**: MySQL + Drizzle ORM
73
+ - **Storage**: AWS S3
74
+ - **AI**: Image generation with custom prompts
75
+ - **UI Components**: Radix UI + shadcn/ui
76
+ - **Testing**: Vitest
77
+
78
+ ## Project Structure
79
+ ```
80
+ ├── client/ # React frontend application
81
+ ├── server/ # Express backend server
82
+ ├── shared/ # Shared types and utilities
83
+ ├── drizzle/ # Database migrations
84
+ └── patches/ # Patch files for dependencies
85
+ ```
86
+
87
+ ## Key Features
88
+ ✅ Photo upload with drag-and-drop
89
+ ✅ AI-powered image transformations (3 variations)
90
+ ✅ S3 storage for images
91
+ ✅ User authentication & history
92
+ ✅ Transformation gallery view
93
+ ✅ Image download functionality
94
+ ✅ Real-time status updates
95
+ ✅ Responsive design
96
+
97
+ ## Scripts
98
+ - `pnpm run dev` - Start development server
99
+ - `pnpm run build` - Build for production
100
+ - `pnpm start` - Run production server
101
+ - `pnpm test` - Run tests
102
+ - `pnpm run format` - Format code
103
+ - `pnpm run check` - Type checking
104
+ - `pnpm run db:push` - Push database schema
105
+ """
106
+ return info
107
+
108
+ def get_setup_guide():
109
+ """Get setup instructions"""
110
+ guide = """
111
+ # Setup Guide
112
+
113
+ ## Prerequisites
114
+ - Node.js 18+
115
+ - pnpm package manager
116
+ - MySQL database
117
+ - AWS S3 account
118
+
119
+ ## Installation Steps
120
+
121
+ ### 1. Install Dependencies
122
+ Click the button below to install all required packages:
123
+ (Note: This requires pnpm to be installed)
124
+
125
+ ### 2. Environment Variables
126
+ Create a `.env` file with:
127
+ ```
128
+ DATABASE_URL=mysql://user:password@localhost:3306/dr_image_magic
129
+ AWS_ACCESS_KEY_ID=your_key
130
+ AWS_SECRET_ACCESS_KEY=your_secret
131
+ AWS_S3_BUCKET=your_bucket
132
+ OPENAI_API_KEY=your_openai_key
133
+ ```
134
+
135
+ ### 3. Database Setup
136
+ Run migrations to set up your database:
137
+ ```bash
138
+ pnpm run db:push
139
+ ```
140
+
141
+ ### 4. Development
142
+ Start the development server:
143
+ ```bash
144
+ pnpm run dev
145
+ ```
146
+
147
+ The app will be available at `http://localhost:5173`
148
+
149
+ ### 5. Production Deployment
150
+ Build the app:
151
+ ```bash
152
+ pnpm run build
153
+ ```
154
+
155
+ Start the production server:
156
+ ```bash
157
+ pnpm start
158
+ ```
159
+ """
160
+ return guide
161
+
162
+ # Create the Gradio interface
163
+ with gr.Blocks(title="DR-Image-Magic") as demo:
164
+ gr.Markdown("""
165
+ # 🎨 DR-Image-Magic Dashboard
166
+ ## Artistic Photo Transform Project Manager
167
+ """)
168
+
169
+ with gr.Tabs():
170
+ # Tab 1: Project Info
171
+ with gr.Tab("📋 Project Info"):
172
+ project_info = gr.Markdown(get_project_info())
173
+
174
+ # Tab 2: Setup
175
+ with gr.Tab("⚙️ Setup"):
176
+ gr.Markdown(get_setup_guide())
177
+ with gr.Row():
178
+ install_btn = gr.Button("📥 Install Dependencies", size="lg", variant="primary")
179
+ db_btn = gr.Button("🗄️ Push Database Schema", size="lg")
180
+
181
+ install_output = gr.Textbox(
182
+ label="Installation Output",
183
+ lines=10,
184
+ interactive=False,
185
+ placeholder="Click 'Install Dependencies' to start..."
186
+ )
187
+ db_output = gr.Textbox(
188
+ label="Database Push Output",
189
+ lines=10,
190
+ interactive=False,
191
+ placeholder="Click 'Push Database Schema' to start..."
192
+ )
193
+
194
+ install_btn.click(install_dependencies, outputs=install_output)
195
+ db_btn.click(push_db_schema, outputs=db_output)
196
+
197
+ # Tab 3: Development
198
+ with gr.Tab("🚀 Development"):
199
+ gr.Markdown("""
200
+ ## Development Tools
201
+
202
+ Use these tools during development:
203
+ - **Type Check**: Verify TypeScript types
204
+ - **Format Code**: Auto-format with Prettier
205
+ - **Run Tests**: Execute test suite
206
+ """)
207
+
208
+ with gr.Row():
209
+ type_check_btn = gr.Button("✓ Type Check", size="lg", variant="secondary")
210
+ format_btn = gr.Button("📝 Format Code", size="lg", variant="secondary")
211
+ test_btn = gr.Button("🧪 Run Tests", size="lg", variant="secondary")
212
+
213
+ with gr.Row():
214
+ type_check_output = gr.Textbox(
215
+ label="Type Check Results",
216
+ lines=8,
217
+ interactive=False
218
+ )
219
+ format_output = gr.Textbox(
220
+ label="Format Results",
221
+ lines=8,
222
+ interactive=False
223
+ )
224
+ test_output = gr.Textbox(
225
+ label="Test Results",
226
+ lines=8,
227
+ interactive=False
228
+ )
229
+
230
+ type_check_btn.click(type_check, outputs=type_check_output)
231
+ format_btn.click(format_code, outputs=format_output)
232
+ test_btn.click(run_tests, outputs=test_output)
233
+
234
+ # Tab 4: Production
235
+ with gr.Tab("📦 Production"):
236
+ gr.Markdown("""
237
+ ## Production Deployment
238
+
239
+ Build your application for production:
240
+ 1. Click "Build for Production" to optimize the code
241
+ 2. Once complete, run `pnpm start` to launch the production server
242
+ 3. Your app will be available at the server URL
243
+ """)
244
+
245
+ build_btn = gr.Button("🏗️ Build for Production", size="lg", variant="stop")
246
+ build_output = gr.Textbox(
247
+ label="Build Output",
248
+ lines=15,
249
+ interactive=False,
250
+ placeholder="Click 'Build for Production' to start..."
251
+ )
252
+
253
+ build_btn.click(build_production, outputs=build_output)
254
+
255
+ gr.Markdown("""
256
+ ---
257
+ ## 💡 Tips
258
+ - This interface provides a web UI for managing the DR-Image-Magic project
259
+ - Most operations require pnpm and Node.js to be installed
260
+ - For full development, clone the repository locally
261
+ - Check the GitHub repo: [DR-Studios/DR-Image-Magic](https://github.com/DR-Studios/DR-Image-Magic)
262
+ """)
263
+
264
+ if __name__ == "__main__":
265
+ demo.launch(share=False, theme=gr.themes.Soft(primary_hue="orange"))
package.json ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "artistic-photo-transform",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "scripts": {
7
+ "dev": "NODE_ENV=development tsx watch server/_core/index.ts",
8
+ "build": "vite build && esbuild server/_core/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist",
9
+ "start": "NODE_ENV=production node dist/index.js",
10
+ "check": "tsc --noEmit",
11
+ "format": "prettier --write .",
12
+ "test": "vitest run",
13
+ "db:push": "drizzle-kit generate && drizzle-kit migrate"
14
+ },
15
+ "dependencies": {
16
+ "@aws-sdk/client-s3": "^3.693.0",
17
+ "@aws-sdk/s3-request-presigner": "^3.693.0",
18
+ "@hookform/resolvers": "^5.2.2",
19
+ "@radix-ui/react-accordion": "^1.2.12",
20
+ "@radix-ui/react-alert-dialog": "^1.1.15",
21
+ "@radix-ui/react-aspect-ratio": "^1.1.7",
22
+ "@radix-ui/react-avatar": "^1.1.10",
23
+ "@radix-ui/react-checkbox": "^1.3.3",
24
+ "@radix-ui/react-collapsible": "^1.1.12",
25
+ "@radix-ui/react-context-menu": "^2.2.16",
26
+ "@radix-ui/react-dialog": "^1.1.15",
27
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
28
+ "@radix-ui/react-hover-card": "^1.1.15",
29
+ "@radix-ui/react-label": "^2.1.7",
30
+ "@radix-ui/react-menubar": "^1.1.16",
31
+ "@radix-ui/react-navigation-menu": "^1.2.14",
32
+ "@radix-ui/react-popover": "^1.1.15",
33
+ "@radix-ui/react-progress": "^1.1.7",
34
+ "@radix-ui/react-radio-group": "^1.3.8",
35
+ "@radix-ui/react-scroll-area": "^1.2.10",
36
+ "@radix-ui/react-select": "^2.2.6",
37
+ "@radix-ui/react-separator": "^1.1.7",
38
+ "@radix-ui/react-slider": "^1.3.6",
39
+ "@radix-ui/react-slot": "^1.2.3",
40
+ "@radix-ui/react-switch": "^1.2.6",
41
+ "@radix-ui/react-tabs": "^1.1.13",
42
+ "@radix-ui/react-toggle": "^1.1.10",
43
+ "@radix-ui/react-toggle-group": "^1.1.11",
44
+ "@radix-ui/react-tooltip": "^1.2.8",
45
+ "@tanstack/react-query": "^5.90.2",
46
+ "@trpc/client": "^11.6.0",
47
+ "@trpc/react-query": "^11.6.0",
48
+ "@trpc/server": "^11.6.0",
49
+ "axios": "^1.12.0",
50
+ "class-variance-authority": "^0.7.1",
51
+ "clsx": "^2.1.1",
52
+ "cmdk": "^1.1.1",
53
+ "cookie": "^1.0.2",
54
+ "date-fns": "^4.1.0",
55
+ "dotenv": "^17.2.2",
56
+ "drizzle-orm": "^0.44.5",
57
+ "embla-carousel-react": "^8.6.0",
58
+ "express": "^4.21.2",
59
+ "framer-motion": "^12.23.22",
60
+ "input-otp": "^1.4.2",
61
+ "jose": "6.1.0",
62
+ "lucide-react": "^0.453.0",
63
+ "mysql2": "^3.15.0",
64
+ "nanoid": "^5.1.5",
65
+ "next-themes": "^0.4.6",
66
+ "react": "^19.2.1",
67
+ "react-day-picker": "^9.11.1",
68
+ "react-dom": "^19.2.1",
69
+ "react-hook-form": "^7.64.0",
70
+ "react-resizable-panels": "^3.0.6",
71
+ "recharts": "^2.15.2",
72
+ "sonner": "^2.0.7",
73
+ "streamdown": "^1.4.0",
74
+ "superjson": "^1.13.3",
75
+ "tailwind-merge": "^3.3.1",
76
+ "tailwindcss-animate": "^1.0.7",
77
+ "vaul": "^1.1.2",
78
+ "wouter": "^3.3.5",
79
+ "zod": "^4.1.12"
80
+ },
81
+ "devDependencies": {
82
+ "@builder.io/vite-plugin-jsx-loc": "^0.1.1",
83
+ "@tailwindcss/typography": "^0.5.15",
84
+ "@tailwindcss/vite": "^4.1.3",
85
+ "@types/express": "4.17.21",
86
+ "@types/google.maps": "^3.58.1",
87
+ "@types/node": "^24.7.0",
88
+ "@types/react": "^19.2.1",
89
+ "@types/react-dom": "^19.2.1",
90
+ "@vitejs/plugin-react": "^5.0.4",
91
+ "add": "^2.0.6",
92
+ "autoprefixer": "^10.4.20",
93
+ "drizzle-kit": "^0.31.4",
94
+ "esbuild": "^0.25.0",
95
+ "pnpm": "^10.15.1",
96
+ "postcss": "^8.4.47",
97
+ "prettier": "^3.6.2",
98
+ "tailwindcss": "^4.1.14",
99
+ "tsx": "^4.19.1",
100
+ "tw-animate-css": "^1.4.0",
101
+ "typescript": "5.9.3",
102
+ "vite": "^7.1.7",
103
+ "vite-plugin-manus-runtime": "^0.0.57",
104
+ "vitest": "^2.1.4"
105
+ },
106
+ "packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af",
107
+ "pnpm": {
108
+ "patchedDependencies": {
109
+ "wouter@3.7.1": "patches/wouter@3.7.1.patch"
110
+ },
111
+ "overrides": {
112
+ "tailwindcss>nanoid": "3.3.7"
113
+ }
114
+ }
115
+ }
simple_image_magic_gui.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ DR-Image-Magic - Simple GUI Version
4
+ Quick image transformations without database setup
5
+ """
6
+
7
+ import tkinter as tk
8
+ from tkinter import filedialog, messagebox, ttk
9
+ from PIL import Image, ImageTk, ImageEnhance, ImageFilter
10
+ import os
11
+ from pathlib import Path
12
+
13
+ class ImageMagicGUI:
14
+ def __init__(self, root):
15
+ self.root = root
16
+ self.root.title("DR-Image-Magic - Simple GUI")
17
+ self.root.geometry("1000x850") # Bigger window
18
+ self.root.configure(bg='#1a1a2e')
19
+
20
+ self.current_image = None
21
+ self.original_image = None
22
+ self.image_path = None
23
+
24
+ self.setup_ui()
25
+
26
+ def setup_ui(self):
27
+ # Title
28
+ title = tk.Label(
29
+ self.root,
30
+ text="🎨 DR-Image-Magic",
31
+ font=("Arial", 24, "bold"),
32
+ bg='#1a1a2e',
33
+ fg='#ff6b35'
34
+ )
35
+ title.pack(pady=20)
36
+
37
+ # Upload Button
38
+ upload_btn = tk.Button(
39
+ self.root,
40
+ text="📁 Upload Image",
41
+ command=self.upload_image,
42
+ font=("Arial", 14),
43
+ bg='#ff6b35',
44
+ fg='white',
45
+ padx=20,
46
+ pady=10,
47
+ cursor='hand2'
48
+ )
49
+ upload_btn.pack(pady=10)
50
+
51
+ # Image Preview
52
+ self.image_label = tk.Label(
53
+ self.root,
54
+ bg='#16213e',
55
+ text="No image loaded",
56
+ fg='#aaa',
57
+ font=("Arial", 12)
58
+ )
59
+ self.image_label.pack(pady=20, padx=20, fill=tk.BOTH, expand=True)
60
+
61
+ # Effects Frame
62
+ effects_frame = tk.Frame(self.root, bg='#1a1a2e')
63
+ effects_frame.pack(pady=10)
64
+
65
+ tk.Label(
66
+ effects_frame,
67
+ text="🎨 Effects:",
68
+ font=("Arial", 14, "bold"),
69
+ bg='#1a1a2e',
70
+ fg='#ff6b35'
71
+ ).grid(row=0, column=0, columnspan=4, pady=10)
72
+
73
+ # Effect Buttons
74
+ effects = [
75
+ ("✨ Enhance", self.enhance_image),
76
+ ("🌈 Vibrant", self.vibrant_image),
77
+ ("🌙 Dark & Moody", self.dark_moody),
78
+ ("☀️ Bright & Warm", self.bright_warm),
79
+ ("🎭 High Contrast", self.high_contrast),
80
+ ("🌊 Cool Tones", self.cool_tones),
81
+ ("🔥 Warm Tones", self.warm_tones),
82
+ ("📸 Vintage", self.vintage_effect),
83
+ ("💎 Sharpen", self.sharpen_image),
84
+ ("🌫️ Blur", self.blur_image),
85
+ ("🎨 Abstract", self.abstract_effect),
86
+ ("⚫ Grayscale", self.grayscale_image),
87
+ ]
88
+
89
+ row = 1
90
+ col = 0
91
+ for text, command in effects:
92
+ btn = tk.Button(
93
+ effects_frame,
94
+ text=text,
95
+ command=command,
96
+ font=("Arial", 10),
97
+ bg='#0f3460',
98
+ fg='white',
99
+ padx=10,
100
+ pady=5,
101
+ cursor='hand2'
102
+ )
103
+ btn.grid(row=row, column=col, padx=5, pady=5, sticky='ew')
104
+ col += 1
105
+ if col > 3:
106
+ col = 0
107
+ row += 1
108
+
109
+ # Action Buttons
110
+ action_frame = tk.Frame(self.root, bg='#1a1a2e')
111
+ action_frame.pack(pady=20)
112
+
113
+ reset_btn = tk.Button(
114
+ action_frame,
115
+ text="🔄 Reset",
116
+ command=self.reset_image,
117
+ font=("Arial", 12),
118
+ bg='#e94560',
119
+ fg='white',
120
+ padx=15,
121
+ pady=8
122
+ )
123
+ reset_btn.pack(side=tk.LEFT, padx=10)
124
+
125
+ save_btn = tk.Button(
126
+ action_frame,
127
+ text="💾 Save As...",
128
+ command=self.save_image,
129
+ font=("Arial", 12),
130
+ bg='#16a34a',
131
+ fg='white',
132
+ padx=15,
133
+ pady=8
134
+ )
135
+ save_btn.pack(side=tk.LEFT, padx=10)
136
+
137
+ def upload_image(self):
138
+ file_path = filedialog.askopenfilename(
139
+ title="Select an Image",
140
+ filetypes=[
141
+ ("Image Files", "*.png *.jpg *.jpeg *.webp *.bmp"),
142
+ ("All Files", "*.*")
143
+ ]
144
+ )
145
+
146
+ if file_path:
147
+ try:
148
+ self.image_path = file_path
149
+ self.original_image = Image.open(file_path)
150
+ self.current_image = self.original_image.copy()
151
+ self.display_image(self.current_image)
152
+ except Exception as e:
153
+ messagebox.showerror("Error", f"Failed to load image: {e}")
154
+
155
+ def display_image(self, image):
156
+ # Resize for display (max 500x300 to save space for buttons)
157
+ display_img = image.copy()
158
+ display_img.thumbnail((500, 300), Image.Resampling.LANCZOS)
159
+
160
+ photo = ImageTk.PhotoImage(display_img)
161
+ self.image_label.configure(image=photo, text="")
162
+ self.image_label.image = photo
163
+
164
+ def check_image_loaded(self):
165
+ if self.current_image is None:
166
+ messagebox.showwarning("No Image", "Please upload an image first!")
167
+ return False
168
+ return True
169
+
170
+ # Effect Methods
171
+ def enhance_image(self):
172
+ if not self.check_image_loaded(): return
173
+ enhancer = ImageEnhance.Contrast(self.current_image)
174
+ self.current_image = enhancer.enhance(1.3)
175
+ enhancer = ImageEnhance.Sharpness(self.current_image)
176
+ self.current_image = enhancer.enhance(1.2)
177
+ self.display_image(self.current_image)
178
+
179
+ def vibrant_image(self):
180
+ if not self.check_image_loaded(): return
181
+ enhancer = ImageEnhance.Color(self.current_image)
182
+ self.current_image = enhancer.enhance(1.5)
183
+ self.display_image(self.current_image)
184
+
185
+ def dark_moody(self):
186
+ if not self.check_image_loaded(): return
187
+ enhancer = ImageEnhance.Brightness(self.current_image)
188
+ self.current_image = enhancer.enhance(0.7)
189
+ enhancer = ImageEnhance.Contrast(self.current_image)
190
+ self.current_image = enhancer.enhance(1.4)
191
+ self.display_image(self.current_image)
192
+
193
+ def bright_warm(self):
194
+ if not self.check_image_loaded(): return
195
+ enhancer = ImageEnhance.Brightness(self.current_image)
196
+ self.current_image = enhancer.enhance(1.2)
197
+ enhancer = ImageEnhance.Color(self.current_image)
198
+ self.current_image = enhancer.enhance(1.3)
199
+ self.display_image(self.current_image)
200
+
201
+ def high_contrast(self):
202
+ if not self.check_image_loaded(): return
203
+ enhancer = ImageEnhance.Contrast(self.current_image)
204
+ self.current_image = enhancer.enhance(2.0)
205
+ self.display_image(self.current_image)
206
+
207
+ def cool_tones(self):
208
+ if not self.check_image_loaded(): return
209
+ # Convert to RGB if needed
210
+ if self.current_image.mode != 'RGB':
211
+ self.current_image = self.current_image.convert('RGB')
212
+ # Apply blue tint
213
+ r, g, b = self.current_image.split()
214
+ r = r.point(lambda i: i * 0.9)
215
+ g = g.point(lambda i: i * 0.95)
216
+ b = b.point(lambda i: i * 1.1)
217
+ self.current_image = Image.merge('RGB', (r, g, b))
218
+ self.display_image(self.current_image)
219
+
220
+ def warm_tones(self):
221
+ if not self.check_image_loaded(): return
222
+ # Convert to RGB if needed
223
+ if self.current_image.mode != 'RGB':
224
+ self.current_image = self.current_image.convert('RGB')
225
+ # Apply orange tint
226
+ r, g, b = self.current_image.split()
227
+ r = r.point(lambda i: min(255, i * 1.1))
228
+ g = g.point(lambda i: i * 1.05)
229
+ b = b.point(lambda i: i * 0.9)
230
+ self.current_image = Image.merge('RGB', (r, g, b))
231
+ self.display_image(self.current_image)
232
+
233
+ def vintage_effect(self):
234
+ if not self.check_image_loaded(): return
235
+ # Sepia effect
236
+ self.current_image = self.current_image.convert('L')
237
+ self.current_image = self.current_image.convert('RGB')
238
+ r, g, b = self.current_image.split()
239
+ r = r.point(lambda i: min(255, i * 1.2))
240
+ g = g.point(lambda i: i * 1.0)
241
+ b = b.point(lambda i: i * 0.8)
242
+ self.current_image = Image.merge('RGB', (r, g, b))
243
+ enhancer = ImageEnhance.Contrast(self.current_image)
244
+ self.current_image = enhancer.enhance(0.9)
245
+ self.display_image(self.current_image)
246
+
247
+ def sharpen_image(self):
248
+ if not self.check_image_loaded(): return
249
+ self.current_image = self.current_image.filter(ImageFilter.SHARPEN)
250
+ self.display_image(self.current_image)
251
+
252
+ def blur_image(self):
253
+ if not self.check_image_loaded(): return
254
+ self.current_image = self.current_image.filter(ImageFilter.GaussianBlur(radius=2))
255
+ self.display_image(self.current_image)
256
+
257
+ def abstract_effect(self):
258
+ if not self.check_image_loaded(): return
259
+ self.current_image = self.current_image.filter(ImageFilter.EDGE_ENHANCE_MORE)
260
+ enhancer = ImageEnhance.Color(self.current_image)
261
+ self.current_image = enhancer.enhance(1.8)
262
+ self.display_image(self.current_image)
263
+
264
+ def grayscale_image(self):
265
+ if not self.check_image_loaded(): return
266
+ self.current_image = self.current_image.convert('L').convert('RGB')
267
+ self.display_image(self.current_image)
268
+
269
+ def reset_image(self):
270
+ if not self.check_image_loaded(): return
271
+ self.current_image = self.original_image.copy()
272
+ self.display_image(self.current_image)
273
+
274
+ def save_image(self):
275
+ if not self.check_image_loaded(): return
276
+
277
+ file_path = filedialog.asksaveasfilename(
278
+ defaultextension=".png",
279
+ filetypes=[
280
+ ("PNG", "*.png"),
281
+ ("JPEG", "*.jpg"),
282
+ ("WebP", "*.webp"),
283
+ ("All Files", "*.*")
284
+ ]
285
+ )
286
+
287
+ if file_path:
288
+ try:
289
+ self.current_image.save(file_path)
290
+ messagebox.showinfo("Success", f"Image saved to:\n{file_path}")
291
+ except Exception as e:
292
+ messagebox.showerror("Error", f"Failed to save image: {e}")
293
+
294
+ if __name__ == "__main__":
295
+ root = tk.Tk()
296
+ app = ImageMagicGUI(root)
297
+ root.mainloop()
todo.md ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Artistic Photo Transform - Project TODO
2
+
3
+ ## Core Features
4
+
5
+ - [x] Database schema for images and transformations
6
+ - [x] Photo upload API with file validation (max 16MB, jpg/png/webp)
7
+ - [x] Drag-and-drop upload interface
8
+ - [x] AI-powered image transformation endpoint (3 variations)
9
+ - [x] S3 file storage integration for original and transformed images
10
+ - [x] Transformation history database queries
11
+ - [x] Custom text prompt support for guided transformations
12
+ - [x] Gallery view with original + 3 transformed variations
13
+ - [x] Image download functionality
14
+ - [x] Loading states with progress indication (5-20 seconds)
15
+ - [x] Error handling with retry option
16
+ - [x] User authentication and history tracking
17
+
18
+ ## UI/Design
19
+
20
+ - [x] Implement chiaroscuro aesthetic (deep black background, golden light)
21
+ - [x] Bold uppercase sans-serif typography with gradient
22
+ - [x] Atmospheric elements (light rays, lens flares)
23
+ - [x] Responsive layout for desktop and mobile
24
+ - [x] Loading skeleton states
25
+ - [x] Empty state messaging
26
+
27
+ ## Testing & Deployment
28
+
29
+ - [x] Unit tests for transformation logic
30
+ - [x] Integration tests for file upload
31
+ - [x] All tests passing (7/7)
32
+ - [ ] Generate 3 example transformations
33
+ - [ ] Final checkpoint and deployment
34
+
35
+ ## Completed Features
36
+
37
+ - Backend image transformation service with AI integration
38
+ - Database schema with images and transformations tables
39
+ - Upload handler with file validation and S3 storage
40
+ - Transformation queue for async processing
41
+ - Frontend Transform page with drag-and-drop upload
42
+ - Status polling for real-time transformation updates
43
+ - Gallery display with download functionality
44
+ - Comprehensive test suite
vite.config.ts ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { jsxLocPlugin } from "@builder.io/vite-plugin-jsx-loc";
2
+ import tailwindcss from "@tailwindcss/vite";
3
+ import react from "@vitejs/plugin-react";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { defineConfig, type Plugin, type ViteDevServer } from "vite";
7
+ import { vitePluginManusRuntime } from "vite-plugin-manus-runtime";
8
+
9
+ // =============================================================================
10
+ // Manus Debug Collector - Vite Plugin
11
+ // Writes browser logs directly to files, trimmed when exceeding size limit
12
+ // =============================================================================
13
+
14
+ const PROJECT_ROOT = import.meta.dirname;
15
+ const LOG_DIR = path.join(PROJECT_ROOT, ".manus-logs");
16
+ const MAX_LOG_SIZE_BYTES = 1 * 1024 * 1024; // 1MB per log file
17
+ const TRIM_TARGET_BYTES = Math.floor(MAX_LOG_SIZE_BYTES * 0.6); // Trim to 60% to avoid constant re-trimming
18
+
19
+ type LogSource = "browserConsole" | "networkRequests" | "sessionReplay";
20
+
21
+ function ensureLogDir() {
22
+ if (!fs.existsSync(LOG_DIR)) {
23
+ fs.mkdirSync(LOG_DIR, { recursive: true });
24
+ }
25
+ }
26
+
27
+ function trimLogFile(logPath: string, maxSize: number) {
28
+ try {
29
+ if (!fs.existsSync(logPath) || fs.statSync(logPath).size <= maxSize) {
30
+ return;
31
+ }
32
+
33
+ const lines = fs.readFileSync(logPath, "utf-8").split("\n");
34
+ const keptLines: string[] = [];
35
+ let keptBytes = 0;
36
+
37
+ // Keep newest lines (from end) that fit within 60% of maxSize
38
+ const targetSize = TRIM_TARGET_BYTES;
39
+ for (let i = lines.length - 1; i >= 0; i--) {
40
+ const lineBytes = Buffer.byteLength(`${lines[i]}\n`, "utf-8");
41
+ if (keptBytes + lineBytes > targetSize) break;
42
+ keptLines.unshift(lines[i]);
43
+ keptBytes += lineBytes;
44
+ }
45
+
46
+ fs.writeFileSync(logPath, keptLines.join("\n"), "utf-8");
47
+ } catch {
48
+ /* ignore trim errors */
49
+ }
50
+ }
51
+
52
+ function writeToLogFile(source: LogSource, entries: unknown[]) {
53
+ if (entries.length === 0) return;
54
+
55
+ ensureLogDir();
56
+ const logPath = path.join(LOG_DIR, `${source}.log`);
57
+
58
+ // Format entries with timestamps
59
+ const lines = entries.map(entry => {
60
+ const ts = new Date().toISOString();
61
+ return `[${ts}] ${JSON.stringify(entry)}`;
62
+ });
63
+
64
+ // Append to log file
65
+ fs.appendFileSync(logPath, `${lines.join("\n")}\n`, "utf-8");
66
+
67
+ // Trim if exceeds max size
68
+ trimLogFile(logPath, MAX_LOG_SIZE_BYTES);
69
+ }
70
+
71
+ /**
72
+ * Vite plugin to collect browser debug logs
73
+ * - POST /__manus__/logs: Browser sends logs, written directly to files
74
+ * - Files: browserConsole.log, networkRequests.log, sessionReplay.log
75
+ * - Auto-trimmed when exceeding 1MB (keeps newest entries)
76
+ */
77
+ function vitePluginManusDebugCollector(): Plugin {
78
+ return {
79
+ name: "manus-debug-collector",
80
+
81
+ transformIndexHtml(html) {
82
+ if (process.env.NODE_ENV === "production") {
83
+ return html;
84
+ }
85
+ return {
86
+ html,
87
+ tags: [
88
+ {
89
+ tag: "script",
90
+ attrs: {
91
+ src: "/__manus__/debug-collector.js",
92
+ defer: true,
93
+ },
94
+ injectTo: "head",
95
+ },
96
+ ],
97
+ };
98
+ },
99
+
100
+ configureServer(server: ViteDevServer) {
101
+ // POST /__manus__/logs: Browser sends logs (written directly to files)
102
+ server.middlewares.use("/__manus__/logs", (req, res, next) => {
103
+ if (req.method !== "POST") {
104
+ return next();
105
+ }
106
+
107
+ const handlePayload = (payload: any) => {
108
+ // Write logs directly to files
109
+ if (payload.consoleLogs?.length > 0) {
110
+ writeToLogFile("browserConsole", payload.consoleLogs);
111
+ }
112
+ if (payload.networkRequests?.length > 0) {
113
+ writeToLogFile("networkRequests", payload.networkRequests);
114
+ }
115
+ if (payload.sessionEvents?.length > 0) {
116
+ writeToLogFile("sessionReplay", payload.sessionEvents);
117
+ }
118
+
119
+ res.writeHead(200, { "Content-Type": "application/json" });
120
+ res.end(JSON.stringify({ success: true }));
121
+ };
122
+
123
+ const reqBody = (req as { body?: unknown }).body;
124
+ if (reqBody && typeof reqBody === "object") {
125
+ try {
126
+ handlePayload(reqBody);
127
+ } catch (e) {
128
+ res.writeHead(400, { "Content-Type": "application/json" });
129
+ res.end(JSON.stringify({ success: false, error: String(e) }));
130
+ }
131
+ return;
132
+ }
133
+
134
+ let body = "";
135
+ req.on("data", chunk => {
136
+ body += chunk.toString();
137
+ });
138
+
139
+ req.on("end", () => {
140
+ try {
141
+ const payload = JSON.parse(body);
142
+ handlePayload(payload);
143
+ } catch (e) {
144
+ res.writeHead(400, { "Content-Type": "application/json" });
145
+ res.end(JSON.stringify({ success: false, error: String(e) }));
146
+ }
147
+ });
148
+ });
149
+ },
150
+ };
151
+ }
152
+
153
+ const plugins = [
154
+ react(),
155
+ tailwindcss(),
156
+ jsxLocPlugin(),
157
+ vitePluginManusRuntime(),
158
+ vitePluginManusDebugCollector(),
159
+ ];
160
+
161
+ export default defineConfig({
162
+ plugins,
163
+ resolve: {
164
+ alias: {
165
+ "@": path.resolve(import.meta.dirname, "client", "src"),
166
+ "@shared": path.resolve(import.meta.dirname, "shared"),
167
+ "@assets": path.resolve(import.meta.dirname, "attached_assets"),
168
+ },
169
+ },
170
+ envDir: path.resolve(import.meta.dirname),
171
+ root: path.resolve(import.meta.dirname, "client"),
172
+ publicDir: path.resolve(import.meta.dirname, "client", "public"),
173
+ build: {
174
+ outDir: path.resolve(import.meta.dirname, "dist/public"),
175
+ emptyOutDir: true,
176
+ },
177
+ server: {
178
+ host: true,
179
+ allowedHosts: [
180
+ ".manuspre.computer",
181
+ ".manus.computer",
182
+ ".manus-asia.computer",
183
+ ".manuscomputer.ai",
184
+ ".manusvm.computer",
185
+ "localhost",
186
+ "127.0.0.1",
187
+ ],
188
+ fs: {
189
+ strict: true,
190
+ deny: ["**/.*"],
191
+ },
192
+ },
193
+ });