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.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="<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
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)

View file

@ -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"
]

View file

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

View file

@ -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,
};
}

View file

@ -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();
}
}
});
}
});

View file

@ -172,6 +172,10 @@
<label>Reference Images</label>
</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 class="input-group">