paint_match / ui.py
szwendaczjakomaj's picture
chore: HF Space deployment β€” full pipeline + inventory
816f700
Raw
History Blame Contribute Delete
8.76 kB
import base64
import io
import os
import gradio as gr
from PIL import Image
from app import analyze, FORMATS
# ---------- Assets ----------
def _load_asset_b64(filename: str, max_px: int | None = None) -> str:
path = os.path.join(os.path.dirname(__file__), "assets", filename)
if max_px:
img = Image.open(path)
if max(img.size) > max_px:
img.thumbnail((max_px, max_px))
buf = io.BytesIO()
fmt = "JPEG" if filename.lower().endswith((".jpg", ".jpeg")) else "PNG"
save_kw = {"quality": 85} if fmt == "JPEG" else {}
img.save(buf, format=fmt, **save_kw)
return base64.b64encode(buf.getvalue()).decode()
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode()
_TEXTURE_B64 = _load_asset_b64("green_metal_rust_diff_4k.jpg", max_px=600)
_LOGO_B64 = _load_asset_b64("logo-kopia.png")
# ---------- CSS ----------
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;600;700&display=swap');
/* ── Theme overrides ── */
:root {
--body-background-fill: #c8b878;
--body-text-color: #a8b080;
--body-text-weight: 400;
--background-fill-primary: rgba(38,44,26,0.92);
--background-fill-secondary: rgba(28,34,16,0.96);
--block-background-fill: rgba(38,44,26,0.92);
--block-border-color: #5a6840;
--block-border-width: 1.5px;
--block-label-text-color: #b8c090;
--block-label-text-size: var(--text-sm);
--block-label-text-weight: 600;
--block-label-background-fill: linear-gradient(180deg, rgba(48,56,32,0.98) 0%, rgba(28,34,16,0.98) 100%);
--block-label-border-color: #485830;
--input-background-fill: rgba(18,24,10,0.78);
--input-border-color: rgba(70,90,45,0.55);
--neutral-950: rgba(18,24,10,0.99);
--neutral-900: rgba(22,28,14,0.99);
--neutral-800: rgba(28,34,18,0.97);
--neutral-700: rgba(35,42,22,0.95);
--button-primary-background-fill: linear-gradient(180deg, #e09030 0%, #b06010 55%, #904800 100%);
--button-primary-background-fill-hover: linear-gradient(180deg, #f0a040 0%, #c07020 55%, #a05800 100%);
--button-primary-border-color: #c07020;
--button-primary-border-color-hover: #d08030;
--button-primary-text-color: #fff8e0;
--button-primary-shadow: 0 4px 10px rgba(0,0,0,0.5);
}
/* ── Font ── */
*, *::before, *::after {
font-family: 'Barlow', sans-serif !important;
}
/* ── Page background (texture + fallback colour) ── */
body, gradio-app {
background-image: url('data:image/jpeg;base64,TEXTURE_DATA_URI') !important;
background-size: 400px !important;
background-repeat: repeat !important;
background-color: #c8b878 !important;
}
/* ── Max-width and centering ── */
.app.svelte-182fdeq, .app {
max-width: 900px !important;
margin: 0 auto !important;
padding: 16px !important;
}
/* ── Label uppercase + letter-spacing ── */
label > span, .block label span {
text-transform: uppercase !important;
letter-spacing: 2px !important;
}
/* ── Button pill ── */
#analyze-btn button {
border-radius: 26px !important;
font-size: 14px !important;
font-weight: 600 !important;
letter-spacing: 3px !important;
padding: 14px 0 !important;
width: 100% !important;
}
/* ── Header: transparent β€” elem_id is the direct element (container=False) ── */
#app-header {
background: transparent !important;
background-color: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}
/* ── Olive block backgrounds β€” correct container class for Gradio 6.16 ── */
.gradio-container-6-16-0 .block,
.gradio-container-6-16-0 .form {
background: rgba(38,44,26,0.92) !important;
}
/* ── Gradient label strips β€” correct selectors from Gradio 6 source ── */
label[data-testid="block-label"],
button.label-wrap {
background: linear-gradient(180deg, rgba(48,56,32,0.98) 0%, rgba(28,34,16,0.98) 100%) !important;
border-bottom: 1px solid #485830 !important;
}
/* ── Consistent label font ── */
label[data-testid="block-label"] span,
button.label-wrap > span:first-child {
font-size: 11px !important;
text-transform: uppercase !important;
letter-spacing: 2px !important;
font-weight: 600 !important;
color: #b8c090 !important;
}
/* ── Unified section headings (gr.HTML, one mechanism for all three) ── */
.sec-head {
color: #b8c090 !important;
font-size: 13px !important;
font-weight: 600 !important;
letter-spacing: 2px !important;
text-align: left !important;
padding: 2px 0 !important;
margin: 0 !important;
}
/* ── Dropdown options popup ── */
.options, ul[role="listbox"] {
background: rgba(28,34,16,0.98) !important;
border-color: #485830 !important;
}
/* ── Empty manifest: collapse when no content ── */
#manifest { min-height: 0 !important; padding: 0 !important; }
/* ── Manifest text (match Field report olive); links stay orange below ── */
#manifest, #manifest li {
color: #a8b080 !important;
}
/* ── Manifest list: plain lines (no bullets), comfortable rhythm ── */
#manifest ul {
list-style: none !important;
margin: 0 !important;
padding: 2px 0 !important;
}
#manifest li {
display: flex !important;
flex-wrap: wrap !important;
align-items: baseline !important;
gap: 0.3em !important;
line-height: 1.7 !important;
margin: 0 !important;
}
/* ── Field report: same looser line spacing ── */
#raw-output textarea {
line-height: 1.7 !important;
}
/* ── Manifest links ── */
#manifest a {
color: #e09030 !important;
margin: 0 !important;
padding: 0 !important;
}
.in-stock {
color: #a0c890 !important;
}
/* ── Input text colour ── */
input, textarea, select {
color: #a8b080 !important;
border-radius: 7px !important;
}
/* ── Mobile: stack columns vertically ── */
@media (max-width: 768px) {
.gap-4 { flex-direction: column !important; }
.gap-4 > * { width: 100% !important; min-width: 100% !important; }
}
/* ── Upload zone: custom emoji icon ── */
#img-upload .wrap::before {
content: "πŸ“‚";
font-size: 30px;
display: block;
text-align: center;
padding: 14px 0 2px;
line-height: 1;
}
#img-upload .wrap svg { display: none !important; }
@media (max-width: 768px) {
#img-upload .wrap::before { content: "πŸ“Έ"; }
}
"""
CSS = CSS.replace("TEXTURE_DATA_URI", _TEXTURE_B64)
# ---------- Header HTML ----------
HEADER_HTML = f"""
<link href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;600;700&display=swap" rel="stylesheet">
<div style="text-align:center; padding: 20px 0 8px;">
<img src="data:image/png;base64,{_LOGO_B64}"
style="height:130px; width:auto; max-width:85%; display:block; margin:0 auto;"
alt="Paint Match">
<div style="font-family:'Barlow',sans-serif; color:#b0a878; font-size:13px; letter-spacing:5px; font-weight:700;
margin-top:-42px; position:relative; z-index:1; padding-bottom:20px;">
COLOUR IDENTIFICATION UNIT
</div>
</div>
"""
# ---------- Layout ----------
with gr.Blocks(title="Paint Match") as demo:
gr.HTML(HEADER_HTML, elem_id="app-header")
with gr.Row():
with gr.Column():
with gr.Group():
gr.HTML("<div class='sec-head'>Document classification</div>")
format_in = gr.Dropdown(
choices=[(cfg["label"], fid) for fid, cfg in FORMATS.items()],
value="airfix_shop",
show_label=False,
elem_id="format-dropdown",
)
img_in = gr.Image(type="pil", label="Upload Document", elem_id="img-upload")
btn = gr.Button("β—‰ COMMENCE ANALYSIS", variant="primary", elem_id="analyze-btn")
with gr.Column():
with gr.Group():
gr.HTML("<div class='sec-head'>Field report</div>")
raw_out = gr.Textbox(
show_label=False,
lines=8,
elem_id="raw-output",
placeholder="Intelligence report β€” commence analysis to populate.",
)
with gr.Group():
gr.HTML("<div class='sec-head'>Resupply manifest</div>")
codes_out = gr.HTML(elem_id="manifest")
btn.click(
analyze,
inputs=[img_in, format_in],
outputs=[raw_out, codes_out],
show_progress="hidden",
)
if __name__ == "__main__":
os.environ.setdefault("GRADIO_DEFAULT_LOCALE", "en")
demo.launch(css=CSS, allowed_paths=["assets/"])