update
This commit is contained in:
parent
ac7303588f
commit
9e0f3b80b2
7 changed files with 213 additions and 2 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
136
app.py
136
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="<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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
|
|
@ -2,3 +2,5 @@ flask
|
|||
google-genai
|
||||
pillow
|
||||
Send2Trash
|
||||
gallery-dl
|
||||
requests
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Reference in a new issue