import streamlit as st import time import base64 import io import zipfile from PIL import Image from together import Together import os from dotenv import load_dotenv from pydantic import BaseModel from openai import OpenAI import pandas as pd load_dotenv() api_together = os.getenv("TOGETHER_API_KEY") api_gemini = os.getenv("API_GEMINI") MODEL = "gemini-2.0-flash-exp" clientOpenAI = OpenAI( api_key=api_gemini, base_url="https://generativelanguage.googleapis.com/v1beta/openai/" ) ambientazioni = { "Roma Repubblicana": { "nome": "Roma Repubblicana", "stile_immagine": ( "A painterly, highly detailed visual narrative rich with graphic elements Roman Repubblic " "Incorporate ornate Roman columns, laurel wreaths, engraved inscriptions, and intricate mosaic patterns. " "Textures of weathered marble, aged frescoes, and tattered papyrus blend with a dramatic chiaroscuro effect. " "A color palette of imperial red, bronze, and muted gold accentuates secretive senatorial silhouettes and subtle sigils. " "Designed for a tabletop card game, this style captures clandestine intrigue and political tension with dynamic visual flair." ) }, "Sacro Romano Impero": { "nome": "Sacro Romano Impero", "stile_immagine": ( "In the italian Holy Roman Empire, Highly detailed, painterly style with a historical yet stylized aesthetic. " "Rich textures, ornate patterns, and a color palette dominated by imperial gold, " "deep red, and aged marble tones. Inspired by medieval European art, " "gothic illuminations, and the grandeur of cathedrals. " "Designed for a tabletop card game, ensuring clarity, readability, " "and a visually immersive experience." ) }, "Antico Egitto": { "nome": "Antico Egitto", "stile_immagine": ( "A stylized Egyptian art style, reminiscent of ancient tomb paintings. " "Use sandy, ochre and turquoise color palette, with hieroglyphic details. " "Geometric shapes and patterns evoke the grandeur of pharaohs and pyramids. " "Designed for a tabletop card game with clarity and distinct thematic flourishes." ) }, "Dinastia Han Cinese": { "nome": "Dinastia Han Cinese", "stile_immagine": ( "Traditional Chinese art style, with ink wash influences, ornate calligraphy, " "and vibrant silk robe patterns. Use subtle brush strokes and bold reds/golds. " "Designed for clarity in a card game with a distinctly ancient Asian aesthetic." ) }, "Impero Persiano": { "nome": "Impero Persiano", "stile_immagine": ( "Intricate Persian miniature style, lavish details, floral ornaments, " "and strong jewel tones like turquoise, gold, and purple. Stylized silhouettes " "in a rich, decorative composition suited for card art." ) }, "Impero Ottomano": { "nome": "Impero Ottomano", "stile_immagine": ( "Ottoman illuminated manuscript style, featuring ornate arabesque borders, " "rich gold leaf, and deep sapphire or emerald backgrounds. " "Figures with flowing robes and turbans, capturing courtly splendor." ) }, "Maya": { "nome": "Maya", "stile_immagine": ( "Mesoamerican style with stylized glyphs, stepped pyramids, and bright colors. " "Incorporate traditional motifs of jaguars and feathers in a decorative, yet readable card layout." ) }, "Vichinghi": { "nome": "Vichinghi", "stile_immagine": ( "Norse-inspired style, with knotwork borders, runic inscriptions, and rugged, " "weathered textures. Use earthy colors and references to Viking longships, " "mythology, and wooden carvings. Balanced for a legible card game design." ) }, "Pirati": { "nome": "Pirati", "stile_immagine": ( "A dynamic and adventurous style inspired by leggende dei mari e pirati romantici. " "Immagini di navi in tempesta, mappe del tesoro, e personaggi con bandane e cappelli tricorni. " "Toni marinari, textures consumate dal sale e dall'acqua, e un'atmosfera ribelle ma affascinante, " "perfetta per un gioco di carte che evoca il brivido dell'avventura sul mare." ) }, "Cowboy del Far West": { "nome": "Cowboy del Far West", "stile_immagine": ( "Highly detailed, painterly style with a rugged yet cinematic aesthetic. " "Rich textures, earthy tones, and a palette dominated by dusty browns, deep reds, and sunset golds. " "Inspired by classic Western art, frontier landscapes, and vintage posters. " "Iconic elements like cowboy hats, revolvers, and stagecoaches evoke the romance of the Wild West. " "Designed for a tabletop card game, ensuring clarity, readability, and an immersive sense of adventure." ) }, "Steampunk": { "nome": "Steampunk", "stile_immagine": ( "Highly detailed, painterly style with a fusion of Victorian elegance and mechanical ingenuity. " "Brass gears, intricate machinery, steam-powered contraptions, and industrial backdrops " "create a retro-futuristic atmosphere. " "A color palette dominated by warm metallics, deep browns, and aged parchment tones. " "Inspired by classic steampunk literature and art, ensuring a visually immersive experience " "for a tabletop card game with a sense of mystery and innovation." ) }, "Alieni": { "nome": "Alieni", "stile_immagine": ( "Highly detailed, painterly style with a futuristic and surreal aesthetic. " "Glowing neon hues, organic and biomechanical forms, and impossible cosmic landscapes " "blend to create an otherworldly atmosphere. " "A color palette dominated by electric blues, deep purples, and iridescent greens. " "Inspired by sci-fi concept art and space opera visuals, ensuring clarity, readability, " "and a visually stunning experience for a tabletop card game set in a mysterious universe." ) }, "Homo Sapiens": { "nome": "Homo Sapiens", "stile_immagine": ( "Highly detailed, painterly style with a blend of realism and symbolic representation. " "Natural tones, textures inspired by organic matter, and visual elements reflecting " "human evolution, history, and cultural development. " "A fusion of prehistoric cave paintings, Renaissance anatomical studies, and modern artistic interpretations. " "Designed for a tabletop card game, ensuring clarity, readability, and a thought-provoking visual narrative " "that bridges the ancient past with the present." ) } } # Definizione dello schema Pydantic per un personaggio class Character(BaseModel): nome: str classe: str forza: int destrezza: int intelligenza: int descrizione: str english_description:str storia: str # Definizione dello schema per la risposta contenente una lista di personaggi class CharactersResponse(BaseModel): personaggi: list[Character] class ActionCard(BaseModel): nome_azione: str breve_descrizione: str breve_descrizione_inglese: str class ActionCardsResponse(BaseModel): carte_azione: list[ActionCard] def generate_story(character: Character, carte_azione: ActionCardsResponse, nome_ambientazione, creativita): descrizione_carte_azione = "" descrizione_personaggi = "" if carte_azione: descrizione_carte_azione = f"Ecco anche le CARTE AZIONE del gioco: {carte_azione.model_dump_json()}" if character: descrizione_personaggi = f"Ecco i personaggi del gioco di RUOLO a carte. {character.model_dump_json()}" response = clientOpenAI.chat.completions.create( model='gemini-2.0-flash-thinking-exp-01-21', n=1, stream=True, temperature=creativita, messages=[ {"role": "system", "content": f"Tu sei un creatore di giochi di ruolo a CARTE. Crea un regolamento per un gioco di ruolo sul {nome_ambientazione} sulla base dei personaggi che ti fornirò"}, { "role": "user", "content": f"Crea un regolamento!!! {descrizione_personaggi} \n\n {descrizione_carte_azione}" } ] ) story = "" st.subheader('Regole 📜') placeholder = st.empty() for chunk in response: if chunk.choices[0].delta.content is not None: text_chunk = chunk.choices[0].delta.content story += text_chunk placeholder.markdown(story) print(story) return story def generate_ai(num_personaggi, nome_ambientazione, creativita): # Costruzione del prompt in italiano per generare i personaggi prompt = ( f"Genera {num_personaggi} personaggi AMBIENTATI NELL'EPOCA {nome_ambientazione}, per un gioco di ruolo a carte." "Ogni personaggio deve avere i seguenti campi specificati nel modello ed devono essere COERENTI con l'epoca e l'ambientazione fornita (per esempio, se siamo nel Sacro Romano Impero devono essere nomi italiani) " "nel campo STORIA, inventa la una BREVE storia di background del personaggio specificando aneddoti, e altre cose che rendono il personaggio unico. " "Restituisci il risultato in formato JSON seguendo lo schema fornito") # Esecuzione della chiamata all'API utilizzando il formato response_format completion = clientOpenAI.beta.chat.completions.parse( model=MODEL, messages=[ {"role": "system", "content": f"Sei un assistente utile per la generazione di personaggi per un gioco di RUOLO sul {nome_ambientazione}."}, {"role": "user", "content": prompt}, ], temperature=creativita, response_format=CharactersResponse, ) characters_response = completion.choices[0].message.parsed print(characters_response) return characters_response def generate_card_ai(num_carte_azione, nome_ambientazione, creativita): prompt = ( f"Genera {num_carte_azione} CARTE AZIONE, per un gioco di ruolo ambientato in {nome_ambientazione}. " "Attieniti allo schema formito e sii molto SINTETICO") # Esecuzione della chiamata all'API utilizzando il formato response_format completion = clientOpenAI.beta.chat.completions.parse( model=MODEL, messages=[ {"role": "system", "content": f"Sei un assistente utile per la generazione di CARTE AZIONE per un gioco di RUOLO sul {nome_ambientazione}."}, {"role": "user", "content": prompt}, ], temperature=creativita, response_format=ActionCardsResponse, ) action_card_response = completion.choices[0].message.parsed return action_card_response # Funzione per generare le immagini, con gestione errori e retry dopo 10 secondi def generate_image(prompt, max_retries=5): client = Together(api_key=api_together) retries = 0 while retries < max_retries: try: response = client.images.generate( prompt=prompt, model="black-forest-labs/FLUX.1-schnell-Free", width=960, height=1440, steps=4, n=1, response_format="b64_json" ) return response.data # Una lista di oggetti con attributo b64_json except Exception as e: print(f"Errore durante la generazione delle immagini: {e}. Riprovo tra 10 secondi...") time.sleep(9) retries += 1 st.error("Numero massimo di tentativi raggiunto. Impossibile generare le immagini.") return None def generate_images(character: Character, carta_azione: ActionCard, stile_immagine, num_immagini, tipo_carta: str = None, nome_carta: str = None, colore_bordo: str = None): # Lista per salvare le immagini generate come tuple (nome_file, bytes) if character: images_bytes_list = [] if character.nome == "": if tipo_carta == "Personaggio": prompt = f"{character.english_description} WITH THIS BOLD INSCRIPTION '{nome_carta}'. BORDER COLOR = {colore_bordo}). Use this Style: {stile_immagine}" if tipo_carta == "Fronte": prompt = f"FRONT OF CARD GAME (WITH THIS BOLD INSCRIPTION IN THE CENTER OF THE CARD = '{nome_carta}'. BORDER COLOR = {colore_bordo}). Use this Syle: {stile_immagine}" if tipo_carta == "Retro": prompt = ("Design an ornate playing card back with symmetrical filigree" "At the center, prominently display the name '" + nome_carta + "' in an elegant, calligraphic style. ") prompt += f"BACKGROUND COLOR = {colore_bordo}. Use this Syle: {stile_immagine}" else: prompt = f"{character.english_description} {stile_immagine}. (INSERT TEXT NAME = {character.nome})" st.subheader(f"{character.nome} 🦸‍♂️") st.write( f"- **Classe:** {character.classe}\n" f"- **Forza:** {character.forza}\n" f"- **Destrezza:** {character.destrezza}\n" f"- **Intelligenza:** {character.intelligenza}\n" f"- **Descrizione:** {character.descrizione}\n", f"- **Storia:** {character.storia}" ) if carta_azione: prompt = f"PLAYING CARD FOR A CARD GAME WITHOUT CHARACTER WITH THIS BOLD INSCRIPTION IN THE CENTER OF THE CARD: '{carta_azione.nome_azione}'. USE THIS STYLE: {stile_immagine}.)" images_bytes_list = [] st.subheader(f"{carta_azione.nome_azione} 🃏") st.write(f"- **Descrizione:** {carta_azione.breve_descrizione}") print(prompt) for numero in range(num_immagini): images_data = generate_image(prompt) if images_data is not None: for i, img_obj in enumerate(images_data): try: image_bytes = base64.b64decode(img_obj.b64_json) image = Image.open(io.BytesIO(image_bytes)) st.image(image, caption="") img_byte_arr = io.BytesIO() image.save(img_byte_arr, format='PNG') images_bytes_list.append((f"image_{numero+1}_{i+1}.png", img_byte_arr.getvalue())) except Exception as e: st.error(f"Errore nella visualizzazione dell'immagine {i+1}: {e}") else: st.error("Non è stato possibile generare le immagini. Riprova più tardi.") time.sleep(0.5) return images_bytes_list def main(): st.title("Imperium AI 🏰") st.sidebar.header("Impostazioni") selected_ambientazione = st.sidebar.selectbox("Ambientazione", list(ambientazioni.keys()), index=0) stile_default = ambientazioni[selected_ambientazione]["stile_immagine"] nome_ambientazione = ambientazioni[selected_ambientazione]["nome"] stile_immagine = st.sidebar.text_area("Stile Immagine", stile_default, disabled=False) auto = False auto = st.sidebar.toggle(label= 'Generazione automatica', value = True) prompt_input = "" if auto == False: tipo_carta = st.sidebar.selectbox("Tipo Carta",("Personaggio", "Fronte", "Retro")) if tipo_carta == "Personaggio": prompt_input = st.sidebar.text_input("Prompt Immagine (in inglese)", value="Soldier") nome_carta = st.sidebar.text_input("Nome Carta", value="Carmine") colore_bordo = st.sidebar.selectbox("Colore Bordo",("Blue", "Red", "Yellow", "Green", "Pink", "Black", "White")) else: num_personaggi = st.sidebar.slider("Personaggi", min_value=0, max_value=30, value=5, disabled=not auto) num_carte_azione = st.sidebar.slider("Carte Azione", min_value=0, max_value=10, value=0, disabled=not auto) genera_regolamento = st.sidebar.toggle(label= 'Regolamento', value = True, disabled=not auto) tipo_carta = None nome_carta = None colore_bordo = None num_immagini = st.sidebar.slider("Variazioni Immagini", min_value=1, max_value=6, value=2) creativita = st.sidebar.slider("Creativita", min_value=0.1, max_value=1.0, value=0.8, step=0.1) submit_button = st.sidebar.button(label="Genera Immagine", type="primary", use_container_width=True) st.write("Forgia il tuo **destino nell'Impero**: crea, combatti e domina nel più grande gioco di ruolo a carte generato dall'AI") if submit_button: carte_azione = None characters = None if auto: if num_personaggi > 0: with st.spinner('Generazione Personaggi'): characters = generate_ai(num_personaggi, nome_ambientazione, creativita) st.subheader('Personaggi 🎭') df = pd.DataFrame([{k: v for k, v in character.model_dump().items() if k != "english_description"} for character in characters.personaggi]) st.dataframe(df, hide_index=True, use_container_width=True) st.divider() if num_carte_azione > 0: with st.spinner('Generazione Carte Azione'): carte_azione = generate_card_ai(num_carte_azione, nome_ambientazione, creativita) st.subheader('Carte Azione 📒') df = pd.DataFrame([{k: v for k, v in carta_azione.model_dump().items() if k != "breve_descrizione_inglese"} for carta_azione in carte_azione.carte_azione]) st.dataframe(df, hide_index=True, use_container_width=True) st.divider() if genera_regolamento: with st.spinner('Generazione Regolamento'): generate_story(characters, carte_azione, nome_ambientazione, creativita) st.divider() else: characters = CharactersResponse(personaggi=[Character(nome="", classe="Guerriero", forza=10, destrezza=8, intelligenza=6, storia="", descrizione="Un forte guerriero", english_description=prompt_input)]) with st.spinner('Generazione Immagini'): images = [] if characters: for character in characters.personaggi: images.extend(generate_images(character, None, stile_immagine, num_immagini, tipo_carta, nome_carta, colore_bordo)) if carte_azione: for carta_azione in carte_azione.carte_azione: images.extend(generate_images(None, carta_azione, stile_immagine, num_immagini)) if images: zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file: for file_name, file_bytes in images: zip_file.writestr(file_name, file_bytes) zip_buffer.seek(0) st.download_button( label="Download All Images", data=zip_buffer, file_name="images.zip", mime="application/zip", type='primary' ) st.success("Immagini generate con successo!") if __name__ == "__main__": st.set_page_config(page_title="Imperium AI", page_icon="🏰", layout="wide") main()