Spaces:
Runtime error
Runtime error
drakosfire
commited on
Commit
•
738437e
1
Parent(s):
533717d
fixing build
Browse files- .gitattributes +1 -0
- .gitignore +4 -2
- Dockerfile +0 -88
- README.md +27 -0
- __pycache__/description_helper.cpython-310.pyc +0 -0
- __pycache__/process_html.cpython-310.pyc +0 -0
- __pycache__/sd_generator.cpython-310.pyc +0 -0
- __pycache__/statblock_helper.cpython-310.pyc +0 -0
- __pycache__/utilities.cpython-310.pyc +0 -0
- app.py +460 -0
- dependencies/bundle.css +0 -0
- dependencies/style.css +576 -1
- dependencies/themes/assets/parchmentBackground.jpg +0 -0
- dependencies/themes/assets/parchmentBackgroundGrayscale.jpg +0 -0
- description_helper.py +247 -98
- docker_build.log +0 -0
- flagged/log.csv +2 -0
- get-pip.py +0 -0
- main.py +0 -158
- normalize_then_alpha.py +89 -0
- process_html.py +299 -88
- process_text.py +60 -0
- requirements.txt +0 -158
- sd_generator.py +22 -63
- statblock_helper.py +0 -165
- style.css +60 -0
- tripo3d.py +126 -0
- utilities.py +52 -14
.gitattributes
CHANGED
@@ -1 +1,2 @@
|
|
1 |
*.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
1 |
*.png filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
.gitignore
CHANGED
@@ -1,11 +1,13 @@
|
|
1 |
-
#Directories used for testing
|
2 |
models/
|
3 |
homebrewery/
|
4 |
exllamav2/
|
5 |
output/
|
6 |
|
7 |
-
#Test and backup Docker files
|
8 |
Dockerfile - node
|
9 |
Dockerfile copy
|
10 |
Dockerfile Master copy
|
11 |
|
|
|
|
|
|
1 |
+
# Directories used for testing
|
2 |
models/
|
3 |
homebrewery/
|
4 |
exllamav2/
|
5 |
output/
|
6 |
|
7 |
+
# Test and backup Docker files
|
8 |
Dockerfile - node
|
9 |
Dockerfile copy
|
10 |
Dockerfile Master copy
|
11 |
|
12 |
+
# File type to ignore
|
13 |
+
*.glb
|
Dockerfile
DELETED
@@ -1,88 +0,0 @@
|
|
1 |
-
# Stage 1: Node and NPM setup
|
2 |
-
FROM ubuntu:22.04 as node-setup
|
3 |
-
|
4 |
-
ENV NVM_DIR /usr/local/nvm
|
5 |
-
ENV NODE_VERSION 20.10.0
|
6 |
-
|
7 |
-
# Install dependencies and Node.js
|
8 |
-
RUN mkdir -p /usr/local/nvm \
|
9 |
-
&& apt-get update \
|
10 |
-
&& apt-get remove -y nodejs \
|
11 |
-
&& apt-get autoremove -y \
|
12 |
-
&& rm -rf /var/lib/apt/lists/*
|
13 |
-
# Bundle app source and build application
|
14 |
-
RUN apt-get update \
|
15 |
-
&& apt-get install git -y\
|
16 |
-
&& apt-get install npm -y \
|
17 |
-
&& apt-get install -y python3 python3-pip \
|
18 |
-
&& apt install -y git \
|
19 |
-
# && pip install --upgrade pip \
|
20 |
-
&& apt-get install wget --assume-yes \
|
21 |
-
&& wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \
|
22 |
-
&& . $NVM_DIR/nvm.sh \
|
23 |
-
&& nvm install $NODE_VERSION \
|
24 |
-
&& nvm use $NODE_VERSION \
|
25 |
-
&& nvm alias default $NODE_VERSION \
|
26 |
-
&& node --version \
|
27 |
-
&& nvm --version
|
28 |
-
|
29 |
-
FROM ubuntu:22.04 as python-env
|
30 |
-
|
31 |
-
# Install Python and dependencies
|
32 |
-
COPY requirements.txt /tmp/
|
33 |
-
RUN apt-get update && \
|
34 |
-
apt install -y git && \
|
35 |
-
apt-get install -y python3 python3-pip && \
|
36 |
-
pip3 install -r /tmp/requirements.txt && \
|
37 |
-
pip install auto-gptq && \
|
38 |
-
rm /tmp/requirements.txt
|
39 |
-
|
40 |
-
|
41 |
-
# Stage 3: Final image based on NVIDIA CUDA base
|
42 |
-
FROM nvidia/cuda:12.1.0-devel-ubuntu22.04
|
43 |
-
|
44 |
-
RUN useradd -m -u 1000 user
|
45 |
-
# Copy over the node_modules from the previous stage
|
46 |
-
|
47 |
-
COPY --from=node-setup /usr/local/nvm /usr/local/nvm
|
48 |
-
|
49 |
-
# Copy Python environment from python-env stage
|
50 |
-
COPY --from=python-env /usr/local /usr/local
|
51 |
-
|
52 |
-
# Set up environment variables for Node and Python
|
53 |
-
ENV NVM_DIR /usr/local/nvm
|
54 |
-
ENV NODE_VERSION 20.10.0
|
55 |
-
ENV NODE_PATH $NVM_DIR/versions/node/v$NODE_VERSION/lib/node_modules
|
56 |
-
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
|
57 |
-
|
58 |
-
#Copy Homebrewery css dependencies
|
59 |
-
COPY dependencies /home/user/app
|
60 |
-
|
61 |
-
|
62 |
-
# Set working directory and user
|
63 |
-
WORKDIR /home/user/app
|
64 |
-
|
65 |
-
# Additional setup, must make .cache to bypass an exllama build bug, make sure user can access, then mk dir for app, and steps to install flash-attn which must be done AFTER everything else is built and in this stage to access CUDA
|
66 |
-
RUN mkdir -p /home/user/.cache && \
|
67 |
-
chmod 777 /home/user/.cache && \
|
68 |
-
apt-get update && \
|
69 |
-
apt install -y git && \
|
70 |
-
apt-get install -y python3-pip && \
|
71 |
-
pip install flash-attn && \
|
72 |
-
git clone -b experimentalCommandLineBrewProcess https://github.com/G-Ambatte/homebrewery.git && \
|
73 |
-
cd homebrewery && \
|
74 |
-
npm install && \
|
75 |
-
chown -R user:user /home/user/app/ && \
|
76 |
-
chown -R user:user /home/user/app/homebrewery
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
USER user
|
82 |
-
|
83 |
-
|
84 |
-
# Set the entrypoint
|
85 |
-
EXPOSE 8000
|
86 |
-
ENTRYPOINT ["/usr/bin/python3", "main.py"]
|
87 |
-
|
88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Drakosfires Dungeons and Dragons Statblock Generator
|
3 |
+
emoji: 🃏
|
4 |
+
colorFrom: yellow
|
5 |
+
colorTo: purple
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 4.26.0
|
8 |
+
app_file: app.py
|
9 |
+
pinned: false
|
10 |
+
---
|
11 |
+
|
12 |
+
# Drakosfire's Dungeons and Dragons Statblock Generator
|
13 |
+
|
14 |
+
Welcome to the Drakosfire's Dungeons and Dragons Statblock Generator! This innovative tool harnesses the power of AI to generate unique statblocks as html pages that are ready to print! Including images for paper tokens and 3D models for your D&D adventures.
|
15 |
+
|
16 |
+
## Overview
|
17 |
+
|
18 |
+
This generator leverages an API call to Open AI ChatGPT 4o, [Replicate](https://replicate.com/) combined with a custom fine-tuned version of the Stable Diffusion SDXL model to generate paper token images. You can find more about the specific model this project was based on at [Civitai](https://civitai.com/models/129681/sdxl-faetastic).
|
19 |
+
Leveraging the incredible [Tripo3d](https://www.tripo3d.ai/) to convert 2d images into 3d models with textures.
|
20 |
+
|
21 |
+
## Key Features
|
22 |
+
|
23 |
+
Coming Soon
|
24 |
+
|
25 |
+
|
26 |
+
|
27 |
+
I hope you enjoy enhancing your Dungeons and Dragons experience with this unique tool. Happy adventuring!
|
__pycache__/description_helper.cpython-310.pyc
DELETED
Binary file (2.58 kB)
|
|
__pycache__/process_html.cpython-310.pyc
DELETED
Binary file (3.67 kB)
|
|
__pycache__/sd_generator.cpython-310.pyc
DELETED
Binary file (1.83 kB)
|
|
__pycache__/statblock_helper.cpython-310.pyc
DELETED
Binary file (3.31 kB)
|
|
__pycache__/utilities.cpython-310.pyc
DELETED
Binary file (4.79 kB)
|
|
app.py
ADDED
@@ -0,0 +1,460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# this imports the code from files and modules
|
2 |
+
import description_helper as dsh
|
3 |
+
import gradio as gr
|
4 |
+
import sd_generator as sd
|
5 |
+
import utilities as u
|
6 |
+
import process_html
|
7 |
+
import process_text
|
8 |
+
import os
|
9 |
+
import ctypes
|
10 |
+
import tripo3d as tripo3d
|
11 |
+
|
12 |
+
# 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
|
13 |
+
libc = ctypes.cdll.LoadLibrary("libc.so.6")
|
14 |
+
M_MMAP_THRESHOLD = -3
|
15 |
+
|
16 |
+
# Set malloc mmap threshold.
|
17 |
+
libc.mallopt(M_MMAP_THRESHOLD, 2**20)
|
18 |
+
|
19 |
+
style_css = custom_css = """
|
20 |
+
<link href='file=/media/drakosfire/Shared/Docker/StatblockGenerator/dependencies/all.css' rel='stylesheet' />
|
21 |
+
<link href='file=/media/drakosfire/Shared/Docker/StatblockGenerator/dependencies/css.css?family=Open+Sans:400,300,600,700' rel='stylesheet' type='text/css' />
|
22 |
+
<link href='file=/media/drakosfire/Shared/Docker/StatblockGenerator/dependencies/bundle.css' rel='stylesheet' />
|
23 |
+
<link href='file=/media/drakosfire/Shared/Docker/StatblockGenerator/dependencies/style.css' rel='stylesheet' />
|
24 |
+
<link href='file=/media/drakosfire/Shared/Docker/StatblockGenerator/dependencies/5ePHBstyle.css' rel='stylesheet' />
|
25 |
+
"""
|
26 |
+
|
27 |
+
# Build gradio app
|
28 |
+
|
29 |
+
with gr.Blocks(css = "style.css") as demo:
|
30 |
+
# Functions and State Variables
|
31 |
+
mon_name = gr.State()
|
32 |
+
mon_size = gr.State()
|
33 |
+
mon_type = gr.State()
|
34 |
+
mon_subtype = gr.State()
|
35 |
+
mon_alignment = gr.State()
|
36 |
+
mon_armor_class = gr.State()
|
37 |
+
mon_hp = gr.State()
|
38 |
+
mon_hit_dice = gr.State()
|
39 |
+
mon_speed = gr.State()
|
40 |
+
mon_abilities = gr.State()
|
41 |
+
mon_saving_throws = gr.State()
|
42 |
+
mon_skills = gr.State()
|
43 |
+
mon_damage_resistance = gr.State()
|
44 |
+
mon_senses = gr.State()
|
45 |
+
mon_languages = gr.State()
|
46 |
+
mon_challenge_rating = gr.State()
|
47 |
+
mon_xp = gr.State()
|
48 |
+
mon_actions = gr.State()
|
49 |
+
mon_cantrips = gr.State()
|
50 |
+
mon_spells = gr.State()
|
51 |
+
mon_spell_slots = gr.State()
|
52 |
+
mon_legendary_actions = gr.State()
|
53 |
+
mon_description = gr.State()
|
54 |
+
mon_sd_prompt = gr.State()
|
55 |
+
# Image Variables
|
56 |
+
generated_image_list = gr.State([])
|
57 |
+
selected_generated_image = gr.State()
|
58 |
+
selected_seed_image = gr.State()
|
59 |
+
selected_token_image = gr.State()
|
60 |
+
|
61 |
+
|
62 |
+
|
63 |
+
# Take input from gradio user and call the llm in description_helper
|
64 |
+
def gen_mon_desc(user_monster_text, spellcaster, legendary_actions):
|
65 |
+
|
66 |
+
# declare cantrips, spells, and spell slots as empty string unless the variable is changed.
|
67 |
+
mon_cantrips = ""
|
68 |
+
mon_spells = ""
|
69 |
+
mon_spell_slots = ""
|
70 |
+
mon_legendary_actions = ""
|
71 |
+
|
72 |
+
llm_output = dsh.call_llm_and_cleanup(user_monster_text, spellcaster, legendary_actions)
|
73 |
+
user_monster = dsh.convert_to_dict(llm_output)
|
74 |
+
user_monster = llm_output
|
75 |
+
keys_list = list(user_monster)
|
76 |
+
mon_name = user_monster['name']
|
77 |
+
mon_size = user_monster['size']
|
78 |
+
mon_type = user_monster['type']
|
79 |
+
mon_subtype = user_monster['subtype']
|
80 |
+
mon_alignment = user_monster['alignment']
|
81 |
+
mon_armor_class = user_monster['armor_class']
|
82 |
+
mon_hp = user_monster['hit_points']
|
83 |
+
mon_hit_dice = user_monster['hit_dice']
|
84 |
+
mon_speed = user_monster['speed']
|
85 |
+
if type(mon_speed) == dict:
|
86 |
+
mon_speed = process_text.format_mon_qualities(mon_speed)
|
87 |
+
|
88 |
+
mon_abilities = process_text.format_abilities_for_editing(user_monster['abilities'])
|
89 |
+
mon_saving_throws = user_monster['saving_throws']
|
90 |
+
if type(mon_saving_throws) == dict:
|
91 |
+
mon_saving_throws = process_text.format_mon_qualities(mon_saving_throws)
|
92 |
+
|
93 |
+
mon_skills = user_monster['skills']
|
94 |
+
if type(mon_skills) == dict:
|
95 |
+
mon_skills = process_text.format_mon_qualities(mon_skills)
|
96 |
+
mon_damage_resistance = user_monster['damage_resistance']
|
97 |
+
if type(mon_damage_resistance) == dict:
|
98 |
+
mon_damage_resistance = process_text.format_mon_qualities(mon_damage_resistance)
|
99 |
+
mon_senses = user_monster['senses']
|
100 |
+
if type(mon_senses) == dict:
|
101 |
+
mon_senses = process_text.format_mon_qualities(mon_senses)
|
102 |
+
mon_languages = user_monster['languages']
|
103 |
+
mon_challenge_rating = user_monster['challenge_rating']
|
104 |
+
mon_xp = user_monster['xp']
|
105 |
+
if 'actions' in keys_list:
|
106 |
+
mon_actions = process_text.format_actions_for_editing(user_monster['actions'])
|
107 |
+
|
108 |
+
|
109 |
+
if "spells" in keys_list :
|
110 |
+
print(user_monster['spells'])
|
111 |
+
print(f"Length of spells : {len(user_monster['spells'])}")
|
112 |
+
if len(user_monster['spells']) >= 1 and user_monster['spells'] != "{}":
|
113 |
+
mon_spells = user_monster['spells']
|
114 |
+
mon_cantrips,mon_spells,mon_spell_slots = process_text.format_spells_for_editing(mon_spells)
|
115 |
+
print(mon_cantrips,mon_spells, mon_spell_slots)
|
116 |
+
|
117 |
+
if 'legendary_actions' in keys_list and len(user_monster['legendary_actions']) >= 1 and user_monster['legendary_actions'] != "{}":
|
118 |
+
mon_legendary_actions = user_monster['legendary_actions']
|
119 |
+
if type(mon_legendary_actions) == dict:
|
120 |
+
mon_legendary_actions = process_text.format_legendaries_for_editing(mon_legendary_actions)
|
121 |
+
|
122 |
+
mon_description = user_monster['description']
|
123 |
+
mon_sd_prompt = user_monster['sd_prompt']
|
124 |
+
|
125 |
+
#Return each State variable twice, once to the variable and once to the textbox
|
126 |
+
return [mon_name,mon_name,
|
127 |
+
mon_size,mon_size,
|
128 |
+
mon_type,mon_type,
|
129 |
+
mon_subtype,mon_subtype,
|
130 |
+
mon_alignment, mon_alignment,
|
131 |
+
mon_armor_class, mon_armor_class,
|
132 |
+
mon_hp, mon_hp,
|
133 |
+
mon_hit_dice, mon_hit_dice,
|
134 |
+
mon_speed, mon_speed,
|
135 |
+
mon_abilities,mon_abilities,
|
136 |
+
mon_saving_throws, mon_saving_throws,
|
137 |
+
mon_skills, mon_skills,
|
138 |
+
mon_damage_resistance, mon_damage_resistance,
|
139 |
+
mon_senses, mon_senses,
|
140 |
+
mon_languages, mon_languages,
|
141 |
+
mon_challenge_rating, mon_challenge_rating,
|
142 |
+
mon_xp, mon_xp,
|
143 |
+
mon_actions, mon_actions,
|
144 |
+
mon_cantrips, mon_cantrips, mon_spells, mon_spells,mon_spell_slots,mon_spell_slots,
|
145 |
+
mon_legendary_actions, mon_legendary_actions,
|
146 |
+
mon_description, mon_description,
|
147 |
+
mon_sd_prompt,mon_sd_prompt
|
148 |
+
]
|
149 |
+
|
150 |
+
|
151 |
+
# Called on user selecting an image from the gallery, outputs the path of the image
|
152 |
+
def assign_img_path(evt: gr.SelectData):
|
153 |
+
img_dict = evt.value
|
154 |
+
print(img_dict)
|
155 |
+
selected_image_path = img_dict['image']['url']
|
156 |
+
print(selected_image_path)
|
157 |
+
return selected_image_path
|
158 |
+
|
159 |
+
# Make a list of files in image_temp and delete them
|
160 |
+
def delete_temp_images():
|
161 |
+
image_list = u.directory_contents('./image_temp')
|
162 |
+
u.delete_files(image_list)
|
163 |
+
#img2img.image_list.clear()
|
164 |
+
|
165 |
+
# Called when pressing button to generate image, updates gallery by returning the list of image URLs
|
166 |
+
def generate_image_update_gallery(image_prompt,image_name, token = False):
|
167 |
+
delete_temp_images()
|
168 |
+
print(f"sd_prompt is a {type(image_prompt)}")
|
169 |
+
image_list = []
|
170 |
+
|
171 |
+
num_img = 4
|
172 |
+
for x in range(num_img):
|
173 |
+
preview = sd.preview_and_generate_image(image_name, image_prompt,token)
|
174 |
+
image_list.append(preview)
|
175 |
+
yield image_list
|
176 |
+
del preview
|
177 |
+
|
178 |
+
return image_list
|
179 |
+
|
180 |
+
def generate_token_update_gallery(image_prompt,image_name,token = True):
|
181 |
+
delete_temp_images()
|
182 |
+
print(f"sd_prompt is a {type(image_prompt)}")
|
183 |
+
image_list = []
|
184 |
+
|
185 |
+
num_img = 4
|
186 |
+
for x in range(num_img):
|
187 |
+
preview = sd.preview_and_generate_image(image_name, image_prompt,token)
|
188 |
+
image_list.append(preview)
|
189 |
+
yield image_list
|
190 |
+
del preview
|
191 |
+
|
192 |
+
return image_list
|
193 |
+
|
194 |
+
|
195 |
+
# Generate a 3D model by passing the chosen token image to Tripo3D.ai API
|
196 |
+
def generate_model_update_gallery(image):
|
197 |
+
generated_model = tripo3d.generate_model(image)
|
198 |
+
return generated_model
|
199 |
+
|
200 |
+
# Build html text by processing the generated dictionaries.
|
201 |
+
def build_html_file(mon_name_output,
|
202 |
+
mon_size_output,
|
203 |
+
mon_type_output,
|
204 |
+
mon_subtype_output,
|
205 |
+
mon_alignment_output,
|
206 |
+
mon_armor_class_output,
|
207 |
+
mon_hp_output,
|
208 |
+
mon_hit_dice_output,
|
209 |
+
mon_speed_output,
|
210 |
+
mon_abilities,
|
211 |
+
mon_saving_throws_output,
|
212 |
+
mon_skills_output,
|
213 |
+
mon_damage_resistance_output,
|
214 |
+
mon_senses_output,
|
215 |
+
mon_languages_output,
|
216 |
+
mon_challenge_rating_output,
|
217 |
+
mon_xp_output,
|
218 |
+
mon_actions_output,
|
219 |
+
selected_generated_image,
|
220 |
+
mon_description_output,
|
221 |
+
mon_cantrips_output,
|
222 |
+
mon_spells_output,
|
223 |
+
mon_spell_slot_output,
|
224 |
+
mon_legendary_actions_output
|
225 |
+
):
|
226 |
+
mon_file_path = process_html.build_html_base(
|
227 |
+
mon_name_output,
|
228 |
+
mon_size_output,
|
229 |
+
mon_type_output,
|
230 |
+
mon_subtype_output,
|
231 |
+
mon_alignment_output,
|
232 |
+
mon_armor_class_output,
|
233 |
+
mon_hp_output,
|
234 |
+
mon_hit_dice_output,
|
235 |
+
mon_speed_output,
|
236 |
+
mon_abilities,
|
237 |
+
mon_saving_throws_output,
|
238 |
+
mon_skills_output,
|
239 |
+
mon_damage_resistance_output,
|
240 |
+
mon_senses_output,
|
241 |
+
mon_languages_output,
|
242 |
+
mon_challenge_rating_output,
|
243 |
+
mon_xp_output,
|
244 |
+
mon_actions_output,
|
245 |
+
selected_generated_image,
|
246 |
+
mon_description_output,
|
247 |
+
mon_cantrips_output,
|
248 |
+
mon_spells_output,
|
249 |
+
mon_spell_slot_output,
|
250 |
+
mon_legendary_actions_output,
|
251 |
+
|
252 |
+
|
253 |
+
)
|
254 |
+
mon_file_path = u.file_name_list[0]+'/' + u.file_name_list[1] +'.html'
|
255 |
+
if not os.path.exists(mon_file_path):
|
256 |
+
print(f"{mon_file_path} not found")
|
257 |
+
else: print(f"{mon_file_path} found")
|
258 |
+
iframe = iframe = f"""<iframe src="file={mon_file_path}" width="100%" height="500px"></iframe>"""
|
259 |
+
|
260 |
+
return iframe
|
261 |
+
|
262 |
+
# Build the html and file path to pass to gradio to output html file in gr.html
|
263 |
+
def gen_link():
|
264 |
+
mon_file_path = u.file_name_list[0]+'/' + u.file_name_list[1] +'.html'
|
265 |
+
if not os.path.exists(mon_file_path):
|
266 |
+
print(f"{mon_file_path} not found")
|
267 |
+
else: print(f"{mon_file_path} found")
|
268 |
+
iframe = iframe = f"""<iframe src="file={mon_file_path}" width="100%" height="500px"></iframe>"""
|
269 |
+
link = f'<a href="file={mon_file_path}" target="_blank">{u.file_name_list[1] +".html"}</a>'
|
270 |
+
return iframe
|
271 |
+
|
272 |
+
gr.HTML(""" <div id="inner"> <header>
|
273 |
+
<h1>Monster Statblock Generator</h1>
|
274 |
+
<p>
|
275 |
+
With this AI driven tool you will build a collectible style card of a fantasy flavored item with details.
|
276 |
+
</p>
|
277 |
+
</div>""")
|
278 |
+
|
279 |
+
markdown_instructions = """## How It Works
|
280 |
+
This tool is a fun way to quickly generate Dungeons and Dragons monster manual style statblocks with art and a token for a Virtual Table Top.
|
281 |
+
1. Your intitial text along with the prompt is sent to Llama 3 70b to generate all the values for a Dungeons and Dragons creature.
|
282 |
+
a. Include as much or as little information as you'd like.
|
283 |
+
b. Just a name : The Flavor Lich
|
284 |
+
c. A bit of detail : A friendly skeletal lich who is a master of flavor, called The Flavor Lich
|
285 |
+
d. Lots of detail : A friendly skeletal lich who is a master of flavor, called The Flavor Lich, the Lich is Challenge Rating 8 and is a 4th level spell caster whose spells are all about food.
|
286 |
+
2. The results will populate below in editable fields that are saved on edit.
|
287 |
+
3. Review the results, make any changes you'd like.
|
288 |
+
## The first image generation take about 2 minutes for model to 'cold boot' after that it's ~10s per image.
|
289 |
+
**Image and Text Generation**: Now you can generate 4 images for the statblock page without text and pick your favorite.
|
290 |
+
4. Click 'Generate Statblock Art' and wait for the images to generate, then select the one you'd like to use.
|
291 |
+
5. Click 'Generate HTML' to generate a webpage that can be saved or printed as PDF.
|
292 |
+
6. Last, you can generate a Virtual Tabletop token or figure of your creature.
|
293 |
+
"""
|
294 |
+
|
295 |
+
gr.Markdown(markdown_instructions)
|
296 |
+
with gr.Tab("Generator"):
|
297 |
+
|
298 |
+
with gr.Row():
|
299 |
+
with gr.Column():
|
300 |
+
user_mon_description = gr.Textbox(label = "Step 1 : Write a description and give a name or type to your creation!",
|
301 |
+
lines = 1, placeholder=f"Ex : A friendly skeletal lich who is a master of flavor, called The Flavor Lich",
|
302 |
+
|
303 |
+
elem_id= "custom-textbox")
|
304 |
+
with gr.Column():
|
305 |
+
spells_checkbox = gr.Checkbox(label= "Spellcaster?")
|
306 |
+
legendary_action_checkbox = gr.Checkbox(label= "Legendary Actions?")
|
307 |
+
|
308 |
+
desc_gen = gr.Button(value = "Step 2 : Generate Description")
|
309 |
+
|
310 |
+
mon_description_output = gr.Textbox(label = 'Description', lines = 2, interactive=True)
|
311 |
+
|
312 |
+
|
313 |
+
with gr.Row():
|
314 |
+
with gr.Column(scale = 1):
|
315 |
+
mon_name_output = gr.Textbox(label = 'Name', lines = 1, interactive=True)
|
316 |
+
mon_size_output = gr.Textbox(label = 'Size', lines = 1, interactive=True)
|
317 |
+
mon_alignment_output = gr.Textbox(label = 'Alignment', lines = 1, interactive=True)
|
318 |
+
mon_armor_class_output = gr.Textbox(label = 'Armor Class', lines = 1, interactive=True)
|
319 |
+
mon_hit_dice_output = gr.Textbox(label = 'Hit Dice', lines = 1, interactive=True)
|
320 |
+
mon_senses_output = gr.Textbox(label = 'Senses', lines = 1, interactive=True)
|
321 |
+
mon_actions_output = gr.Textbox(label = 'Actions', lines = 16, interactive=True)
|
322 |
+
|
323 |
+
with gr.Column(scale = 1):
|
324 |
+
mon_type_output = gr.Textbox(label = 'Type', lines = 1, interactive=True)
|
325 |
+
mon_speed_output = gr.Textbox(label = 'Speed', lines = 1, interactive=True)
|
326 |
+
mon_abilities_output = gr.Textbox(label ='Ability Scores', lines = 5, interactive=True)
|
327 |
+
mon_damage_resistance_output = gr.Textbox(label = 'Damage Resistance', lines = 1, interactive=True)
|
328 |
+
mon_challenge_rating_output = gr.Textbox(label = 'Challenge Rating', lines = 1, interactive=True)
|
329 |
+
mon_cantrips_output = gr.Textbox(label = 'Cantrips', lines = 16, interactive=True)
|
330 |
+
mon_spells_output = gr.Textbox(label = 'Spells', lines = 16, interactive=True)
|
331 |
+
mon_spell_slot_output = gr.Textbox(label = 'Spell Slots', lines = 8, interactive=True)
|
332 |
+
|
333 |
+
with gr.Column(scale = 1):
|
334 |
+
mon_subtype_output = gr.Textbox(label = 'Subtype', lines = 1, interactive=True)
|
335 |
+
mon_saving_throws_output = gr.Textbox(label = 'Saving Throws', lines = 1, interactive=True)
|
336 |
+
mon_skills_output = gr.Textbox(label = 'Skills', lines = 1, interactive=True)
|
337 |
+
mon_hp_output = gr.Textbox(label = 'Health Points', lines = 1, interactive=True)
|
338 |
+
mon_languages_output = gr.Textbox(label = 'Languages', lines = 1, interactive=True)
|
339 |
+
mon_xp_output = gr.Textbox(label = 'XP', lines = 1, interactive=True)
|
340 |
+
mon_legendary_actions_output = gr.Textbox(label = 'Legendary Actions', lines = 16, interactive=True)
|
341 |
+
|
342 |
+
mon_sd_prompt_output = gr.Textbox(label = 'Image Generation Prompt', lines = 1, interactive=True)
|
343 |
+
|
344 |
+
with gr.Row():
|
345 |
+
with gr.Column():
|
346 |
+
image_gen = gr.Button(value = "Generate Art for the statblock" )
|
347 |
+
mon_image_gallery = gr.Gallery(label = "Generated Images",
|
348 |
+
show_label = True,
|
349 |
+
elem_id = "gallery",
|
350 |
+
columns =[4], rows =[1],
|
351 |
+
object_fit = "contain", height ="auto")
|
352 |
+
|
353 |
+
with gr.Column():
|
354 |
+
token_gen = gr.Button(value = "Generate Token Image" )
|
355 |
+
mon_token_gallery = gr.Gallery(label = "Generated Tokens",
|
356 |
+
show_label = True,
|
357 |
+
elem_id = "token_gallery",
|
358 |
+
columns =[4], rows =[1],
|
359 |
+
object_fit = "contain", height ="auto")
|
360 |
+
model_gen = gr.Button(value= "Generate a 3D model")
|
361 |
+
mon_model_gallery = gr.Model3D(label = "3D Model Display",
|
362 |
+
elem_id = "model_gallery",
|
363 |
+
)
|
364 |
+
|
365 |
+
|
366 |
+
|
367 |
+
|
368 |
+
|
369 |
+
|
370 |
+
image_gen.click(generate_image_update_gallery, inputs = [mon_sd_prompt_output,mon_name], outputs = mon_image_gallery)
|
371 |
+
token_gen.click(generate_token_update_gallery, inputs = [mon_sd_prompt_output,mon_name], outputs = mon_token_gallery)
|
372 |
+
model_gen.click(generate_model_update_gallery, inputs = selected_generated_image, outputs = mon_model_gallery)
|
373 |
+
|
374 |
+
mon_image_gallery.select(fn = assign_img_path, outputs=selected_generated_image)
|
375 |
+
mon_token_gallery.select(fn = assign_img_path, outputs=selected_token_image)
|
376 |
+
|
377 |
+
desc_gen.click(fn = gen_mon_desc, inputs = [user_mon_description,spells_checkbox, legendary_action_checkbox],
|
378 |
+
outputs= [mon_name, mon_name_output,
|
379 |
+
mon_size,mon_size_output,
|
380 |
+
mon_type,mon_type_output,
|
381 |
+
mon_subtype,mon_subtype_output,
|
382 |
+
mon_alignment, mon_alignment_output,
|
383 |
+
mon_armor_class, mon_armor_class_output,
|
384 |
+
mon_hp, mon_hp_output,
|
385 |
+
mon_hit_dice, mon_hit_dice_output,
|
386 |
+
mon_speed, mon_speed_output,
|
387 |
+
mon_abilities, mon_abilities_output,
|
388 |
+
mon_saving_throws, mon_saving_throws_output,
|
389 |
+
mon_skills, mon_skills_output,
|
390 |
+
mon_damage_resistance, mon_damage_resistance_output,
|
391 |
+
mon_senses, mon_senses_output,
|
392 |
+
mon_languages, mon_languages_output,
|
393 |
+
mon_challenge_rating, mon_challenge_rating_output,
|
394 |
+
mon_xp, mon_xp_output,
|
395 |
+
mon_actions, mon_actions_output,
|
396 |
+
mon_cantrips,mon_cantrips_output,
|
397 |
+
mon_spells, mon_spells_output,
|
398 |
+
mon_spell_slots, mon_spell_slot_output,
|
399 |
+
mon_legendary_actions, mon_legendary_actions_output,
|
400 |
+
mon_description, mon_description_output,
|
401 |
+
mon_sd_prompt,mon_sd_prompt_output
|
402 |
+
|
403 |
+
])
|
404 |
+
|
405 |
+
|
406 |
+
# Build buttons to modify to html and show html
|
407 |
+
gen_html = gr.Button(value = "Step 8 : Generate html, click once then go to Step 9")
|
408 |
+
html = gr.HTML(label="HTML preview", show_label=True)
|
409 |
+
gen_html.click(build_html_file,inputs =[
|
410 |
+
mon_name_output,
|
411 |
+
mon_size_output,
|
412 |
+
mon_type_output,
|
413 |
+
mon_subtype_output,
|
414 |
+
mon_alignment_output,
|
415 |
+
mon_armor_class_output,
|
416 |
+
mon_hp_output,
|
417 |
+
mon_hit_dice_output,
|
418 |
+
mon_speed_output,
|
419 |
+
mon_abilities_output,
|
420 |
+
mon_saving_throws_output,
|
421 |
+
mon_skills_output,
|
422 |
+
mon_damage_resistance_output,
|
423 |
+
mon_senses_output,
|
424 |
+
mon_languages_output,
|
425 |
+
mon_challenge_rating_output,
|
426 |
+
mon_xp_output,
|
427 |
+
mon_actions_output,
|
428 |
+
mon_description_output,
|
429 |
+
selected_generated_image,
|
430 |
+
mon_cantrips_output,
|
431 |
+
mon_spells_output,
|
432 |
+
mon_spell_slot_output,
|
433 |
+
mon_legendary_actions_output,
|
434 |
+
|
435 |
+
],
|
436 |
+
outputs= html
|
437 |
+
)
|
438 |
+
|
439 |
+
|
440 |
+
|
441 |
+
|
442 |
+
|
443 |
+
|
444 |
+
def main() -> None:
|
445 |
+
# run web server, expose port 8000, share to create web link, give app access folder path, gradio was updated for security and can no longer serve any directory not specified.
|
446 |
+
if __name__ == '__main__':
|
447 |
+
demo.launch(server_name = "0.0.0.0", server_port = 8000, share = False, allowed_paths = ["/media/drakosfire/Shared/Docker/StatblockGenerator/output","/media/drakosfire/Shared/Docker/StatblockGenerator/dependencies"])
|
448 |
+
|
449 |
+
main()
|
450 |
+
|
451 |
+
|
452 |
+
|
453 |
+
|
454 |
+
|
455 |
+
|
456 |
+
|
457 |
+
|
458 |
+
|
459 |
+
|
460 |
+
|
dependencies/bundle.css
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
dependencies/style.css
CHANGED
@@ -17,4 +17,579 @@
|
|
17 |
@font-face{font-family:Overpass;src:url('./themes/fonts/5e/Overpass Medium.woff2');font-weight:500;font-style:normal}@font-face{font-family:Davek;src:url('./themes/fonts/5e/Davek.woff2');font-weight:500;font-style:normal}
|
18 |
@font-face{font-family:Iokharic;src:url('./themes/fonts/5e/Iokharic.woff2');font-weight:500;font-style:normal}
|
19 |
@font-face{font-family:Rellanic;src:url('./themes/fonts/5e/Rellanic.woff2');font-weight:500;font-style:normal}:root{--HB_Color_Background:#FFFFFF;--HB_Color_WatercolorStain:#000000}
|
20 |
-
@page{margin:0}body{counter-reset:phb-page-numbers}*{-webkit-print-color-adjust:exact}.page .block{break-inside:avoid;display:inline-block;width:100%}.page .block img{z-index:0}.page .inline-block{display:inline-block;text-indent:initial}.columnWrapper{max-height:100%;column-span:all;columns:inherit;column-gap:inherit;column-fill:inherit}.page{column-fill:auto;column-count:2;height:279.4mm;width:215.9mm;padding:1.4cm 1.9cm 1.7cm;counter-increment:phb-page-numbers;background-color:var(--HB_Color_Background);position:relative;z-index:15;box-sizing:border-box;overflow:hidden;text-rendering:optimizeLegibility;page-break-before:always;page-break-after:always;contain:size}.page p{overflow-wrap:break-word;display:block}.page strong{font-weight:bold}.page em{font-style:italic}.page sup{vertical-align:super;font-size:smaller;line-height:0}.page sub{vertical-align:sub;font-size:smaller;line-height:0}.page ul{list-style-position:outside;list-style-type:disc;padding-left:1.4em}.page ol{list-style-position:outside;list-style-type:decimal;padding-left:1.4em}.page img{z-index:-1}.page h1,.page h2,.page h3,.page h4,.page h5,.page h6{font-weight:bold;line-height:1.2em}.page h1{font-size:2em}.page h2{font-size:1.5em}.page h3{font-size:1.17em}.page h4{font-size:1em}.page h5{font-size:.83em}.page table{width:100%}.page table thead{display:table-row-group;font-weight:bold}.page div:not(.columnWrapper)>table+table{margin-top:0}.page code{font-family:"Courier New",Courier,monospace;white-space:pre-wrap;overflow-wrap:break-word}.page pre code{width:100%;display:inline-block}.page .columnSplit{visibility:hidden;-webkit-column-break-after:always;break-after:always;-moz-column-break-after:always;margin-top:0}.page .columnSplit+*{margin-top:0}.page blockquote,.page table{z-index:15;-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid}.page ul ul,.page ol ol,.page ul ol,.page ol ul{margin-bottom:0px;margin-left:1.5em}.page li{-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid}.page .watermark{display:grid !important;place-items:center;justify-content:center;position:absolute;margin:0;top:0;left:0;width:100%;height:100%;font-size:120px;text-transform:uppercase;mix-blend-mode:overlay;opacity:30%;transform:rotate(-45deg);z-index:500}.page .watermark p{margin-bottom:none}.page [class*="watercolor"]{position:absolute;width:2000px;height:2000px;-webkit-mask-image:var(--wc);-webkit-mask-size:contain;-webkit-mask-repeat:no-repeat;mask-image:var(--wc);mask-size:contain;mask-repeat:no-repeat;background-size:cover;background-color:var(--HB_Color_WatercolorStain);--wc:url('/assets/watercolor/watercolor1.png');z-index:-2}.page .watercolor1{--wc:url('/assets/watercolor/watercolor1.png')}.page .watercolor2{--wc:url('/assets/watercolor/watercolor2.png')}.page .watercolor3{--wc:url('/assets/watercolor/watercolor3.png')}.page .watercolor4{--wc:url('/assets/watercolor/watercolor4.png')}.page .watercolor5{--wc:url('/assets/watercolor/watercolor5.png')}.page .watercolor6{--wc:url('/assets/watercolor/watercolor6.png')}.page .watercolor7{--wc:url('/assets/watercolor/watercolor7.png')}.page .watercolor8{--wc:url('/assets/watercolor/watercolor8.png')}.page .watercolor9{--wc:url('/assets/watercolor/watercolor9.png')}.page .watercolor10{--wc:url('/assets/watercolor/watercolor10.png')}.page .watercolor11{--wc:url('/assets/watercolor/watercolor11.png')}.page .watercolor12{--wc:url('/assets/watercolor/watercolor12.png')}.page [class*="imageMask"]{position:absolute;height:200%;width:200%;left:50%;bottom:50%;--rotation:0;--revealer:none;--checkerboard:none;--scaleX:1;--scaleY:1;-webkit-mask-image:var(--wc),var(--revealer);-webkit-mask-repeat:repeat-x;-webkit-mask-size:50%;-webkit-mask-position:50% calc(50% - var(--offset));mask-image:var(--wc);mask-repeat:repeat-x;mask-size:50%;mask-position:50% calc(50% - var(--offset));background-image:var(--checkerboard);background-size:20px;z-index:-1;transform:translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY))}.page [class*="imageMask"]>p:has(img){position:absolute;width:50%;height:50%;bottom:50%;left:50%;transform:translateX(-50%) translateY(50%) rotate(calc(-1deg * var(--rotation))) scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)))}.page [class*="imageMask"] img{position:absolute;display:block;bottom:0}.page [class*="imageMask"].bottom{--rotation:0}.page [class*="imageMask"].bottom img{bottom:0}.page [class*="imageMask"].top{--rotation:180}.page [class*="imageMask"].top img{top:0}.page [class*="imageMask"].left{--rotation:90}.page [class*="imageMask"].left img{left:0}.page [class*="imageMask"].right{--rotation:-90}.page [class*="imageMask"].right img{right:0}.page [class*="imageMask"].revealImage{--revealer:linear-gradient(0deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.2));--checkerboard:url(/assets/waterColorMasks/missingImage.png)}.page .imageMaskEdge1{--wc:url(/assets/waterColorMasks/edge/0001.webp)}.page .imageMaskEdge2{--wc:url(/assets/waterColorMasks/edge/0002.webp)}.page .imageMaskEdge3{--wc:url(/assets/waterColorMasks/edge/0003.webp)}.page .imageMaskEdge4{--wc:url(/assets/waterColorMasks/edge/0004.webp)}.page .imageMaskEdge5{--wc:url(/assets/waterColorMasks/edge/0005.webp)}.page .imageMaskEdge6{--wc:url(/assets/waterColorMasks/edge/0006.webp)}.page .imageMaskEdge7{--wc:url(/assets/waterColorMasks/edge/0007.webp)}.page .imageMaskEdge8{--wc:url(/assets/waterColorMasks/edge/0008.webp)}.page [class*="imageMaskCenter"]{width:100%;height:100%;left:calc(var(--offsetX));bottom:calc(var(--offsetY));-webkit-mask-image:var(--wc),var(--revealer);-webkit-mask-repeat:no-repeat;-webkit-mask-size:100% 100%;-webkit-mask-position:0% 0%;mask-image:var(--wc),var(--revealer);mask-repeat:no-repeat;mask-size:100% 100%;mask-position:50% 50%;transform:rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY))}.page [class*="imageMaskCenter"]>p:has(img){position:absolute;width:100%;height:100%;bottom:0;left:0;transform:unset;transform:scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY))) rotate(calc(-1deg * var(--rotation))) translateX(calc(-1 * var(--offsetX))) translateY(calc(1 * var(--offsetY)))}.page .imageMaskCenter1{--wc:url(/assets/waterColorMasks/center/0001.webp)}.page .imageMaskCenter2{--wc:url(/assets/waterColorMasks/center/0002.webp)}.page .imageMaskCenter3{--wc:url(/assets/waterColorMasks/center/0003.webp)}.page .imageMaskCenter4{--wc:url(/assets/waterColorMasks/center/0004.webp)}.page .imageMaskCenter5{--wc:url(/assets/waterColorMasks/center/0005.webp)}.page .imageMaskCenter6{--wc:url(/assets/waterColorMasks/center/0006.webp)}.page .imageMaskCenter7{--wc:url(/assets/waterColorMasks/center/0007.webp)}.page .imageMaskCenter8{--wc:url(/assets/waterColorMasks/center/0008.webp)}.page .imageMaskCenter9{--wc:url(/assets/waterColorMasks/center/0009.webp)}.page .imageMaskCenter10{--wc:url(/assets/waterColorMasks/center/0010.webp)}.page .imageMaskCenter11{--wc:url(/assets/waterColorMasks/center/0011.webp)}.page .imageMaskCenter12{--wc:url(/assets/waterColorMasks/center/0012.webp)}.page .imageMaskCenter13{--wc:url(/assets/waterColorMasks/center/0013.webp)}.page .imageMaskCenter14{--wc:url(/assets/waterColorMasks/center/0014.webp)}.page .imageMaskCenter15{--wc:url(/assets/waterColorMasks/center/0015.webp)}.page .imageMaskCenter16{--wc:url(/assets/waterColorMasks/center/0016.webp)}.page .imageMaskCenterspecial{--wc:url(/assets/waterColorMasks/center/special.webp)}.page [class*="imageMaskCorner"]{height:200%;width:200%;left:calc(-50% + var(--offsetX));bottom:calc(-50% + var(--offsetY));-webkit-mask-image:var(--wc),var(--revealer);-webkit-mask-repeat:no-repeat;-webkit-mask-size:100% 100%;-webkit-mask-position:50% 50%;mask-image:var(--wc),var(--revealer);mask-repeat:no-repeat;mask-size:100% 100%;mask-position:50% 50%;transform:rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY))}.page [class*="imageMaskCorner"]>p:has(img){width:50%;height:50%;left:25%;bottom:25%;transform:scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY))) rotate(calc(-1deg * var(--rotation))) translateX(calc(-1 * var(--offsetX))) translateY(calc(1 * var(--offsetY)))}.page .imageMaskCorner1{--wc:url(/assets/waterColorMasks/corner/0001.webp)}.page .imageMaskCorner2{--wc:url(/assets/waterColorMasks/corner/0002.webp)}.page .imageMaskCorner3{--wc:url(/assets/waterColorMasks/corner/0003.webp)}.page .imageMaskCorner4{--wc:url(/assets/waterColorMasks/corner/0004.webp)}.page .imageMaskCorner5{--wc:url(/assets/waterColorMasks/corner/0005.webp)}.page .imageMaskCorner6{--wc:url(/assets/waterColorMasks/corner/0006.webp)}.page .imageMaskCorner7{--wc:url(/assets/waterColorMasks/corner/0007.webp)}.page .imageMaskCorner8{--wc:url(/assets/waterColorMasks/corner/0008.webp)}.page .imageMaskCorner9{--wc:url(/assets/waterColorMasks/corner/0009.webp)}.page .imageMaskCorner10{--wc:url(/assets/waterColorMasks/corner/0010.webp)}.page .imageMaskCorner11{--wc:url(/assets/waterColorMasks/corner/0011.webp)}.page .imageMaskCorner12{--wc:url(/assets/waterColorMasks/corner/0012.webp)}.page .imageMaskCorner13{--wc:url(/assets/waterColorMasks/corner/0013.webp)}.page .imageMaskCorner14{--wc:url(/assets/waterColorMasks/corner/0014.webp)}.page .imageMaskCorner15{--wc:url(/assets/waterColorMasks/corner/0015.webp)}.page .imageMaskCorner16{--wc:url(/assets/waterColorMasks/corner/0016.webp)}.page .imageMaskCorner17{--wc:url(/assets/waterColorMasks/corner/0017.webp)}.page .imageMaskCorner18{--wc:url(/assets/waterColorMasks/corner/0018.webp)}.page .imageMaskCorner19{--wc:url(/assets/waterColorMasks/corner/0019.webp)}.page .imageMaskCorner20{--wc:url(/assets/waterColorMasks/corner/0020.webp)}.page .imageMaskCorner21{--wc:url(/assets/waterColorMasks/corner/0021.webp)}.page .imageMaskCorner22{--wc:url(/assets/waterColorMasks/corner/0022.webp)}.page .imageMaskCorner23{--wc:url(/assets/waterColorMasks/corner/0023.webp)}.page .imageMaskCorner24{--wc:url(/assets/waterColorMasks/corner/0024.webp)}.page .imageMaskCorner25{--wc:url(/assets/waterColorMasks/corner/0025.webp)}.page .imageMaskCorner26{--wc:url(/assets/waterColorMasks/corner/0026.webp)}.page .imageMaskCorner27{--wc:url(/assets/waterColorMasks/corner/0027.webp)}.page .imageMaskCorner28{--wc:url(/assets/waterColorMasks/corner/0028.webp)}.page .imageMaskCorner29{--wc:url(/assets/waterColorMasks/corner/0029.webp)}.page .imageMaskCorner30{--wc:url(/assets/waterColorMasks/corner/0030.webp)}.page .imageMaskCorner31{--wc:url(/assets/waterColorMasks/corner/0031.webp)}.page .imageMaskCorner32{--wc:url(/assets/waterColorMasks/corner/0032.webp)}.page .imageMaskCorner33{--wc:url(/assets/waterColorMasks/corner/0033.webp)}.page .imageMaskCorner34{--wc:url(/assets/waterColorMasks/corner/0034.webp)}.page .imageMaskCorner35{--wc:url(/assets/waterColorMasks/corner/0035.webp)}.page .imageMaskCorner36{--wc:url(/assets/waterColorMasks/corner/0036.webp)}.page .imageMaskCorner37{--wc:url(/assets/waterColorMasks/corner/0037.webp)}.page dl{padding-left:1em;white-space:pre-line}.page dt{display:inline;margin-right:.5ch;margin-left:-1em}.page dd{display:inline;margin-left:0;text-indent:0}.page .blank{height:1em;margin-top:0}.page .blank+*{margin-top:0}.page .wide{column-span:all;display:block;margin-bottom:1em}.page .wide+*{margin-top:0}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
@font-face{font-family:Overpass;src:url('./themes/fonts/5e/Overpass Medium.woff2');font-weight:500;font-style:normal}@font-face{font-family:Davek;src:url('./themes/fonts/5e/Davek.woff2');font-weight:500;font-style:normal}
|
18 |
@font-face{font-family:Iokharic;src:url('./themes/fonts/5e/Iokharic.woff2');font-weight:500;font-style:normal}
|
19 |
@font-face{font-family:Rellanic;src:url('./themes/fonts/5e/Rellanic.woff2');font-weight:500;font-style:normal}:root{--HB_Color_Background:#FFFFFF;--HB_Color_WatercolorStain:#000000}
|
20 |
+
@page {
|
21 |
+
margin: 0;
|
22 |
+
}
|
23 |
+
|
24 |
+
body {
|
25 |
+
counter-reset: phb-page-numbers;
|
26 |
+
}
|
27 |
+
|
28 |
+
* {
|
29 |
+
-webkit-print-color-adjust: exact;
|
30 |
+
}
|
31 |
+
|
32 |
+
.page .block {
|
33 |
+
break-inside: avoid;
|
34 |
+
display: inline-block;
|
35 |
+
width: 100%;
|
36 |
+
}
|
37 |
+
|
38 |
+
.page .block img {
|
39 |
+
z-index: 0;
|
40 |
+
}
|
41 |
+
|
42 |
+
.page .inline-block {
|
43 |
+
display: inline-block;
|
44 |
+
text-indent: initial;
|
45 |
+
}
|
46 |
+
|
47 |
+
.columnWrapper {
|
48 |
+
max-height: 100%;
|
49 |
+
column-span: all;
|
50 |
+
columns: inherit;
|
51 |
+
column-gap: inherit;
|
52 |
+
column-fill: inherit;
|
53 |
+
}
|
54 |
+
|
55 |
+
.page {
|
56 |
+
column-fill: auto;
|
57 |
+
column-count: 2;
|
58 |
+
height: 279.4mm;
|
59 |
+
width: 215.9mm;
|
60 |
+
padding: 1.4cm 1.9cm 1.7cm;
|
61 |
+
counter-increment: phb-page-numbers;
|
62 |
+
background-color: var(--HB_Color_Background);
|
63 |
+
position: relative;
|
64 |
+
z-index: 15;
|
65 |
+
box-sizing: border-box;
|
66 |
+
overflow: hidden;
|
67 |
+
text-rendering: optimizeLegibility;
|
68 |
+
page-break-before: always;
|
69 |
+
page-break-after: always;
|
70 |
+
contain: size;
|
71 |
+
}
|
72 |
+
|
73 |
+
.page p {
|
74 |
+
overflow-wrap: break-word;
|
75 |
+
display: block;
|
76 |
+
}
|
77 |
+
|
78 |
+
.page strong {
|
79 |
+
font-weight: bold;
|
80 |
+
}
|
81 |
+
|
82 |
+
.page em {
|
83 |
+
font-style: italic;
|
84 |
+
}
|
85 |
+
|
86 |
+
.page sup {
|
87 |
+
vertical-align: super;
|
88 |
+
font-size: smaller;
|
89 |
+
line-height: 0;
|
90 |
+
}
|
91 |
+
|
92 |
+
.page sub {
|
93 |
+
vertical-align: sub;
|
94 |
+
font-size: smaller;
|
95 |
+
line-height: 0;
|
96 |
+
}
|
97 |
+
|
98 |
+
.page ul {
|
99 |
+
list-style-position: outside;
|
100 |
+
list-style-type: disc;
|
101 |
+
padding-left: 1.4em;
|
102 |
+
}
|
103 |
+
|
104 |
+
.page ol {
|
105 |
+
list-style-position: outside;
|
106 |
+
list-style-type: decimal;
|
107 |
+
padding-left: 1.4em;
|
108 |
+
}
|
109 |
+
|
110 |
+
.page img {
|
111 |
+
z-index: -1;
|
112 |
+
}
|
113 |
+
|
114 |
+
.page h1,
|
115 |
+
.page h2,
|
116 |
+
.page h3,
|
117 |
+
.page h4,
|
118 |
+
.page h5,
|
119 |
+
.page h6 {
|
120 |
+
font-weight: bold;
|
121 |
+
line-height: 1.2em;
|
122 |
+
}
|
123 |
+
|
124 |
+
.page h1 {
|
125 |
+
font-size: 2em;
|
126 |
+
}
|
127 |
+
|
128 |
+
.page h2 {
|
129 |
+
font-size: 1.5em;
|
130 |
+
}
|
131 |
+
|
132 |
+
.page h3 {
|
133 |
+
font-size: 1.17em;
|
134 |
+
}
|
135 |
+
|
136 |
+
.page h4 {
|
137 |
+
font-size: 1em;
|
138 |
+
}
|
139 |
+
|
140 |
+
.page h5 {
|
141 |
+
font-size: 0.83em;
|
142 |
+
}
|
143 |
+
|
144 |
+
.page table {
|
145 |
+
width: 100%;
|
146 |
+
}
|
147 |
+
|
148 |
+
.page table thead {
|
149 |
+
display: table-row-group;
|
150 |
+
font-weight: bold;
|
151 |
+
}
|
152 |
+
|
153 |
+
.page div:not(.columnWrapper) > table + table {
|
154 |
+
margin-top: 0;
|
155 |
+
}
|
156 |
+
|
157 |
+
.page code {
|
158 |
+
font-family: "Courier New", Courier, monospace;
|
159 |
+
white-space: pre-wrap;
|
160 |
+
overflow-wrap: break-word;
|
161 |
+
}
|
162 |
+
|
163 |
+
.page pre code {
|
164 |
+
width: 100%;
|
165 |
+
display: inline-block;
|
166 |
+
}
|
167 |
+
|
168 |
+
.page .columnSplit {
|
169 |
+
visibility: hidden;
|
170 |
+
-webkit-column-break-after: always;
|
171 |
+
break-after: always;
|
172 |
+
-moz-column-break-after: always;
|
173 |
+
margin-top: 0;
|
174 |
+
}
|
175 |
+
|
176 |
+
.page .columnSplit + * {
|
177 |
+
margin-top: 0;
|
178 |
+
}
|
179 |
+
|
180 |
+
.page blockquote,
|
181 |
+
.page table {
|
182 |
+
z-index: 15;
|
183 |
+
-webkit-column-break-inside: avoid;
|
184 |
+
page-break-inside: avoid;
|
185 |
+
break-inside: avoid;
|
186 |
+
}
|
187 |
+
|
188 |
+
.page ul ul,
|
189 |
+
.page ol ol,
|
190 |
+
.page ul ol,
|
191 |
+
.page ol ul {
|
192 |
+
margin-bottom: 0px;
|
193 |
+
margin-left: 1.5em;
|
194 |
+
}
|
195 |
+
|
196 |
+
.page li {
|
197 |
+
-webkit-column-break-inside: avoid;
|
198 |
+
page-break-inside: avoid;
|
199 |
+
break-inside: avoid;
|
200 |
+
}
|
201 |
+
|
202 |
+
.page .watermark {
|
203 |
+
display: grid !important;
|
204 |
+
place-items: center;
|
205 |
+
justify-content: center;
|
206 |
+
position: absolute;
|
207 |
+
margin: 0;
|
208 |
+
top: 0;
|
209 |
+
left: 0;
|
210 |
+
width: 100%;
|
211 |
+
height: 100%;
|
212 |
+
font-size: 120px;
|
213 |
+
text-transform: uppercase;
|
214 |
+
mix-blend-mode: overlay;
|
215 |
+
opacity: 30%;
|
216 |
+
transform: rotate(-45deg);
|
217 |
+
z-index: 500;
|
218 |
+
}
|
219 |
+
|
220 |
+
.page .watermark p {
|
221 |
+
margin-bottom: none;
|
222 |
+
}
|
223 |
+
|
224 |
+
.page [class*="watercolor"] {
|
225 |
+
position: absolute;
|
226 |
+
width: 2000px;
|
227 |
+
height: 2000px;
|
228 |
+
-webkit-mask-image: var(--wc);
|
229 |
+
-webkit-mask-size: contain;
|
230 |
+
-webkit-mask-repeat: no-repeat;
|
231 |
+
mask-image: var(--wc);
|
232 |
+
mask-size: contain;
|
233 |
+
mask-repeat: no-repeat;
|
234 |
+
background-size: cover;
|
235 |
+
background-color: var(--HB_Color_WatercolorStain);
|
236 |
+
--wc: url('/assets/watercolor/watercolor1.png');
|
237 |
+
z-index: -2;
|
238 |
+
}
|
239 |
+
|
240 |
+
.page .watercolor1 {
|
241 |
+
--wc: url('/assets/watercolor/watercolor1.png');
|
242 |
+
}
|
243 |
+
|
244 |
+
.page .watercolor2 {
|
245 |
+
--wc: url('/assets/watercolor/watercolor2.png');
|
246 |
+
}
|
247 |
+
|
248 |
+
.page .watercolor3 {
|
249 |
+
--wc: url('/assets/watercolor/watercolor3.png');
|
250 |
+
}
|
251 |
+
|
252 |
+
.page .watercolor4 {
|
253 |
+
--wc: url('/assets/watercolor/watercolor4.png');
|
254 |
+
}
|
255 |
+
|
256 |
+
.page .watercolor5 {
|
257 |
+
--wc: url('/assets/watercolor/watercolor5.png');
|
258 |
+
}
|
259 |
+
|
260 |
+
.page .watercolor6 {
|
261 |
+
--wc: url('/assets/watercolor/watercolor6.png');
|
262 |
+
}
|
263 |
+
|
264 |
+
.page .watercolor7 {
|
265 |
+
--wc: url('/assets/watercolor/watercolor7.png');
|
266 |
+
}
|
267 |
+
|
268 |
+
.page .watercolor8 {
|
269 |
+
--wc: url('/assets/watercolor/watercolor8.png');
|
270 |
+
}
|
271 |
+
|
272 |
+
.page .watercolor9 {
|
273 |
+
--wc: url('/assets/watercolor/watercolor9.png');
|
274 |
+
}
|
275 |
+
|
276 |
+
.page .watercolor10 {
|
277 |
+
--wc: url('/assets/watercolor/watercolor10.png');
|
278 |
+
}
|
279 |
+
|
280 |
+
.page .watercolor11 {
|
281 |
+
--wc: url('/assets/watercolor/watercolor11.png');
|
282 |
+
}
|
283 |
+
|
284 |
+
.page .watercolor12 {
|
285 |
+
--wc: url('/assets/watercolor/watercolor12.png');
|
286 |
+
}
|
287 |
+
|
288 |
+
.page [class*="imageMask"] {
|
289 |
+
position: absolute;
|
290 |
+
height: 200%;
|
291 |
+
width: 200%;
|
292 |
+
left: 50%;
|
293 |
+
bottom: 50%;
|
294 |
+
--rotation: 0;
|
295 |
+
--revealer: none;
|
296 |
+
--checkerboard: none;
|
297 |
+
--scaleX: 1;
|
298 |
+
--scaleY: 1;
|
299 |
+
-webkit-mask-image: var(--wc), var(--revealer);
|
300 |
+
-webkit-mask-repeat: repeat-x;
|
301 |
+
-webkit-mask-size: 50%;
|
302 |
+
-webkit-mask-position: 50% calc(50% - var(--offset));
|
303 |
+
mask-image: var(--wc);
|
304 |
+
mask-repeat: repeat-x;
|
305 |
+
mask-size: 50%;
|
306 |
+
mask-position: 50% calc(50% - var(--offset));
|
307 |
+
background-image: var(--checkerboard);
|
308 |
+
background-size: 20px;
|
309 |
+
z-index: -1;
|
310 |
+
transform: translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
|
311 |
+
}
|
312 |
+
|
313 |
+
.page [class*="imageMask"] > p:has(img) {
|
314 |
+
position: absolute;
|
315 |
+
width: 50%;
|
316 |
+
height: 50%;
|
317 |
+
bottom: 50%;
|
318 |
+
left: 50%;
|
319 |
+
transform: translateX(-50%) translateY(50%) rotate(calc(-1deg * var(--rotation))) scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)));
|
320 |
+
}
|
321 |
+
|
322 |
+
.page [class*="imageMask"] img {
|
323 |
+
position: absolute;
|
324 |
+
display: block;
|
325 |
+
bottom: 0;
|
326 |
+
}
|
327 |
+
|
328 |
+
.page [class*="imageMask"].bottom {
|
329 |
+
--rotation: 0;
|
330 |
+
}
|
331 |
+
|
332 |
+
.page [class*="imageMask"].bottom img {
|
333 |
+
bottom: 0;
|
334 |
+
}
|
335 |
+
|
336 |
+
.page [class*="imageMask"].top {
|
337 |
+
--rotation: 180;
|
338 |
+
}
|
339 |
+
|
340 |
+
.page [class*="imageMask"].top img {
|
341 |
+
top: 0;
|
342 |
+
}
|
343 |
+
|
344 |
+
.page [class*="imageMask"].left {
|
345 |
+
--rotation: 90;
|
346 |
+
}
|
347 |
+
|
348 |
+
.page [class*="imageMask"].left img {
|
349 |
+
left: 0;
|
350 |
+
}
|
351 |
+
|
352 |
+
.page [class*="imageMask"].right {
|
353 |
+
--rotation: -90;
|
354 |
+
}
|
355 |
+
|
356 |
+
.page [class*="imageMask"].right img {
|
357 |
+
right: 0;
|
358 |
+
}
|
359 |
+
|
360 |
+
.page [class*="imageMask"].revealImage {
|
361 |
+
--revealer: linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2));
|
362 |
+
--checkerboard: url(/assets/waterColorMasks/missingImage.png);
|
363 |
+
}
|
364 |
+
|
365 |
+
.page .imageMaskEdge1 {
|
366 |
+
--wc: url(/assets/waterColorMasks/edge/0001.webp);
|
367 |
+
}
|
368 |
+
|
369 |
+
.page .imageMaskEdge2 {
|
370 |
+
--wc: url(/assets/waterColorMasks/edge/0002.webp);
|
371 |
+
}
|
372 |
+
|
373 |
+
.page .imageMaskEdge3 {
|
374 |
+
--wc: url(/assets/waterColorMasks/edge/0003.webp);
|
375 |
+
}
|
376 |
+
|
377 |
+
.page .imageMaskEdge4 {
|
378 |
+
--wc: url(/assets/waterColorMasks/edge/0004.webp);
|
379 |
+
}
|
380 |
+
|
381 |
+
.page .imageMaskEdge5 {
|
382 |
+
--wc: url(/assets/waterColorMasks/edge/0005.webp);
|
383 |
+
}
|
384 |
+
|
385 |
+
.page .imageMaskEdge6 {
|
386 |
+
--wc: url(/assets/waterColorMasks/edge/0006.webp);
|
387 |
+
}
|
388 |
+
|
389 |
+
.page .imageMaskEdge7 {
|
390 |
+
--wc: url(/assets/waterColorMasks/edge/0007.webp);
|
391 |
+
}
|
392 |
+
|
393 |
+
.page .imageMaskEdge8 {
|
394 |
+
--wc: url(/assets/waterColorMasks/edge/0008.webp);
|
395 |
+
}
|
396 |
+
|
397 |
+
.page .imageMaskEdge9 {
|
398 |
+
--wc: url(/assets/waterColorMasks/edge/0009.webp);
|
399 |
+
}
|
400 |
+
|
401 |
+
.page .imageMaskEdge10 {
|
402 |
+
--wc: url(/assets/waterColorMasks/edge/0010.webp);
|
403 |
+
}
|
404 |
+
|
405 |
+
.page .imageMaskEdge11 {
|
406 |
+
--wc: url(/assets/waterColorMasks/edge/0011.webp);
|
407 |
+
}
|
408 |
+
|
409 |
+
.page .imageMaskEdge12 {
|
410 |
+
--wc: url(/assets/waterColorMasks/edge/0012.webp);
|
411 |
+
}
|
412 |
+
|
413 |
+
.page .imageMaskCorner1 {
|
414 |
+
--wc: url(/assets/waterColorMasks/corner/0001.webp);
|
415 |
+
}
|
416 |
+
|
417 |
+
.page .imageMaskCorner2 {
|
418 |
+
--wc: url(/assets/waterColorMasks/corner/0002.webp);
|
419 |
+
}
|
420 |
+
|
421 |
+
.page .imageMaskCorner3 {
|
422 |
+
--wc: url(/assets/waterColorMasks/corner/0003.webp);
|
423 |
+
}
|
424 |
+
|
425 |
+
.page .imageMaskCorner4 {
|
426 |
+
--wc: url(/assets/waterColorMasks/corner/0004.webp);
|
427 |
+
}
|
428 |
+
|
429 |
+
.page .imageMaskCorner5 {
|
430 |
+
--wc: url(/assets/waterColorMasks/corner/0005.webp);
|
431 |
+
}
|
432 |
+
|
433 |
+
.page .imageMaskCorner6 {
|
434 |
+
--wc: url(/assets/waterColorMasks/corner/0006.webp);
|
435 |
+
}
|
436 |
+
|
437 |
+
.page .imageMaskCorner7 {
|
438 |
+
--wc: url(/assets/waterColorMasks/corner/0007.webp);
|
439 |
+
}
|
440 |
+
|
441 |
+
.page .imageMaskCorner8 {
|
442 |
+
--wc: url(/assets/waterColorMasks/corner/0008.webp);
|
443 |
+
}
|
444 |
+
|
445 |
+
.page .imageMaskCorner9 {
|
446 |
+
--wc: url(/assets/waterColorMasks/corner/0009.webp);
|
447 |
+
}
|
448 |
+
|
449 |
+
.page .imageMaskCorner10 {
|
450 |
+
--wc: url(/assets/waterColorMasks/corner/0010.webp);
|
451 |
+
}
|
452 |
+
|
453 |
+
.page .imageMaskCorner11 {
|
454 |
+
--wc: url(/assets/waterColorMasks/corner/0011.webp);
|
455 |
+
}
|
456 |
+
|
457 |
+
.page .imageMaskCorner12 {
|
458 |
+
--wc: url(/assets/waterColorMasks/corner/0012.webp);
|
459 |
+
}
|
460 |
+
|
461 |
+
.page .imageMaskCorner13 {
|
462 |
+
--wc: url(/assets/waterColorMasks/corner/0013.webp);
|
463 |
+
}
|
464 |
+
|
465 |
+
.page .imageMaskCorner14 {
|
466 |
+
--wc: url(/assets/waterColorMasks/corner/0014.webp);
|
467 |
+
}
|
468 |
+
|
469 |
+
.page .imageMaskCorner15 {
|
470 |
+
--wc: url(/assets/waterColorMasks/corner/0015.webp);
|
471 |
+
}
|
472 |
+
|
473 |
+
.page .imageMaskCorner16 {
|
474 |
+
--wc: url(/assets/waterColorMasks/corner/0016.webp);
|
475 |
+
}
|
476 |
+
|
477 |
+
.page .imageMaskCorner17 {
|
478 |
+
--wc: url(/assets/waterColorMasks/corner/0017.webp);
|
479 |
+
}
|
480 |
+
|
481 |
+
.page .imageMaskCorner18 {
|
482 |
+
--wc: url(/assets/waterColorMasks/corner/0018.webp);
|
483 |
+
}
|
484 |
+
|
485 |
+
.page .imageMaskCorner19 {
|
486 |
+
--wc: url(/assets/waterColorMasks/corner/0019.webp);
|
487 |
+
}
|
488 |
+
|
489 |
+
.page .imageMaskCorner20 {
|
490 |
+
--wc: url(/assets/waterColorMasks/corner/0020.webp);
|
491 |
+
}
|
492 |
+
|
493 |
+
.page .imageMaskCorner21 {
|
494 |
+
--wc: url(/assets/waterColorMasks/corner/0021.webp);
|
495 |
+
}
|
496 |
+
|
497 |
+
.page .imageMaskCorner22 {
|
498 |
+
--wc: url(/assets/waterColorMasks/corner/0022.webp);
|
499 |
+
}
|
500 |
+
|
501 |
+
.page .imageMaskCorner23 {
|
502 |
+
--wc: url(/assets/waterColorMasks/corner/0023.webp);
|
503 |
+
}
|
504 |
+
|
505 |
+
.page .imageMaskCorner24 {
|
506 |
+
--wc: url(/assets/waterColorMasks/corner/0024.webp);
|
507 |
+
}
|
508 |
+
|
509 |
+
.page .imageMaskCorner25 {
|
510 |
+
--wc: url(/assets/waterColorMasks/corner/0025.webp);
|
511 |
+
}
|
512 |
+
|
513 |
+
.page .imageMaskCorner26 {
|
514 |
+
--wc: url(/assets/waterColorMasks/corner/0026.webp);
|
515 |
+
}
|
516 |
+
|
517 |
+
.page .imageMaskCorner27 {
|
518 |
+
--wc: url(/assets/waterColorMasks/corner/0027.webp);
|
519 |
+
}
|
520 |
+
|
521 |
+
.page .imageMaskCorner28 {
|
522 |
+
--wc: url(/assets/waterColorMasks/corner/0028.webp);
|
523 |
+
}
|
524 |
+
|
525 |
+
.page .imageMaskCorner29 {
|
526 |
+
--wc: url(/assets/waterColorMasks/corner/0029.webp);
|
527 |
+
}
|
528 |
+
|
529 |
+
.page .imageMaskCorner30 {
|
530 |
+
--wc: url(/assets/waterColorMasks/corner/0030.webp);
|
531 |
+
}
|
532 |
+
|
533 |
+
.page .imageMaskCorner31 {
|
534 |
+
--wc: url(/assets/waterColorMasks/corner/0031.webp);
|
535 |
+
}
|
536 |
+
|
537 |
+
.page .imageMaskCorner32 {
|
538 |
+
--wc: url(/assets/waterColorMasks/corner/0032.webp);
|
539 |
+
}
|
540 |
+
|
541 |
+
.page .imageMaskCorner33 {
|
542 |
+
--wc: url(/assets/waterColorMasks/corner/0033.webp);
|
543 |
+
}
|
544 |
+
|
545 |
+
.page .imageMaskCorner34 {
|
546 |
+
--wc: url(/assets/waterColorMasks/corner/0034.webp);
|
547 |
+
}
|
548 |
+
|
549 |
+
.page .imageMaskCorner35 {
|
550 |
+
--wc: url(/assets/waterColorMasks/corner/0035.webp);
|
551 |
+
}
|
552 |
+
|
553 |
+
.page .imageMaskCorner36 {
|
554 |
+
--wc: url(/assets/waterColorMasks/corner/0036.webp);
|
555 |
+
}
|
556 |
+
|
557 |
+
.page .imageMaskCorner37 {
|
558 |
+
--wc: url(/assets/waterColorMasks/corner/0037.webp);
|
559 |
+
}
|
560 |
+
|
561 |
+
.page dl {
|
562 |
+
padding-left: 1em;
|
563 |
+
white-space: normal;
|
564 |
+
}
|
565 |
+
|
566 |
+
.page dt {
|
567 |
+
display: inline;
|
568 |
+
margin-right: 0.5ch;
|
569 |
+
margin-left: -1em;
|
570 |
+
}
|
571 |
+
|
572 |
+
.page dd {
|
573 |
+
display: inline;
|
574 |
+
margin-left: 0;
|
575 |
+
text-indent: 0;
|
576 |
+
}
|
577 |
+
|
578 |
+
.page .blank {
|
579 |
+
height: 1em;
|
580 |
+
margin-top: 0;
|
581 |
+
}
|
582 |
+
|
583 |
+
.page .blank + * {
|
584 |
+
margin-top: 0;
|
585 |
+
}
|
586 |
+
|
587 |
+
.page .wide {
|
588 |
+
column-span: all;
|
589 |
+
display: block;
|
590 |
+
margin-bottom: 1em;
|
591 |
+
}
|
592 |
+
|
593 |
+
.page .wide + * {
|
594 |
+
margin-top: 0;
|
595 |
+
}
|
dependencies/themes/assets/parchmentBackground.jpg
CHANGED
Git LFS Details
|
dependencies/themes/assets/parchmentBackgroundGrayscale.jpg
CHANGED
Git LFS Details
|
description_helper.py
CHANGED
@@ -1,105 +1,254 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
#sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
4 |
-
import torch
|
5 |
-
import random
|
6 |
-
from random import randint
|
7 |
import gc
|
8 |
import os
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
)
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
def del_sd_input() :
|
33 |
-
del sd_input[:]
|
34 |
-
print(sd_input)
|
35 |
-
|
36 |
-
# Initialize model and cache
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
# Function to load the llm, pass user input into a prompt, pass that prompt to llm and log memory and time then cleanup
|
41 |
-
def generate_monster_desc(monster_type, monster_color,monster_size,monster_loc):
|
42 |
-
u.reclaim_mem()
|
43 |
-
del_sd_input()
|
44 |
-
print(sd_input)
|
45 |
-
prompt_template = f"You are an intelligent, accomplished, expert writer of fantasy fiction that uses diverse and wide ranging vocabulary with a particular knack for painting mental images. Do not write instructions or tips, only write a concise, visually descriptive, detailed and colorful three to 5 sentences about the appearance of the {monster_type}, include description of its body and all of it's parts. This is a wholly orginial and unique creation {monster_color} is size {monster_size} and can be found {monster_loc}. The focus should be on describing it's appearance and it's very important it is FIVE sentences! Always start with the creatures name/type : {monster_type}"
|
46 |
-
prompt_list.append(prompt_template)
|
47 |
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
-
config = ExLlamaV2Config()
|
55 |
-
config.model_dir = model_directory
|
56 |
-
config.prepare()
|
57 |
-
|
58 |
-
model = ExLlamaV2(config)
|
59 |
-
print("Loading model: " + model_directory)
|
60 |
-
print(f"Model load took {time.time() - start_time}")
|
61 |
-
print(f"Memory allocated : {torch.cuda.memory_allocated()}")
|
62 |
-
|
63 |
-
cache = ExLlamaV2Cache(model, lazy = True)
|
64 |
-
model.load_autosplit(cache)
|
65 |
-
|
66 |
-
tokenizer = ExLlamaV2Tokenizer(config)
|
67 |
-
# Initialize generator
|
68 |
-
|
69 |
-
generator = ExLlamaV2BaseGenerator(model, cache, tokenizer)
|
70 |
-
#generator.set_stop_conditions([tokenizer.eos_token_id])
|
71 |
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
settings.temperature = 1.10
|
76 |
-
settings.top_k = 50
|
77 |
-
settings.top_p = 0.8
|
78 |
-
settings.token_repetition_penalty = 1.15
|
79 |
-
|
80 |
-
|
81 |
-
max_new_tokens = 512
|
82 |
-
start_time = time.time()
|
83 |
-
generator.warmup()
|
84 |
|
85 |
-
|
86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import replicate
|
2 |
+
import ast
|
|
|
|
|
|
|
|
|
3 |
import gc
|
4 |
import os
|
5 |
+
from openai import OpenAI
|
6 |
+
|
7 |
+
api_key = os.getenv('REPLICATE_API_TOKEN')
|
8 |
+
client = OpenAI()
|
9 |
+
|
10 |
+
def load_llm(user_input, spellcaster, legendary_actions ):
|
11 |
+
prompt = f"the subject is {user_input}, Spellcaster : {spellcaster}, Legendary Actions : {legendary_actions}"
|
12 |
+
print(prompt)
|
13 |
+
response = client.chat.completions.create(
|
14 |
+
model="gpt-4o",
|
15 |
+
messages=[
|
16 |
+
{
|
17 |
+
"role": "user",
|
18 |
+
"content": f"{prompt_instructions} {prompt}"
|
19 |
+
}
|
20 |
+
],
|
21 |
+
temperature=1,
|
22 |
+
max_tokens=2000,
|
23 |
+
top_p=1,
|
24 |
+
frequency_penalty=0,
|
25 |
+
presence_penalty=0
|
26 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
+
return response.choices[0].message.content
|
29 |
+
|
30 |
+
model_path = "meta/meta-llama-3-70b-instruct"
|
31 |
+
#def load_llm(user_input, spellcaster, legendary_actions):
|
32 |
+
# prompt = f"the subject is {user_input}, Spellcaster : {spellcaster}, Legendary Actions : {legendary_actions}"
|
33 |
+
#input = {"prompt" : f" {prompt_instructions} {prompt}","max_tokens":2000}
|
34 |
+
#print(f"Generation Started, \n Prompt = {prompt}")
|
35 |
+
#output = replicate.run(model_path,
|
36 |
+
#input=input
|
37 |
+
|
38 |
+
# )
|
39 |
+
#return output
|
40 |
+
|
41 |
+
|
42 |
+
def call_llm_and_cleanup(user_input,spellcaster, legendary_actions):
|
43 |
+
# Call the LLM and store its output
|
44 |
+
llm_output = load_llm(user_input, spellcaster, legendary_actions)
|
45 |
+
llm_output = "".join(llm_output)
|
46 |
+
print(llm_output)
|
47 |
+
llm_output = ast.literal_eval(llm_output)
|
48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
+
gc.collect()
|
51 |
+
|
52 |
+
# llm_output is still available for use here
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
+
return llm_output
|
55 |
+
|
56 |
+
|
57 |
+
def convert_to_dict(string):
|
58 |
+
# Check if the input is already a dictionary
|
59 |
+
if isinstance(string, dict):
|
60 |
+
print("Input is already a dictionary.")
|
61 |
+
return string
|
62 |
+
|
63 |
+
# Function to try parsing the string to a dictionary
|
64 |
+
def try_parse(s):
|
65 |
+
try:
|
66 |
+
result = ast.literal_eval(s)
|
67 |
+
if isinstance(result, dict):
|
68 |
+
print("Item dictionary is valid")
|
69 |
+
return result
|
70 |
+
except SyntaxError as e:
|
71 |
+
error_message = str(e)
|
72 |
+
print("Syntax Error:", error_message)
|
73 |
+
# Check if the error message indicates an unclosed '{'
|
74 |
+
if "'{' was never closed" in error_message:
|
75 |
+
return try_parse(s + '}') # Attempt to fix by adding a closing '}'
|
76 |
+
except ValueError as e:
|
77 |
+
print("Value Error:", e)
|
78 |
+
return None
|
79 |
+
|
80 |
+
# First, try parsing the original string
|
81 |
+
result = try_parse(string)
|
82 |
+
if result is not None:
|
83 |
+
return result
|
84 |
+
|
85 |
+
# Check if braces are missing
|
86 |
+
if not string.startswith('{'):
|
87 |
+
string = '{' + string
|
88 |
+
if not string.endswith('}'):
|
89 |
+
string = string + '}'
|
90 |
+
|
91 |
+
# Try parsing again with added braces
|
92 |
+
return try_parse(string) or "Dictionary not valid"
|
93 |
+
|
94 |
+
|
95 |
+
|
96 |
+
# Instructions past 4 are not time tested and may need to be removed.
|
97 |
+
### Meta prompted :
|
98 |
+
prompt_instructions = """ **Purpose**: ONLY Generate a structured json following the provided format. The job is to generate a balance, creative, interesting monster statblock in the rule style of Dungeons and Dragons. You do not need to stick strictly to the abilities and spells of the game, if it fits the style and flavor of the user input, get weird, scary, or silly with the details. You will also be writing a paragraph of interesting flavor text and description, and a brief one sentence image generation prompt.Include the type and subtype in the image prompt.
|
99 |
+
|
100 |
+
Image Generation Prompt Examples :
|
101 |
+
"A hooded stout dwarf necromancer, in black robes, emanating evil magic "
|
102 |
+
"A black and tan battle dog with spike collar, hackles up and ready to strike"
|
103 |
+
"a tanned human barbarian with bleeding red axes"
|
104 |
+
"a magical zombie tiger, colorful and decaying"
|
105 |
+
|
106 |
+
1. Only output file structure starting with { and ending with } it is CRITICAL to end with a }, DO NOT say anything, don't add ''' or json"
|
107 |
+
2. You tend to build over powered monsters, tend towards average to a bit underpowered. Damage Resistance can be left blank.
|
108 |
+
3. All Actions should have explicit instructions on how to use and what dice and effects they have
|
109 |
+
4. DO NOT type other_sense_type, other_skill_type or any other label or item.
|
110 |
+
5. DO NOT use null, use "".
|
111 |
+
5. If Spellcaster : False then Spells should not be printed. If Spellcaster
|
112 |
+
6. If Legendary Actions : False then legendary_action should not be printed
|
113 |
+
7. Review and stick closely to this table.
|
114 |
+
|
115 |
+
Monster Statistics by Challenge Rating
|
116 |
+
Fractions MUST be surrounded by double quotes ""
|
117 |
+
| CR | XP | Prof. Bonus | Armor Class | Hit Points | Attack Bonus | Damage/Round | Save DC | Prob. of Spell | Num. of Spells | Level of Spells | Prob. of Legendary Actions | Num. of Legendary Actions |
|
118 |
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
119 |
+
| 0 | 10 | 2 | 10-13 | 1-3 | 3 | 0-1 | 13 | 0% | 0 | - | 0% | 0 |
|
120 |
+
| "1/8" | 25 | 2 | 10-13 | 2-7 | 3 | 2-3 | 13 | 05% | 1 | 1st | 0% | 0 |
|
121 |
+
| "1/4" | 50 | 2 | 10-13 | 4-8 | 3 | 4-5 | 13 | 10% | 2 | 1st | 0% | 0 |
|
122 |
+
| "1/2" | 100 | 2 | 10-13 | 5-11 | 3 | 6-8 | 13 | 15% | 3 | 1st | 0% | 0 |
|
123 |
+
| 1 | 200 | 2 | 10-13 | 8-20 | 3 | 9-14 | 13 | 15% | 4 | 1st | 0% | 0 |
|
124 |
+
| 2 | 450 | 2 | 11-13 | 15-30 | 3 | 15-20 | 13 | 20% | 5 | 2nd | 0% | 0 |
|
125 |
+
| 3 | 700 | 2 | 11-13 | 25-50 | 4 | 21-26 | 13 | 25% | 6 | 2nd |05% | 1 |
|
126 |
+
| 4 | 1100 | 2 | 11-14 | 35-75 | 5 | 27-32 | 14 | 30% | 7 | 2nd | 10% | 1 |
|
127 |
+
| 5 | 1800 | 3 | 12-15 | 45-95 | 6 | 33-38 | 15 | 50% | 8 | 3rd | 10% | 1 |
|
128 |
+
| 6 | 2300 | 3 | 12-15 | 60-110 | 6 | 39-44 | 15 | 60% | 9 | 3rd | 15% | 2 |
|
129 |
+
| 7 | 2900 | 3 | 12-15 | 70-140 | 6 | 45-50 | 15 | 75% | 10 | 3rd | 20% | 2 |
|
130 |
+
| 8 | 3900 | 3 | 13-16 | 80-160 | 7 | 51-56 | 16 | 90% | 11 | 4th | 20% | 3 |
|
131 |
+
| 9 | 5000 | 4 | 13-16 | 105-205 | 7 | 57-62 | 16 | 90% | 12 | 4th | 20% | 3 |
|
132 |
+
| 10 | 5900 | 4 | 14-17 | 206-220 | 7 | 63-68 | 16 | 100% | 13 | 4th | 30% | 3 |
|
133 |
+
| 11 | 7200 | 4 | 14-17 | 221-235 | 8 | 69-74 | 17 | 100% | 14 | 5th | 40% | 3 |
|
134 |
+
| 12 | 8400 | 4 | 14-17 | 236-250 | 8 | 75-80 | 18 | 100% | 15 | 6th | 60% | 4 |
|
135 |
+
| 13 | 10000 | 5 | 15-18 | 251-265 | 8 | 81-86 | 18 | 100% | 16 | 7th | 80% | 4 |
|
136 |
+
| 14 | 11500 | 5 | 15-18 | 266-280 | 8 | 87-92 | 18 | 100% | 17 | 7th | 100% | 4 |
|
137 |
+
| 15 | 13000 | 5 | 15-18 | 281-295 | 8 | 93-98 | 18 | 100% | 18 | 8th | 100% | 5 |
|
138 |
+
| 16 | 15000 | 5 | 15-18 | 296-310 | 9 | 99-104 | 18 | 100% | 19 | 8th | 100% | 5 |
|
139 |
+
| 17 | 18000 | 6 | 16-19 | 311-325 | 10 | 105-110 | 19 | 100% | 20 | 8th | 100% | 5 |
|
140 |
+
| 18 | 20000 | 6 | 16-19 | 326-340 | 10 | 111-116 | 19 | 100% | 21 | 9th | 100% | 5 |
|
141 |
+
| 19 | 22000 | 6 | 16-19 | 341-355 | 10 | 117-122 | 19 | 100% | 22 | 9th | 100% | 5 |
|
142 |
+
| 20 | 25000 | 6 | 16-19 | 356-400 | 10 | 123-140 | 19 | 100% | 23 | 9th | 100% | 5 |
|
143 |
+
| 21 | 33000 | 7 | 16-19 | 401-445 | 11 | 141-158 | 20 | 100% | 24 | 9th | 100% | 5 |
|
144 |
+
| 22 | 41000 | 7 | 16-19 | 446-490 | 11 | 159-176 | 20 | 100% | 25 | 9th | 100% | 5 |
|
145 |
+
| 23 | 50000 | 7 | 16-19 | 491-535 | 11 | 177-194 | 20 | 100% | 26 | 9th | 100% | 5 |
|
146 |
+
| 24 | 62000 | 7 | 16-19 | 536-580 | 11 | 195-212 | 21 | 100% | 27 | 9th | 100% | 5 |
|
147 |
+
| 25 | 75000 | 8 | 16-19 | 581-625 | 12 | 213-230 | 21 | 100% | 28 | 9th | 100% | 5 |
|
148 |
+
| 26 | 90000 | 8 | 16-19 | 626-670 | 12 | 231-248 | 21 | 100% | 29 | 9th | 100% | 5 |
|
149 |
+
| 27 | 105000 | 8 | 16-19 | 671-715 | 13 | 249-266 | 22 | 100% | 30 | 9th | 100% | 5 |
|
150 |
+
| 28 | 120000 | 8 | 16-19 | 716-760 | 13 | 267-284 | 22 | 100% | 31 | 9th | 100% | 5 |
|
151 |
+
| 29 | 135000 | 9 | 16-19 | 760-805 | 13 | 285-302 | 22 | 100% | 32 | 9th | 100% | 5 |
|
152 |
+
| 30 | 155000 | 9 | 16-19 | 805-850 | 14 | 303-320 | 23 | 100% | 33 | 9th | 100% | 5 |
|
153 |
+
|
154 |
+
8. If a creature has Legendary Actions they need to be fully described.
|
155 |
+
Example : "legendary_actions": {
|
156 |
+
"actions": "Hermione the Grumpy can take 3 legendary actions, choosing from the options below. Only one legendary action can be used at a time and only at the end of another creature's turn. Hermione regains spent legendary actions at the start of her turn.",
|
157 |
+
"options": [
|
158 |
+
{
|
159 |
+
"name": "Imperial Claw Strike",
|
160 |
+
"desc": "With a swift motion that belies her regal composure, Hermione unsheathes her gleaming claws, striking with the precision of a seasoned sovereign. She makes one attack with her claws that deal an extra 2d6 slashing damage. If the attack hits a creature, that creature also suffers a -2 penalty to AC until the start of Hermione's next turn, as her claws leave rending tears in their armor or flesh, symbolizing her disdain for any challenge to her rule."
|
161 |
+
},
|
162 |
+
{
|
163 |
+
"name": "Royal Command",
|
164 |
+
"desc": "Hermione gazes across the battlefield, her eyes glowing with an ethereal light. She issues a commanding meow that resonates with arcane power. All creatures within 60 feet that can hear her must succeed on a DC 20 Wisdom saving throw or become charmed by Hermione for 1 minute. A charmed creature regards Hermione as its beloved monarch and will protect her as if it were guarding its own life. This charm effect ends if the charmed creature suffers any harm or if Hermione dismisses it as unworthy with a wave of her paw."
|
165 |
+
},
|
166 |
+
{
|
167 |
+
"name": "Divine Whisker Quiver",
|
168 |
+
"desc": "Hermione channels the cosmic power of her royal lineage through her whiskers, which quiver with the energy of the universe itself. She chooses up to three creatures she can see within 100 feet of her. Each target must succeed on a DC 18 Constitution saving throw or be stunned by the overwhelming presence of the Empress until the end of their next turn. Creatures that fail their saving throw by 5 or more are also teleported up to 30 feet in any direction Hermione chooses, as she rearranges the battlefield to her liking with but a thought."
|
169 |
+
}
|
170 |
+
]
|
171 |
+
|
172 |
+
|
173 |
+
Output format :
|
174 |
+
{
|
175 |
+
"name": "",
|
176 |
+
"size": "",
|
177 |
+
"type": "",
|
178 |
+
"subtype": "",
|
179 |
+
"alignment": "",
|
180 |
+
"armor_class": ,
|
181 |
+
"hit_points": ,
|
182 |
+
"hit_dice": "",
|
183 |
+
"speed": {
|
184 |
+
"walk":
|
185 |
|
186 |
+
},
|
187 |
+
"abilities": {
|
188 |
+
"str": ,
|
189 |
+
"dex": ,
|
190 |
+
"con": ,
|
191 |
+
"int": ,
|
192 |
+
"wis": ,
|
193 |
+
"cha":
|
194 |
+
},
|
195 |
+
"saving_throws": {
|
196 |
+
"str": ,
|
197 |
+
"dex": ,
|
198 |
+
"wis":
|
199 |
+
},
|
200 |
+
"skills": {
|
201 |
+
"perception":
|
202 |
+
},
|
203 |
+
"damage_resistance":
|
204 |
+
"senses": {
|
205 |
+
"darkvision":
|
206 |
+
},
|
207 |
+
"languages": "",
|
208 |
+
"challenge_rating": ,
|
209 |
+
"xp": ,
|
210 |
+
"actions": [
|
211 |
+
{
|
212 |
+
"name": "",
|
213 |
+
"desc": ""
|
214 |
+
}
|
215 |
+
],
|
216 |
+
"spells": {
|
217 |
+
"cantrips": [
|
218 |
+
{
|
219 |
+
"name": "",
|
220 |
+
"desc": ""
|
221 |
+
}
|
222 |
+
],
|
223 |
+
"known_spells": [
|
224 |
+
{
|
225 |
+
"name": "",
|
226 |
+
"level":"",
|
227 |
+
"desc": ""
|
228 |
+
}
|
229 |
+
],
|
230 |
+
"spell_slots": {
|
231 |
+
"1st_level": ,
|
232 |
+
"2nd_level": ,
|
233 |
+
"3rd_level": ,
|
234 |
+
"4th_level": ,
|
235 |
+
"5th_level": ,
|
236 |
+
"6th_level": ,
|
237 |
+
"7th_level": ,
|
238 |
+
"8th_level": ,
|
239 |
+
"9th_level":
|
240 |
+
}
|
241 |
+
},
|
242 |
+
"legendary_actions": {
|
243 |
+
"actions": ,
|
244 |
+
"options": [
|
245 |
+
{
|
246 |
+
"name": "",
|
247 |
+
"desc": ""
|
248 |
+
}
|
249 |
+
]
|
250 |
+
},
|
251 |
+
"description":"",
|
252 |
+
"sd_prompt":""
|
253 |
+
}
|
254 |
+
"""
|
docker_build.log
DELETED
The diff for this file is too large to render.
See raw diff
|
|
flagged/log.csv
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
name,hp,ac,str_stat,dex_stat,con_stat,int_stat,wis_stat,cha_stat,output,flag,username,timestamp
|
2 |
+
,,,,,,,,,"{""name"": """", ""hp"": """", ""ac"": """", ""str_stat"": """", ""dex_stat"": """", ""con_stat"": """", ""int_stat"": """", ""wis_stat"": """", ""cha_stat"": """"}",,,2024-05-21 22:22:06.438871
|
get-pip.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
main.py
DELETED
@@ -1,158 +0,0 @@
|
|
1 |
-
# this imports the code from files and modules
|
2 |
-
import description_helper as dsh
|
3 |
-
import gradio as gr
|
4 |
-
import sd_generator as sd
|
5 |
-
import statblock_helper as sth
|
6 |
-
import utilities as u
|
7 |
-
import process_html
|
8 |
-
import os
|
9 |
-
import ctypes
|
10 |
-
|
11 |
-
print("Building app")
|
12 |
-
# 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
|
13 |
-
libc = ctypes.cdll.LoadLibrary("libc.so.6")
|
14 |
-
M_MMAP_THRESHOLD = -3
|
15 |
-
|
16 |
-
# Set malloc mmap threshold.
|
17 |
-
libc.mallopt(M_MMAP_THRESHOLD, 2**20)
|
18 |
-
|
19 |
-
|
20 |
-
# Build functions that will be called by Gradio UI
|
21 |
-
|
22 |
-
|
23 |
-
# Take input from gradio user and call the llm in description_helper
|
24 |
-
def gen_mon_desc(user_monster_type, user_monster_color,user_monster_size, user_monster_loc):
|
25 |
-
u.reclaim_mem()
|
26 |
-
# Delete the list of images in sd
|
27 |
-
sd.del_image_list
|
28 |
-
dsh.del_sd_input
|
29 |
-
|
30 |
-
# Prompt template with user inputs added to be printed to log and terminal
|
31 |
-
prompt_template = f"You are an intelligent and very good writer of fantasy fiction that uses a diverse and wide ranging vocabulary. Write a very concise, visually descriptive, detailed and colorful paragraph description of a {user_monster_type}, include description of its body, face, and limbs, it is {user_monster_color} and size {user_monster_size} at {user_monster_loc}. The {user_monster_color} {user_monster_type} "
|
32 |
-
print("Prompt Template : " + prompt_template)
|
33 |
-
|
34 |
-
# setting sd_input as global to be called and moved between models, this could probably be done better as a variable called from description helper
|
35 |
-
|
36 |
-
response = dsh.generate_monster_desc(user_monster_type, user_monster_color, user_monster_size,user_monster_loc)
|
37 |
-
print(response)
|
38 |
-
|
39 |
-
# A list to hold user inputs and start user log,
|
40 |
-
# Build static folders, this too needs to go to utilities
|
41 |
-
#Create user log path all this needs to be moved to utilities
|
42 |
-
|
43 |
-
|
44 |
-
u.reclaim_mem()
|
45 |
-
return response
|
46 |
-
|
47 |
-
|
48 |
-
# Pass sd_input and call function to generate image and save bu calling name making function in utilities
|
49 |
-
def gen_mon_img():
|
50 |
-
print(dsh.sd_input)
|
51 |
-
print(type(dsh.sd_input))
|
52 |
-
sd_input = dsh.sd_input[0]
|
53 |
-
generated_images = sd.generate_image(sd_input)
|
54 |
-
|
55 |
-
# return a list of image relative paths to pass to gallery
|
56 |
-
return generated_images
|
57 |
-
|
58 |
-
# Take the selected image in gallery and assign to global variable
|
59 |
-
|
60 |
-
|
61 |
-
# Pass modified input to statblock generator
|
62 |
-
def gen_mon_statblock(challenge_rating,abilities):
|
63 |
-
input = dsh.sd_input[0] + f"it is very important it is built with a challenge rating {challenge_rating}, create and give it the abilities {abilities}"
|
64 |
-
generated_statblock = sth.generate_statblock(input)
|
65 |
-
return generated_statblock
|
66 |
-
|
67 |
-
# Call the html process program and point to the file
|
68 |
-
def mon_html_process(user_text):
|
69 |
-
input_dir = sth.file_name_list[0]
|
70 |
-
md_path = f"{input_dir}/my-brew.md"
|
71 |
-
mon_file_name = sth.file_name_list[0]+'/' + sth.file_name_list[1] +'.html'
|
72 |
-
sth.md_process(md_path, sth.file_name_list[1])
|
73 |
-
print("Ouput path = " + mon_file_name)
|
74 |
-
print(f"User Text : {user_text}")
|
75 |
-
process_html.process_html(mon_file_name,user_text)
|
76 |
-
|
77 |
-
# Build the html and file path to pass to gradio to output html file in gr.html
|
78 |
-
def gen_link():
|
79 |
-
mon_file_path = sth.file_name_list[0]+'/' + sth.file_name_list[1] +'.html'
|
80 |
-
if not os.path.exists(mon_file_path):
|
81 |
-
print(f"{mon_file_path} not found")
|
82 |
-
else: print(f"{mon_file_path} found")
|
83 |
-
iframe = iframe = f"""<iframe src="file={mon_file_path}" width="100%" height="500px"></iframe>"""
|
84 |
-
link = f'<a href="file={mon_file_path}" target="_blank">{sth.file_name_list[1] +".html"}</a>'
|
85 |
-
return link, iframe
|
86 |
-
|
87 |
-
# Build gradio app
|
88 |
-
demo = gr.Blocks()
|
89 |
-
with gr.Blocks() as demo:
|
90 |
-
# Title, eventually turn this into an updated variable with Monster name
|
91 |
-
gr.HTML(""" <div id="inner"> <header>
|
92 |
-
<h1>Monster Statblock Generator</h1>
|
93 |
-
</div>""")
|
94 |
-
with gr.Tab("Generator"):
|
95 |
-
with gr.Row():
|
96 |
-
|
97 |
-
with gr.Column(scale = 1):
|
98 |
-
# Does this need to be a column? Building interface to receive input
|
99 |
-
|
100 |
-
mon_name = gr.Textbox(label = "Step 1 : The Monster's Name or type", lines = 1, placeholder=f"Ex : A friendly skeletal lich", elem_id= "Monster Name")
|
101 |
-
mon_color = gr.Textbox(label = "Step 2 : Colors and description", lines = 3, placeholder="Ex: wearing a glimmering robe ", elem_id= "Monster Color")
|
102 |
-
mon_size = gr.Dropdown(['Tiny','Small','Medium','Large','Huge','Gigantic','Titanic'], label = 'Step 3 : Size ', elem_id="Monster Size")
|
103 |
-
mon_loc = gr.Textbox(label = "Step 4 Optional : Describe it's location", lines = 3, placeholder="in a dusty library", elem_id = "Monster Location" )
|
104 |
-
desc_gen = gr.Button(value = "Step 5. Generate Description")
|
105 |
-
with gr.Column(scale = 1):
|
106 |
-
|
107 |
-
mon_desc = gr.Textbox(label = 'Monster Description', lines = 16, interactive=True)
|
108 |
-
desc_gen.click(fn = gen_mon_desc, inputs = [mon_name, mon_color, mon_size,mon_loc], outputs= mon_desc)
|
109 |
-
|
110 |
-
|
111 |
-
# Output object for image, and button to trigger
|
112 |
-
image_Gen = gr.Button(value = "Step 6 : Generate 4x Images about 1 minute" )
|
113 |
-
output_gallery = gr.Gallery(label = "Generated Images",
|
114 |
-
show_label = False,
|
115 |
-
elem_id = "gallery",
|
116 |
-
columns =[4], rows =[1],
|
117 |
-
object_fit = "contain", height ="auto")
|
118 |
-
|
119 |
-
image_Gen.click(gen_mon_img, outputs = output_gallery)
|
120 |
-
output_gallery.select(fn = process_html.assign_img)
|
121 |
-
|
122 |
-
# Create a tab to split off statblock generation
|
123 |
-
with gr.Tab("Step 7 : Statblock"):
|
124 |
-
|
125 |
-
# Block to take in
|
126 |
-
gr.Interface(
|
127 |
-
fn=gen_mon_statblock,
|
128 |
-
inputs = [gr.Textbox(label="Challenge Rating, 1-20", lines =1),
|
129 |
-
gr.Textbox(label="Names of Abilities seperated by commas", lines = 3)],
|
130 |
-
outputs = gr.Textbox(label = "Statblock", lines = 20, interactive=True, elem_id= "User Input"),
|
131 |
-
allow_flagging="never"
|
132 |
-
)
|
133 |
-
|
134 |
-
# Build buttons to modify to html and show html
|
135 |
-
gen_html = gr.Button(value = "Step 8 : Generate html, click once then go to Step 9")
|
136 |
-
gen_html.click(mon_html_process,inputs =[mon_desc], outputs=[])
|
137 |
-
markdown = gr.Markdown(label="Output Box")
|
138 |
-
html = gr.HTML(label="HTML preview", show_label=True)
|
139 |
-
new_link_btn = gr.Button("Step 9: Display HTML and Link")
|
140 |
-
new_link_btn.click(fn = gen_link, inputs = [], outputs = [markdown, html])
|
141 |
-
|
142 |
-
def main() -> None:
|
143 |
-
# run web server, expose port 8000, share to create web link, give app access folder path, gradio was updated for security and can no longer serve any directory not specified.
|
144 |
-
if __name__ == '__main__':
|
145 |
-
demo.launch(server_name = "0.0.0.0", server_port = 8000, share = False, allowed_paths = ["/home/user/app/output","/home/user/app/dependencies"])
|
146 |
-
|
147 |
-
main()
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
normalize_then_alpha.py
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageOps
|
2 |
+
import numpy as np
|
3 |
+
import cv2
|
4 |
+
|
5 |
+
def apply_transparency(image):
|
6 |
+
# Load the image in RGBA format to handle transparency
|
7 |
+
print(f"Image Type is : {type(image)}")
|
8 |
+
|
9 |
+
# Ensure the image has an alpha channel
|
10 |
+
if image.mode != 'RGBA':
|
11 |
+
image = image.convert('RGBA')
|
12 |
+
|
13 |
+
original = image.copy() # Keep the original for final processing
|
14 |
+
|
15 |
+
|
16 |
+
# Convert the image to a grayscale NumPy array
|
17 |
+
gray_image = ImageOps.grayscale(image)
|
18 |
+
gray_array = np.array(gray_image)
|
19 |
+
|
20 |
+
# Normalize the background by setting near-white to white
|
21 |
+
normalized_background = np.where(gray_array > 200, 255, gray_array)
|
22 |
+
normalized_background_image = Image.fromarray(normalized_background.astype(np.uint8))
|
23 |
+
|
24 |
+
|
25 |
+
# Convert the normalized background image to a numpy array before thresholding
|
26 |
+
normalized_background_array = np.array(normalized_background_image)
|
27 |
+
|
28 |
+
# Threshold the image to isolate the white areas
|
29 |
+
_, binary_image = cv2.threshold(normalized_background_array, 240, 255, cv2.THRESH_BINARY)
|
30 |
+
binary_image = cv2.bitwise_not(binary_image) # Invert to make white areas black for flood fill
|
31 |
+
binary_image = Image.fromarray(binary_image)
|
32 |
+
|
33 |
+
# Convert RGB and Alpha to separate arrays for OpenCV processing
|
34 |
+
rgb_image = np.array(normalized_background_image.convert('RGB'))
|
35 |
+
alpha_channel = np.array(image)[:, :, 3] # Extract the alpha channel directly from the original RGBA image
|
36 |
+
|
37 |
+
h, w = rgb_image.shape[:2]
|
38 |
+
mask = np.zeros((h+2, w+2), np.uint8)
|
39 |
+
|
40 |
+
# Define the seed point for flood fill, assuming it's set to a point known to be within the background
|
41 |
+
seed_point = (0, 0) # Adjust this as needed
|
42 |
+
|
43 |
+
# Sample the color at the seed point from the RGB image
|
44 |
+
seed_color = rgb_image[seed_point[1], seed_point[0]].tolist()
|
45 |
+
|
46 |
+
# Set tolerance levels such that the fill will stop at or before hitting black
|
47 |
+
# Black in BGR is (0, 0, 0), and we set a very low tolerance to stop at any near-black color
|
48 |
+
# Lower numbers are more restrictive to fill, higher is more permissive
|
49 |
+
lo_diff = (3, 3, 3) # Lower bounds for color differences (can be adjusted)
|
50 |
+
up_diff = (3, 3, 3) # Upper bounds for color differences (can be adjusted)
|
51 |
+
|
52 |
+
# Perform the flood fill operation
|
53 |
+
cv2.floodFill(rgb_image, mask, seed_point, seed_color, lo_diff, up_diff, 8)
|
54 |
+
# Convert back to RGB and then to Image for saving
|
55 |
+
filled_image = cv2.cvtColor(rgb_image, cv2.COLOR_BGR2RGB)
|
56 |
+
filled_image = Image.fromarray(filled_image)
|
57 |
+
|
58 |
+
# Save the filled image to disk
|
59 |
+
|
60 |
+
# Optionally, save the mask to review which areas were filled
|
61 |
+
mask_image = Image.fromarray(mask[1:-1, 1:-1] * 255) # Scale mask to 0-255 for visibility
|
62 |
+
|
63 |
+
|
64 |
+
|
65 |
+
# Dilate the mask to extend the transparency slightly
|
66 |
+
kernel = np.ones((12,12), np.uint8) # You can adjust the kernel size for more/less dilation
|
67 |
+
dilated_mask = cv2.dilate(mask, kernel, iterations = 1)
|
68 |
+
|
69 |
+
# Apply Gaussian blur to the dilated mask to smooth the edges
|
70 |
+
blurred_mask = cv2.GaussianBlur(dilated_mask, (5, 5), 0)
|
71 |
+
|
72 |
+
# Update alpha channel based on the blurred mask
|
73 |
+
alpha_channel[blurred_mask[1:h+1, 1:w+1] == 1] = 0
|
74 |
+
|
75 |
+
# Combine RGB and modified Alpha into the final image
|
76 |
+
final_image = np.dstack((rgb_image, alpha_channel))
|
77 |
+
|
78 |
+
# Apply original colors back only to non-transparent areas
|
79 |
+
original_rgb = np.array(original.convert('RGB'))
|
80 |
+
final_rgb = final_image[:, :, :3] # Extract RGB channels
|
81 |
+
final_rgb[blurred_mask[1:h+1, 1:w+1] != 1] = original_rgb[blurred_mask[1:h+1, 1:w+1] != 1]
|
82 |
+
|
83 |
+
# Recombine with alpha channel
|
84 |
+
final_image_with_original_colors = np.dstack((final_rgb, alpha_channel))
|
85 |
+
final_image_with_original_colors = Image.fromarray(final_image_with_original_colors)
|
86 |
+
|
87 |
+
return final_image_with_original_colors
|
88 |
+
|
89 |
+
|
process_html.py
CHANGED
@@ -1,108 +1,319 @@
|
|
1 |
-
import os
|
2 |
import re, fileinput, sys
|
3 |
-
import statblock_helper as sth
|
4 |
import utilities as u
|
5 |
import description_helper as dsh
|
6 |
|
7 |
import gradio as gr
|
8 |
|
9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
# Assigning strings to variables for replacing location of dependencies for the webpage to local static folders
|
12 |
# Path is ../../ for the html files location in output/dated_folder/
|
13 |
break_tag = "<br>"
|
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 |
-
def insert_tag_before(html_file, pattern, new_tag):
|
48 |
-
index = html_file.find(pattern)
|
49 |
-
if index != -1:
|
50 |
-
output = html_file[:index] + new_tag + html_file[index:]
|
51 |
-
return output
|
52 |
-
|
53 |
-
def insert_tag_after(html_file, pattern, new_tag):
|
54 |
-
len_new_tag = len(new_tag)
|
55 |
-
print(len_new_tag)
|
56 |
-
index = html_file.find(pattern)
|
57 |
-
output = html_file[:index + len(pattern)] + new_tag + html_file[ index + len(pattern):]
|
58 |
-
return output
|
59 |
-
|
60 |
-
# Modify the output from the home/userbrewery process.js
|
61 |
-
def process_html(self, user_text):
|
62 |
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
html_as_string = html_as_string.replace(old_style, new_style)
|
73 |
-
html_as_string = html_as_string.replace(old_5estyle, new_5estyle)
|
74 |
-
|
75 |
-
# Strip out <br> so that it can be readded after strong marker to be bold, then remove it from Armor and Speed for reasons of formatting
|
76 |
-
html_as_string = html_as_string.replace("<br><strong>","<strong>")
|
77 |
-
html_as_string = html_as_string.replace("<strong>","<br><strong>")
|
78 |
-
html_as_string = html_as_string.replace("<br><strong>Armor","<strong>Armor" )
|
79 |
-
html_as_string = html_as_string.replace("<br><strong>Speed","<strong>Speed" )
|
80 |
-
html_as_string = html_as_string.replace("</p>","" )
|
81 |
-
html_as_string = html_as_string.replace("<p><br>","" )
|
82 |
-
html_as_string = insert_tag_before(html_as_string,"<hr>", "</dl>" )
|
83 |
-
html_as_string = insert_tag_before(html_as_string,"<strong>Armor Class</strong>", '<hr><dl>' )
|
84 |
|
85 |
-
# Call global usr_img and assign a local variable
|
86 |
-
input_img = usr_img
|
87 |
-
print(usr_img)
|
88 |
-
|
89 |
-
# Store image location string as variable to make it accesisble for locating and formatting
|
90 |
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
-
|
94 |
-
|
|
|
|
|
|
|
|
|
95 |
|
96 |
-
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
|
100 |
-
#
|
101 |
-
|
102 |
-
|
|
|
|
|
103 |
clean_html.close()
|
104 |
-
#
|
105 |
del u.link_list[:]
|
106 |
-
u.link_list.append(
|
107 |
-
u.link_list.append(
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import re, fileinput, sys
|
|
|
2 |
import utilities as u
|
3 |
import description_helper as dsh
|
4 |
|
5 |
import gradio as gr
|
6 |
|
7 |
+
# HTML section headers
|
8 |
+
actions_header = """<h4 id="actions">Actions</h4>
|
9 |
+
"""
|
10 |
+
cantrips_header = """<h4 id="cantrips">Cantrips</h4>
|
11 |
+
"""
|
12 |
+
spells_header = """<h4 id="known spells">Known Spells</h4>
|
13 |
+
"""
|
14 |
+
spell_slot_header = """<h4 id="spell slots">Spell Slots</h4>
|
15 |
+
"""
|
16 |
+
legendary_actions_header = """<h4 id="legendary actions">Legendary Actions</h4>
|
17 |
+
"""
|
18 |
|
19 |
# Assigning strings to variables for replacing location of dependencies for the webpage to local static folders
|
20 |
# Path is ../../ for the html files location in output/dated_folder/
|
21 |
break_tag = "<br>"
|
22 |
+
def build_html_base(
|
23 |
+
mon_name,
|
24 |
+
mon_size,
|
25 |
+
mon_type,
|
26 |
+
mon_subtype,
|
27 |
+
mon_alignment,
|
28 |
+
mon_armor_class,
|
29 |
+
mon_hp,
|
30 |
+
mon_hit_dice,
|
31 |
+
mon_speed,
|
32 |
+
mon_abilities,
|
33 |
+
mon_saving_throws,
|
34 |
+
mon_skills,
|
35 |
+
mon_damage_resistance,
|
36 |
+
mon_senses,
|
37 |
+
mon_languages,
|
38 |
+
mon_challenge_rating,
|
39 |
+
mon_xp,
|
40 |
+
mon_actions,
|
41 |
+
mon_description,
|
42 |
+
mon_image_path,
|
43 |
+
mon_cantrips = False,
|
44 |
+
mon_spells = False,
|
45 |
+
mon_spell_slots = False,
|
46 |
+
mon_legendary_actions = False
|
47 |
+
|
48 |
+
) :
|
49 |
|
50 |
+
|
51 |
+
|
52 |
+
# Combine the properties that will go on a single line
|
53 |
+
if mon_subtype != "" :
|
54 |
+
mon_properties = f"{mon_size}, {mon_type}, {mon_subtype}, {mon_alignment}"
|
55 |
+
else: mon_properties = f"{mon_size}, {mon_type}, {mon_alignment}"
|
56 |
+
mon_abilities = parse_abilities_from_text(mon_abilities)
|
57 |
+
|
58 |
+
# Template for the page
|
59 |
+
html_base = f"""<!DOCTYPE html>
|
60 |
+
<html>
|
61 |
+
<head>
|
62 |
+
|
63 |
+
<link href="../../dependencies/all.css" rel="stylesheet" />
|
64 |
+
<link href="../../dependencies/css.css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
65 |
+
<link href='../../dependencies/bundle.css' rel='stylesheet' />
|
66 |
+
<link rel="icon" href="../../dependencies/favicon.ico" type="image/x-icon" />
|
67 |
+
<title>{mon_name}</title>
|
68 |
+
</head>
|
69 |
+
<body>
|
70 |
+
<link href='../../dependencies/style.css' rel='stylesheet' />
|
71 |
+
<link href='../../dependencies/5ePHBstyle.css' rel='stylesheet' />
|
72 |
+
|
73 |
+
<div class='brewRenderer'>
|
74 |
+
<style>undefined</style>
|
75 |
+
<div class='pages'>
|
76 |
+
<div class='page phb' id='p1' key='0' >
|
77 |
+
<div className='columnWrapper'>
|
78 |
+
<div class="block monster frame wide" >
|
79 |
+
<h4 id="user-monster-name">{mon_name}</h4>
|
80 |
+
<p><em>{mon_properties}</em>
|
81 |
+
<p><img class=" " style="width:330px; mix-blend-mode:multiply; border:3px solid black;" src={mon_image_path} alt="image"></p>
|
82 |
+
<div class="block descriptive">
|
83 |
+
<h5 id="user-monster-description">{mon_description}</h5>
|
84 |
+
|
85 |
+
</div>
|
86 |
+
<hr>
|
87 |
+
<dl>
|
88 |
+
<strong>Armor Class</strong> : {mon_armor_class}
|
89 |
+
<strong>Hit Points</strong>: {mon_hp} Hit Dice : {mon_hit_dice}
|
90 |
+
<strong>Speed</strong>: {mon_speed}
|
91 |
+
</dl>
|
92 |
+
<hr>
|
93 |
+
<table>
|
94 |
+
<thead>
|
95 |
+
<tr>
|
96 |
+
<th align=center>STR</th>
|
97 |
+
<th align=center>DEX</th>
|
98 |
+
<th align=center>CON</th>
|
99 |
+
<th align=center>INT</th>
|
100 |
+
<th align=center>WIS</th>
|
101 |
+
<th align=center>CHA</th>
|
102 |
+
</tr>
|
103 |
+
</thead>
|
104 |
+
<tbody>
|
105 |
+
<tr>
|
106 |
+
<td align=center>{mon_abilities[0]}</td>
|
107 |
+
<td align=center>{mon_abilities[1]}</td>
|
108 |
+
<td align=center>{mon_abilities[2]}</td>
|
109 |
+
<td align=center>{mon_abilities[3]}</td>
|
110 |
+
<td align=center>{mon_abilities[4]}</td>
|
111 |
+
<td align=center>{mon_abilities[5]}</td>
|
112 |
+
</tr>
|
113 |
+
</tbody>
|
114 |
+
</table>
|
115 |
+
<hr>
|
116 |
+
<strong>Saving Throws</strong> : {mon_saving_throws}
|
117 |
+
<br><strong>Skills</strong> : {mon_skills}
|
118 |
+
<br><strong>Resistances</strong> : {mon_damage_resistance}
|
119 |
+
<br><strong>Senses</strong> : {mon_senses}
|
120 |
+
<br><strong>Languages</strong> : {mon_languages}
|
121 |
+
<br><strong>Challenge Rating</strong> : {mon_challenge_rating} ({mon_xp})"""
|
122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
+
if mon_actions :
|
125 |
+
print("Actions : True")
|
126 |
+
parsed_actions = parse_actions_from_text(mon_actions)
|
127 |
+
|
128 |
+
html_file_as_text = f"""{html_base} <hr> {actions_header}
|
129 |
+
{''.join(parsed_actions)}"""
|
130 |
+
else:
|
131 |
+
print("Actions : False")
|
132 |
+
html_file_as_text = html_base
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
+
if mon_cantrips:
|
136 |
+
print(mon_cantrips)
|
137 |
+
mon_cantrips = mon_cantrips.replace("Cantrips", '')
|
138 |
+
mon_cantrips = parse_cantrips_from_text(mon_cantrips)
|
139 |
+
html_file_as_text = html_file_as_text + cantrips_header + mon_cantrips
|
140 |
+
|
141 |
+
if mon_spells :
|
142 |
+
print(mon_spells)
|
143 |
+
mon_spells = mon_spells.replace("Known Spells",'')
|
144 |
+
mon_spells = parse_spells_from_text(mon_spells)
|
145 |
+
html_file_as_text = html_file_as_text + spells_header + mon_spells
|
146 |
|
147 |
+
if mon_spell_slots:
|
148 |
+
print(mon_spell_slots)
|
149 |
+
mon_spell_slots = mon_spell_slots.replace("Spell Slots", '')
|
150 |
+
mon_spell_slots = parse_spell_slots_from_text(mon_spell_slots)
|
151 |
+
html_file_as_text = html_file_as_text + spell_slot_header + mon_spell_slots
|
152 |
+
|
153 |
|
154 |
+
else:
|
155 |
+
print("Spells : False")
|
156 |
+
|
157 |
+
if mon_legendary_actions:
|
158 |
+
print("Legendary Actions : True")
|
159 |
+
mon_legendary_actions = mon_legendary_actions.replace("Legendary Actions \n\n", '')
|
160 |
+
mon_legendary_actions = parse_legendary_action_from_text(mon_legendary_actions)
|
161 |
+
html_file_as_text = html_file_as_text +legendary_actions_header + mon_legendary_actions
|
162 |
+
else:
|
163 |
+
print("Legendary Actions : False")
|
164 |
|
165 |
+
# Open a file path that will receive the processed text
|
166 |
+
u.gen_file_name(mon_name)
|
167 |
+
mon_file_path = f"{u.file_name_list[0]}/{u.file_name_list[1]}.html"
|
168 |
+
with open(mon_file_path, 'w') as clean_html:
|
169 |
+
clean_html.write(html_file_as_text)
|
170 |
clean_html.close()
|
171 |
+
# Clear link list and append with new entries
|
172 |
del u.link_list[:]
|
173 |
+
u.link_list.append(u.file_name_list[0]+'/' + u.file_name_list[1] +'.html')
|
174 |
+
u.link_list.append(mon_type)
|
175 |
+
|
176 |
+
#Passing back a file path that Gradio can access and is local
|
177 |
+
return mon_file_path
|
178 |
+
|
179 |
+
def parse_actions_from_text(edited_text):
|
180 |
+
html_content = '<dl>'
|
181 |
+
actions = []
|
182 |
+
action_entries = edited_text.strip().split('\n\n')
|
183 |
+
print(action_entries)
|
184 |
+
for entry in action_entries:
|
185 |
+
parts = entry.split(';')
|
186 |
+
action_dict = {
|
187 |
+
"name": parts[0].split(": ")[1].strip(),
|
188 |
+
"desc": parts[1].split("Description: ")[1].strip()
|
189 |
+
}
|
190 |
+
actions.append(action_dict)
|
191 |
+
|
192 |
+
for action in actions:
|
193 |
+
html_content += f"<dt><em><strong>{action['name']}</strong></em> :</dt><dd>‘{action['desc']}</dd>"
|
194 |
+
html_content += "<br>"
|
195 |
+
html_content=html_content.rstrip('br')
|
196 |
+
|
197 |
+
html_content += '</dl>'
|
198 |
+
return html_content
|
199 |
+
|
200 |
+
def parse_abilities_from_text(abilities):
|
201 |
+
abilities_list = []
|
202 |
+
ability_entries = abilities.strip().split('\n')
|
203 |
+
for entry in ability_entries:
|
204 |
+
parts = entry.split(':')
|
205 |
+
ability_value = parts[1]
|
206 |
+
abilities_list.append(ability_value)
|
207 |
+
return abilities_list
|
208 |
+
|
209 |
+
def parse_cantrips_from_text(cantrips):
|
210 |
+
html_content = '<dl>'
|
211 |
+
cantrips_list = []
|
212 |
+
cantrip_entries = cantrips.strip().split('\n\n')
|
213 |
+
for entry in cantrip_entries:
|
214 |
+
parts = entry.split(';')
|
215 |
+
cantrip_dict = {
|
216 |
+
"name": parts[0],
|
217 |
+
"desc": parts[1].split("Description: ")[1].strip()
|
218 |
+
}
|
219 |
+
cantrips_list.append(cantrip_dict)
|
220 |
+
for cantrip in cantrips_list:
|
221 |
+
html_content += f"<dt><em><strong>{cantrip['name']}</strong></em> :</dt> <dd> ‘{cantrip['desc']}’</dd>"
|
222 |
+
html_content += "<br>"
|
223 |
+
html_content=html_content.rstrip('br')
|
224 |
+
html_content += '</dl>'
|
225 |
+
return html_content
|
226 |
+
|
227 |
+
def parse_spells_from_text(spells):
|
228 |
+
html_content = '<dl>'
|
229 |
+
spells_list = []
|
230 |
+
spell_entries = spells.strip().split('\n\n')
|
231 |
+
for entry in spell_entries:
|
232 |
+
print(f"Spell entry = {entry}")
|
233 |
+
parts = entry.split(';')
|
234 |
+
spell_name_part = parts[0]
|
235 |
+
level_desc_part = parts[1]
|
236 |
+
|
237 |
+
# Extract the spell's name (before 'level:')
|
238 |
+
name = spell_name_part.strip()
|
239 |
+
|
240 |
+
# Further split level and description
|
241 |
+
level_part = level_desc_part.split(", Description:")[0].strip()
|
242 |
+
description_part = level_desc_part.split(", Description:")[1].strip() if ", Description:" in level_desc_part else ""
|
243 |
+
|
244 |
+
# Extract the level (assuming it follows 'Level: ' directly)
|
245 |
+
level = level_part.replace("Level: ", "").strip()
|
246 |
+
print(f"Level = {level}")
|
247 |
+
|
248 |
+
|
249 |
+
# Assemble the dictionary for this spell
|
250 |
+
spell_dict = {
|
251 |
+
"name": name,
|
252 |
+
"level": level,
|
253 |
+
"desc": description_part
|
254 |
+
}
|
255 |
+
spells_list.append(spell_dict)
|
256 |
+
for spell in spells_list:
|
257 |
+
html_content += f"<dt><em><strong>{spell['name']}</strong></em> :</dt> <dd> ‘Level : {spell['level']} {spell['desc']}’</dd>"
|
258 |
+
html_content += " <br>"
|
259 |
+
html_content=html_content.rstrip('br')
|
260 |
+
html_content += '</dl>'
|
261 |
+
return html_content
|
262 |
+
|
263 |
+
def parse_spell_slots_from_text(spell_slots):
|
264 |
+
html_content = '<dl>'
|
265 |
+
spell_slots_list = []
|
266 |
+
spell_slot_entries = spell_slots.strip().split('\n\n')
|
267 |
+
for entry in spell_slot_entries:
|
268 |
+
|
269 |
+
if '0' not in entry:
|
270 |
+
parts = entry.split(':')
|
271 |
+
spell_slot_dict = {
|
272 |
+
"level": parts[0],
|
273 |
+
"number": parts[1]
|
274 |
+
|
275 |
+
}
|
276 |
+
|
277 |
+
spell_slots_list.append(spell_slot_dict)
|
278 |
+
for spell_slot in spell_slots_list:
|
279 |
+
html_content += f"<dt><em><strong>{spell_slot['level']}</strong></em>:</dt> <dd>‘{spell_slot['number']}’</dd>"
|
280 |
+
html_content += " <br>"
|
281 |
+
html_content=html_content.rstrip(' br')
|
282 |
+
html_content += '</dl>'
|
283 |
+
return html_content
|
284 |
+
|
285 |
+
def parse_legendary_action_from_text(legendary_actions):
|
286 |
+
html_content = '<dl>'
|
287 |
+
parts = legendary_actions.split('\n\n')
|
288 |
+
description = parts[0].strip()
|
289 |
+
description = description + "<br>"
|
290 |
+
print(f"Description : {description}")
|
291 |
+
actions_text = parts[1:]
|
292 |
+
actions = ';'.join(actions_text)
|
293 |
+
actions = actions.split(';')
|
294 |
+
print(f"actions : {actions}")
|
295 |
+
|
296 |
+
legendary_actions_dict = {
|
297 |
+
"description": description,
|
298 |
+
"actions":[]
|
299 |
+
}
|
300 |
+
html_content += description
|
301 |
+
# Process each action
|
302 |
+
for action in actions:
|
303 |
+
print(f"action to be parsed: {action}")
|
304 |
+
action_split = action.split(':')
|
305 |
+
print(f"Split Action : {action_split}")
|
306 |
+
legendary_actions_dict['actions'].append({
|
307 |
+
"name": action_split[0],
|
308 |
+
"desc": action_split[1]
|
309 |
+
})
|
310 |
+
|
311 |
+
for action in legendary_actions_dict['actions']:
|
312 |
+
print(action)
|
313 |
+
html_content += f"<dt><em><strong>{action['name']}</strong></em>:</dt> <dd> ‘{action['desc']}’</dd>"
|
314 |
+
html_content += " <br>"
|
315 |
+
html_content=html_content.rstrip(' br')
|
316 |
+
html_content += '</dl>'
|
317 |
+
return html_content
|
318 |
+
|
319 |
+
|
process_text.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
#Function to process text from Key Value pairs into User Friendly text
|
3 |
+
|
4 |
+
def format_mon_qualities(qualities):
|
5 |
+
formatted_text = ""
|
6 |
+
for key, value in qualities.items():
|
7 |
+
formatted_text += f" {key} : {value}, "
|
8 |
+
formatted_text = formatted_text.rstrip(", ")
|
9 |
+
return formatted_text
|
10 |
+
|
11 |
+
def format_actions_for_editing(actions):
|
12 |
+
formatted_text = ""
|
13 |
+
for action in actions:
|
14 |
+
formatted_text += f"Action Name: {action['name']}; Description: {action['desc']} \n\n"
|
15 |
+
formatted_text = formatted_text.rstrip(", ")
|
16 |
+
return formatted_text
|
17 |
+
|
18 |
+
def format_abilities_for_editing(abilities):
|
19 |
+
formatted_text = ""
|
20 |
+
key_list = list(abilities)
|
21 |
+
for key in key_list:
|
22 |
+
formatted_text += f"{key} : {abilities[key]}\n"
|
23 |
+
return formatted_text
|
24 |
+
|
25 |
+
def format_spells_for_editing(spells):
|
26 |
+
print(f"Spells in format_spells function : {spells}")
|
27 |
+
formatted_cantrips = ""
|
28 |
+
formatted_spells = ""
|
29 |
+
formatted_spell_slots = ""
|
30 |
+
if spells['cantrips'] and len(spells['cantrips']) >= 1:
|
31 |
+
print(f"Cantrips : {spells['cantrips']}")
|
32 |
+
formatted_cantrips += "Cantrips \n\n"
|
33 |
+
for cantrip in spells['cantrips']:
|
34 |
+
formatted_cantrips += f"{cantrip['name']}; Description: {cantrip['desc']}, \n\n"
|
35 |
+
if spells['known_spells'] and len(spells['known_spells']) >= 1:
|
36 |
+
formatted_spells += "Known Spells \n\n"
|
37 |
+
for spell in spells['known_spells']:
|
38 |
+
formatted_spells += f"{spell['name']}; Level: {spell['level']}, Description: {spell['desc']}, \n\n"
|
39 |
+
if spells['spell_slots'] and len(spells['spell_slots']) >= 1:
|
40 |
+
print (f"Spell Slots : {spells['spell_slots']}")
|
41 |
+
formatted_spell_slots += "Spell Slots \n\n"
|
42 |
+
for key, value in spells['spell_slots'].items():
|
43 |
+
if value != 0:
|
44 |
+
formatted_spell_slots += f"{key.replace('_',' ')}: {value}, \n\n"
|
45 |
+
formatted_cantrips = formatted_cantrips.rstrip(", ")
|
46 |
+
formatted_spells = formatted_spells.rstrip(", ")
|
47 |
+
formatted_spell_slots = formatted_spell_slots.rstrip(", ")
|
48 |
+
return formatted_cantrips, formatted_spells, formatted_spell_slots
|
49 |
+
|
50 |
+
def format_legendaries_for_editing(legendary_actions):
|
51 |
+
formatted_text = ""
|
52 |
+
if legendary_actions['actions'] and len(legendary_actions['actions']) >= 1:
|
53 |
+
formatted_text += "Legendary Actions \n\n"
|
54 |
+
formatted_text += f"{legendary_actions['actions']} \n\n"
|
55 |
+
if legendary_actions['options']:
|
56 |
+
for option in legendary_actions['options']:
|
57 |
+
formatted_text += f"{option['name']} : {option['desc']}, \n\n"
|
58 |
+
formatted_text = formatted_text.rstrip(", \n\n")
|
59 |
+
return formatted_text
|
60 |
+
|
requirements.txt
DELETED
@@ -1,158 +0,0 @@
|
|
1 |
-
# install last
|
2 |
-
# pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
3 |
-
# pip install flash-attn
|
4 |
-
#pip install auto-gptq
|
5 |
-
accelerate
|
6 |
-
aiofiles
|
7 |
-
aiohttp
|
8 |
-
aiosignal
|
9 |
-
altair
|
10 |
-
annotated-types
|
11 |
-
antlr4-python3-runtime
|
12 |
-
anyio
|
13 |
-
async-timeout
|
14 |
-
attrs
|
15 |
-
backoff
|
16 |
-
blinker
|
17 |
-
cachetools
|
18 |
-
certifi
|
19 |
-
charset-normalizer
|
20 |
-
clarifai
|
21 |
-
clarifai-grpc
|
22 |
-
click
|
23 |
-
cmake
|
24 |
-
cohere
|
25 |
-
colorama
|
26 |
-
coloredlogs
|
27 |
-
compel
|
28 |
-
contextlib2
|
29 |
-
contourpy
|
30 |
-
ctransformers
|
31 |
-
cycler
|
32 |
-
dataclasses-json
|
33 |
-
datasets
|
34 |
-
diffusers
|
35 |
-
dill
|
36 |
-
einops
|
37 |
-
exceptiongroup
|
38 |
-
exllamav2
|
39 |
-
fastapi
|
40 |
-
fastavro
|
41 |
-
ffmpy
|
42 |
-
filelock
|
43 |
-
fonttools
|
44 |
-
frozenlist
|
45 |
-
fsspec
|
46 |
-
gekko
|
47 |
-
gitdb
|
48 |
-
GitPython
|
49 |
-
googleapis-common-protos
|
50 |
-
gradio
|
51 |
-
gradio_client
|
52 |
-
greenlet
|
53 |
-
grpcio
|
54 |
-
h11
|
55 |
-
httpcore
|
56 |
-
httpx
|
57 |
-
huggingface-hub
|
58 |
-
humanfriendly
|
59 |
-
idna
|
60 |
-
importlib-metadata
|
61 |
-
importlib-resources
|
62 |
-
iniconfig
|
63 |
-
Jinja2
|
64 |
-
jsonpatch
|
65 |
-
jsonpointer
|
66 |
-
jsonschema
|
67 |
-
jsonschema-specifications
|
68 |
-
kiwisolver
|
69 |
-
manifest-ml
|
70 |
-
Markdown
|
71 |
-
markdown-it-py
|
72 |
-
MarkupSafe
|
73 |
-
marshmallow
|
74 |
-
matplotlib
|
75 |
-
mdurl
|
76 |
-
mpmath
|
77 |
-
multidict
|
78 |
-
multiprocess
|
79 |
-
mypy-extensions
|
80 |
-
networkx
|
81 |
-
ninja
|
82 |
-
nlpcloud
|
83 |
-
numpy
|
84 |
-
omegaconf
|
85 |
-
openai
|
86 |
-
openlm
|
87 |
-
optimum
|
88 |
-
orjson
|
89 |
-
packaging
|
90 |
-
pandas
|
91 |
-
peft
|
92 |
-
Pillow
|
93 |
-
pluggy
|
94 |
-
protobuf
|
95 |
-
psutil
|
96 |
-
py-cpuinfo
|
97 |
-
pyarrow
|
98 |
-
pydantic
|
99 |
-
pydantic_core
|
100 |
-
pydeck
|
101 |
-
pydub
|
102 |
-
Pygments
|
103 |
-
pyparsing
|
104 |
-
pyreadline3
|
105 |
-
pytest
|
106 |
-
python-dateutil
|
107 |
-
python-dotenv
|
108 |
-
python-multipart
|
109 |
-
python-rapidjson
|
110 |
-
python_on_whales
|
111 |
-
pytz
|
112 |
-
PyYAML
|
113 |
-
qinfer
|
114 |
-
redis
|
115 |
-
referencing
|
116 |
-
regex
|
117 |
-
reportlab
|
118 |
-
requests
|
119 |
-
rich
|
120 |
-
rouge
|
121 |
-
rpds-py
|
122 |
-
safetensors
|
123 |
-
schema
|
124 |
-
semantic-version
|
125 |
-
sentencepiece
|
126 |
-
six
|
127 |
-
smmap
|
128 |
-
sniffio
|
129 |
-
soupsieve
|
130 |
-
SQLAlchemy
|
131 |
-
sqlitedict
|
132 |
-
starlette
|
133 |
-
streamlit
|
134 |
-
sympy
|
135 |
-
tenacity
|
136 |
-
tokenizers
|
137 |
-
toml
|
138 |
-
tomli
|
139 |
-
toolz
|
140 |
-
|
141 |
-
tornado
|
142 |
-
tqdm
|
143 |
-
transformers
|
144 |
-
tritonclient
|
145 |
-
typing-inspect
|
146 |
-
typing_extensions
|
147 |
-
tzdata
|
148 |
-
tzlocal
|
149 |
-
urllib3
|
150 |
-
uvicorn
|
151 |
-
validators
|
152 |
-
watchdog
|
153 |
-
websockets
|
154 |
-
wheel
|
155 |
-
xxhash
|
156 |
-
yarl
|
157 |
-
zipp
|
158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sd_generator.py
CHANGED
@@ -1,73 +1,32 @@
|
|
1 |
-
from diffusers import StableDiffusionXLPipeline
|
2 |
-
import torch
|
3 |
-
from compel import Compel, ReturnedEmbeddingsType
|
4 |
-
import utilities as u
|
5 |
import time
|
|
|
|
|
|
|
|
|
6 |
|
7 |
|
8 |
-
|
9 |
-
|
10 |
-
del image_list
|
11 |
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
start_time = time.time()
|
22 |
-
|
23 |
|
24 |
-
# create variable that calls SD model and work in float 16
|
25 |
-
# from_single_file is critical for loading a local file
|
26 |
-
pipeline = StableDiffusionXLPipeline.from_single_file(model_path, custom_pipeline="lpw_stable_diffusion", torch_dtype=torch.float16, variant="fp16" ).to("cuda")
|
27 |
-
|
28 |
-
# enable vae slicing ton prevent Out Of Memory Error when generating batches
|
29 |
-
# pipeline.enable_vae_slicing()
|
30 |
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED,
|
35 |
-
requires_pooled=[False, True],
|
36 |
-
truncate_long_prompts=False)
|
37 |
-
|
38 |
-
|
39 |
-
# assign prompt as global sd_input
|
40 |
-
prompt = sd_input
|
41 |
-
|
42 |
-
|
43 |
-
# Not sure what conditioning or pooled means, but it's in the demo code from here https://github.com/damian0815/compel/blob/main/compel-demo-sdxl.ipynb
|
44 |
-
negative_prompt = "sex, lingerie, midriff, watermark, text, fastnegative2, blurry, ugly, low quality, worst quality, 3d"
|
45 |
-
conditioning, pooled = compel([prompt, negative_prompt])
|
46 |
-
print(conditioning.shape, pooled.shape)
|
47 |
|
48 |
-
|
49 |
-
|
50 |
-
for x in range(num_img):
|
51 |
-
image = pipeline(prompt_embeds=conditioning[0:1], pooled_prompt_embeds=pooled[0:1],
|
52 |
-
negative_prompt_embeds=conditioning[1:2], negative_pooled_prompt_embeds=pooled[1:2],
|
53 |
-
num_inference_steps=30, width=1024, height=1024).images[0]
|
54 |
-
image_name = u.make_image_name()
|
55 |
-
image.save(image_name)
|
56 |
-
image_list.append(image_name)
|
57 |
-
del image
|
58 |
-
|
59 |
-
|
60 |
-
del pipeline
|
61 |
-
del compel
|
62 |
-
|
63 |
-
del image_name
|
64 |
-
del prompt
|
65 |
-
|
66 |
-
u.reclaim_mem()
|
67 |
-
print(image_list)
|
68 |
-
stop_time = time.time()
|
69 |
-
run_time = stop_time - start_time
|
70 |
-
print(f"Time to generate : {run_time}")
|
71 |
|
72 |
-
return
|
73 |
|
|
|
|
|
|
|
|
|
|
|
1 |
import time
|
2 |
+
import utilities as u
|
3 |
+
from PIL import Image
|
4 |
+
import replicate
|
5 |
+
from pathlib import Path
|
6 |
|
7 |
|
8 |
+
start_time = time.time()
|
9 |
+
temp_image_path = "./image_temp/"
|
|
|
10 |
|
11 |
+
def preview_and_generate_image(character_name,sd_prompt, token):
|
12 |
+
img_start = time.time()
|
13 |
+
output=replicate.run(
|
14 |
+
"drakosfire/dnd_monster_generator:80c9d4247c1e1bd92084af24fa61e88b6a809a42585e343af9722d90337c9ada",
|
15 |
+
input={
|
16 |
+
"character_name":character_name,
|
17 |
+
"sd_prompt":sd_prompt,
|
18 |
+
"token":token
|
19 |
|
20 |
+
}
|
21 |
+
)
|
|
|
|
|
|
|
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
|
24 |
+
img_time = time.time() - img_start
|
25 |
+
img_its = 35/img_time
|
26 |
+
print(f"image gen time = {img_time} and {img_its} it/s")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
+
total_time = time.time() - start_time
|
29 |
+
print(total_time)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
+
return output
|
32 |
|
statblock_helper.py
DELETED
@@ -1,165 +0,0 @@
|
|
1 |
-
import description_helper as dsh
|
2 |
-
import time
|
3 |
-
import os
|
4 |
-
import subprocess
|
5 |
-
import utilities as u
|
6 |
-
from python_on_whales import docker
|
7 |
-
|
8 |
-
from exllamav2 import (
|
9 |
-
ExLlamaV2,
|
10 |
-
ExLlamaV2Config,
|
11 |
-
ExLlamaV2Cache,
|
12 |
-
ExLlamaV2Tokenizer,
|
13 |
-
ExLlamaV2Lora,
|
14 |
-
)
|
15 |
-
|
16 |
-
from exllamav2.generator import (
|
17 |
-
ExLlamaV2BaseGenerator,
|
18 |
-
ExLlamaV2StreamingGenerator,
|
19 |
-
ExLlamaV2Sampler
|
20 |
-
)
|
21 |
-
|
22 |
-
file_name_list = []
|
23 |
-
output_list = []
|
24 |
-
|
25 |
-
# This needs to be moved to utilities
|
26 |
-
|
27 |
-
def gen_file_name():
|
28 |
-
del file_name_list[:]
|
29 |
-
timestr = time.strftime("%H%M%S")
|
30 |
-
input_dir = f"/home/user/app/output/{u.make_folder()}"
|
31 |
-
|
32 |
-
mon_file_name = dsh.generate_monster_desc.monster_type.replace(' ', '')
|
33 |
-
file_name = mon_file_name + "_" + timestr
|
34 |
-
file_name_list.append(input_dir)
|
35 |
-
file_name_list.append(file_name)
|
36 |
-
file_name_list.append(mon_file_name)
|
37 |
-
|
38 |
-
# Function to find the beginning of the output markdown and remove the prompt before it
|
39 |
-
|
40 |
-
def rembeforestart(text):
|
41 |
-
where_start = text.find('{{')
|
42 |
-
if where_start == -1:
|
43 |
-
return text
|
44 |
-
return text[where_start:]
|
45 |
-
|
46 |
-
# Function to remove any extra after the end of the Markdown
|
47 |
-
|
48 |
-
def remafterend(text):
|
49 |
-
where_end = text.find('}}')
|
50 |
-
if where_end == -1:
|
51 |
-
return text
|
52 |
-
return text[:where_end + 2]
|
53 |
-
|
54 |
-
def generate_statblock(input):
|
55 |
-
gen_file_name()
|
56 |
-
run_time = time.time()
|
57 |
-
generate_statblock.input = input
|
58 |
-
|
59 |
-
# Initialize model and cache
|
60 |
-
|
61 |
-
model_directory = "/home/user/app/models/Speechless-Llama2-Hermes-Orca-Platypus-WizardLM-13B-GPTQ"
|
62 |
-
config = ExLlamaV2Config()
|
63 |
-
config.model_dir = model_directory
|
64 |
-
config.prepare()
|
65 |
-
|
66 |
-
model = ExLlamaV2(config)
|
67 |
-
print("Loading model: " + model_directory)
|
68 |
-
model.load()
|
69 |
-
|
70 |
-
tokenizer = ExLlamaV2Tokenizer(config)
|
71 |
-
|
72 |
-
cache = ExLlamaV2Cache(model)
|
73 |
-
|
74 |
-
# Load LoRA
|
75 |
-
|
76 |
-
lora_directory = "/home/user/app/models/statblock-alpha"
|
77 |
-
lora = ExLlamaV2Lora.from_directory(model, lora_directory)
|
78 |
-
|
79 |
-
# Initialize generators
|
80 |
-
|
81 |
-
|
82 |
-
simple_generator = ExLlamaV2BaseGenerator(model, cache, tokenizer)
|
83 |
-
|
84 |
-
# Sampling settings
|
85 |
-
|
86 |
-
settings = ExLlamaV2Sampler.Settings()
|
87 |
-
settings.temperature = 0.85
|
88 |
-
settings.top_k = 50
|
89 |
-
settings.top_p = 0.8
|
90 |
-
settings.token_repetition_penalty = 1.1
|
91 |
-
|
92 |
-
max_new_tokens = 1100
|
93 |
-
|
94 |
-
# Build prompt
|
95 |
-
|
96 |
-
monster_frame_wide ="""{{monster,frame,wide"""
|
97 |
-
statblock_start = f"""{monster_frame_wide} \n## {dsh.generate_monster_desc.monster_type} """
|
98 |
-
input_context = f"Write a .brewery formatted dungeons and dragons statblock of {generate_statblock.input} any abilities that reference actions need to have those actions defined \n" + statblock_start
|
99 |
-
print(input_context)
|
100 |
-
prompt = input_context
|
101 |
-
|
102 |
-
|
103 |
-
generate_time = time.time()
|
104 |
-
output_text = simple_generator.generate_simple(prompt, settings, max_new_tokens, loras = lora)
|
105 |
-
output_text = rembeforestart(output_text)
|
106 |
-
|
107 |
-
generate_statblock.output_text = remafterend(output_text)
|
108 |
-
print(generate_statblock.output_text)
|
109 |
-
print("statblock generation time : " + str(time.time() - generate_time))
|
110 |
-
|
111 |
-
input_dir = file_name_list[0]
|
112 |
-
input_md = open(f"{input_dir}/my-brew.md", 'w')
|
113 |
-
print(generate_statblock.output_text, file = input_md)
|
114 |
-
|
115 |
-
#md_process(md_path, file_name_list[1])
|
116 |
-
|
117 |
-
|
118 |
-
del model
|
119 |
-
del tokenizer
|
120 |
-
del output_text
|
121 |
-
del simple_generator
|
122 |
-
del cache
|
123 |
-
del lora
|
124 |
-
u.reclaim_mem()
|
125 |
-
print("total statblock generation time : " + str(time.time() - run_time))
|
126 |
-
output_list.append(input_context)
|
127 |
-
output_list.append(generate_statblock.output_text)
|
128 |
-
u.make_user_log()
|
129 |
-
return generate_statblock.output_text
|
130 |
-
|
131 |
-
# Function to process the my-brew.md file into a named html inside docker using process.js
|
132 |
-
def md_process(input_md,output_name):
|
133 |
-
|
134 |
-
file_name = output_name
|
135 |
-
# Passing in file name to derive absolute directory, and the desired output name
|
136 |
-
abs_path = os.path.abspath(input_md)
|
137 |
-
input_dir = os.path.dirname(abs_path)
|
138 |
-
|
139 |
-
print(abs_path)
|
140 |
-
print(input_dir)
|
141 |
-
# Subprocess to pass the docker command to the command line
|
142 |
-
# Docker compused from this https://github.com/G-Ambatte/.brewery/tree/experimentalCommandLineBrewProcess
|
143 |
-
process_call = f"node /home/user/app/homebrewery/cli/process.js --input {abs_path} --output {input_dir}/{file_name}.html --renderer v3 --overwrite"
|
144 |
-
print(process_call)
|
145 |
-
subprocess.run(process_call, shell=True)
|
146 |
-
|
147 |
-
# beginning of code to run html process and display in one shot
|
148 |
-
#while True:
|
149 |
-
#if os.path.isfile(file_name_list[0]+'/' + file_name_list[1] +'.html'):
|
150 |
-
#gen_html()
|
151 |
-
#break
|
152 |
-
#else:
|
153 |
-
#print("File not found, waiting.")
|
154 |
-
#time.sleep(1) # wait for 1 second before checking again
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
style.css
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body {
|
2 |
+
font-family: 'Libre Baskerville', serif;
|
3 |
+
background-color: #f9f6f1;
|
4 |
+
margin: 0;
|
5 |
+
padding: 0;
|
6 |
+
}
|
7 |
+
|
8 |
+
.stat-block {
|
9 |
+
max-width: 800px;
|
10 |
+
margin: 20px auto;
|
11 |
+
padding: 20px;
|
12 |
+
background-color: #fff;
|
13 |
+
border: 2px solid #333;
|
14 |
+
border-radius: 10px;
|
15 |
+
}
|
16 |
+
|
17 |
+
.stat-block h1, .stat-block h2 {
|
18 |
+
text-align: center;
|
19 |
+
color: #b33;
|
20 |
+
margin: 0;
|
21 |
+
}
|
22 |
+
|
23 |
+
.stat-block h1 {
|
24 |
+
font-size: 2em;
|
25 |
+
}
|
26 |
+
|
27 |
+
.stat-block h2 {
|
28 |
+
font-size: 1.5em;
|
29 |
+
margin-top: 10px;
|
30 |
+
}
|
31 |
+
|
32 |
+
.stat-section {
|
33 |
+
display: flex;
|
34 |
+
justify-content: space-between;
|
35 |
+
margin: 10px 0;
|
36 |
+
}
|
37 |
+
|
38 |
+
.stat-section div {
|
39 |
+
flex: 1;
|
40 |
+
padding: 10px;
|
41 |
+
margin: 5px;
|
42 |
+
background-color: #eee;
|
43 |
+
border-radius: 5px;
|
44 |
+
text-align: center;
|
45 |
+
}
|
46 |
+
|
47 |
+
.stat-section div label {
|
48 |
+
display: block;
|
49 |
+
margin-bottom: 5px;
|
50 |
+
color: #666;
|
51 |
+
}
|
52 |
+
|
53 |
+
.stat-section div input {
|
54 |
+
width: 100%;
|
55 |
+
padding: 5px;
|
56 |
+
border: 1px solid #333;
|
57 |
+
border-radius: 5px;
|
58 |
+
text-align: center;
|
59 |
+
font-family: 'Roboto', sans-serif;
|
60 |
+
}
|
tripo3d.py
ADDED
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import requests
|
3 |
+
import time
|
4 |
+
from pathlib import Path
|
5 |
+
|
6 |
+
# Load API key from environment variable
|
7 |
+
API_KEY = os.getenv('TRIPO3D_API_KEY')
|
8 |
+
if not API_KEY:
|
9 |
+
raise EnvironmentError("TRIPO3D_API_KEY not found in environment variables")
|
10 |
+
|
11 |
+
BASE_URL = "https://api.tripo3d.ai/v2/openapi"
|
12 |
+
headers = {
|
13 |
+
"Authorization": f"Bearer {API_KEY}"
|
14 |
+
}
|
15 |
+
|
16 |
+
def download_image(url, local_filename):
|
17 |
+
response = requests.get(url, stream=True)
|
18 |
+
if response.status_code == 200:
|
19 |
+
print("download of original image successful")
|
20 |
+
with open(local_filename, 'wb') as f:
|
21 |
+
for chunk in response.iter_content(1024):
|
22 |
+
f.write(chunk)
|
23 |
+
return local_filename
|
24 |
+
else:
|
25 |
+
raise Exception(f"Failed to download image from {url}, status code: {response.status_code}")
|
26 |
+
|
27 |
+
def upload_image(file_path):
|
28 |
+
url = f"{BASE_URL}/upload"
|
29 |
+
files = {'file': open(file_path, 'rb')}
|
30 |
+
|
31 |
+
response = requests.post(url, headers=headers, files=files)
|
32 |
+
|
33 |
+
if response.status_code == 200:
|
34 |
+
print(f"upload successful \n {response}")
|
35 |
+
return response.json()
|
36 |
+
else:
|
37 |
+
print(f"Error: {response.status_code}, {response.json()}")
|
38 |
+
return None
|
39 |
+
|
40 |
+
def create_image_to_model_task(image_token):
|
41 |
+
url = f"{BASE_URL}/task"
|
42 |
+
payload = {
|
43 |
+
"type": "image_to_model",
|
44 |
+
"file": {
|
45 |
+
"type": "png", # Adjust this based on the actual file type
|
46 |
+
"file_token": image_token
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
response = requests.post(url, headers=headers, json=payload)
|
51 |
+
|
52 |
+
if response.status_code == 200:
|
53 |
+
print(f"generation successful \n {response}")
|
54 |
+
return response.json()
|
55 |
+
else:
|
56 |
+
print(f"Error: {response.status_code}, {response.json()}")
|
57 |
+
return None
|
58 |
+
|
59 |
+
def check_task_status(task_id):
|
60 |
+
url = f"{BASE_URL}/task/{task_id}"
|
61 |
+
response = requests.get(url, headers=headers)
|
62 |
+
|
63 |
+
if response.status_code == 200:
|
64 |
+
return response.json()
|
65 |
+
else:
|
66 |
+
print(f"Error: {response.status_code}, {response.json()}")
|
67 |
+
return None
|
68 |
+
|
69 |
+
def download_file(url, local_filename):
|
70 |
+
with requests.get(url, headers=headers, stream=True) as r:
|
71 |
+
r.raise_for_status()
|
72 |
+
with open(local_filename, 'wb') as f:
|
73 |
+
for chunk in r.iter_content(chunk_size=8192):
|
74 |
+
f.write(chunk)
|
75 |
+
return local_filename
|
76 |
+
|
77 |
+
def process_image(image_url):
|
78 |
+
|
79 |
+
local_image_path = download_image(image_url, "temp_image.jpg")
|
80 |
+
# Step 1: Upload the image
|
81 |
+
upload_response = upload_image(local_image_path)
|
82 |
+
if not upload_response or upload_response.get('code') != 0:
|
83 |
+
return "Error uploading image", None
|
84 |
+
|
85 |
+
image_token = upload_response['data']['image_token']
|
86 |
+
|
87 |
+
# Step 2: Create image-to-model task
|
88 |
+
task_response = create_image_to_model_task(image_token)
|
89 |
+
if not task_response or task_response.get('code') != 0:
|
90 |
+
return "Error creating task", None
|
91 |
+
|
92 |
+
task_id = task_response['data']['task_id']
|
93 |
+
|
94 |
+
# Step 3: Check task status
|
95 |
+
while True:
|
96 |
+
status_response = check_task_status(task_id)
|
97 |
+
if status_response and status_response.get('code') == 0:
|
98 |
+
status = status_response['data']['status']
|
99 |
+
progress = status_response['data']['progress']
|
100 |
+
print(f"Task Status: {status}, Progress: {progress}%")
|
101 |
+
|
102 |
+
if status == "success":
|
103 |
+
output = status_response['data']['output']
|
104 |
+
model_url = output['model'] # Assuming 'model' key contains the URL
|
105 |
+
|
106 |
+
# Step 4: Download the .glb file
|
107 |
+
local_filename = f"{task_id}.glb"
|
108 |
+
download_file(model_url, local_filename)
|
109 |
+
print(local_filename)
|
110 |
+
|
111 |
+
return local_filename
|
112 |
+
elif status in ["failed", "cancelled"]:
|
113 |
+
return f"Task {status}", None
|
114 |
+
else:
|
115 |
+
time.sleep(5) # Wait for 5 seconds before checking again
|
116 |
+
else:
|
117 |
+
return "Failed to check task status", None
|
118 |
+
|
119 |
+
# Gradio Interface
|
120 |
+
def generate_model(file):
|
121 |
+
model_path = process_image(file)
|
122 |
+
if model_path:
|
123 |
+
return model_path
|
124 |
+
else:
|
125 |
+
return None
|
126 |
+
|
utilities.py
CHANGED
@@ -4,10 +4,11 @@ import os
|
|
4 |
import gc
|
5 |
import torch
|
6 |
import description_helper as dsh
|
7 |
-
import statblock_helper as sth
|
8 |
-
import ctypes
|
9 |
-
import psutil
|
10 |
|
|
|
|
|
|
|
|
|
11 |
image_name_list = []
|
12 |
link_list =['something','Link to monster statblock once generated']
|
13 |
random_prompt_list = []
|
@@ -36,7 +37,10 @@ def reclaim_mem():
|
|
36 |
torch.cuda.ipc_collect()
|
37 |
gc.collect()
|
38 |
torch.cuda.empty_cache()
|
|
|
39 |
time.sleep(0.01)
|
|
|
|
|
40 |
print(f"Memory Allocated after del {mem_alloc}")
|
41 |
print(f"Memory Cached after del {mem_cache}")
|
42 |
|
@@ -48,17 +52,17 @@ def generate_datetime():
|
|
48 |
|
49 |
def make_folder():
|
50 |
foldertimestr = time.strftime("%Y%m%d_%H")
|
51 |
-
folder_path = f"/
|
52 |
-
if not os.path.exists("/
|
53 |
-
os.mkdir("/
|
54 |
if not os.path.exists(folder_path):
|
55 |
os.mkdir(folder_path)
|
56 |
return foldertimestr
|
57 |
|
58 |
-
def make_image_name():
|
59 |
del image_name_list[:]
|
60 |
timestr = time.strftime("%Y%m%d-%H%M%S")
|
61 |
-
image_name = f"/
|
62 |
image_name = image_name.replace(' ', '_')
|
63 |
image_name_list.append(image_name)
|
64 |
print("Image name is : " + image_name_list[-1])
|
@@ -67,7 +71,7 @@ def make_image_name():
|
|
67 |
def make_user_log() :
|
68 |
del user_log[:]
|
69 |
timestr = time.strftime("%Y%m%d-%H%M%S")
|
70 |
-
folder_path = f"/
|
71 |
user_log_file = open(f"{folder_path}/userlog-{timestr}.txt","w")
|
72 |
user_log.append(dsh.prompt_list[0])
|
73 |
user_log.append(f"Output from LLM: {dsh.sd_input[0]}")
|
@@ -79,9 +83,43 @@ def make_user_log() :
|
|
79 |
print(user_log, file= user_log_file)
|
80 |
user_log_file.close()
|
81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
import gc
|
5 |
import torch
|
6 |
import description_helper as dsh
|
|
|
|
|
|
|
7 |
|
8 |
+
# Utility scripts for all modules
|
9 |
+
|
10 |
+
# List for file locations to point at
|
11 |
+
file_name_list = []
|
12 |
image_name_list = []
|
13 |
link_list =['something','Link to monster statblock once generated']
|
14 |
random_prompt_list = []
|
|
|
37 |
torch.cuda.ipc_collect()
|
38 |
gc.collect()
|
39 |
torch.cuda.empty_cache()
|
40 |
+
torch.cuda.synchronize()
|
41 |
time.sleep(0.01)
|
42 |
+
allocated_memory = torch.cuda.memory_allocated()
|
43 |
+
cached_memory = torch.cuda.memory_reserved()
|
44 |
print(f"Memory Allocated after del {mem_alloc}")
|
45 |
print(f"Memory Cached after del {mem_cache}")
|
46 |
|
|
|
52 |
|
53 |
def make_folder():
|
54 |
foldertimestr = time.strftime("%Y%m%d_%H")
|
55 |
+
folder_path = f"/media/drakosfire/Shared/Docker/StatblockGenerator/output/{foldertimestr}"
|
56 |
+
if not os.path.exists("/media/drakosfire/Shared/Docker/StatblockGenerator/output"):
|
57 |
+
os.mkdir("/media/drakosfire/Shared/Docker/StatblockGenerator/output")
|
58 |
if not os.path.exists(folder_path):
|
59 |
os.mkdir(folder_path)
|
60 |
return foldertimestr
|
61 |
|
62 |
+
def make_image_name(name):
|
63 |
del image_name_list[:]
|
64 |
timestr = time.strftime("%Y%m%d-%H%M%S")
|
65 |
+
image_name = f"/media/drakosfire/Shared/Docker/StatblockGenerator/output/{make_folder()}/{name}{timestr}.png"
|
66 |
image_name = image_name.replace(' ', '_')
|
67 |
image_name_list.append(image_name)
|
68 |
print("Image name is : " + image_name_list[-1])
|
|
|
71 |
def make_user_log() :
|
72 |
del user_log[:]
|
73 |
timestr = time.strftime("%Y%m%d-%H%M%S")
|
74 |
+
folder_path = f"/media/drakosfire/Shared/Docker/StatblockGenerator/output/{make_folder()}"
|
75 |
user_log_file = open(f"{folder_path}/userlog-{timestr}.txt","w")
|
76 |
user_log.append(dsh.prompt_list[0])
|
77 |
user_log.append(f"Output from LLM: {dsh.sd_input[0]}")
|
|
|
83 |
print(user_log, file= user_log_file)
|
84 |
user_log_file.close()
|
85 |
|
86 |
+
# Create a unique time stamped file name
|
87 |
+
def gen_file_name(mon_name):
|
88 |
+
del file_name_list[:]
|
89 |
+
timestr = time.strftime("%H%M%S")
|
90 |
+
input_dir = f"/media/drakosfire/Shared/Docker/StatblockGenerator/output/{make_folder()}"
|
91 |
+
|
92 |
+
mon_file_name = mon_name
|
93 |
+
file_name = mon_file_name + "_" + timestr
|
94 |
+
file_name_list.append(input_dir)
|
95 |
+
file_name_list.append(file_name)
|
96 |
+
file_name_list.append(mon_file_name)
|
97 |
|
98 |
+
def make_folder():
|
99 |
+
foldertimestr = time.strftime("%Y%m%d_%H")
|
100 |
+
folder_path = f"/media/drakosfire/Shared/Docker/StatblockGenerator/output/{foldertimestr}"
|
101 |
+
if not os.path.exists("/media/drakosfire/Shared/Docker/StatblockGenerator/output"):
|
102 |
+
os.mkdir("/media/drakosfire/Shared/Docker/StatblockGenerator/output")
|
103 |
+
if not os.path.exists(folder_path):
|
104 |
+
os.mkdir(folder_path)
|
105 |
+
return foldertimestr
|
106 |
+
|
107 |
+
# Create a list of a directory if directory exists
|
108 |
+
def directory_contents(directory_path):
|
109 |
+
if os.path.isdir(directory_path) :
|
110 |
+
contents = os.listdir(directory_path)
|
111 |
+
return contents
|
112 |
+
else : pass
|
113 |
+
|
114 |
+
|
115 |
+
# Delete a list of file
|
116 |
+
def delete_files(file_paths):
|
117 |
+
if file_paths:
|
118 |
+
|
119 |
+
for file_path in file_paths:
|
120 |
+
try:
|
121 |
+
os.remove(f"./image_temp/{file_path}")
|
122 |
+
print(f"Remove : ./image_temp/{file_path}")
|
123 |
+
except OSError as e:
|
124 |
+
print(f"Error: {file_path} : {e.strerror}")
|
125 |
+
file_paths.clear()
|