import os
import time
import base64
from pathlib import Path
import gradio as gr
import requests
API_BASE_URL = os.getenv("SOLRICKS_API_BASE_URL", "https://api.solricks.com").rstrip("/")
API_KEY = os.getenv("SOLRICKS_SPACE_API_KEY", "")
API_ENDPOINT = f"{API_BASE_URL}/api/workflows/visual-ai/run"
REQUEST_TIMEOUT = int(os.getenv("SOLRICKS_REQUEST_TIMEOUT", "600"))
FETCH_TIMEOUT = int(os.getenv("SOLRICKS_FETCH_TIMEOUT", "180"))
ALLOWED_STYLES = ["Realistic", "Portrait", "Cinematic", "Product"]
ALLOWED_RESOLUTIONS = ["1024x1024", "768x1344", "1344x768"]
MAX_PROMPT_LENGTH = 500
APP_DIR = Path(__file__).resolve().parent
CSS_PATH = APP_DIR / "image-generator.css"
LOGO_PATH = APP_DIR / "solricks-logo.png"
custom_css = CSS_PATH.read_text(encoding="utf-8") if CSS_PATH.exists() else ""
def file_to_data_uri(file_path: Path, mime_type: str) -> str:
encoded = base64.b64encode(file_path.read_bytes()).decode("utf-8")
return f"data:{mime_type};base64,{encoded}"
if LOGO_PATH.exists():
LOGO_DATA_URI = file_to_data_uri(LOGO_PATH, "image/png")
HERO_LOGO_HTML = f'
'
else:
HERO_LOGO_HTML = '
SOLRICKS
'
def render_state_html(
title: str = "Result Preview",
subtitle: str = "Your result will appear here.",
processing: bool = False
) -> str:
processing_class = " is-processing" if processing else ""
return f"""
"""
EMPTY_IMAGE_HTML = render_state_html()
PROCESSING_IMAGE_HTML = render_state_html(subtitle="Processing...", processing=True)
def normalize_image_url(image_url: str) -> str:
image_url = (image_url or "").strip()
if not image_url:
raise ValueError("Empty image URL")
if image_url.startswith("data:image/"):
return image_url
if image_url.startswith("/"):
return f"{API_BASE_URL}{image_url}"
return image_url
def fetch_image_as_data_uri(image_url: str) -> tuple[str, str, str]:
image_url = normalize_image_url(image_url)
if image_url.startswith("data:image/"):
mime_type = image_url.split(";", 1)[0].replace("data:", "") or "image/png"
extension = "png"
if "jpeg" in mime_type or "jpg" in mime_type:
extension = "jpg"
elif "webp" in mime_type:
extension = "webp"
return image_url, mime_type, extension
headers = {}
if API_KEY:
headers["Authorization"] = f"Bearer {API_KEY}"
last_error = None
for attempt in range(6):
try:
response = requests.get(
image_url,
headers=headers,
timeout=(10, FETCH_TIMEOUT)
)
if response.status_code in [404, 409, 425, 429, 500, 502, 503, 504]:
last_error = f"Image not ready yet. HTTP {response.status_code}"
print(f"IMAGE FETCH RETRY {attempt + 1}/6:", last_error, flush=True)
time.sleep(2 + attempt * 3)
continue
response.raise_for_status()
mime_type = (
response.headers.get("content-type") or "image/png"
).split(";", 1)[0].strip().lower()
if not mime_type.startswith("image/"):
raise RuntimeError(f"Returned file is not an image. Content-Type: {mime_type}")
extension = "png"
if "jpeg" in mime_type or "jpg" in mime_type:
extension = "jpg"
elif "webp" in mime_type:
extension = "webp"
encoded = base64.b64encode(response.content).decode("utf-8")
return f"data:{mime_type};base64,{encoded}", mime_type, extension
except Exception as error:
last_error = error
print(f"IMAGE FETCH ERROR {attempt + 1}/6:", error, flush=True)
time.sleep(2 + attempt * 3)
raise RuntimeError(f"Could not fetch generated image after retries: {last_error}")
def render_image_html(image_data_uri: str | None = None, extension: str = "png") -> str:
if not image_data_uri:
return EMPTY_IMAGE_HTML
filename = f"solricks-generated-image.{extension}"
return f"""
"""
def generate_image(prompt: str, style: str, resolution: str):
prompt = (prompt or "").strip()
if not prompt:
return render_state_html(subtitle="Please enter a prompt.")
if len(prompt) > MAX_PROMPT_LENGTH:
return render_state_html(
subtitle=f"Prompt is too long. Keep it under {MAX_PROMPT_LENGTH} characters."
)
if style not in ALLOWED_STYLES:
return render_state_html(subtitle="Invalid style selection.")
if resolution not in ALLOWED_RESOLUTIONS:
return render_state_html(subtitle="Invalid resolution selection.")
payload = {
"prompt": prompt,
"style": style,
"resolution": resolution,
"enhancePrompt": False,
"seedMode": "Random",
"source": "huggingface-space",
}
headers = {"Content-Type": "application/json"}
if API_KEY:
headers["Authorization"] = f"Bearer {API_KEY}"
try:
response = requests.post(
API_ENDPOINT,
json=payload,
headers=headers,
timeout=(10, REQUEST_TIMEOUT)
)
try:
data = response.json()
except Exception:
data = {}
if response.status_code == 429:
return render_state_html(
subtitle="Too many requests. Please wait a little and try again."
)
if not response.ok:
message = (
data.get("error")
or data.get("detail")
or data.get("message")
or f"Backend HTTP {response.status_code}: {response.text[:300]}"
)
print("BACKEND ERROR:", response.status_code, response.text[:1000], flush=True)
return render_state_html(subtitle=message)
image_url = data.get("image_url") or data.get("imageUrl") or data.get("url")
if not image_url:
return render_state_html(subtitle="The backend did not return an image.")
image_data_uri, _mime_type, extension = fetch_image_as_data_uri(image_url)
return render_image_html(image_data_uri, extension)
except requests.exceptions.Timeout:
return render_state_html(subtitle="Generation timed out. Please try again.")
except Exception as error:
print("SPACE ERROR:", error, flush=True)
return render_state_html(subtitle=f"Error: {error}")
def clear_prompt() -> str:
return ""
def begin_processing() -> str:
return PROCESSING_IMAGE_HTML
sol_theme = gr.themes.Soft(
primary_hue="blue",
secondary_hue="indigo",
neutral_hue="slate"
)
def build_demo() -> gr.Blocks:
with gr.Blocks(
title="SOLRICKS Image Generator",
theme=sol_theme,
css=custom_css
) as demo:
with gr.Column(elem_id="sol-shell"):
with gr.Row(equal_height=True, elem_id="sol-main-grid"):
with gr.Column(scale=11, min_width=380, elem_id="sol-left-col"):
with gr.Group(elem_id="sol-left-card", elem_classes=["sol-panel"]):
gr.HTML(
f"""
LIVE
"""
)
gr.HTML('PROMPT
')
prompt = gr.Textbox(
label=None,
placeholder="Describe the scene, subject, and visual style...",
lines=5,
max_lines=6,
max_length=MAX_PROMPT_LENGTH,
show_label=False,
container=False,
elem_id="sol-prompt-box",
elem_classes=["sol-plain-input"],
)
with gr.Row(elem_id="sol-two-col-row"):
with gr.Column(scale=1, min_width=180):
gr.HTML('STYLE
')
style = gr.Dropdown(
label=None,
choices=ALLOWED_STYLES,
value="Cinematic",
show_label=False,
container=False,
filterable=False,
elem_id="sol-style-dropdown",
elem_classes=["sol-plain-select"],
)
with gr.Column(scale=1, min_width=180):
gr.HTML('RESOLUTION
')
resolution = gr.Dropdown(
label=None,
choices=ALLOWED_RESOLUTIONS,
value="1024x1024",
show_label=False,
container=False,
filterable=False,
elem_id="sol-resolution-dropdown",
elem_classes=["sol-plain-select"],
)
with gr.Row(elem_id="sol-action-row"):
clear_button = gr.Button(
"Clear",
variant="secondary",
elem_id="sol-clear-button",
)
generate_button = gr.Button(
"Generate",
variant="primary",
elem_id="sol-generate-button",
)
with gr.Column(scale=11, min_width=380, elem_id="sol-right-col"):
with gr.Group(elem_id="sol-right-card", elem_classes=["sol-panel"]):
gr.HTML(
''
)
output_image = gr.HTML(
value=render_image_html(),
elem_id="sol-output-image"
)
clear_button.click(
fn=clear_prompt,
inputs=None,
outputs=prompt,
queue=False,
show_progress="hidden",
)
generate_button.click(
fn=begin_processing,
inputs=None,
outputs=output_image,
queue=False,
show_progress="hidden",
).then(
fn=generate_image,
inputs=[prompt, style, resolution],
outputs=output_image,
queue=True,
show_progress="hidden",
)
return demo
if __name__ == "__main__":
demo = build_demo()
demo.queue(
default_concurrency_limit=1,
max_size=10
)
demo.launch(
server_name="0.0.0.0",
server_port=int(os.environ.get("PORT", 7860)),
show_error=True,
ssr_mode=False,
)