diff --git a/demo/build_final.py b/demo/build_final.py index 33014df..ee658e6 100644 --- a/demo/build_final.py +++ b/demo/build_final.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -"""Build the final banner (ingest/banner.py) and demo GIF (demo/demo.gif). +"""Build the CLI banner (ingest/banner.py) and the README title-card GIF. -Layout: parrot (left) + ansi_shadow "Doppel"/"ganger" (right), amber wordmark, -tagline centered beneath. Renders the GIF with agg's dracula theme. +- ingest/banner.py: lean parrot + amber wordmark, printed at CLI startup. +- demo/demo.gif: a richer "title card" — tagline, parrot + gradient wordmark, + a keyword line, a version box, and a made-by line — rendered with agg. """ import json import os @@ -11,12 +12,19 @@ import pyfiglet ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -PY = os.path.join(ROOT, "venv", "bin", "python") -AGG = os.environ.get("AGG", "agg") # asciinema agg on PATH; override with $AGG +AGG = os.environ.get("AGG", "agg") GAP = " " -TAG = "fine-tune an LLM to write like you" -CMD = "python -m ingest --source telegram --input demo/sample_export.json" -AMBER, RESET = "\x1b[1;38;2;242;176;76m", "\x1b[0m" + +TAGLINE = "fine-tune an LLM on your chat history to write like you" +KEYWORDS = "ingest · scan · redact · audit · fine-tune" +VERSIONS = "Python 3.11–3.13 · LLaMA-Factory 0.9.4 · local LLM" +MADEBY = "made by @NotYuSheng" + +AMBER = (242, 176, 76) +GRAD0, GRAD1 = (255, 196, 84), (233, 84, 64) # gold -> coral, vertical gradient +DIM = (140, 140, 150) + +RESET = "\x1b[0m" with open(os.path.join(ROOT, "demo/mascot.txt"), encoding="utf-8") as _f: parrot = _f.read().rstrip("\n").split("\n") @@ -32,27 +40,58 @@ def _fig(t): word = _fig("Doppel") + _fig("ganger") TOP = (len(parrot) - len(word)) // 2 -TOTAL_W = PW + len(GAP) + max(len(l) for l in word) +WW = max(len(l) for l in word) +BLOCK_W = PW + len(GAP) + WW + + +def _solid(rgb, s): + return f"\x1b[1;38;2;{rgb[0]};{rgb[1]};{rgb[2]}m{s}{RESET}" if s.strip() else s + + +def _lerp(t): + """Colour at fraction t along the top->bottom gradient.""" + return tuple(round(GRAD0[k] + (GRAD1[k] - GRAD0[k]) * t) for k in range(3)) -def rows(on, off): +def _center(plain, rgb=None, width=BLOCK_W): + pad = max(0, (width - len(plain)) // 2) + body = _solid(rgb, plain) if rgb else plain + return " " * pad + body + + +def lean_rows(on, off): + """Parrot + wordmark only (for the CLI banner).""" r = [] for i, pl in enumerate(parrot): wl = word[i - TOP] if 0 <= i - TOP < len(word) else "" wl = f"{on}{wl}{off}" if wl else "" r.append((pl.ljust(PW) + GAP + wl).rstrip()) - r.append("") - r.append(TAG.center(TOTAL_W).rstrip()) # tagline centred under the whole logo return r +def card_rows(): + """The richer title card (coloured) for the GIF.""" + rows = ["", _center(TAGLINE, DIM), ""] + n = max(1, len(word) - 1) + for i, pl in enumerate(parrot): + j = i - TOP + wl = _solid(_lerp(j / n), word[j]) if 0 <= j < len(word) else "" + rows.append(pl.ljust(PW) + GAP + wl) + rows += ["", _center(KEYWORDS, AMBER), ""] + box = len(VERSIONS) + 2 + rows.append(_center("┌" + "─" * box + "┐", DIM)) + rows.append(_center("│ " + VERSIONS + " │", DIM)) + rows.append(_center("└" + "─" * box + "┘", DIM)) + rows += ["", _center(MADEBY, DIM)] + return rows + + def write_banner_module(): - body = "\n".join(rows("", "")) # sentinels; colourised at runtime + body = "\n".join(lean_rows("", "")) mod = ( '"""ASCII startup banner: a parrot in a mirror (it mimics your voice; the\n' - 'mirror is the doppelganger) beside the wordmark. The wordmark is amber via\n' - 'truecolor ANSI. Regenerate via demo/build_final.py.\n' - 'Set DOPPELGANGER_NO_BANNER=1 to silence it."""\n\n' + 'mirror is the doppelganger) beside the wordmark, in truecolor amber.\n' + 'Regenerate via demo/build_final.py. DOPPELGANGER_NO_BANNER=1 silences it."""\n\n' 'import os\n\n' '_AMBER = "\\x1b[1;38;2;242;176;76m" # truecolor amber\n' '_RESET = "\\x1b[0m"\n\n' @@ -62,40 +101,36 @@ def write_banner_module(): ' return\n' ' print(_BANNER.replace("", _AMBER).replace("", _RESET) + "\\n")\n' ) - open(os.path.join(ROOT, "ingest/banner.py"), "w", encoding="utf-8").write(mod) + with open(os.path.join(ROOT, "ingest/banner.py"), "w", encoding="utf-8") as f: + f.write(mod) def render_gif(): - env = dict(os.environ, LLM_VALIDATE="false", DOPPELGANGER_NO_BANNER="1") - out = subprocess.run([PY, *CMD.split()[1:]], cwd=ROOT, env=env, - capture_output=True, text=True, check=True) - report = ((out.stdout or "") + (out.stderr or "")).split("\n") - + card = card_rows() events, t = [], 0.0 def emit(d, dt): nonlocal t t += dt events.append([round(t, 3), "o", d]) - emit("\x1b[32m$\x1b[0m ", 0.3) - for ch in CMD: - emit(ch, 0.026) - emit("\r\n", 0.5) - for line in rows(AMBER, RESET) + report: - emit(line + "\r\n", 0.05) - emit("\x1b[32m$\x1b[0m ", 1.6) + emit("\x1b[?25l", 0.2) # hide cursor + for line in card: + emit(line + "\r\n", 0.08) + emit("", 2.4) # hold on the finished card + emit("\x1b[?25h", 0.0) # restore cursor cast = os.path.join(ROOT, "demo/demo.cast") with open(cast, "w", encoding="utf-8") as f: - f.write(json.dumps({"version": 2, "width": 94, "height": 34}) + "\n") + f.write(json.dumps({"version": 2, "width": BLOCK_W + 6, "height": len(card) + 2}) + "\n") for ev in events: f.write(json.dumps(ev, ensure_ascii=False) + "\n") - subprocess.run([AGG, "--font-size", "18", "--theme", "dracula", cast, - os.path.join(ROOT, "demo/demo.gif")], - stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, check=True) + subprocess.run( + [AGG, "--font-size", "18", "--theme", "dracula", cast, + os.path.join(ROOT, "demo/demo.gif")], + stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, check=True, + ) if __name__ == "__main__": write_banner_module() render_gif() - print("=== layout preview ===") - print("\n".join(rows("", ""))) + print("\n".join(r for r in card_rows())) diff --git a/demo/demo.gif b/demo/demo.gif index 799a218..29c1333 100644 Binary files a/demo/demo.gif and b/demo/demo.gif differ diff --git a/ingest/banner.py b/ingest/banner.py index 8c93b5b..2978d02 100644 --- a/ingest/banner.py +++ b/ingest/banner.py @@ -1,7 +1,6 @@ """ASCII startup banner: a parrot in a mirror (it mimics your voice; the -mirror is the doppelganger) beside the wordmark. The wordmark is amber via -truecolor ANSI. Regenerate via demo/build_final.py. -Set DOPPELGANGER_NO_BANNER=1 to silence it.""" +mirror is the doppelganger) beside the wordmark, in truecolor amber. +Regenerate via demo/build_final.py. DOPPELGANGER_NO_BANNER=1 silences it.""" import os @@ -26,8 +25,6 @@ ⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇ ⢸⣇⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇ ⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁ - - fine-tune an LLM to write like you """