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>
68 lines
2.9 KiB
Python
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}")
|