Fusion AI: play HAL 8 video links inline

HAL 8's signed file links carry no extension, so a clip linked as
[video](...) rendered as a plain link. A HAL 8 file labelled video/clip
is now a player, and a HAL 8 file embedded as an image that will not
decode is swapped for one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 19:56:23 -05:00
parent b2105ffa46
commit 23b05a72da
2 changed files with 15 additions and 2 deletions

View File

@@ -36,6 +36,9 @@ const safeUrl = u => {
return null;
};
const isVideo = u => /\.(mp4|webm|mov)(\?|#|$)/i.test(u) || /[?&](type|kind)=video/i.test(u);
// HAL 8's signed file links carry no extension: a HAL 8 file linked as "video" / "clip" is one
const isHal8File = u => u.startsWith(ORIGIN + '/api/v1/files/');
const namedVideo = (label, u) => isHal8File(u) && /^\s*(video|clip|watch)\b/i.test(label);
// ------------------------------------------------------------------ a small, safe markdown renderer
// Everything is escaped first; only code fences, inline code, emphasis, headings, lists, links,
@@ -51,7 +54,7 @@ export function renderMarkdown(src) {
.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (m, alt, u) => { u = u.replace(/&amp;/g, '&'); const ok = safeUrl(u); if (!ok) return esc(alt);
return isVideo(ok) ? media(ok, 'video', alt) : media(ok, 'image', alt); })
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (m, t, u) => { u = u.replace(/&amp;/g, '&'); const ok = safeUrl(u); if (!ok) return t;
return isVideo(ok) ? media(ok, 'video', t) : `<a href="${esc(ok)}" target="_blank" rel="noopener nofollow">${t}</a>`; })
return isVideo(ok) || namedVideo(t, ok) ? media(ok, 'video', t) : `<a href="${esc(ok)}" target="_blank" rel="noopener nofollow">${t}</a>`; })
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>').replace(/(^|[\s(])\*([^*\n]+)\*/g, '$1<i>$2</i>');
const out = [];
@@ -109,6 +112,16 @@ export function mount(root, opts = {}) {
</div>`;
const $ = s => root.querySelector(s);
const fab = $('.f8-fab'), pane = $('.f8-pane'), body = $('.f8-body'), ta = $('textarea'), sendBtn = $('.f8-send'), micBtn = $('.f8-mic');
// a HAL 8 file embedded as an image that will not decode is a clip: play it instead
body.addEventListener('error', e => {
const img = e.target;
if (img.tagName !== 'IMG' || !img.closest('.f8-media') || !isHal8File(img.src)) return;
const v = document.createElement('video');
Object.assign(v, { src: img.src, controls: true, playsInline: true, preload: 'metadata' });
img.replaceWith(v);
const tag = v.parentElement?.querySelector('.f8-media-bar span');
if (tag) tag.innerHTML = 'Video &middot; HAL 8';
}, true);
function load() { try { return JSON.parse(localStorage.getItem(STORE) || '[]').slice(-60); } catch { return []; } }
function save() { if (state.mode !== 'standard') return; try { localStorage.setItem(STORE, JSON.stringify(state.msgs.standard.slice(-60))); } catch { /* storage off */ } }

File diff suppressed because one or more lines are too long