Spaces:
Running
Running
| 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 = { | |
| "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): | |
| # Lista per salvare le immagini generate come tuple (nome_file, bytes) | |
| if character: | |
| prompt = f"{character.english_description} {stile_immagine}. (INSERT TEXT NAME = {character.nome})" | |
| images_bytes_list = [] | |
| if 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 | |
| prompt_input = st.sidebar.text_input("Prompt Immagine (in inglese)", value="Soldier", disabled=auto) | |
| num_immagini = st.sidebar.slider("Variazioni Immagini", min_value=1, max_value=6, value=2) | |
| auto = st.sidebar.toggle(label= 'Generazione automatica', value = True) | |
| 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) | |
| 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: | |
| if not prompt_input.strip() and not auto: | |
| st.error("Per favore, inserisci un prompt per l'immagine!") | |
| return | |
| 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)) | |
| 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() |