# this imports the code from files and modules
import description_helper as dsh
import gradio as gr
import sd_generator as sd
import utilities as u
import process_html
import process_text
import os
import ctypes
import tripo3d as tripo3d
import uuid
# This is a fix for the way that python doesn't release system memory back to the OS and it was leading to locking up the system
libc = ctypes.cdll.LoadLibrary("libc.so.6")
M_MMAP_THRESHOLD = -3
# Set malloc mmap threshold.
libc.mallopt(M_MMAP_THRESHOLD, 2**20)
# Declare accessible directories
base_dir = os.path.dirname(os.path.abspath(__file__)) # Gets the directory where the script is located
print(f"Base Directory :",base_dir)
list_of_static_dir = [os.path.join(base_dir, "output"),
os.path.join(base_dir, "dependencies"),
os.path.join(base_dir, "galleries")]
gr.set_static_paths(paths=list_of_static_dir)
style_css = custom_css = """
"""
# Build gradio app
with gr.Blocks(css = "style.css") as demo:
# Functions and State Variables
mon_name = gr.State()
mon_size = gr.State()
mon_type = gr.State()
mon_subtype = gr.State()
mon_alignment = gr.State()
mon_armor_class = gr.State()
mon_hp = gr.State()
mon_hit_dice = gr.State()
mon_speed = gr.State()
mon_abilities = gr.State()
mon_saving_throws = gr.State()
mon_skills = gr.State()
mon_damage_resistance = gr.State()
mon_senses = gr.State()
mon_languages = gr.State()
mon_challenge_rating = gr.State()
mon_xp = gr.State()
mon_actions = gr.State()
mon_cantrips = gr.State()
mon_spells = gr.State()
mon_spell_slots = gr.State()
mon_legendary_actions = gr.State()
mon_description = gr.State()
mon_sd_prompt = gr.State()
# Image Variables
generated_image_list = gr.State([])
selected_generated_image = gr.State()
selected_seed_image = gr.State()
selected_token_image = gr.State()
# Take input from gradio user and call the llm in description_helper
def gen_mon_desc(user_monster_text, spellcaster, legendary_actions):
# declare cantrips, spells, and spell slots as empty string unless the variable is changed.
mon_cantrips = ""
mon_spells = ""
mon_spell_slots = ""
mon_legendary_actions = ""
llm_output = dsh.call_llm_and_cleanup(user_monster_text, spellcaster, legendary_actions)
user_monster = dsh.convert_to_dict(llm_output)
user_monster = llm_output
keys_list = list(user_monster)
mon_name = user_monster['name']
mon_size = user_monster['size']
mon_type = user_monster['type']
mon_subtype = user_monster['subtype']
mon_alignment = user_monster['alignment']
mon_armor_class = user_monster['armor_class']
mon_hp = user_monster['hit_points']
mon_hit_dice = user_monster['hit_dice']
mon_speed = user_monster['speed']
if type(mon_speed) == dict:
mon_speed = process_text.format_mon_qualities(mon_speed)
mon_abilities = process_text.format_abilities_for_editing(user_monster['abilities'])
mon_saving_throws = user_monster['saving_throws']
if type(mon_saving_throws) == dict:
mon_saving_throws = process_text.format_mon_qualities(mon_saving_throws)
mon_skills = user_monster['skills']
if type(mon_skills) == dict:
mon_skills = process_text.format_mon_qualities(mon_skills)
mon_damage_resistance = user_monster['damage_resistance']
if type(mon_damage_resistance) == dict:
mon_damage_resistance = process_text.format_mon_qualities(mon_damage_resistance)
mon_senses = user_monster['senses']
if type(mon_senses) == dict:
mon_senses = process_text.format_mon_qualities(mon_senses)
mon_languages = user_monster['languages']
mon_challenge_rating = user_monster['challenge_rating']
mon_xp = user_monster['xp']
if 'actions' in keys_list:
mon_actions = process_text.format_actions_for_editing(user_monster['actions'])
if "spells" in keys_list :
print(user_monster['spells'])
print(f"Length of spells : {len(user_monster['spells'])}")
if len(user_monster['spells']) >= 1 and user_monster['spells'] != "{}":
mon_spells = user_monster['spells']
mon_cantrips,mon_spells,mon_spell_slots = process_text.format_spells_for_editing(mon_spells)
print(mon_cantrips,mon_spells, mon_spell_slots)
if 'legendary_actions' in keys_list and len(user_monster['legendary_actions']) >= 1 and user_monster['legendary_actions'] != "{}":
mon_legendary_actions = user_monster['legendary_actions']
if type(mon_legendary_actions) == dict:
mon_legendary_actions = process_text.format_legendaries_for_editing(mon_legendary_actions)
mon_description = user_monster['description']
mon_sd_prompt = user_monster['sd_prompt']
#Return each State variable twice, once to the variable and once to the textbox
return [mon_name,mon_name,
mon_size,mon_size,
mon_type,mon_type,
mon_subtype,mon_subtype,
mon_alignment, mon_alignment,
mon_armor_class, mon_armor_class,
mon_hp, mon_hp,
mon_hit_dice, mon_hit_dice,
mon_speed, mon_speed,
mon_abilities,mon_abilities,
mon_saving_throws, mon_saving_throws,
mon_skills, mon_skills,
mon_damage_resistance, mon_damage_resistance,
mon_senses, mon_senses,
mon_languages, mon_languages,
mon_challenge_rating, mon_challenge_rating,
mon_xp, mon_xp,
mon_actions, mon_actions,
mon_cantrips, mon_cantrips, mon_spells, mon_spells,mon_spell_slots,mon_spell_slots,
mon_legendary_actions, mon_legendary_actions,
mon_description, mon_description,
mon_sd_prompt,mon_sd_prompt
]
#Function to dynamically render textbox if it has text.
def update_visibility(textbox):
if not textbox:
return gr.update(visible=False)
return gr.update(visible=True)
# Called on user selecting an image from the gallery, outputs the path of the image
def assign_img_path(evt: gr.SelectData):
img_dict = evt.value
print(img_dict)
selected_image_path = img_dict['image']['url']
print(selected_image_path)
return selected_image_path
# Make a list of files in image_temp and delete them
def delete_temp_images():
image_list = u.directory_contents('./image_temp')
u.delete_files(image_list)
#img2img.image_list.clear()
# Called when pressing button to generate image, updates gallery by returning the list of image URLs
def generate_image_update_gallery(image_prompt,image_name, token = False):
delete_temp_images()
print(f"sd_prompt is a {type(image_prompt)}")
image_list = []
num_img = 4
for x in range(num_img):
preview = sd.preview_and_generate_image(image_name, image_prompt,token)
image_list.append(preview)
yield image_list
del preview
return image_list
def generate_token_update_gallery(image_prompt,image_name,token = True):
delete_temp_images()
print(f"sd_prompt is a {type(image_prompt)}")
image_list = []
num_img = 4
for x in range(num_img):
preview = sd.preview_and_generate_image(image_name, image_prompt,token)
image_list.append(preview)
yield image_list
del preview
return image_list
# Generate a 3D model by passing the chosen token image to Tripo3D.ai API
def generate_model_update_gallery(image):
generated_model = tripo3d.generate_model(image)
return generated_model
# Build html text by processing the generated dictionaries.
def build_html_file(mon_name_output,
mon_size_output,
mon_type_output,
mon_subtype_output,
mon_alignment_output,
mon_armor_class_output,
mon_hp_output,
mon_hit_dice_output,
mon_speed_output,
mon_abilities,
mon_saving_throws_output,
mon_skills_output,
mon_damage_resistance_output,
mon_senses_output,
mon_languages_output,
mon_challenge_rating_output,
mon_xp_output,
mon_actions_output,
selected_generated_image,
mon_description_output,
mon_cantrips_output,
mon_spells_output,
mon_spell_slot_output,
mon_legendary_actions_output
):
mon_file_path = process_html.build_html_base(
mon_name_output,
mon_size_output,
mon_type_output,
mon_subtype_output,
mon_alignment_output,
mon_armor_class_output,
mon_hp_output,
mon_hit_dice_output,
mon_speed_output,
mon_abilities,
mon_saving_throws_output,
mon_skills_output,
mon_damage_resistance_output,
mon_senses_output,
mon_languages_output,
mon_challenge_rating_output,
mon_xp_output,
mon_actions_output,
selected_generated_image,
mon_description_output,
mon_cantrips_output,
mon_spells_output,
mon_spell_slot_output,
mon_legendary_actions_output,
)
mon_file_path = u.file_name_list[0]+'/' + u.file_name_list[1] +'.html'
if not os.path.exists(mon_file_path):
print(f"{mon_file_path} not found")
else: print(f"{mon_file_path} found")
iframe = iframe = f""""""
return iframe
# Build the html and file path to pass to gradio to output html file in gr.html
def gen_link():
mon_file_path = u.file_name_list[0]+'/' + u.file_name_list[1] +'.html'
if not os.path.exists(mon_file_path):
print(f"{mon_file_path} not found")
else: print(f"{mon_file_path} found")
iframe = iframe = f""""""
link = f'{u.file_name_list[1] +".html"}'
return iframe
with gr.Tab("Instructions"):
image_path_list= u.absolute_path("./galleries/instructions")
try:
md_img_0 = f"""/file={image_path_list[0]} """
md_img_1 =f"""/file={image_path_list[1]} """
md_img_2 =f"""/file={image_path_list[2]} """
except IndexError:
# Handle the case where the list is empty
md_img_0 = "No images found."
md_img_1 = "No images found."
md_img_2 = "No images found."
gr.HTML("""
Monster Statblock Generator
""")
md_instructions_header = """## How It Works:"""
gr.Markdown(md_instructions_header)
md_instructions_1=""" **Include as much or as little information as you'd like.** """
gr.Markdown(md_instructions_1)
gr.Image(value=md_img_0, show_label=False)
gr.Image(value=md_img_1, show_label=False)
md_instructions_2="""## Image Generation:
** The first image generation take about 2 minutes to 'warm up' after that it's ~10s per image. ** \n
1. Click 'Generate Statblock Art' and wait for the images to generate, then select the one you'd like to use. \n
2. Click 'Generate HTML' to generate a webpage that can be saved or printed as PDF. \n
3. Last, you can generate a token or figure of your creature or a 3d model to download. \n """
gr.Markdown(md_instructions_2)
gr.Image(value=md_img_2, show_label=False)
with gr.Tab("Generator"):
with gr.Row():
with gr.Column():
user_mon_description = gr.Textbox(label = "Write a description and give a name or type to your creation!",
lines = 1, placeholder=f"Ex : A friendly skeletal lich who is a master of flavor, called The Flavor Lich",
elem_id= "custom-textbox")
with gr.Column():
spells_checkbox = gr.Checkbox(label= "Spellcaster?")
legendary_action_checkbox = gr.Checkbox(label= "Legendary Actions?")
desc_gen = gr.Button(value = "Click to Generate Description")
mon_description_output = gr.Textbox(label = 'Description', lines = 2, interactive=True, visible=False)
mon_description_output.change(fn=update_visibility,
inputs=[mon_description_output],
outputs=[mon_description_output])
with gr.Row():
with gr.Column(scale = 1):
mon_name_output = gr.Textbox(label = 'Name', lines = 1, interactive=True, visible=False)
mon_name_output.change(fn=update_visibility,
inputs=[mon_name_output],
outputs=[mon_name_output])
mon_size_output = gr.Textbox(label = 'Size', lines = 1, interactive=True, visible=False)
mon_size_output.change(fn=update_visibility,
inputs=[mon_size_output],
outputs=[mon_size_output])
mon_alignment_output = gr.Textbox(label = 'Alignment', lines = 1, interactive=True, visible=False)
mon_alignment_output.change(fn=update_visibility,
inputs=[mon_alignment_output],
outputs=[mon_alignment_output])
mon_armor_class_output = gr.Textbox(label = 'Armor Class', lines = 1, interactive=True, visible=False)
mon_armor_class_output.change(fn=update_visibility,
inputs=[mon_armor_class_output],
outputs=[mon_armor_class_output])
mon_hit_dice_output = gr.Textbox(label = 'Hit Dice', lines = 1, interactive=True, visible=False)
mon_hit_dice_output.change(fn=update_visibility,
inputs=[mon_hit_dice_output],
outputs=[mon_hit_dice_output])
mon_senses_output = gr.Textbox(label = 'Senses', lines = 1, interactive=True, visible =False)
mon_senses_output.change(fn=update_visibility,
inputs=[mon_senses_output],
outputs=[mon_senses_output])
mon_actions_output = gr.Textbox(label = 'Actions', lines = 16, interactive=True, visible = False)
mon_actions_output.change(fn=update_visibility,
inputs=[mon_actions_output],
outputs=[mon_actions_output])
with gr.Column(scale = 1):
mon_type_output = gr.Textbox(label = 'Type', lines = 1, interactive=True, visible=False)
mon_actions_output.change(fn=update_visibility,
inputs=[mon_actions_output],
outputs=[mon_actions_output])
mon_speed_output = gr.Textbox(label = 'Speed', lines = 1, interactive=True, visible=False)
mon_speed_output.change(fn=update_visibility,
inputs=[mon_speed_output],
outputs=[mon_speed_output])
mon_abilities_output = gr.Textbox(label ='Ability Scores', lines = 5, interactive=True, visible=False)
mon_abilities_output.change(fn=update_visibility,
inputs=[mon_abilities_output],
outputs=[mon_abilities_output])
mon_damage_resistance_output = gr.Textbox(label = 'Damage Resistance', lines = 1, interactive=True, visible=False)
mon_damage_resistance_output.change(fn=update_visibility,
inputs=[mon_damage_resistance_output],
outputs=[mon_damage_resistance_output])
mon_challenge_rating_output = gr.Textbox(label = 'Challenge Rating', lines = 1, interactive=True, visible=False)
mon_cantrips_output = gr.Textbox(label = 'Cantrips', lines = 16, interactive=True, visible=False)
mon_cantrips_output.change(fn=update_visibility,
inputs=[mon_cantrips_output],
outputs=[mon_cantrips_output])
mon_spells_output = gr.Textbox(label = 'Spells', lines = 16, interactive=True, visible=False)
mon_spells_output.change(fn=update_visibility,
inputs=[mon_spells_output],
outputs=[mon_spells_output])
mon_spell_slot_output = gr.Textbox(label = 'Spell Slots', lines = 8, interactive=True, visible=False)
mon_spell_slot_output.change(fn=update_visibility,
inputs=[mon_spell_slot_output],
outputs=[mon_spell_slot_output])
with gr.Column(scale = 1):
mon_subtype_output = gr.Textbox(label = 'Subtype', lines = 1, interactive=True, visible = False)
mon_subtype_output.change(fn=update_visibility,
inputs=[mon_subtype_output],
outputs=[mon_subtype_output])
mon_saving_throws_output = gr.Textbox(label = 'Saving Throws', lines = 1, interactive=True, visible=False)
mon_saving_throws_output.change(fn=update_visibility,
inputs=[mon_saving_throws_output],
outputs=[mon_saving_throws_output])
mon_skills_output = gr.Textbox(label = 'Skills', lines = 1, interactive=True, visible=False)
mon_skills_output.change(fn=update_visibility,
inputs=[mon_skills_output],
outputs=[mon_skills_output])
mon_hp_output = gr.Textbox(label = 'Health Points', lines = 1, interactive=True, visible=False)
mon_hp_output.change(fn=update_visibility,
inputs=[mon_hp_output],
outputs=[mon_hp_output])
mon_languages_output = gr.Textbox(label = 'Languages', lines = 1, interactive=True, visible=False)
mon_languages_output.change(fn=update_visibility,
inputs=[mon_languages_output],
outputs=[mon_languages_output])
mon_xp_output = gr.Textbox(label = 'XP', lines = 1, interactive=True, visible=False)
mon_xp_output.change(fn=update_visibility,
inputs=[mon_xp_output],
outputs=[mon_xp_output])
mon_legendary_actions_output = gr.Textbox(label = 'Legendary Actions', lines = 16, interactive=True, visible = False)
mon_legendary_actions_output.change(fn = update_visibility,
inputs =[mon_legendary_actions_output],
outputs=[mon_legendary_actions_output])
mon_sd_prompt_output = gr.Textbox(label = 'Image Generation Prompt', lines = 1, interactive=True, visible=True)
mon_sd_prompt_output.change(fn=update_visibility,
inputs=[mon_sd_prompt_output],
outputs=[mon_sd_prompt_output])
image_gen_instructions = """ ## Image Generation \n
1. Review the text in the 'Image Generation Prompt' Textbox\n
2. Click 'Generate Statblock Art' \n
3. This will take 2 minutes for the first image, then about 10 seconds each. \n
** *Additional generation will take about 10 seconds each, until the server goes to sleep ~3 minutes inactivity. \n
4. Click your favorite of the four images, this loads it for the page builder and 3d model generator.
"""
gr.Markdown(image_gen_instructions)
with gr.Row():
with gr.Column():
image_gen = gr.Button(value = "Generate Statblock Art" )
mon_image_gallery = gr.Gallery(label = "Generated Images",
show_label = True,
elem_id = "gallery",
columns =[4], rows =[1],
object_fit = "cover",
height ="auto")
with gr.Column():
token_gen = gr.Button(value = "Generate Token Image" )
mon_token_gallery = gr.Gallery(label = "Generated Tokens",
show_label = True,
elem_id = "token_gallery",
columns =[4], rows =[1],
object_fit = "cover",
height ="auto")
model_gen_instructions = """## Generate a 3d model
1. Make sure an image was clicked in the "Generated Images" gallery.
*Works best with images without thin qualities, like antenna \n
2. Click Generate a 3d model button.
3. Wait about 30 seconds, then review and download as an .glb file.
"""
gr.Markdown(model_gen_instructions)
model_gen = gr.Button(value= "Generate a 3D model")
mon_model_gallery = gr.Model3D(label = "3D Model Display",
elem_id = "model_gallery",
)
image_gen.click(generate_image_update_gallery, inputs = [mon_sd_prompt_output,mon_name], outputs = mon_image_gallery)
token_gen.click(generate_token_update_gallery, inputs = [mon_sd_prompt_output,mon_name], outputs = mon_token_gallery)
model_gen.click(generate_model_update_gallery, inputs = selected_generated_image, outputs = mon_model_gallery)
mon_image_gallery.select(fn = assign_img_path, outputs=selected_generated_image)
mon_token_gallery.select(fn = assign_img_path, outputs=selected_token_image)
desc_gen.click(fn = gen_mon_desc, inputs = [user_mon_description,spells_checkbox, legendary_action_checkbox],
outputs= [mon_name, mon_name_output,
mon_size,mon_size_output,
mon_type,mon_type_output,
mon_subtype,mon_subtype_output,
mon_alignment, mon_alignment_output,
mon_armor_class, mon_armor_class_output,
mon_hp, mon_hp_output,
mon_hit_dice, mon_hit_dice_output,
mon_speed, mon_speed_output,
mon_abilities, mon_abilities_output,
mon_saving_throws, mon_saving_throws_output,
mon_skills, mon_skills_output,
mon_damage_resistance, mon_damage_resistance_output,
mon_senses, mon_senses_output,
mon_languages, mon_languages_output,
mon_challenge_rating, mon_challenge_rating_output,
mon_xp, mon_xp_output,
mon_actions, mon_actions_output,
mon_cantrips,mon_cantrips_output,
mon_spells, mon_spells_output,
mon_spell_slots, mon_spell_slot_output,
mon_legendary_actions, mon_legendary_actions_output,
mon_description, mon_description_output,
mon_sd_prompt,mon_sd_prompt_output
])
# Build buttons to modify to html and show html
with gr.Row():
with gr.Column():
gen_html = gr.Button(value = "Step 3 : Generate html")
html = gr.HTML(label="HTML preview", show_label=True)
gen_html.click(build_html_file,inputs =[
mon_name_output,
mon_size_output,
mon_type_output,
mon_subtype_output,
mon_alignment_output,
mon_armor_class_output,
mon_hp_output,
mon_hit_dice_output,
mon_speed_output,
mon_abilities_output,
mon_saving_throws_output,
mon_skills_output,
mon_damage_resistance_output,
mon_senses_output,
mon_languages_output,
mon_challenge_rating_output,
mon_xp_output,
mon_actions_output,
mon_description_output,
selected_generated_image,
mon_cantrips_output,
mon_spells_output,
mon_spell_slot_output,
mon_legendary_actions_output,
],
outputs= html
)
example_headers = ['## Statblock Examples',
'## Token Examples',
'## 3D Model Examples']
with gr.Tab("Statblock Examples"):
gr.Markdown(example_headers[0])
examples = u.absolute_path("./galleries/examples/statblocks")
example_gallery = gr.Gallery(label = "Statblock Examples",
show_label = True,
elem_id = "gallery",
columns =[4], rows =[1],
object_fit = "fill",
height = 768,
value = examples)
with gr.Tab("Token Examples"):
example_headers = ['## Statblock Examples',
'## Token Examples',
'## 3D Model Examples']
gr.Markdown(example_headers[1])
examples = u.absolute_path("./galleries/examples/tokens")
example_gallery = gr.Gallery(label = "Token Examples",
show_label = True,
elem_id = "gallery",
columns =[4], rows =[1],
object_fit = "fill",
height = 768,
value = examples)
with gr.Tab("3d Model Examples"):
example_headers = ['## Statblock Examples',
'## Token Examples',
'## 3D Model Examples']
gr.Markdown(example_headers[2])
examples = u.absolute_path("./galleries/examples/models")
with gr.Row():
example_gallery = gr.Model3D(label = "3d Model Examples",
show_label = True,
value = examples[0])
example_gallery = gr.Model3D(label = "3d Model Examples",
show_label = True,
value = examples[1])
if __name__ == "__main__":
demo.launch(allowed_paths=list_of_static_dir)