imagine-AI / app.py
Muyumba's picture
app.py
8d6d784 verified
Raw
History Blame Contribute Delete
25.1 kB
# orionai_app.py
import gradio as gr
import numpy as np
import random
import torch
from diffusers import DiffusionPipeline
import traceback
import time
import psycopg2
from psycopg2.extras import RealDictCursor
import hashlib
import secrets
from datetime import datetime, timedelta, timezone
import os
from io import BytesIO
import base64
from PIL import Image
print("๐Ÿš€ Dรฉmarrage de l'application OrionAI...")
# ===================================================================
# CONFIGURATION DATABASE NEON
# ===================================================================
DATABASE_URL = "postgresql://neondb_owner:npg_AtKIqbxoN7s1@ep-rough-fog-adc0kzr6-pooler.c-2.us-east-1.aws.neon.tech/neondb?sslmode=require"
def get_db_connection():
"""Connexion au database PostgreSQL Neon"""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except Exception as e:
print(f"โŒ Errore connessione DB: {e}")
return None
def init_database():
"""Initialise les tables du database"""
conn = get_db_connection()
if not conn:
print("โŒ Impossibile connettersi al database!")
return False
try:
cur = conn.cursor()
# users
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# image_generate
cur.execute("""
CREATE TABLE IF NOT EXISTS image_generate (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
prompt TEXT NOT NULL,
image_base64 TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# user_reset_password
# NOTE: expires_at is NOT NULL but has default = current_timestamp + interval '1 hour'
cur.execute("""
CREATE TABLE IF NOT EXISTS user_reset_password (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token VARCHAR(255),
reset_token VARCHAR(255),
expires_at TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP + INTERVAL '1 hour'),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
used BOOLEAN DEFAULT FALSE
)
""")
conn.commit()
cur.close()
conn.close()
print("โœ… Database inizializzato con successo!")
return True
except Exception as e:
print(f"โŒ Errore inizializzazione DB: {e}")
print(traceback.format_exc())
try:
conn.rollback()
except:
pass
if conn:
conn.close()
return False
# initialize DB at startup
init_database()
# ===================================================================
# UTILITAIRES
# ===================================================================
def hash_password(password: str) -> str:
return hashlib.sha256(password.encode('utf-8')).hexdigest()
def now_utc():
return datetime.now(timezone.utc)
# convert PIL.Image -> base64 (PNG)
def pil_to_base64(img: Image.Image) -> str:
buffer = BytesIO()
img.save(buffer, format="PNG")
b = buffer.getvalue()
return base64.b64encode(b).decode('utf-8') # store without data: prefix
# convert base64 (stored) -> PIL.Image
def base64_to_pil(b64: str) -> Image.Image:
b = base64.b64decode(b64)
return Image.open(BytesIO(b)).convert("RGBA")
# ===================================================================
# AUTHENTICATION / USER FUNCTIONS
# ===================================================================
def register_user(username, email, password):
"""Register new user"""
print(f"๐Ÿ”ต Tentativo registrazione: {username}, {email}")
if not username or not email or not password:
return False, "โŒ Tutti i campi sono obbligatori!"
if len(password) < 6:
return False, "โŒ La password deve essere almeno 6 caratteri!"
conn = get_db_connection()
if not conn:
return False, "โŒ Errore connessione database!"
try:
cur = conn.cursor()
password_hash = hash_password(password)
cur.execute(
"INSERT INTO users (username, email, password_hash) VALUES (%s, %s, %s) RETURNING id",
(username, email, password_hash)
)
user_id = cur.fetchone()[0]
conn.commit()
cur.close()
conn.close()
print(f"โœ… Utente registrato con ID: {user_id}")
return True, f"โœ… Registrazione completata!\n\nUsername: **{username}**\n\nOra puoi accedere."
except psycopg2.IntegrityError as e:
print(f"โŒ IntegrityError: {e}")
try:
conn.rollback()
except:
pass
if conn:
conn.close()
return False, "โŒ Username o email giร  esistenti!"
except Exception as e:
print(f"โŒ Errore registrazione: {e}")
print(traceback.format_exc())
if conn:
conn.close()
return False, f"โŒ Errore: {str(e)}"
def login_user(username, password):
"""Login"""
print(f"๐Ÿ”ต Tentativo login: {username}")
if not username or not password:
return None, "โŒ Inserisci username e password!"
conn = get_db_connection()
if not conn:
return None, "โŒ Errore connessione database!"
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
password_hash = hash_password(password)
cur.execute("SELECT id, username, password_hash FROM users WHERE username = %s", (username,))
user = cur.fetchone()
if not user:
cur.close()
conn.close()
return None, "โŒ Username non esistente!"
if user['password_hash'] == password_hash:
user_dict = {'id': user['id'], 'username': user['username']}
cur.close()
conn.close()
print(f"โœ… Login riuscito per: {username}")
return user_dict, "โœ… Login effettuato!"
else:
cur.close()
conn.close()
return None, "โŒ Password errata!"
except Exception as e:
print(f"โŒ Errore login: {e}")
print(traceback.format_exc())
if conn:
conn.close()
return None, f"โŒ Errore: {str(e)}"
# Password reset: request
def request_password_reset(email, expires_hours=1):
"""Generate a reset token and store with expires_at"""
conn = get_db_connection()
if not conn:
return False, "โŒ Errore connessione database!"
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
cur.execute("SELECT id FROM users WHERE email = %s", (email,))
user = cur.fetchone()
if not user:
cur.close()
conn.close()
return False, "โŒ Email non trovata!"
reset_token = secrets.token_urlsafe(32)
expires_at = datetime.now() + timedelta(hours=expires_hours) # naive local; DB default is UTC-ish, but acceptable
# Insert token + explicit expires_at (to avoid NOT NULL violation)
cur.execute(
"INSERT INTO user_reset_password (user_id, token, reset_token, expires_at) VALUES (%s, %s, %s, %s)",
(user['id'], reset_token, reset_token, expires_at)
)
conn.commit()
cur.close()
conn.close()
print(f"โœ… Reset token generated for user_id {user['id']} (expires at {expires_at})")
# In real app, you'd email the token; here we return it for debug/demo
return True, f"โœ… Token di reset generato!\n\n**Token:** {reset_token}\n\nโš ๏ธ Copia questo token per resettare la password!"
except Exception as e:
print(f"โŒ Errore reset request: {e}")
print(traceback.format_exc())
try:
conn.rollback()
except:
pass
if conn:
conn.close()
return False, f"โŒ Errore: {str(e)}"
def reset_password(reset_token, new_password):
"""Reset password using token; checks expiry and used flag"""
if len(new_password) < 6:
return False, "โŒ La password deve essere almeno 6 caratteri!"
conn = get_db_connection()
if not conn:
return False, "โŒ Errore connessione database!"
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
# fetch record, check used & expiry
cur.execute(
"SELECT id, user_id, expires_at, used FROM user_reset_password WHERE (token = %s OR reset_token = %s)",
(reset_token, reset_token)
)
rec = cur.fetchone()
if not rec:
cur.close()
conn.close()
return False, "โŒ Token non valido!"
if rec['used']:
cur.close()
conn.close()
return False, "โŒ Token giร  utilizzato!"
# expires_at can be a datetime
if rec['expires_at'] and datetime.now() > rec['expires_at']:
cur.close()
conn.close()
return False, "โŒ Token scaduto!"
# update password
password_hash = hash_password(new_password)
cur.execute("UPDATE users SET password_hash = %s WHERE id = %s", (password_hash, rec['user_id']))
# mark token used
cur.execute("UPDATE user_reset_password SET used = TRUE WHERE id = %s", (rec['id'],))
conn.commit()
cur.close()
conn.close()
return True, "โœ… Password resettata con successo! Ora puoi accedere."
except Exception as e:
print(f"โŒ Errore reset password: {e}")
print(traceback.format_exc())
try:
conn.rollback()
except:
pass
if conn:
conn.close()
return False, f"โŒ Errore: {str(e)}"
# ===================================================================
# GESTIONE IMMAGINI (DB store: base64)
# ===================================================================
def save_image_to_db(user_id, prompt, pil_image: Image.Image):
"""Save image in DB as base64 (no data: prefix)"""
try:
img_b64 = pil_to_base64(pil_image)
conn = get_db_connection()
if not conn:
return False
cur = conn.cursor()
cur.execute(
"INSERT INTO image_generate (user_id, prompt, image_base64) VALUES (%s, %s, %s)",
(user_id, prompt, img_b64)
)
conn.commit()
cur.close()
conn.close()
print(f"โœ… Immagine salvata per user_id: {user_id}")
return True
except Exception as e:
print(f"โŒ Errore salvataggio immagine: {e}")
print(traceback.format_exc())
try:
conn.rollback()
except:
pass
if conn:
conn.close()
return False
def get_user_gallery(user_id, limit=50):
"""Return list of dicts with id, prompt, image_base64, created_at"""
conn = get_db_connection()
if not conn:
return []
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
cur.execute(
"SELECT id, prompt, image_base64, created_at FROM image_generate WHERE user_id = %s ORDER BY created_at DESC LIMIT %s",
(user_id, limit)
)
rows = cur.fetchall()
cur.close()
conn.close()
return [dict(r) for r in rows]
except Exception as e:
print(f"โŒ Errore recupero galleria: {e}")
print(traceback.format_exc())
if conn:
conn.close()
return []
# ===================================================================
# LOAD DIFFUSION MODEL
# ===================================================================
device = "cpu"
model_repo_id = "Muyumba/orion_ai"
print(f"๐Ÿ’ป Device: CPU")
pipe = None
try:
print(f"๐Ÿ“ฆ Caricamento modello {model_repo_id}...")
pipe = DiffusionPipeline.from_pretrained(
model_repo_id,
torch_dtype=torch.float32,
trust_remote_code=True,
use_safetensors=True,
low_cpu_mem_usage=True
)
pipe = pipe.to("cpu")
if hasattr(pipe, "enable_attention_slicing"):
pipe.enable_attention_slicing(1)
print("โœ… Modello caricato!")
except Exception as e:
print(f"โŒ Errore caricamento modello: {e}")
print(traceback.format_exc())
MAX_SEED = np.iinfo(np.int32).max
# ===================================================================
# IMAGE GENERATION
# ===================================================================
def generate_image(prompt, negative_prompt, seed, randomize_seed, width, height, guidance_scale, steps, user_id):
if pipe is None:
return None, "โŒ Modello non caricato!"
if randomize_seed:
seed = random.randint(0, MAX_SEED)
try:
generator = torch.Generator(device="cpu").manual_seed(seed)
start = time.time()
result = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
width=width,
height=height,
guidance_scale=guidance_scale,
num_inference_steps=steps,
generator=generator
)
elapsed = time.time() - start
image = result.images[0].convert("RGBA") # PIL Image
save_status = ""
if user_id:
if save_image_to_db(user_id, prompt, image):
save_status = " | โœ… Salvata in galleria"
else:
save_status = " | โš ๏ธ Errore salvataggio in galleria"
return image, f"โœ… Immagine generata in {elapsed:.1f}s! (Seed: {seed}){save_status}"
except Exception as e:
print(f"โŒ Errore generazione: {e}")
print(traceback.format_exc())
return None, f"โŒ Errore: {str(e)}"
# ===================================================================
# CSS & GRADIO UI
# ===================================================================
css = """
.container {max-width: 900px; margin: auto; padding: 20px;}
.login-box {max-width: 450px; margin: 50px auto; padding: 40px; border-radius: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 10px 25px rgba(0,0,0,0.2);}
.gr-button-primary {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-weight: bold;}
"""
with gr.Blocks(css=css, title="OrionAI") as demo:
user_state = gr.State(None)
# Login screen
with gr.Column(visible=True) as login_screen:
with gr.Column(elem_classes="login-box"):
gr.Markdown("# ๐Ÿ” OrionAI - Login\n### Generatore di Immagini IA")
login_username = gr.Textbox(label="๐Ÿ‘ค Username", placeholder="Inserisci username")
login_password = gr.Textbox(label="๐Ÿ”‘ Password", type="password", placeholder="Inserisci password")
with gr.Row():
login_btn = gr.Button("๐Ÿš€ Accedi", variant="primary", size="lg")
register_link = gr.Button("๐Ÿ“ Registrati", variant="secondary", size="lg")
forgot_password_link = gr.Button("๐Ÿ”„ Password dimenticata?", variant="secondary", size="sm")
login_msg = gr.Markdown("")
# Register screen
with gr.Column(visible=False) as register_screen:
with gr.Column(elem_classes="login-box"):
gr.Markdown("# ๐Ÿ“ Registrazione\n### Crea il tuo account")
reg_username = gr.Textbox(label="๐Ÿ‘ค Username", placeholder="Scegli un username")
reg_email = gr.Textbox(label="๐Ÿ“ง Email", placeholder="tuaemail@example.com")
reg_password = gr.Textbox(label="๐Ÿ”‘ Password", type="password", placeholder="Min. 6 caratteri")
reg_password_confirm = gr.Textbox(label="๐Ÿ”‘ Conferma Password", type="password", placeholder="Ripeti la password")
with gr.Row():
register_btn = gr.Button("โœ… Registrati", variant="primary", size="lg")
back_to_login = gr.Button("โ† Torna al Login", variant="secondary", size="lg")
register_msg = gr.Markdown("")
# Reset screen
with gr.Column(visible=False) as reset_screen:
with gr.Column(elem_classes="login-box"):
gr.Markdown("# ๐Ÿ”„ Reset Password\n### Recupera il tuo account")
with gr.Tab("1๏ธโƒฃ Richiedi Reset"):
reset_email = gr.Textbox(label="๐Ÿ“ง Email", placeholder="tuaemail@example.com")
request_reset_btn = gr.Button("๐Ÿ“ง Invia Token Reset", variant="primary")
reset_msg1 = gr.Markdown("")
with gr.Tab("2๏ธโƒฃ Nuova Password"):
reset_token = gr.Textbox(label="๐ŸŽซ Token Reset", placeholder="Incolla il token ricevuto")
new_password = gr.Textbox(label="๐Ÿ”‘ Nuova Password", type="password", placeholder="Min. 6 caratteri")
reset_password_btn = gr.Button("โœ… Resetta Password", variant="primary")
reset_msg2 = gr.Markdown("")
back_to_login2 = gr.Button("โ† Torna al Login", variant="secondary")
# Main app
with gr.Column(visible=False) as main_app:
gr.Markdown("# ๐ŸŒŸ OrionAI - Generatore di Immagini IA")
user_info = gr.Markdown("")
with gr.Tabs():
with gr.Tab("๐ŸŽจ Genera Immagine"):
with gr.Row():
prompt = gr.Textbox(label="๐Ÿ“ Descrivi l'immagine", placeholder="A beautiful landscape...", lines=3)
generate_btn = gr.Button("๐ŸŽจ Genera", variant="primary", scale=0)
result_image = gr.Image(label="๐Ÿ–ผ๏ธ Immagine Generata", height=400)
result_info = gr.Textbox(label="โ„น๏ธ Info", interactive=False)
with gr.Accordion("โš™๏ธ Parametri", open=False):
negative_prompt = gr.Textbox(label="โŒ Elementi da evitare", value="blurry, bad quality, distorted")
with gr.Row():
seed = gr.Slider(0, MAX_SEED, 0, label="๐ŸŽฒ Seed")
randomize = gr.Checkbox(True, label="๐Ÿ”„ Random")
with gr.Row():
width = gr.Slider(256, 768, 512, 64, label="๐Ÿ“ Larghezza")
height = gr.Slider(256, 768, 512, 64, label="๐Ÿ“ Altezza")
with gr.Row():
guidance = gr.Slider(1.0, 15.0, 7.5, 0.5, label="๐ŸŽฏ Guidance")
steps = gr.Slider(10, 30, 20, 5, label="๐Ÿ”ง Steps")
gr.Markdown("**๐Ÿ’ก Suggerimento:** Per risultati veloci su CPU, usa 512x512 con 20 steps")
with gr.Tab("๐Ÿ“š La Mia Galleria"):
gr.Markdown("**๐Ÿ’ก Info:** La galleria si aggiorna automaticamente dopo ogni generazione!")
refresh_gallery_btn = gr.Button("๐Ÿ”„ Aggiorna Manualmente", variant="secondary")
gallery = gr.Gallery(label="Le Tue Immagini", columns=3, height=600)
logout_btn = gr.Button("๐Ÿšช Logout", variant="secondary")
# ========== EVENTS ==========
def handle_login(username, password):
user, msg = login_user(username, password)
if user:
# load gallery images as PIL images
images = get_user_gallery(user['id'])
gallery_images = []
for img in images:
try:
pil = base64_to_pil(img['image_base64'])
caption = img['prompt'][:50] if img['prompt'] else ""
gallery_images.append((pil, caption))
except Exception as e:
print(f"โŒ Errore decode image for gallery: {e}")
return {
login_screen: gr.update(visible=False),
main_app: gr.update(visible=True),
user_state: user,
user_info: gr.update(value=f"๐Ÿ‘ค **Benvenuto, {user['username']}!**"),
login_msg: gr.update(value=""),
login_username: gr.update(value=""),
login_password: gr.update(value=""),
gallery: gallery_images
}
return {
login_screen: gr.update(visible=True),
main_app: gr.update(visible=False),
user_state: None,
login_msg: gr.update(value=msg),
gallery: []
}
login_btn.click(
handle_login,
[login_username, login_password],
[login_screen, main_app, user_state, user_info, login_msg, login_username, login_password, gallery]
)
def show_register():
return {login_screen: gr.update(visible=False), register_screen: gr.update(visible=True), register_msg: gr.update(value="")}
register_link.click(show_register, outputs=[login_screen, register_screen, register_msg])
def handle_register(username, email, password, password_confirm):
if password != password_confirm:
return {register_msg: gr.update(value="โŒ Le password non corrispondono!")}
success, msg = register_user(username, email, password)
if success:
return {
register_screen: gr.update(visible=False),
login_screen: gr.update(visible=True),
register_msg: gr.update(value=""),
login_msg: gr.update(value=msg),
reg_username: gr.update(value=""),
reg_email: gr.update(value=""),
reg_password: gr.update(value=""),
reg_password_confirm: gr.update(value="")
}
return {register_msg: gr.update(value=msg)}
register_btn.click(
handle_register,
[reg_username, reg_email, reg_password, reg_password_confirm],
[register_screen, login_screen, register_msg, login_msg, reg_username, reg_email, reg_password, reg_password_confirm]
)
back_to_login.click(
lambda: {register_screen: gr.update(visible=False), login_screen: gr.update(visible=True)},
outputs=[register_screen, login_screen]
)
forgot_password_link.click(
lambda: {login_screen: gr.update(visible=False), reset_screen: gr.update(visible=True)},
outputs=[login_screen, reset_screen]
)
request_reset_btn.click(
lambda email: request_password_reset(email)[1],
reset_email,
reset_msg1
)
reset_password_btn.click(
lambda token, pwd: reset_password(token, pwd)[1],
[reset_token, new_password],
reset_msg2
)
back_to_login2.click(
lambda: {reset_screen: gr.update(visible=False), login_screen: gr.update(visible=True)},
outputs=[reset_screen, login_screen]
)
def handle_generate(prompt_text, neg, seed_val, rand, w, h, guid, st, user):
if not user:
return None, "โŒ Errore: utente non autenticato!", []
image, info = generate_image(prompt_text, neg, seed_val, rand, w, h, guid, st, user['id'])
gallery_images = []
if user:
images = get_user_gallery(user['id'])
for img in images:
try:
pil = base64_to_pil(img['image_base64'])
caption = img['prompt'][:50] if img['prompt'] else ""
gallery_images.append((pil, caption))
except Exception as e:
print(f"โŒ Errore decode image for gallery after generation: {e}")
return image, info, gallery_images
generate_btn.click(
handle_generate,
[prompt, negative_prompt, seed, randomize, width, height, guidance, steps, user_state],
[result_image, result_info, gallery]
)
def load_gallery(user):
if not user:
return []
images = get_user_gallery(user['id'])
gallery_list = []
for img in images:
try:
pil = base64_to_pil(img['image_base64'])
caption = img['prompt'][:50] if img['prompt'] else ""
gallery_list.append((pil, caption))
except Exception as e:
print(f"โŒ Errore decode gallery image on refresh: {e}")
return gallery_list
refresh_gallery_btn.click(load_gallery, user_state, gallery)
def handle_logout():
return {
main_app: gr.update(visible=False),
login_screen: gr.update(visible=True),
user_state: None,
login_username: gr.update(value=""),
login_password: gr.update(value="")
}
logout_btn.click(
handle_logout,
outputs=[main_app, login_screen, user_state, login_username, login_password]
)
# ===================================================================
# LAUNCH
# ===================================================================
if __name__ == "__main__":
print("๐ŸŒ Avvio OrionAI...")
demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)