This commit is contained in:
phamhungd 2025-12-30 16:20:57 +07:00
parent ac7303588f
commit 9e0f3b80b2
7 changed files with 213 additions and 2 deletions

BIN
.DS_Store vendored

Binary file not shown.

136
app.py
View file

@ -11,6 +11,8 @@ from flask import Flask, render_template, request, jsonify, url_for
from google import genai from google import genai
from google.genai import types from google.genai import types
from PIL import Image, PngImagePlugin from PIL import Image, PngImagePlugin
import threading, time, subprocess, re
import logging import logging
@ -1157,10 +1159,144 @@ def refine_prompt():
except Exception as e: except Exception as e:
return jsonify({'error': str(e)}), 500 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="<div><code style='color:yellow'>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}'</code></div>"
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 port_sever = 8888
Sever_Pinggy = "Auto"
if __name__ == '__main__': if __name__ == '__main__':
# Use ANSI green text so the startup banner stands out in terminals # 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" + "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://localhost:{port_sever}" + " " + "\033[0m", flush=True)
print("\033[32m" + f"http://127.0.0.1:{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) app.run(debug=True, port=port_sever)

View file

@ -4,8 +4,10 @@
"gemini-3-pro-image-preview_20251125_42.png", "gemini-3-pro-image-preview_20251125_42.png",
"gemini-3-pro-image-preview_20251125_41.png", "gemini-3-pro-image-preview_20251125_41.png",
"gemini-3-pro-image-preview_20251125_37.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", "gemini-3-pro-image-preview_20251125_24.png",
"generated/gemini-3-pro-image-preview_20251124_10.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"
] ]

View file

@ -2,3 +2,5 @@ flask
google-genai google-genai
pillow pillow
Send2Trash Send2Trash
gallery-dl
requests

View file

@ -340,11 +340,29 @@ export function createReferenceSlotManager(imageInputGrid, options = {}) {
return null; 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 { return {
initialize, initialize,
getReferenceFiles, getReferenceFiles,
getReferencePaths, getReferencePaths,
serializeReferenceImages, serializeReferenceImages,
setReferenceImages, setReferenceImages,
addReferenceFromUrl,
}; };
} }

View file

@ -102,6 +102,7 @@ document.addEventListener('DOMContentLoaded', () => {
const downloadLink = document.getElementById('download-link'); const downloadLink = document.getElementById('download-link');
const galleryGrid = document.getElementById('gallery-grid'); const galleryGrid = document.getElementById('gallery-grid');
const imageInputGrid = document.getElementById('image-input-grid'); const imageInputGrid = document.getElementById('image-input-grid');
const referenceUrlInput = document.getElementById('reference-url-input');
const imageDisplayArea = document.querySelector('.image-display-area'); const imageDisplayArea = document.querySelector('.image-display-area');
const canvasToolbar = document.querySelector('.canvas-toolbar'); const canvasToolbar = document.querySelector('.canvas-toolbar');
const sidebar = document.querySelector('.sidebar'); 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();
}
}
});
}
}); });

View file

@ -172,6 +172,10 @@
<label>Reference Images</label> <label>Reference Images</label>
</div> </div>
<div id="image-input-grid" class="image-input-grid" aria-live="polite"></div> <div id="image-input-grid" class="image-input-grid" aria-live="polite"></div>
<div class="image-url-input-wrapper" style="margin-top: 0.5rem;">
<input type="text" id="reference-url-input" placeholder="Nhập URL hoặc đường dẫn ảnh..."
style="width: 100%; padding: 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: rgba(0,0,0,0.2); color: var(--text-primary); font-size: 0.85rem;">
</div>
</div> </div>
<div class="input-group"> <div class="input-group">