File size: 19,808 Bytes
d60dac5
cebfd3c
293ffb8
 
c368803
c17cf76
293ffb8
 
c368803
 
 
 
 
 
 
 
 
 
 
 
 
 
ffafa9e
9301ad1
 
 
 
 
 
 
 
 
 
2f9c799
 
 
90c5698
2f9c799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90c5698
 
 
 
 
2f9c799
 
 
 
 
90c5698
 
 
 
 
 
2f9c799
 
 
 
 
90c5698
 
 
 
 
 
2f9c799
 
 
 
 
90c5698
 
 
 
 
 
2f9c799
90c5698
2f9c799
 
ffafa9e
c368803
 
 
 
 
 
 
 
 
90c5698
c368803
 
 
 
 
90c5698
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c368803
ffafa9e
c368803
 
2f9c799
c368803
ffafa9e
c368803
 
90c5698
c368803
 
 
 
 
 
 
 
 
 
 
 
 
 
2f9c799
c368803
 
90c5698
 
 
c368803
 
 
 
 
 
ffafa9e
c368803
 
2f9c799
c368803
 
 
 
 
a6058bc
90c5698
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293ffb8
 
c368803
293ffb8
 
c17cf76
293ffb8
 
 
 
 
 
 
 
 
 
 
90c5698
 
293ffb8
 
 
 
9301ad1
c368803
90c5698
 
9301ad1
 
 
 
 
 
 
 
 
 
 
90c5698
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293ffb8
 
 
 
 
 
c368803
293ffb8
c368803
 
 
293ffb8
 
7376a17
293ffb8
90c5698
c368803
293ffb8
 
c368803
293ffb8
ffafa9e
 
 
293ffb8
c368803
90c5698
9301ad1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f9c799
c368803
90c5698
c368803
293ffb8
90c5698
 
c368803
90c5698
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed5749a
90c5698
c368803
 
90c5698
 
9301ad1
90c5698
 
 
c368803
 
 
 
 
 
 
 
 
 
 
 
 
 
293ffb8
 
c368803
9301ad1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
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()