diff --git a/Dockerfile b/Dockerfile index 8684c28..840a1d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,11 @@ COPY assets/custom.css /app/code/librechat/client/dist/assets/custom.css # rebuild trigger COPY assets/custom2.js /app/code/librechat/client/dist/assets/custom2.js +# Co-op UI strings: rename the "MCP Servers" button to "Tools" and replace +# the LibreChat footer tagline with the co-op footer. Non-fatal at build. +COPY patch_ui_strings.py /app/code/patch_ui_strings.py +RUN python3 /app/code/patch_ui_strings.py + # Inject branding into built HTML (dist) before image is packaged # rebuild marker 20261019-$(date +%s) RUN sed -i 's|LibreChat|Inference Co‑op|' /app/code/librechat/client/dist/index.html && \ diff --git a/assets/custom.css b/assets/custom.css index 82b6c7d..6547adf 100644 --- a/assets/custom.css +++ b/assets/custom.css @@ -10,10 +10,10 @@ ============================================================ */ html.light { - --surface-primary: 250 248 245; /* --cream #faf8f5 */ - --surface-secondary: 238 244 234; /* --wash #eef4ea */ - --surface-tertiary: 232 226 216; /* --border #e8e2d8 */ - --surface-hover: 232 245 232; /* #e8f5e8 (homepage wash hover) */ + --surface-primary: 234 243 234; /* #eaf3ea — dashboard app green background */ + --surface-secondary: 223 237 222; /* deeper step of the dashboard green */ + --surface-tertiary: 213 228 211; + --surface-hover: 223 237 222; --surface-submit: 91 140 90; /* --green-deep #5b8c5a */ --surface-submit-hover: 107 168 107; /* --green-mid #6ba86b */ --accent-primary: 91 140 90; /* --green-deep */ @@ -22,7 +22,7 @@ html.light { --link: 91 140 90; /* --green-deep */ --text-primary: 45 51 39; /* --ink #2d3327 */ --text-secondary: 107 122 98; /* --muted #6b7a62 */ - --border-light: 232 226 216; /* --border #e8e2d8 */ + --border-light: 205 224 203; /* green-tinted border */ } html.dark { @@ -43,7 +43,7 @@ html.dark { /* ---- Loading screen: brand the pre-React flash instead of white/black ---- */ #loading-container { - background-color: rgb(250, 248, 245); /* cream */ + background-color: rgb(234, 243, 234); /* dashboard green */ } @media (prefers-color-scheme: dark) { #loading-container { @@ -63,16 +63,9 @@ a[href*="signup"] { display: none !important; } -/* ---- Footer: simple centered link to the co-op homepage ---- */ +/* ---- Footer: the LibreChat tagline is replaced at build time + (patch_ui_strings.py) with the co-op footer text; CSS just centers it ---- */ footer { text-align: center; padding: 1rem; } -footer::before { - content: "© Inference Co-op – "; -} -footer::after { - content: "inference.coop"; - color: rgb(var(--accent-primary)); - cursor: pointer; -} diff --git a/patch_ui_strings.py b/patch_ui_strings.py new file mode 100644 index 0000000..c9a31c7 --- /dev/null +++ b/patch_ui_strings.py @@ -0,0 +1,59 @@ +"""Replace LibreChat's default UI strings with Inference Co-op wording. + +Run at BUILD time (the runtime rootfs is read-only under Cloudron). Non-fatal: +walks the built client assets, replaces exact strings, and deletes the stale +precompressed (.gz/.br) siblings of any edited file so the server never serves +an old compressed copy. Idempotent: skips files already patched. +""" +import sys +import glob +import os + +REPLACEMENTS = [ + # "MCP Servers" button -> "Tools" (more generic / accessible) + ("MCP Servers", "Tools"), + # LibreChat footer tagline -> co-op footer + ("Every AI for Everyone.", "Inference Cooperative · Our AI, governed together · inference.coop"), +] + +assets_dir = "/app/code/librechat/client/dist/assets" +files = glob.glob(os.path.join(assets_dir, "*.js")) + glob.glob( + os.path.join(assets_dir, "*.json") +) + +changed_any = False +for path in files: + try: + with open(path, encoding="utf-8") as f: + src = f.read() + except (UnicodeDecodeError, OSError): + continue + + new = src + applied = [] + for old, replacement in REPLACEMENTS: + if old in new: + new = new.replace(old, replacement) + applied.append(old) + + if not applied: + continue + + with open(path, "w", encoding="utf-8") as f: + f.write(new) + + # Stale precompressed copies of an edited file must go, or the server + # may keep serving the old compressed content. + for ext in (".gz", ".br"): + stale = path + ext + if os.path.exists(stale): + os.remove(stale) + + print(f"INFO: patched {os.path.basename(path)}: {applied}") + changed_any = True + +if not changed_any: + print("WARN: no UI strings patched (files may already be patched) — non-fatal", + file=sys.stderr) + +sys.exit(0)