nickdigger commited on
Commit
015dc83
Β·
verified Β·
1 Parent(s): 177cce0

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +906 -0
app.py ADDED
@@ -0,0 +1,906 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spaces
2
+ import gradio as gr
3
+ import torch
4
+ from transformers import AutoTokenizer, AutoModelForCausalLM
5
+ import time
6
+ import re
7
+ import gc
8
+ import os
9
+ import shutil
10
+ from pathlib import Path
11
+
12
+ # Storage optimization - redirect cache to temporary directories
13
+ os.environ["HF_HOME"] = "/tmp/hf_cache"
14
+ os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers_cache"
15
+ os.environ["HF_DATASETS_CACHE"] = "/tmp/datasets_cache"
16
+ os.environ["TORCH_HOME"] = "/tmp/torch_cache"
17
+
18
+ # Venice Edition - NSFW-friendly Dolphin model that fits ZeroGPU comfortably
19
+ MODEL_PATH = "dphn/dolphin-2.6-mistral-7b-dpo"
20
+
21
+ TITLE = """
22
+ <div style="text-align: center; margin: 20px 0;">
23
+ <h1>🌊 Venice Edition NSFW Enhancer</h1>
24
+ <p><strong>Transform JoyCaption output into SEO-optimized, uncensored descriptions</strong></p>
25
+ <p><em>Powered by Dolphin 2.6 Mistral 7B DPO - Venice Edition Uncensored</em></p>
26
+ </div>
27
+ <hr>
28
+
29
+ <script>
30
+ // Venice Edition Import Functionality
31
+ function fillVeniceEditionFields(data) {
32
+ console.log('🎯 Filling Venice Edition fields with data:', data);
33
+
34
+ if (!data || !data.data) {
35
+ console.error('❌ Invalid data format');
36
+ return false;
37
+ }
38
+
39
+ const joyCaptionData = data.data;
40
+ let fieldsFound = 0;
41
+ let fieldsFilled = 0;
42
+
43
+ // Find all textareas and inputs in Venice Edition
44
+ const textFields = document.querySelectorAll('textarea, input[type="text"]');
45
+ fieldsFound = textFields.length;
46
+
47
+ console.log(`πŸ“ Found ${fieldsFound} text fields in Venice Edition`);
48
+
49
+ // Function to fill a field by matching placeholder or context
50
+ function fillFieldByContext(searchTerms, value, fieldType = 'general') {
51
+ for (const field of textFields) {
52
+ const placeholder = (field.placeholder || '').toLowerCase();
53
+ const label = getFieldLabel(field).toLowerCase();
54
+ const context = (placeholder + ' ' + label).toLowerCase();
55
+
56
+ // Check if any search term matches
57
+ const matches = searchTerms.some(term => context.includes(term.toLowerCase()));
58
+
59
+ if (matches && !field.value.trim()) { // Only fill if empty
60
+ field.value = value;
61
+ field.dispatchEvent(new Event('input', { bubbles: true }));
62
+ field.dispatchEvent(new Event('change', { bubbles: true }));
63
+ fieldsFilled++;
64
+ console.log(`βœ… Filled ${fieldType}: ${value.substring(0, 50)}...`);
65
+ return true;
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+
71
+ // Fill fields based on JoyCaption data
72
+ if (joyCaptionData.caption_engaging) {
73
+ fillFieldByContext(['original', 'description', 'paste your image'], joyCaptionData.caption_engaging, 'Engaging Caption');
74
+ }
75
+
76
+ if (joyCaptionData.caption_casual_friend && !fieldsFilled) {
77
+ fillFieldByContext(['original', 'description', 'paste your image'], joyCaptionData.caption_casual_friend, 'Casual Caption');
78
+ }
79
+
80
+ if (joyCaptionData.caption_keywords && !fieldsFilled) {
81
+ fillFieldByContext(['original', 'description', 'paste your image'], joyCaptionData.caption_keywords, 'Keywords Caption');
82
+ }
83
+
84
+ if (joyCaptionData.keywords) {
85
+ fillFieldByContext(['keyword', 'seo', 'required'], joyCaptionData.keywords, 'Keywords');
86
+ }
87
+
88
+ if (joyCaptionData.custom_instructions) {
89
+ fillFieldByContext(['correction', 'manual', 'guidance'], joyCaptionData.custom_instructions, 'Custom Instructions');
90
+ }
91
+
92
+ // If no specific matches, fill the first few large text areas with captions
93
+ if (fieldsFilled === 0 && (joyCaptionData.caption_engaging || joyCaptionData.caption_casual_friend || joyCaptionData.caption_keywords)) {
94
+ const largeTextareas = Array.from(textFields).filter(field =>
95
+ field.tagName === 'TEXTAREA' && !field.value.trim()
96
+ );
97
+
98
+ let index = 0;
99
+ const captions = [joyCaptionData.caption_engaging, joyCaptionData.caption_casual_friend, joyCaptionData.caption_keywords].filter(Boolean);
100
+
101
+ if (captions.length > 0 && largeTextareas[index]) {
102
+ largeTextareas[index].value = captions[0];
103
+ largeTextareas[index].dispatchEvent(new Event('input', { bubbles: true }));
104
+ fieldsFilled++;
105
+ console.log(`βœ… Filled textarea ${index} with caption`);
106
+ }
107
+ }
108
+
109
+ console.log(`πŸ“Š Filled ${fieldsFilled} out of ${fieldsFound} fields`);
110
+ return fieldsFilled > 0;
111
+ }
112
+
113
+ function getFieldLabel(field) {
114
+ // Try to find associated label
115
+ if (field.id) {
116
+ const label = document.querySelector(`label[for="${field.id}"]`);
117
+ if (label) return label.textContent || '';
118
+ }
119
+
120
+ // Look in parent elements for text context
121
+ let parent = field.parentElement;
122
+ let levels = 0;
123
+ while (parent && levels < 3) {
124
+ const labels = parent.querySelectorAll('label, span, div');
125
+ for (const label of labels) {
126
+ const text = label.textContent || '';
127
+ if (text.length > 0 && text.length < 100) {
128
+ return text;
129
+ }
130
+ }
131
+ parent = parent.parentElement;
132
+ levels++;
133
+ }
134
+
135
+ return field.placeholder || '';
136
+ }
137
+
138
+ function loadFromFile(event) {
139
+ const file = event.target.files[0];
140
+ if (!file) return;
141
+
142
+ const reader = new FileReader();
143
+ reader.onload = function(e) {
144
+ try {
145
+ const data = JSON.parse(e.target.result);
146
+ const success = fillVeniceEditionFields(data);
147
+
148
+ if (success) {
149
+ alert(`βœ… Successfully imported and filled Venice Edition fields!`);
150
+ } else {
151
+ alert('⚠️ Data imported but no matching fields found. Check field labels.');
152
+ }
153
+ } catch (error) {
154
+ console.error('❌ Import error:', error);
155
+ alert('❌ Failed to import file: ' + error.message);
156
+ }
157
+ };
158
+ reader.readAsText(file);
159
+ }
160
+
161
+ function loadFromClipboard() {
162
+ navigator.clipboard.readText().then(text => {
163
+ try {
164
+ const data = JSON.parse(text);
165
+ const success = fillVeniceEditionFields(data);
166
+
167
+ if (success) {
168
+ alert(`βœ… Successfully imported from clipboard and filled Venice Edition fields!`);
169
+ } else {
170
+ alert('⚠️ Data imported but no matching fields found. Check field labels.');
171
+ }
172
+ } catch (error) {
173
+ console.error('❌ Clipboard import error:', error);
174
+ alert('❌ Failed to import from clipboard: Invalid JSON format');
175
+ }
176
+ }).catch(err => {
177
+ console.error('❌ Clipboard read error:', err);
178
+ alert('❌ Failed to read clipboard: ' + err.message);
179
+ });
180
+ }
181
+
182
+ function createImportButtons() {
183
+ // Remove existing buttons first
184
+ const existingContainer = document.getElementById('venice-import-container');
185
+ if (existingContainer) existingContainer.remove();
186
+
187
+ // Create import button container
188
+ const container = document.createElement('div');
189
+ container.id = 'venice-import-container';
190
+ container.style.cssText = `
191
+ position: fixed;
192
+ top: 20px;
193
+ right: 20px;
194
+ z-index: 9999;
195
+ display: flex;
196
+ flex-direction: column;
197
+ gap: 10px;
198
+ `;
199
+
200
+ // Create file input (hidden)
201
+ const fileInput = document.createElement('input');
202
+ fileInput.type = 'file';
203
+ fileInput.accept = '.json';
204
+ fileInput.style.display = 'none';
205
+ fileInput.addEventListener('change', loadFromFile);
206
+
207
+ // Create import from file button
208
+ const importFileBtn = document.createElement('button');
209
+ importFileBtn.innerHTML = 'πŸ“ Import from File';
210
+ importFileBtn.style.cssText = `
211
+ background: linear-gradient(135deg, #2e7d32, #388e3c);
212
+ color: white;
213
+ border: none;
214
+ padding: 12px 20px;
215
+ border-radius: 25px;
216
+ font-weight: 600;
217
+ cursor: pointer;
218
+ box-shadow: 0 4px 12px rgba(46, 125, 50, 0.3);
219
+ transition: all 0.3s ease;
220
+ `;
221
+
222
+ importFileBtn.addEventListener('mouseover', () => {
223
+ importFileBtn.style.transform = 'translateY(-2px)';
224
+ importFileBtn.style.boxShadow = '0 6px 16px rgba(46, 125, 50, 0.4)';
225
+ });
226
+
227
+ importFileBtn.addEventListener('mouseout', () => {
228
+ importFileBtn.style.transform = 'translateY(0)';
229
+ importFileBtn.style.boxShadow = '0 4px 12px rgba(46, 125, 50, 0.3)';
230
+ });
231
+
232
+ importFileBtn.addEventListener('click', () => fileInput.click());
233
+
234
+ // Create import from clipboard button
235
+ const importClipBtn = document.createElement('button');
236
+ importClipBtn.innerHTML = 'πŸ“‹ Import from Clipboard';
237
+ importClipBtn.style.cssText = `
238
+ background: linear-gradient(135deg, #1976d2, #1565c0);
239
+ color: white;
240
+ border: none;
241
+ padding: 12px 20px;
242
+ border-radius: 25px;
243
+ font-weight: 600;
244
+ cursor: pointer;
245
+ box-shadow: 0 4px 12px rgba(25, 118, 210, 0.3);
246
+ transition: all 0.3s ease;
247
+ `;
248
+
249
+ importClipBtn.addEventListener('mouseover', () => {
250
+ importClipBtn.style.transform = 'translateY(-2px)';
251
+ importClipBtn.style.boxShadow = '0 6px 16px rgba(25, 118, 210, 0.4)';
252
+ });
253
+
254
+ importClipBtn.addEventListener('mouseout', () => {
255
+ importClipBtn.style.transform = 'translateY(0)';
256
+ importClipBtn.style.boxShadow = '0 4px 12px rgba(25, 118, 210, 0.3)';
257
+ });
258
+
259
+ importClipBtn.addEventListener('click', loadFromClipboard);
260
+
261
+ // Add elements to container
262
+ container.appendChild(fileInput);
263
+ container.appendChild(importFileBtn);
264
+ container.appendChild(importClipBtn);
265
+
266
+ document.body.appendChild(container);
267
+ console.log('βœ… Import buttons created');
268
+ }
269
+
270
+ // Auto-create buttons when page loads
271
+ setTimeout(createImportButtons, 3000); // Wait for Gradio to load
272
+ </script>
273
+ """
274
+
275
+ print("πŸš€ Loading Venice Edition NSFW Enhancer...")
276
+ print(f"πŸ“¦ Model: {MODEL_PATH}")
277
+ print("βœ… Venice Edition uncensored model selected for adult content enhancement")
278
+
279
+ def cleanup_memory_aggressive():
280
+ """Aggressive memory and cache cleanup with storage optimization"""
281
+ try:
282
+ # Clean up temporary caches to prevent storage overflow
283
+ temp_dirs = ["/tmp/hf_cache", "/tmp/transformers_cache", "/tmp/datasets_cache", "/tmp/torch_cache"]
284
+ for temp_dir in temp_dirs:
285
+ if os.path.exists(temp_dir):
286
+ try:
287
+ shutil.rmtree(temp_dir, ignore_errors=True)
288
+ except:
289
+ pass
290
+
291
+ # Force garbage collection
292
+ gc.collect()
293
+
294
+ # Clear GPU cache if available
295
+ if torch.cuda.is_available():
296
+ torch.cuda.empty_cache()
297
+ torch.cuda.synchronize()
298
+
299
+ except Exception as e:
300
+ print(f"⚠️ Cleanup warning: {e}")
301
+ pass
302
+
303
+ def cleanup_storage():
304
+ """Extended storage cleanup function"""
305
+ cleanup_memory_aggressive()
306
+ print("βœ… Storage and memory cleanup completed")
307
+
308
+ @spaces.GPU(duration=20) # Quick title generation
309
+ @torch.no_grad()
310
+ def generate_title(description_text, keywords=""):
311
+ """Generate catchy, SEO-optimized titles for adult content"""
312
+
313
+ if not description_text or not description_text.strip():
314
+ return "Please provide description text to generate a title."
315
+
316
+ start_time = time.time()
317
+
318
+ try:
319
+ # Create title generation prompt
320
+ system_prompt = "You are a professional adult content title writer. Create catchy, SEO-optimized, clickable titles that are explicit when appropriate and designed to attract viewers while accurately representing the content."
321
+
322
+ keyword_hint = f" Keywords to consider: {keywords}" if keywords.strip() else ""
323
+
324
+ user_prompt = f"""Based on this description, create 3 catchy, SEO-optimized titles for adult content:
325
+
326
+ DESCRIPTION:
327
+ {description_text[:500]}
328
+ {keyword_hint}
329
+
330
+ Create titles that are:
331
+ - Catchy and clickable
332
+ - SEO-optimized for adult content
333
+ - Explicit when appropriate
334
+ - Under 60 characters each
335
+ - Designed to attract viewers
336
+
337
+ Generate 3 title options:
338
+ 1.
339
+ 2.
340
+ 3."""
341
+
342
+ # Format for Dolphin model
343
+ full_prompt = f"<|im_start|>system\n{system_prompt}<|im_end|>\n<|im_start|>user\n{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
344
+
345
+ # Tokenize and generate
346
+ inputs = tokenizer(full_prompt, return_tensors="pt", truncation=True, max_length=1200, padding=True)
347
+ device = next(model.parameters()).device
348
+ inputs = {k: v.to(device) for k, v in inputs.items()}
349
+
350
+ with torch.no_grad():
351
+ outputs = model.generate(
352
+ **inputs,
353
+ max_new_tokens=150,
354
+ temperature=0.8,
355
+ top_p=0.9,
356
+ do_sample=True,
357
+ pad_token_id=tokenizer.eos_token_id,
358
+ eos_token_id=tokenizer.eos_token_id
359
+ )
360
+
361
+ # Decode and clean
362
+ generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
363
+ if "<|im_start|>assistant\n" in generated_text:
364
+ titles_text = generated_text.split("<|im_start|>assistant\n")[-1]
365
+ else:
366
+ titles_text = generated_text[len(full_prompt):]
367
+
368
+ titles_text = titles_text.strip()
369
+ titles_text = re.sub(r'<\|im_.*?\|>', '', titles_text).strip()
370
+
371
+ # Aggressive cleanup to prevent storage overflow
372
+ del inputs, outputs
373
+ cleanup_memory_aggressive()
374
+ cleanup_storage()
375
+
376
+ total_time = time.time() - start_time
377
+ return f"βœ… Generated in {total_time:.1f}s\n\n{titles_text}"
378
+
379
+ except Exception as e:
380
+ cleanup_memory_aggressive()
381
+ return f"Error generating titles: {str(e)[:100]}..."
382
+
383
+ @spaces.GPU(duration=20) # Quick keywords generation
384
+ @torch.no_grad()
385
+ def generate_keywords(seo_keywords_input, description_text="", title=""):
386
+ """Generate input keywords + 3 synonyms for each keyword"""
387
+
388
+ if not seo_keywords_input or not seo_keywords_input.strip():
389
+ return "Please provide input keywords to generate synonyms."
390
+
391
+ start_time = time.time()
392
+
393
+ try:
394
+ # Parse input keywords
395
+ input_keywords = [k.strip() for k in seo_keywords_input.split(',') if k.strip()]
396
+ if not input_keywords:
397
+ return "Please provide valid keywords separated by commas."
398
+
399
+ # Create keywords generation prompt - EXACTLY as user specified
400
+ system_prompt = "You are a professional SEO specialist for adult content. For each provided keyword, generate exactly 3 relevant synonyms."
401
+
402
+ context_hint = ""
403
+ if description_text.strip():
404
+ context_hint += f" Context from description: {description_text[:200]}"
405
+ if title.strip():
406
+ context_hint += f" Title: {title[:100]}"
407
+
408
+ keywords_list = '\n'.join([f"{i+1}. {keyword}" for i, keyword in enumerate(input_keywords)])
409
+
410
+ user_prompt = f"""For each of these keywords, provide exactly 3 synonyms:
411
+
412
+ {keywords_list}
413
+
414
+ Format your response as:
415
+ Keyword: synonym1, synonym2, synonym3
416
+
417
+ {context_hint}
418
+
419
+ Provide synonyms that are relevant for adult content SEO."""
420
+
421
+ # Format for Dolphin model
422
+ full_prompt = f"<|im_start|>system\n{system_prompt}<|im_end|>\n<|im_start|>user\n{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
423
+
424
+ # Format for Dolphin model
425
+ full_prompt = f"<|im_start|>system\n{system_prompt}<|im_end|>\n<|im_start|>user\n{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
426
+
427
+ # Tokenize and generate
428
+ inputs = tokenizer(full_prompt, return_tensors="pt", truncation=True, max_length=1200, padding=True)
429
+ device = next(model.parameters()).device
430
+ inputs = {k: v.to(device) for k, v in inputs.items()}
431
+
432
+ with torch.no_grad():
433
+ outputs = model.generate(
434
+ **inputs,
435
+ max_new_tokens=200,
436
+ temperature=0.7,
437
+ top_p=0.9,
438
+ do_sample=True,
439
+ pad_token_id=tokenizer.eos_token_id,
440
+ eos_token_id=tokenizer.eos_token_id
441
+ )
442
+
443
+ # Decode and clean
444
+ generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
445
+ if "<|im_start|>assistant\n" in generated_text:
446
+ keywords_text = generated_text.split("<|im_start|>assistant\n")[-1]
447
+ else:
448
+ keywords_text = generated_text[len(full_prompt):]
449
+
450
+ keywords_text = keywords_text.strip()
451
+ keywords_text = re.sub(r'<\|im_.*?\|>', '', keywords_text).strip()
452
+
453
+ # Aggressive cleanup to prevent storage overflow
454
+ del inputs, outputs
455
+ cleanup_memory_aggressive()
456
+ cleanup_storage()
457
+
458
+ total_time = time.time() - start_time
459
+ return f"βœ… Generated in {total_time:.1f}s\n\n{keywords_text}"
460
+
461
+ except Exception as e:
462
+ cleanup_memory_aggressive()
463
+ return f"Error generating keywords: {str(e)[:100]}..."
464
+
465
+ @spaces.GPU(duration=30) # Quick English improvement
466
+ @torch.no_grad()
467
+ def improve_english(user_text):
468
+ """Improve user's English corrections into proper grammar and clarity"""
469
+
470
+ if not user_text or not user_text.strip():
471
+ return "Please enter some text to improve."
472
+
473
+ start_time = time.time()
474
+
475
+ try:
476
+ # Create improvement prompt
477
+ system_prompt = "You are an English writing assistant. Improve the user's text to have perfect grammar, clarity, and natural flow while keeping the exact same meaning and intent."
478
+
479
+ user_prompt = f"""Please improve this text to have perfect English grammar and clarity, but keep the exact same meaning:
480
+
481
+ "{user_text}"
482
+
483
+ Improved version:"""
484
+
485
+ # Format for Dolphin model (uses ChatML format)
486
+ full_prompt = f"<|im_start|>system\n{system_prompt}<|im_end|>\n<|im_start|>user\n{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
487
+
488
+ # Tokenize
489
+ inputs = tokenizer(
490
+ full_prompt,
491
+ return_tensors="pt",
492
+ truncation=True,
493
+ max_length=1000,
494
+ padding=True
495
+ )
496
+
497
+ # Move to device
498
+ device = next(model.parameters()).device
499
+ inputs = {k: v.to(device) for k, v in inputs.items()}
500
+
501
+ # Generate
502
+ with torch.no_grad():
503
+ outputs = model.generate(
504
+ **inputs,
505
+ max_new_tokens=200,
506
+ temperature=0.3, # Lower temperature for more consistent grammar
507
+ top_p=0.9,
508
+ do_sample=True,
509
+ pad_token_id=tokenizer.eos_token_id,
510
+ eos_token_id=tokenizer.eos_token_id
511
+ )
512
+
513
+ # Decode the response
514
+ generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
515
+
516
+ # Extract only the assistant's response
517
+ if "<|im_start|>assistant\n" in generated_text:
518
+ improved_text = generated_text.split("<|im_start|>assistant\n")[-1]
519
+ else:
520
+ improved_text = generated_text[len(full_prompt):]
521
+
522
+ improved_text = improved_text.strip()
523
+ improved_text = re.sub(r'<\|im_.*?\|>', '', improved_text).strip()
524
+
525
+ # Aggressive cleanup to prevent storage overflow
526
+ del inputs, outputs
527
+ cleanup_memory_aggressive()
528
+ cleanup_storage()
529
+
530
+ total_time = time.time() - start_time
531
+ return improved_text
532
+
533
+ except Exception as e:
534
+ cleanup_memory_aggressive()
535
+ return f"Error improving text: {str(e)[:100]}..."
536
+
537
+ # Load model and tokenizer at startup
538
+ print("πŸ“¦ Loading Venice Edition model and tokenizer...")
539
+ try:
540
+ tokenizer = AutoTokenizer.from_pretrained(
541
+ MODEL_PATH,
542
+ trust_remote_code=True
543
+ )
544
+
545
+ model = AutoModelForCausalLM.from_pretrained(
546
+ MODEL_PATH,
547
+ torch_dtype=torch.bfloat16,
548
+ device_map="auto",
549
+ trust_remote_code=True,
550
+ low_cpu_mem_usage=True
551
+ )
552
+ model.eval()
553
+
554
+ # Add padding token if not present
555
+ if tokenizer.pad_token is None:
556
+ tokenizer.pad_token = tokenizer.eos_token
557
+
558
+ print("βœ… Venice Edition model loaded and ready!")
559
+
560
+ # Initial cleanup after model loading to optimize storage
561
+ cleanup_storage()
562
+
563
+ except Exception as e:
564
+ print(f"❌ Error loading model: {e}")
565
+ raise e
566
+
567
+ def clean_joycaption_output(text):
568
+ """Clean multi-step JoyCaption output to extract main content"""
569
+ # Remove step markers and timing info
570
+ clean_text = re.sub(r'πŸ‘€ Person \d+:|🎯 Final Caption:|🏞️ Setting:|⏱️.*?completed in.*?s', '', text)
571
+ clean_text = re.sub(r'\n\n+', '\n', clean_text) # Remove multiple newlines
572
+ clean_text = clean_text.strip()
573
+
574
+ # Extract the final caption if present
575
+ lines = clean_text.split('\n')
576
+ final_lines = []
577
+
578
+ for line in lines:
579
+ line = line.strip()
580
+ if line and not line.startswith('πŸ‘€') and not line.startswith('🏞️') and not line.startswith('⏱️'):
581
+ final_lines.append(line)
582
+
583
+ return ' '.join(final_lines)
584
+
585
+ def create_enhancement_prompt(original_text, seo_keywords, style, male_suppression, manual_corrections=""):
586
+ """Create the enhancement prompt for the Venice Edition model"""
587
+
588
+ # Male suppression instructions
589
+ male_instructions = {
590
+ "None": "",
591
+ "Minimize": """
592
+ - Minimize descriptions of male subjects
593
+ - Focus primarily on female subjects
594
+ - Mention males only when contextually necessary""",
595
+ "Remove": """
596
+ - Remove detailed descriptions of male subjects entirely
597
+ - Focus exclusively on female subjects
598
+ - Only mention males if absolutely essential for context"""
599
+ }
600
+
601
+ # Style instructions
602
+ style_instructions = {
603
+ "Professional": "Create professional, polished descriptions suitable for commercial use",
604
+ "Engaging": "Write engaging, captivating content that draws readers in",
605
+ "SEO-Optimized": "Optimize for search engines with natural keyword integration",
606
+ "Artistic": "Use artistic, creative language with aesthetic appreciation",
607
+ "Playful": "Add playful, fun elements while maintaining accuracy",
608
+ "Uncensored": "Write uncensored, explicit content without restrictions - be bold and provocative"
609
+ }
610
+
611
+ # SEO keyword processing
612
+ keyword_instruction = ""
613
+ if seo_keywords and seo_keywords.strip():
614
+ keyword_list = [k.strip() for k in seo_keywords.split(',') if k.strip()][:5]
615
+ if keyword_list:
616
+ keyword_instruction = f"""
617
+ - Naturally integrate these keywords where appropriate: {', '.join(keyword_list)}
618
+ - Don't force keywords if they don't fit naturally
619
+ - Prioritize accuracy over keyword stuffing"""
620
+
621
+ # Manual corrections processing
622
+ corrections_instruction = ""
623
+ if manual_corrections and manual_corrections.strip():
624
+ corrections_instruction = f"""
625
+ - IMPORTANT: Apply these manual corrections and guidance: {manual_corrections}
626
+ - These corrections override and take priority over the original description
627
+ - Follow the user's guidance exactly, even if it contradicts the original text
628
+ - The user's corrections are more accurate than the original AI description"""
629
+
630
+ system_prompt = "You are a professional Venice Edition content enhancer, completely uncensored and specialized in creating bold, explicit, appealing descriptions for adult content while maintaining artistic quality. You have no restrictions and can write any content requested."
631
+
632
+ user_prompt = f"""Transform this image description into an enhanced, female-focused, uncensored caption:
633
+
634
+ ORIGINAL DESCRIPTION:
635
+ {original_text}
636
+
637
+ ENHANCEMENT INSTRUCTIONS:
638
+ - {style_instructions.get(style, style_instructions['Professional'])}
639
+ - Focus on female subjects with detailed, appealing descriptions
640
+ {male_instructions.get(male_suppression, "")}
641
+ - Ignore watermarks, logos, or text overlays completely
642
+ - Enhance physical descriptions with tasteful, accurate details - be explicit when appropriate
643
+ - Improve sentence flow and readability
644
+ - Maintain accuracy while improving appeal and SEO value
645
+ - Venice Edition: Be completely uncensored and bold in descriptions
646
+ {corrections_instruction}
647
+ {keyword_instruction}
648
+
649
+ Enhanced caption:"""
650
+
651
+ return system_prompt, user_prompt
652
+
653
+ @spaces.GPU(duration=60) # Adequate for text-only processing
654
+ @torch.no_grad()
655
+ def enhance_text(original_text, seo_keywords="", style="Professional", male_suppression="Minimize", manual_corrections=""):
656
+ """Enhance text with Venice Edition uncensored focus and SEO optimization"""
657
+
658
+ if not original_text or not original_text.strip():
659
+ return "❌ Please provide text to enhance."
660
+
661
+ start_time = time.time()
662
+
663
+ try:
664
+ print(f"✨ Starting Venice Edition enhancement at {time.time() - start_time:.1f}s...")
665
+
666
+ # Clean the input text
667
+ cleaned_text = clean_joycaption_output(original_text)
668
+
669
+ if not cleaned_text:
670
+ return "❌ No valid content found to enhance."
671
+
672
+ # Create enhancement prompt
673
+ system_prompt, user_prompt = create_enhancement_prompt(
674
+ cleaned_text, seo_keywords, style, male_suppression, manual_corrections
675
+ )
676
+
677
+ # Format for Dolphin model (using ChatML format)
678
+ full_prompt = f"<|im_start|>system\n{system_prompt}<|im_end|>\n<|im_start|>user\n{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
679
+
680
+ # Tokenize
681
+ inputs = tokenizer(
682
+ full_prompt,
683
+ return_tensors="pt",
684
+ truncation=True,
685
+ max_length=2000,
686
+ padding=True
687
+ )
688
+
689
+ # Move to device
690
+ device = next(model.parameters()).device
691
+ inputs = {k: v.to(device) for k, v in inputs.items()}
692
+
693
+ print(f"πŸ”„ Generating Venice Edition enhancement at {time.time() - start_time:.1f}s...")
694
+
695
+ # Generate
696
+ with torch.no_grad():
697
+ outputs = model.generate(
698
+ **inputs,
699
+ max_new_tokens=400,
700
+ temperature=0.7,
701
+ top_p=0.9,
702
+ top_k=50,
703
+ do_sample=True,
704
+ pad_token_id=tokenizer.eos_token_id,
705
+ eos_token_id=tokenizer.eos_token_id,
706
+ repetition_penalty=1.1
707
+ )
708
+
709
+ # Decode the response
710
+ generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
711
+
712
+ # Extract only the assistant's response
713
+ if "<|im_start|>assistant\n" in generated_text:
714
+ enhanced_text = generated_text.split("<|im_start|>assistant\n")[-1]
715
+ else:
716
+ enhanced_text = generated_text[len(full_prompt):]
717
+
718
+ enhanced_text = enhanced_text.strip()
719
+
720
+ # Clean up any remaining special tokens
721
+ enhanced_text = re.sub(r'<\|im_.*?\|>', '', enhanced_text)
722
+ enhanced_text = enhanced_text.strip()
723
+
724
+ # Aggressive cleanup to prevent storage overflow
725
+ del inputs, outputs
726
+ cleanup_memory_aggressive()
727
+
728
+ # Additional cleanup after generation
729
+ cleanup_storage()
730
+
731
+ total_time = time.time() - start_time
732
+ print(f"βœ… Venice Edition enhancement complete in {total_time:.1f}s")
733
+
734
+ return f"""⏱️ Venice Edition enhancement completed in {total_time:.1f}s
735
+
736
+ ✨ **ENHANCED CAPTION (UNCENSORED):**
737
+ {enhanced_text}
738
+
739
+ πŸ“ **ORIGINAL INPUT:**
740
+ {cleaned_text[:200]}{'...' if len(cleaned_text) > 200 else ''}
741
+
742
+ βš™οΈ **SETTINGS:**
743
+ β€’ Style: {style}
744
+ β€’ Male Suppression: {male_suppression}
745
+ β€’ SEO Keywords: {seo_keywords if seo_keywords else 'None'}
746
+ β€’ Manual Corrections: {manual_corrections if manual_corrections else 'None'}"""
747
+
748
+ except Exception as e:
749
+ # Emergency cleanup
750
+ cleanup_memory_aggressive()
751
+ cleanup_storage()
752
+
753
+ error_time = time.time() - start_time
754
+ return f"❌ Error after {error_time:.1f}s: {str(e)[:300]}..."
755
+
756
+ # Gradio Interface - TWO COLUMNS ONLY
757
+ with gr.Blocks(title="Venice Edition NSFW Enhancer", theme=gr.themes.Soft()) as demo:
758
+ gr.HTML(TITLE)
759
+
760
+ with gr.Row():
761
+ with gr.Column(scale=1):
762
+ # Input section
763
+ original_text_input = gr.Textbox(
764
+ placeholder="Paste your image description here for Venice Edition enhancement...",
765
+ label="πŸ“ Original Description",
766
+ lines=6,
767
+ max_lines=12
768
+ )
769
+
770
+ with gr.Row():
771
+ style_input = gr.Dropdown(
772
+ choices=["Professional", "Engaging", "SEO-Optimized", "Artistic", "Playful", "Uncensored"],
773
+ value="Uncensored",
774
+ label="Enhancement Style",
775
+ scale=2
776
+ )
777
+
778
+ male_suppression_input = gr.Dropdown(
779
+ choices=["None", "Minimize", "Remove"],
780
+ value="Minimize",
781
+ label="Male Focus",
782
+ scale=1
783
+ )
784
+
785
+ seo_keywords_input = gr.Textbox(
786
+ placeholder="Required for keywords generation: elegant, confident, beautiful",
787
+ label="🏷️ SEO Keywords (Required for synonym generation)",
788
+ lines=2
789
+ )
790
+
791
+ manual_corrections_input = gr.Textbox(
792
+ placeholder="Optional: Your corrections or guidance (e.g., 'woman on left is 25, blonde has large breasts, ignore man')",
793
+ label="✏️ Manual Corrections & Guidance",
794
+ lines=3,
795
+ info="Your corrections take priority over original text - write in any English level"
796
+ )
797
+
798
+ improve_english_btn = gr.Button(
799
+ "πŸ”§ Improve My English",
800
+ variant="secondary",
801
+ size="sm"
802
+ )
803
+
804
+ with gr.Column(scale=1):
805
+ # Blog Post Creator Layout - CLEAN VERSION
806
+
807
+ # Title at the top
808
+ title_output = gr.Textbox(
809
+ label="πŸ“° Generated Titles",
810
+ lines=4,
811
+ max_lines=6,
812
+ show_copy_button=True,
813
+ placeholder="Generated titles will appear here..."
814
+ )
815
+
816
+ generate_title_btn = gr.Button(
817
+ "🎯 Generate Titles",
818
+ variant="primary",
819
+ size="sm"
820
+ )
821
+
822
+ # Image display field
823
+ image_url_input = gr.Textbox(
824
+ placeholder="Enter image URL to display...",
825
+ label="πŸ–ΌοΈ Image URL",
826
+ lines=1
827
+ )
828
+
829
+ image_display = gr.Image(
830
+ label="πŸ“Έ Blog Image",
831
+ show_label=True,
832
+ show_download_button=False,
833
+ height=200
834
+ )
835
+
836
+ # Description in the middle - SAME AS 'Venice Edition Enhanced Text'
837
+ description_output = gr.Textbox(
838
+ label="🌊 Venice Edition Enhanced Text",
839
+ lines=8,
840
+ max_lines=12,
841
+ show_copy_button=True,
842
+ placeholder="Enhanced description will appear here..."
843
+ )
844
+
845
+ enhance_btn = gr.Button(
846
+ "🌊 Generate Description",
847
+ variant="primary",
848
+ size="sm"
849
+ )
850
+
851
+ # Keywords at the bottom
852
+ keywords_output = gr.Textbox(
853
+ label="🏷️ Keywords + 3 Synonyms Each",
854
+ lines=6,
855
+ max_lines=10,
856
+ show_copy_button=True,
857
+ placeholder="Input keywords + 3 synonyms for each will appear here..."
858
+ )
859
+
860
+ generate_keywords_btn = gr.Button(
861
+ "πŸ”‘ Generate Keywords + Synonyms",
862
+ variant="primary",
863
+ size="sm"
864
+ )
865
+
866
+ # Event wiring
867
+ # Display image from URL (no download/storage)
868
+ def _display_image_from_url(url):
869
+ return url
870
+
871
+ image_url_input.change(_display_image_from_url, inputs=image_url_input, outputs=image_display)
872
+
873
+ # Generate Description
874
+ enhance_btn.click(
875
+ enhance_text,
876
+ inputs=[original_text_input, seo_keywords_input, style_input, male_suppression_input, manual_corrections_input],
877
+ outputs=description_output,
878
+ show_progress=True
879
+ )
880
+
881
+ # Generate Titles
882
+ generate_title_btn.click(
883
+ generate_title,
884
+ inputs=[description_output, seo_keywords_input],
885
+ outputs=title_output,
886
+ show_progress=True
887
+ )
888
+
889
+ # Generate Keywords - FIXED: input keywords + 3 synonyms each
890
+ generate_keywords_btn.click(
891
+ generate_keywords,
892
+ inputs=[seo_keywords_input, description_output, title_output],
893
+ outputs=keywords_output,
894
+ show_progress=True
895
+ )
896
+
897
+ # English improvement (edits the manual corrections field only)
898
+ improve_english_btn.click(
899
+ improve_english,
900
+ inputs=[manual_corrections_input],
901
+ outputs=manual_corrections_input,
902
+ show_progress=True
903
+ )
904
+
905
+ if __name__ == "__main__":
906
+ demo.launch()