Files
apex-fusion-site/tools/fusion_inject.py
jerry 716615a1d2 Fusion AI, powered by HAL 8
The hub's assistant becomes a consumer HAL 8 chat:
- The button is HAL 8's red eye (the real logo); the header reads POWERED BY HAL 8.
- One control, a Standard | Incognito pill. Standard keeps the conversation in this browser;
  Incognito keeps nothing and asks the server for no retention.
- Always on, no toggles: chat and code (copy; Python runs in the browser via Pyodide), images and
  video inline, web answers with their sources, voice in (speech recognition) and read-aloud.
- Streams from HAL 8's public Fusion endpoint (hal8.odyssey-works.io/v1/public/fusion/chat); the
  browser never holds a key. Limits come back as a friendly "see you tomorrow" card.
- A small safe markdown renderer: everything escaped, https-only links and media, HAL 8-relative
  file links resolved against HAL 8.
- tools/fusion_inject.py replaces the old assistant block idempotently (between /*F8-BEGIN*/ and
  /*F8-END*/) and inlines fusion/fusion.css.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:50:44 -05:00

68 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Put Fusion AI (powered by HAL 8) into the hub, replacing the old assistant pane.
python3 tools/fusion_inject.py
The widget itself is fusion/fusion.js (served as a file, loaded on demand) and fusion/fusion.css
(inlined into the bundle). The old block, from its "FUSION AI — assistant pane" banner to the end of
the FusionAI component, is replaced by a small component between /*F8-BEGIN*/ and /*F8-END*/;
re-running replaces that block and the style block again, so the script is idempotent.
The endpoint is HAL 8's public Fusion route; window.FUSION_AI_ENDPOINT still overrides it.
"""
import hashlib
import re
import subprocess
import sys
from pathlib import Path
SITE = Path(__file__).resolve().parents[1]
ENDPOINT = "https://hal8.odyssey-works.io/v1/public/fusion/chat"
tool = SITE / "tools" / "bundle.py"
tmp = SITE / "tools" / "_page.html"
subprocess.run([sys.executable, str(tool), "decode", str(SITE / "index.html"), str(tmp)], check=True, capture_output=True)
page = tmp.read_text("utf-8")
VER = hashlib.sha1((SITE / "fusion" / "fusion.js").read_bytes()).hexdigest()[:10]
COMPONENT = f"""/*F8-BEGIN*/
// ============================================================================
// FUSION AI — powered by HAL 8 (fusion/fusion.js; the key lives on HAL 8, never here)
// ============================================================================
function FusionAI() {{
const ref = useRef(null);
useEffect(() => {{
let api = null, dead = false;
import("./fusion/fusion.js?v={VER}")
.then(m => {{ if (!dead) api = m.mount(ref.current, {{ endpoint: window.FUSION_AI_ENDPOINT || "{ENDPOINT}" }}); }})
.catch(e => console.warn("[Fusion AI]", e));
return () => {{ dead = true; if (api) api.destroy(); }};
}}, []);
return MG("div", {{ ref }});
}}
/*F8-END*/
"""
if "/*F8-BEGIN*/" in page:
i = page.index("/*F8-BEGIN*/")
j = page.index("/*F8-END*/\n", i) + len("/*F8-END*/\n")
else:
banner = page.index("// FUSION AI — assistant pane")
i = page.rfind("// ====", 0, banner)
f = page.index("function FusionAI()", banner)
j = re.compile(r"\n}\n").search(page, f).end()
old = page[i:j]
assert old.count("function ") == 1 and "SYSTEM_PROMPT" in old and "Powered by Claude" in old, "unexpected old block"
page = page[:i] + COMPONENT + page[j:]
assert "Powered by Claude" not in page and page.count("function FusionAI()") == 1
css = (SITE / "fusion" / "fusion.css").read_text("utf-8")
page = re.sub(r"<style>/\*f8-css-begin\*/.*?/\*f8-css-end\*/</style>\n", "", page, flags=re.S)
k = page.rfind("</head>")
page = page[:k] + "<style>/*f8-css-begin*/" + css + "/*f8-css-end*/</style>\n" + page[k:]
tmp.write_text(page, "utf-8")
r = subprocess.run([sys.executable, str(tool), "encode", str(SITE / "index.html"), str(tmp), str(SITE / "index.html")], capture_output=True, text=True)
tmp.unlink()
print(r.stdout.strip() or r.stderr.strip())
print(f"fusion.js v{VER}")