Compare commits
2 Commits
feat/fusio
...
feat/fusio
| Author | SHA1 | Date | |
|---|---|---|---|
| dfa72eec45 | |||
| b9b9fe06d8 |
@@ -89,6 +89,14 @@ contract is `docs/FUSION.md` in the HAL 8 repository.
|
|||||||
- Chat, code, web search, images, video and voice are always on.
|
- Chat, code, web search, images, video and voice are always on.
|
||||||
- Python code blocks run in the visitor's browser.
|
- Python code blocks run in the visitor's browser.
|
||||||
|
|
||||||
|
The launch film lives in `fusion/launch/`. It includes:
|
||||||
|
|
||||||
|
- `fusion-ai-launch.mp4`, the 32-second, 16:9 cut shown on the HAL 8 page (the "Meet Fusion AI" card) and in the widget's "Watch the launch film" lightbox;
|
||||||
|
- `poster.jpg`, its poster frame;
|
||||||
|
- `fusion-ai-launch.vtt`, its subtitles.
|
||||||
|
|
||||||
|
The film was rendered in Blender from the Apex Universe scene. The Fusion pane in it is the real widget, playing answers Fusion AI actually gave. The voice is ElevenLabs "Brian", and the score is original.
|
||||||
|
|
||||||
The widget is `fusion/fusion.js` and `fusion/fusion.css`. After editing
|
The widget is `fusion/fusion.js` and `fusion/fusion.css`. After editing
|
||||||
either, run `python3 tools/fusion_inject.py`: it inlines the CSS and
|
either, run `python3 tools/fusion_inject.py`: it inlines the CSS and
|
||||||
re-stamps the module version inside `index.html`, and re-running it
|
re-stamps the module version inside `index.html`, and re-running it
|
||||||
|
|||||||
@@ -76,3 +76,18 @@
|
|||||||
.f8-fab{right:18px;bottom:18px}
|
.f8-fab{right:18px;bottom:18px}
|
||||||
.f8-open .f8-fab{display:none}
|
.f8-open .f8-fab{display:none}
|
||||||
}
|
}
|
||||||
|
.f8-watch{margin-top:14px;display:flex;align-items:center;gap:9px;width:100%;padding:10px 13px;border:1px solid rgba(255,42,26,.35);border-radius:10px;background:linear-gradient(90deg,rgba(255,42,26,.10),rgba(255,42,26,0));color:var(--ink);font-size:13.5px;text-align:left}
|
||||||
|
.f8-watch i{width:9px;height:9px;border-radius:50%;background:#FF2A1A;box-shadow:0 0 12px #FF2A1A;flex:none}
|
||||||
|
.f8-watch span{margin-left:auto;font-family:var(--f-mono);font-size:10.5px;color:var(--ink-3)}
|
||||||
|
.f8-watch:hover{border-color:rgba(255,42,26,.7)}
|
||||||
|
.f8-film{position:fixed;inset:0;z-index:300;background:rgba(0,0,0,.86);display:flex;align-items:center;justify-content:center;padding:24px}
|
||||||
|
.f8-film-frame{position:relative;width:min(1200px,100%);aspect-ratio:16/9;border-radius:12px;overflow:hidden;box-shadow:0 30px 90px rgba(0,0,0,.7),0 0 0 1px rgba(255,255,255,.08)}
|
||||||
|
.f8-film video{width:100%;height:100%;display:block;background:#000}
|
||||||
|
.f8-film-x{position:absolute;top:10px;right:10px;width:38px;height:38px;border-radius:50%;border:0;background:rgba(0,0,0,.55);color:#fff;font-size:24px;line-height:1;cursor:pointer}
|
||||||
|
.f8-launch{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);gap:28px;align-items:center;margin:30px 0 8px;padding:18px;border:1px solid var(--rule);border-radius:16px;background:linear-gradient(135deg,rgba(255,42,26,.06),rgba(255,255,255,.015) 45%)}
|
||||||
|
.f8-launch-media{border-radius:12px;overflow:hidden;background:#000;aspect-ratio:16/9;box-shadow:0 18px 50px rgba(0,0,0,.5)}
|
||||||
|
.f8-launch-media video{width:100%;height:100%;display:block}
|
||||||
|
.f8-launch-kicker{font-family:var(--f-mono);font-size:10.5px;letter-spacing:.22em;color:#FF5A47;margin-bottom:10px}
|
||||||
|
.f8-launch h3{font-family:var(--f-display);font-size:30px;line-height:1.1;margin:0 0 12px;color:var(--ink)}
|
||||||
|
.f8-launch p{color:var(--ink-2);font-size:15px;line-height:1.55;margin:0 0 18px}
|
||||||
|
@media (max-width:900px){.f8-launch{grid-template-columns:1fr}}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// pictures and clips it makes are deleted after a day.
|
// pictures and clips it makes are deleted after a day.
|
||||||
|
|
||||||
const EYE = 'universe/logos/hal8-eye.png';
|
const EYE = 'universe/logos/hal8-eye.png';
|
||||||
|
const LAUNCH = new URL('launch/', import.meta.url);
|
||||||
|
const FILM_V = '1';
|
||||||
const STORE = 'f8.chat.v1';
|
const STORE = 'f8.chat.v1';
|
||||||
const VISITOR = 'f8.visitor';
|
const VISITOR = 'f8.visitor';
|
||||||
const PYODIDE = 'https://cdn.jsdelivr.net/pyodide/v0.26.4/full/';
|
const PYODIDE = 'https://cdn.jsdelivr.net/pyodide/v0.26.4/full/';
|
||||||
@@ -147,7 +149,8 @@ export function mount(root, opts = {}) {
|
|||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
const s = SUGGEST[state.mode];
|
const s = SUGGEST[state.mode];
|
||||||
body.innerHTML = `<div class="f8-empty"><p class="serif">${state.mode === 'incognito' ? "You're incognito. <span class=\"serif-i\">Nothing you say here is kept.</span>" : "Hi, I'm Fusion AI. <span class=\"serif-i\">Ask, code, create or just talk.</span>"}</p>
|
body.innerHTML = `<div class="f8-empty"><p class="serif">${state.mode === 'incognito' ? "You're incognito. <span class=\"serif-i\">Nothing you say here is kept.</span>" : "Hi, I'm Fusion AI. <span class=\"serif-i\">Ask, code, create or just talk.</span>"}</p>
|
||||||
<div class="f8-sugg">${s.map(([k, t]) => `<button data-f8="suggest"><b>${k}</b>${esc(t)}</button>`).join('')}</div></div>`;
|
<div class="f8-sugg">${s.map(([k, t]) => `<button data-f8="suggest"><b>${k}</b>${esc(t)}</button>`).join('')}</div>
|
||||||
|
${state.mode === 'standard' ? '<button class="f8-watch" data-f8="watch"><i></i>Watch the launch film<span>0:32</span></button>' : ''}</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
body.innerHTML = list.map((m, i) => bubble(m, i)).join('');
|
body.innerHTML = list.map((m, i) => bubble(m, i)).join('');
|
||||||
@@ -286,6 +289,27 @@ export function mount(root, opts = {}) {
|
|||||||
else if (act === 'speak') speak(list[+b.dataset.i]?.content || '');
|
else if (act === 'speak') speak(list[+b.dataset.i]?.content || '');
|
||||||
else if (act === 'copy-code') navigator.clipboard?.writeText(b.closest('.f8-code').querySelector('pre').textContent);
|
else if (act === 'copy-code') navigator.clipboard?.writeText(b.closest('.f8-code').querySelector('pre').textContent);
|
||||||
else if (act === 'run') runPython(b);
|
else if (act === 'run') runPython(b);
|
||||||
|
else if (act === 'watch') watchFilm();
|
||||||
});
|
});
|
||||||
return { open: () => setOpen(true), destroy() { state.ctrl?.abort(); stopVoice(); root.innerHTML = ''; } };
|
function watchFilm() {
|
||||||
|
const box = document.createElement('div');
|
||||||
|
box.className = 'f8-film';
|
||||||
|
box.setAttribute('role', 'dialog');
|
||||||
|
box.setAttribute('aria-label', 'Fusion AI launch film');
|
||||||
|
box.innerHTML = `<div class="f8-film-frame"><video controls autoplay playsinline preload="auto"
|
||||||
|
src="${new URL('fusion-ai-launch.mp4?v=' + FILM_V, LAUNCH)}" poster="${new URL('poster.jpg?v=' + FILM_V, LAUNCH)}">
|
||||||
|
<track kind="captions" srclang="en" label="English" src="${new URL('fusion-ai-launch.vtt?v=' + FILM_V, LAUNCH)}"></video>
|
||||||
|
<button class="f8-film-x" aria-label="Close">×</button></div>`;
|
||||||
|
const close = () => { box.querySelector('video')?.pause(); box.remove(); document.removeEventListener('keydown', onKey); };
|
||||||
|
const onKey = e => { if (e.key === 'Escape') close(); };
|
||||||
|
box.addEventListener('click', e => { if (e.target === box || e.target.closest('.f8-film-x')) close(); });
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
document.body.appendChild(box);
|
||||||
|
}
|
||||||
|
const onExternalOpen = () => { setOpen(true); };
|
||||||
|
window.addEventListener('fusion:open', onExternalOpen);
|
||||||
|
return {
|
||||||
|
open: () => setOpen(true),
|
||||||
|
destroy() { state.ctrl?.abort(); stopVoice(); window.removeEventListener('fusion:open', onExternalOpen); root.innerHTML = ''; },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
fusion/launch/fusion-ai-launch.mp4
Normal file
BIN
fusion/launch/fusion-ai-launch.mp4
Normal file
Binary file not shown.
45
fusion/launch/fusion-ai-launch.vtt
Normal file
45
fusion/launch/fusion-ai-launch.vtt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
WEBVTT
|
||||||
|
|
||||||
|
1
|
||||||
|
00:00:01.600 --> 00:00:03.700
|
||||||
|
Across three chains…
|
||||||
|
|
||||||
|
2
|
||||||
|
00:00:06.100 --> 00:00:08.800
|
||||||
|
…one intelligence was always watching.
|
||||||
|
|
||||||
|
3
|
||||||
|
00:00:12.200 --> 00:00:13.600
|
||||||
|
Ask it anything.
|
||||||
|
|
||||||
|
4
|
||||||
|
00:00:14.300 --> 00:00:15.500
|
||||||
|
Write code.
|
||||||
|
|
||||||
|
5
|
||||||
|
00:00:16.350 --> 00:00:17.900
|
||||||
|
Create worlds.
|
||||||
|
|
||||||
|
6
|
||||||
|
00:00:19.600 --> 00:00:21.100
|
||||||
|
Or just… talk.
|
||||||
|
|
||||||
|
7
|
||||||
|
00:00:22.100 --> 00:00:23.900
|
||||||
|
And when it’s just you…
|
||||||
|
|
||||||
|
8
|
||||||
|
00:00:24.000 --> 00:00:25.300
|
||||||
|
…nothing is kept.
|
||||||
|
|
||||||
|
9
|
||||||
|
00:00:26.450 --> 00:00:27.700
|
||||||
|
Fusion AI.
|
||||||
|
|
||||||
|
10
|
||||||
|
00:00:27.950 --> 00:00:29.100
|
||||||
|
Powered by HAL 8.
|
||||||
|
|
||||||
|
11
|
||||||
|
00:00:29.450 --> 00:00:31.500
|
||||||
|
Now live.
|
||||||
BIN
fusion/launch/poster.jpg
Normal file
BIN
fusion/launch/poster.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
File diff suppressed because one or more lines are too long
@@ -24,6 +24,8 @@ subprocess.run([sys.executable, str(tool), "decode", str(SITE / "index.html"), s
|
|||||||
page = tmp.read_text("utf-8")
|
page = tmp.read_text("utf-8")
|
||||||
|
|
||||||
VER = hashlib.sha1((SITE / "fusion" / "fusion.js").read_bytes()).hexdigest()[:10]
|
VER = hashlib.sha1((SITE / "fusion" / "fusion.js").read_bytes()).hexdigest()[:10]
|
||||||
|
FILM = SITE / "fusion" / "launch" / "fusion-ai-launch.mp4"
|
||||||
|
FILM_V = hashlib.sha1(FILM.read_bytes()).hexdigest()[:10] if FILM.exists() else "0"
|
||||||
COMPONENT = f"""/*F8-BEGIN*/
|
COMPONENT = f"""/*F8-BEGIN*/
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// FUSION AI — powered by HAL 8 (fusion/fusion.js; the key lives on HAL 8, never here)
|
// FUSION AI — powered by HAL 8 (fusion/fusion.js; the key lives on HAL 8, never here)
|
||||||
@@ -39,6 +41,21 @@ function FusionAI() {{
|
|||||||
}}, []);
|
}}, []);
|
||||||
return MG("div", {{ ref }});
|
return MG("div", {{ ref }});
|
||||||
}}
|
}}
|
||||||
|
// the launch film, on the HAL 8 page (fusion/launch/; "Talk to Fusion AI" opens the widget)
|
||||||
|
function FusionLaunchCard() {{
|
||||||
|
const film = "fusion/launch/fusion-ai-launch";
|
||||||
|
return MG("div", {{ className: "f8-launch" }},
|
||||||
|
MG("div", {{ className: "f8-launch-media" }},
|
||||||
|
MG("video", {{ src: film + ".mp4?v={FILM_V}", poster: "fusion/launch/poster.jpg?v={FILM_V}", controls: true, playsInline: true, preload: "none" }},
|
||||||
|
MG("track", {{ kind: "captions", src: film + ".vtt?v={FILM_V}", srcLang: "en", label: "English" }}))),
|
||||||
|
MG("div", {{ className: "f8-launch-copy" }},
|
||||||
|
MG("div", {{ className: "f8-launch-kicker" }}, "NEW · FUSION AI"),
|
||||||
|
MG("h3", null, "Meet Fusion AI. ", MG("span", {{ className: "serif-i" }}, "Powered by HAL 8.")),
|
||||||
|
MG("p", null, "The assistant on this site. Ask anything, write and run code, make images and short video, or just talk. Standard keeps your chat in this browser; Incognito keeps nothing."),
|
||||||
|
MG("div", {{ className: "h8-cta" }},
|
||||||
|
MG("button", {{ className: "mgx-btn pri", onClick: () => window.dispatchEvent(new CustomEvent("fusion:open")) }}, "Talk to Fusion AI"),
|
||||||
|
MG("a", {{ className: "mgx-btn", href: film + ".mp4", download: "fusion-ai-launch.mp4" }}, "Download the film"))));
|
||||||
|
}}
|
||||||
/*F8-END*/
|
/*F8-END*/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -54,6 +71,11 @@ else:
|
|||||||
assert old.count("function ") == 1 and "SYSTEM_PROMPT" in old and "Powered by Claude" in old, "unexpected old block"
|
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:]
|
page = page[:i] + COMPONENT + page[j:]
|
||||||
assert "Powered by Claude" not in page and page.count("function FusionAI()") == 1
|
assert "Powered by Claude" not in page and page.count("function FusionAI()") == 1
|
||||||
|
# the card sits in the HAL 8 section, right above "Inside the harness"
|
||||||
|
HOOK = 'MG("div", { className: "mg-subhead" }, "Inside the harness"),'
|
||||||
|
if "MG(FusionLaunchCard, null)" not in page:
|
||||||
|
assert page.count(HOOK) == 1, "HAL 8 section hook not found"
|
||||||
|
page = page.replace(HOOK, "MG(FusionLaunchCard, null),\n " + HOOK)
|
||||||
|
|
||||||
css = (SITE / "fusion" / "fusion.css").read_text("utf-8")
|
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)
|
page = re.sub(r"<style>/\*f8-css-begin\*/.*?/\*f8-css-end\*/</style>\n", "", page, flags=re.S)
|
||||||
|
|||||||
Reference in New Issue
Block a user