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": ( | |
"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." | |
) | |
}, | |
"Grecia Antica": { | |
"nome": "Grecia Antica", | |
"stile_immagine": ( | |
"Classic Greek art style with white marble textures, red-figure pottery motifs, " | |
"and references to mythological scenes. Elegant lines and columns, evoking the " | |
"aesthetics of ancient vases and statues. Ideal for a tabletop card game with readable design." | |
) | |
}, | |
} | |
# 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 | |
# Definizione dello schema per la risposta contenente una lista di personaggi | |
class CharactersResponse(BaseModel): | |
personaggi: list[Character] | |
def generate_story(character: Character, nome_ambientazione): | |
response = clientOpenAI.chat.completions.create( | |
model='gemini-2.0-flash-thinking-exp-01-21', | |
n=1, | |
stream=True, | |
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"Ecco i personaggi del gioco di RUOLO a carte. Crea un regolamento!!! {character.model_dump_json()}" | |
} | |
] | |
) | |
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): | |
# Costruzione del prompt in italiano per generare i personaggi | |
prompt = ( | |
f"Genera {num_personaggi} personaggi per un gioco di ruolo a carte ambientato nel {nome_ambientazione}. " | |
"Ogni personaggio deve avere i seguenti campi specificati nel modello. " | |
"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}, | |
], | |
response_format=CharactersResponse, | |
) | |
characters_response = completion.choices[0].message.parsed | |
print(characters_response) | |
return characters_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: | |
st.error(f"Errore durante la generazione delle immagini: {e}. Riprovo tra 10 secondi...") | |
time.sleep(10) | |
retries += 1 | |
st.error("Numero massimo di tentativi raggiunto. Impossibile generare le immagini.") | |
return None | |
def generate_images(character: Character, stile_immagine, num_immagini): | |
# Lista per salvare le immagini generate come tuple (nome_file, bytes) | |
prompt = f"{character.english_description} {stile_immagine}" | |
images_bytes_list = [] | |
if character.nome != "": | |
st.subheader(f"{character.nome} 🦸♂️") | |
st.markdown(f"- **Classe:** {character.classe}\n- **Forza:** {character.forza}\n- **Destrezza:** {character.destrezza}\n- **Intelligenza:** {character.intelligenza}\n- **Descrizione:** {character.descrizione}", unsafe_allow_html=True) | |
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(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) | |
auto = st.sidebar.toggle(label= 'Generazione automatica') | |
num_personaggi = st.sidebar.slider("Personaggi", min_value=1, max_value=30, value=5, disabled=not auto) | |
num_immagini = st.sidebar.slider("Variazioni Immagini", min_value=1, max_value=6, value=2) | |
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 con 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 | |
if auto: | |
with st.spinner('Generazione Personaggi'): | |
characters = generate_ai(num_personaggi, nome_ambientazione) | |
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) | |
st.divider() | |
generate_story(characters, nome_ambientazione) | |
st.divider() | |
else: | |
characters = CharactersResponse(personaggi=[Character(nome="", classe="Guerriero", forza=10, destrezza=8, intelligenza=6, descrizione="Un forte guerriero", english_description=prompt_input)]) | |
with st.spinner('Generazione Immagini'): | |
images = [] | |
for character in characters.personaggi: | |
images.extend(generate_images(character, 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() |