diff --git a/.DS_Store b/.DS_Store index 13272b1..b0f911e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/app.py b/app.py index 6498745..18e0904 100644 --- a/app.py +++ b/app.py @@ -11,6 +11,8 @@ from flask import Flask, render_template, request, jsonify, url_for from google import genai from google.genai import types from PIL import Image, PngImagePlugin +import threading, time, subprocess, re + import logging @@ -1157,10 +1159,144 @@ def refine_prompt(): except Exception as e: return jsonify({'error': str(e)}), 500 +#Tun sever + +@app.route('/download_image', methods=['POST']) +def download_image(): + import requests + from urllib.parse import urlparse + + data = request.get_json() or {} + url = data.get('url') + + if not url: + return jsonify({'error': 'URL is required'}), 400 + + try: + download_url = url + + # Check if it's a URL (http/https) + if url.startswith('http://') or url.startswith('https://'): + # Try to use gallery-dl to extract the image URL + try: + # -g: get URLs, -q: quiet + cmd = ['gallery-dl', '-g', '-q', url] + # Timeout to prevent hanging on slow sites + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + urls = result.stdout.strip().split('\n') + if urls and urls[0] and urls[0].startswith('http'): + download_url = urls[0] + except Exception as e: + print(f"gallery-dl extraction failed (using direct URL): {e}") + # Fallback to using the original URL directly + + # Download logic (for both direct URL and extracted URL) + if download_url.startswith('http://') or download_url.startswith('https://'): + response = requests.get(download_url, timeout=30) + response.raise_for_status() + + content_type = response.headers.get('content-type', '') + ext = '.png' + if 'image/jpeg' in content_type: ext = '.jpg' + elif 'image/webp' in content_type: ext = '.webp' + elif 'image/gif' in content_type: ext = '.gif' + else: + parsed = urlparse(download_url) + ext = os.path.splitext(parsed.path)[1] or '.png' + + filename = f"{uuid.uuid4()}{ext}" + filepath = os.path.join(UPLOADS_DIR, filename) + + with open(filepath, 'wb') as f: + f.write(response.content) + + rel_path = f"uploads/{filename}" + final_url = url_for('static', filename=rel_path) + + return jsonify({'path': final_url, 'local_path': filepath}) + + else: + # Handle local file path + # Remove quotes if present + clean_path = url.strip('"\'') + + if os.path.exists(clean_path): + ext = os.path.splitext(clean_path)[1] or '.png' + filename = f"{uuid.uuid4()}{ext}" + filepath = os.path.join(UPLOADS_DIR, filename) + shutil.copy2(clean_path, filepath) + + rel_path = f"uploads/{filename}" + final_url = url_for('static', filename=rel_path) + return jsonify({'path': final_url, 'local_path': filepath}) + else: + return jsonify({'error': 'File path not found on server'}), 404 + + except Exception as e: + print(f"Error downloading image: {e}") + return jsonify({'error': str(e)}), 500 + +def pinggy_thread(port,pinggy): + + server = { + "Auto": "", + "USA": "us.", + "Europe": "eu.", + "Asia": "ap.", + "South America": "br.", + "Australia": "au." + + } + + sv = server[Sever_Pinggy] + + import socket + while True: + time.sleep(0.5) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.connect_ex(('127.0.0.1', port)) + if result == 0: + break + sock.close() + try: + if pinggy != None: + if ":" in pinggy: + pinggy, ac, ps = pinggy.split(":") + cmd = ["ssh", "-p", "443", f"-R0:localhost:{port}", "-o", "StrictHostKeyChecking=no", "-o", "ServerAliveInterval=30", f"{pinggy}@{sv}pro.pinggy.io", f'\"b:{ac}:{ps}\"'] + else: + cmd = ["ssh", "-p", "443", f"-R0:localhost:{port}", "-o", "StrictHostKeyChecking=no", "-o", "ServerAliveInterval=30", f"{pinggy}@{sv}pro.pinggy.io"] + else: + cmd = ["ssh", "-p", "443", "-L4300:localhost:4300", "-o", "StrictHostKeyChecking=no", "-o", "ServerAliveInterval=30", f"-R0:localhost:{port}", "free.pinggy.io"] + process = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True) + for line in iter(process.stdout.readline, ''): + match = re.search(r'(https?://[^\s]+)', line) + if match: + url = match.group(1) + # Bỏ qua các link dashboard + if "dashboard.pinggy.io" in url: + continue + print(f"\033[92m🔗 Link online để sử dụng:\033[0m {url}") + if pinggy == None: + html="
Link pinggy free hoạt động trong 60phút, khởi động lại hoặc đăng ký tại [dashboard.pinggy.io] để lấy token, nhập custom pinggy trong tệp Domain_sever.txt trên drive theo cú pháp 'pinggy-{token}'
" + display(HTML(html)) + break + except Exception as e: + print(f"❌ Lỗi: {e}") + +def sever_flare(port, pinggy = None): + threading.Thread(target=pinggy_thread, daemon=True, args=(port,pinggy,)).start() + + port_sever = 8888 +Sever_Pinggy = "Auto" + if __name__ == '__main__': # Use ANSI green text so the startup banner stands out in terminals print("\033[32m" + "aPix Image Workspace running at:" + "\033[0m", flush=True) print("\033[32m" + f"http://localhost:{port_sever}" + " " + "\033[0m", flush=True) print("\033[32m" + f"http://127.0.0.1:{port_sever}" + "\033[0m", flush=True) + + # sever_flare(port_sever, "cXPggKvHuW:sdvn:1231") app.run(debug=True, port=port_sever) diff --git a/gallery_favorites.json b/gallery_favorites.json index e707baa..c7e8115 100644 --- a/gallery_favorites.json +++ b/gallery_favorites.json @@ -4,8 +4,10 @@ "gemini-3-pro-image-preview_20251125_42.png", "gemini-3-pro-image-preview_20251125_41.png", "gemini-3-pro-image-preview_20251125_37.png", - "gemini-3-pro-image-preview_20251125_26.png", "gemini-3-pro-image-preview_20251125_24.png", "generated/gemini-3-pro-image-preview_20251124_10.png", - "generated/gemini-3-pro-image-preview_20251124_9.png" + "generated/gemini-3-pro-image-preview_20251124_9.png", + "generated/gemini-3-pro-image-preview_20251125_26.png", + "generated/gemini-3-pro-image-preview_20251129_12.png", + "generated/gemini-3-pro-image-preview_20251220_1.png" ] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5513fc5..5cd0c82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,5 @@ flask google-genai pillow Send2Trash +gallery-dl +requests diff --git a/static/modules/referenceSlots.js b/static/modules/referenceSlots.js index 9e1c7a5..8d18acc 100644 --- a/static/modules/referenceSlots.js +++ b/static/modules/referenceSlots.js @@ -340,11 +340,29 @@ export function createReferenceSlotManager(imageInputGrid, options = {}) { return null; } + async function addReferenceFromUrl(url) { + // Find first empty slot + let emptyIndex = imageSlotState.findIndex(record => !record.data); + + if (emptyIndex === -1) { + if (imageSlotState.length < MAX_IMAGE_SLOTS) { + addImageSlot(); + emptyIndex = imageSlotState.length - 1; + } else { + return false; + } + } + + await handleSlotDropFromHistory(emptyIndex, url); + return true; + } + return { initialize, getReferenceFiles, getReferencePaths, serializeReferenceImages, setReferenceImages, + addReferenceFromUrl, }; } diff --git a/static/script.js b/static/script.js index 2c08899..6cc8494 100644 --- a/static/script.js +++ b/static/script.js @@ -102,6 +102,7 @@ document.addEventListener('DOMContentLoaded', () => { const downloadLink = document.getElementById('download-link'); const galleryGrid = document.getElementById('gallery-grid'); const imageInputGrid = document.getElementById('image-input-grid'); + const referenceUrlInput = document.getElementById('reference-url-input'); const imageDisplayArea = document.querySelector('.image-display-area'); const canvasToolbar = document.querySelector('.canvas-toolbar'); const sidebar = document.querySelector('.sidebar'); @@ -2121,4 +2122,52 @@ document.addEventListener('DOMContentLoaded', () => { }; } + // Reference URL Input Logic + if (referenceUrlInput && typeof slotManager !== 'undefined') { + referenceUrlInput.addEventListener('keydown', async (event) => { + if (event.key === 'Enter') { + event.preventDefault(); + const url = referenceUrlInput.value.trim(); + if (!url) return; + + referenceUrlInput.disabled = true; + const originalPlaceholder = referenceUrlInput.getAttribute('placeholder'); + referenceUrlInput.setAttribute('placeholder', 'Đang tải...'); + + try { + const response = await fetch('/download_image', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url }) + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to download image'); + } + + if (data.path) { + const success = await slotManager.addReferenceFromUrl(data.path); + if (success) { + referenceUrlInput.value = ''; + } else { + alert('Không còn slot trống cho ảnh tham chiếu.'); + } + } else { + throw new Error('No image path returned'); + } + + } catch (error) { + console.error('Download error:', error); + alert(`Lỗi tải ảnh: ${error.message}`); + } finally { + referenceUrlInput.disabled = false; + referenceUrlInput.setAttribute('placeholder', originalPlaceholder); + referenceUrlInput.focus(); + } + } + }); + } + }); diff --git a/templates/index.html b/templates/index.html index c53c863..f35facf 100644 --- a/templates/index.html +++ b/templates/index.html @@ -172,6 +172,10 @@
+
+ +