Skip to content

Commit 7498c3d

Browse files
authored
Merge pull request #7 from rtuszik/dev
Various UI Changes, Docker Fixes, Build Process Changes
2 parents 8d38712 + d77aab6 commit 7498c3d

File tree

5 files changed

+97
-43
lines changed

5 files changed

+97
-43
lines changed

.github/workflows/docker-build-push.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ on:
44
push:
55
branches:
66
- main
7+
paths:
8+
- src/**
9+
- Dockerfile
710

811
env:
912
REGISTRY: ghcr.io
@@ -35,6 +38,8 @@ jobs:
3538
tags: |
3639
type=raw,value=latest
3740
type=sha,prefix={{branch}}-
41+
type=ref,event=branch
42+
3843
3944
- name: Build and push Docker image
4045
uses: docker/build-push-action@v4
@@ -43,3 +48,5 @@ jobs:
4348
push: true
4449
tags: ${{ steps.meta.outputs.tags }}
4550
labels: ${{ steps.meta.outputs.labels }}
51+
cache-from: type=gha
52+
cache-to: type=gha,mode=max

README.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,16 @@ Docker is the recommended way to run this application. It ensures consistent env
2222
### Docker Compose Setup
2323
1. Create a `docker-compose.yml` file with the following content:
2424
```yaml
25-
services:
26-
replicate-flux-lora:
27-
image: ghcr.io/rtuszik/replicate-flux-lora:latest
28-
container_name: replicate-flux-lora
29-
environment:
30-
- REPLICATE_API_TOKEN=${REPLICATE_API_TOKEN}
31-
ports:
32-
- "8080:8080"
33-
volumes:
34-
- ${HOST_OUTPUT_DIR}:/app/output
35-
restart: unless-stopped
25+
services:
26+
replicate-flux-lora:
27+
image: ghcr.io/rtuszik/replicate-flux-lora:latest
28+
container_name: replicate-flux-lora
29+
env_file: .env
30+
ports:
31+
- "8080:8080"
32+
volumes:
33+
- ${HOST_OUTPUT_DIR}:/app/output
34+
restart: unless-stopped
3635
```
3736
3837
2. Create a `.env` file in the same directory with the following content:

docker-compose.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ services:
33
replicate-flux-lora:
44
image: ghcr.io/rtuszik/replicate-flux-lora:latest
55
container_name: replicate-flux-lora
6-
environment:
7-
- REPLICATE_API_TOKEN=${REPLICATE_API_TOKEN}
6+
env_file: .env
87
ports:
98
- "8080:8080"
109
volumes:

src/gui.py

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
from loguru import logger
1111
from nicegui import events, ui
1212

13-
# Configure Loguru
14-
logger.remove() # Remove the default handler
13+
logger.remove()
1514
logger.add(
1615
sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO"
1716
)
@@ -57,36 +56,67 @@ def __init__(self, image_generator):
5756
self.load_settings()
5857
self.flux_fine_tune_models = self.image_generator.get_flux_fine_tune_models()
5958
self.user_added_models = self.settings.get("user_added_models", [])
59+
60+
self._attributes = [
61+
"prompt",
62+
"flux_model",
63+
"aspect_ratio",
64+
"num_outputs",
65+
"lora_scale",
66+
"num_inference_steps",
67+
"guidance_scale",
68+
"output_format",
69+
"output_quality",
70+
"disable_safety_checker",
71+
"width",
72+
"height",
73+
"seed",
74+
]
75+
76+
for attr in self._attributes:
77+
setattr(self, attr, self.settings.get(attr, None))
78+
6079
self.setup_ui()
6180
logger.info("ImageGeneratorGUI initialized")
6281

82+
def __getattr__(self, name):
83+
if name in self._attributes:
84+
return self.__dict__.get(name, None)
85+
return super().__getattribute__(name)
86+
6387
def setup_ui(self):
6488
ui.dark_mode().enable()
6589

66-
with ui.column().classes("w-full max-w-full mx-auto p-4 space-y-4"):
90+
with ui.column().classes("w-full max-w-full mx-auto space-y-8"):
6791
with ui.card().classes("w-full"):
68-
ui.label("Image Generator").classes("text-2xl font-bold mb-4")
69-
with ui.row().classes("w-full"):
70-
with ui.column().classes("w-1/2 pr-2"):
92+
ui.label("Flux LoRA API").classes("text-2xl font-bold mb-4")
93+
with ui.row():
94+
with ui.column(wrap=False):
7195
self.setup_left_panel()
72-
with ui.column().classes("w-1/2 pl-2"):
96+
with ui.column(wrap=False):
7397
self.setup_right_panel()
98+
ui.separator()
7499
self.setup_bottom_panel()
75100
logger.info("UI setup completed")
76101

77102
def setup_left_panel(self):
78-
self.replicate_model_input = ui.input(
79-
"Replicate Model", value=self.settings.get("replicate_model", "")
80-
).classes("w-full")
103+
self.replicate_model_input = (
104+
ui.input("Replicate Model", value=self.settings.get("replicate_model", ""))
105+
.classes("w-full")
106+
.tooltip("Enter the Replicate model URL or identifier")
107+
)
81108
self.replicate_model_input.on("change", self.update_replicate_model)
82109

83-
self.flux_models_select = ui.select(
84-
options=self.flux_fine_tune_models,
85-
label="Flux Fine-Tune Models",
86-
value=None,
87-
on_change=self.select_flux_model,
88-
).classes("w-full")
89-
110+
self.flux_models_select = (
111+
ui.select(
112+
options=self.flux_fine_tune_models,
113+
label="Flux Fine-Tune Models",
114+
value=None,
115+
on_change=self.select_flux_model,
116+
)
117+
.classes("w-full")
118+
.tooltip("Select Model")
119+
)
90120
with ui.row().classes("w-full"):
91121
self.new_model_input = ui.input(label="New Model").classes("w-3/4")
92122
ui.button("Add Model", on_click=self.add_user_model).classes("w-1/4")
@@ -121,6 +151,9 @@ def setup_left_panel(self):
121151
value=self.settings.get("flux_model", "dev"),
122152
)
123153
.classes("w-full")
154+
.tooltip(
155+
"Which model to run inferences with. the dev model needs around 28 steps but the schnell model only needs around 4 steps."
156+
)
124157
.bind_value(self, "flux_model")
125158
)
126159

@@ -145,6 +178,9 @@ def setup_left_panel(self):
145178
)
146179
.classes("w-full")
147180
.bind_value(self, "aspect_ratio")
181+
.tooltip(
182+
"Width of the generated image. Optional, only used when aspect_ratio=custom. Must be a multiple of 16 (if it's not, it will be rounded to nearest multiple of 16)"
183+
)
148184
)
149185
self.aspect_ratio_select.on("change", self.toggle_custom_dimensions)
150186

@@ -157,13 +193,19 @@ def setup_left_panel(self):
157193
)
158194
.classes("w-full")
159195
.bind_value(self, "width")
196+
.tooltip(
197+
"Width of the generated image. Optional, only used when aspect_ratio=custom. Must be a multiple of 16 (if it's not, it will be rounded to nearest multiple of 16)"
198+
)
160199
)
161200
self.height_input = (
162201
ui.number(
163202
"Height", value=self.settings.get("height", 1024), min=256, max=1440
164203
)
165204
.classes("w-full")
166205
.bind_value(self, "height")
206+
.tooltip(
207+
"Height of the generated image. Optional, only used when aspect_ratio=custom. Must be a multiple of 16 (if it's not, it will be rounded to nearest multiple of 16)"
208+
)
167209
)
168210

169211
self.num_outputs_input = (
@@ -172,6 +214,7 @@ def setup_left_panel(self):
172214
)
173215
.classes("w-full")
174216
.bind_value(self, "num_outputs")
217+
.tooltip("Number of images to output.")
175218
)
176219
self.lora_scale_input = (
177220
ui.number(
@@ -182,6 +225,9 @@ def setup_left_panel(self):
182225
step=0.1,
183226
)
184227
.classes("w-full")
228+
.tooltip(
229+
"Determines how strongly the LoRA should be applied. Sane results between 0 and 1."
230+
)
185231
.bind_value(self, "lora_scale")
186232
)
187233
self.num_inference_steps_input = (
@@ -192,6 +238,7 @@ def setup_left_panel(self):
192238
max=50,
193239
)
194240
.classes("w-full")
241+
.tooltip("Number of Inference Steps")
195242
.bind_value(self, "num_inference_steps")
196243
)
197244
self.guidance_scale_input = (
@@ -203,6 +250,7 @@ def setup_left_panel(self):
203250
step=0.1,
204251
)
205252
.classes("w-full")
253+
.tooltip("Guidance Scale for the diffusion process")
206254
.bind_value(self, "guidance_scale")
207255
)
208256
self.seed_input = (
@@ -222,6 +270,7 @@ def setup_left_panel(self):
222270
value=self.settings.get("output_format", "webp"),
223271
)
224272
.classes("w-full")
273+
.tooltip("Format of the output images")
225274
.bind_value(self, "output_format")
226275
)
227276
self.output_quality_input = (
@@ -232,14 +281,18 @@ def setup_left_panel(self):
232281
max=100,
233282
)
234283
.classes("w-full")
284+
.tooltip(
285+
"Quality when saving the output images, from 0 to 100. 100 is best quality, 0 is lowest quality. Not relevant for .png outputs"
286+
)
235287
.bind_value(self, "output_quality")
236288
)
237289
self.disable_safety_checker_switch = (
238290
ui.switch(
239291
"Disable Safety Checker",
240-
value=self.settings.get("disable_safety_checker", False),
292+
value=self.settings.get("disable_safety_checker", True),
241293
)
242294
.classes("w-full")
295+
.tooltip("Disable safety checker for generated images.")
243296
.bind_value(self, "disable_safety_checker")
244297
)
245298

@@ -288,7 +341,6 @@ def setup_right_panel(self):
288341
self.spinner = ui.spinner(type="infinity", size="xl")
289342
self.spinner.visible = False
290343

291-
# Add gallery view
292344
self.gallery_container = ui.column().classes("w-full mt-4")
293345
self.lightbox = Lightbox()
294346

@@ -297,6 +349,7 @@ def setup_bottom_panel(self):
297349
ui.textarea("Prompt", value=self.settings.get("prompt", ""))
298350
.classes("w-full")
299351
.bind_value(self, "prompt")
352+
.props("clearable")
300353
)
301354
self.generate_button = ui.button(
302355
"Generate Images", on_click=self.start_generation
@@ -342,7 +395,6 @@ async def start_generation(self):
342395
)
343396
return
344397

345-
# Ensure the model is set in the ImageGenerator
346398
self.image_generator.set_model(self.replicate_model_input.value)
347399

348400
self.save_settings()
@@ -392,9 +444,7 @@ async def download_and_display_images(self, image_urls):
392444
response = await client.get(url)
393445
if response.status_code == 200:
394446
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
395-
url_part = urllib.parse.urlparse(url).path.split("/")[-2][
396-
:8
397-
] # Get first 8 chars of the unique part
447+
url_part = urllib.parse.urlparse(url).path.split("/")[-2][:8]
398448
file_name = f"generated_image_{timestamp}_{url_part}_{i+1}.png"
399449
file_path = Path(self.folder_path) / file_name
400450
with open(file_path, "wb") as f:

src/main.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import sys
22

3-
from gui import create_gui
43
from loguru import logger
54
from nicegui import ui
5+
6+
from gui import create_gui
67
from replicate_api import ImageGenerator
78

8-
# Configure Loguru
9-
logger.remove() # Remove the default handler
9+
logger.remove()
1010
logger.add(
1111
sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO"
1212
)
1313
logger.add(
1414
"main.log", rotation="10 MB", format="{time} {level} {message}", level="INFO"
1515
)
1616

17-
# Create the ImageGenerator instance
17+
1818
logger.info("Initializing ImageGenerator")
1919
generator = ImageGenerator()
2020

21-
# Create and setup the GUI
21+
2222
logger.info("Creating and setting up GUI")
2323

2424

@@ -28,7 +28,6 @@ async def main_page():
2828
logger.info("NiceGUI server is running")
2929

3030

31-
# Run the NiceGUI server
3231
logger.info("Starting NiceGUI server")
33-
# ui.run(port=8080)
32+
3433
ui.run(title="Replicate Flux LoRA", port=8080)

0 commit comments

Comments
 (0)