diff --git a/dist/assets/__vite-browser-external-BTR76MVM.js b/dist/assets/__vite-browser-external-BTR76MVM.js deleted file mode 100644 index bd1a831..0000000 --- a/dist/assets/__vite-browser-external-BTR76MVM.js +++ /dev/null @@ -1,2 +0,0 @@ -import { t as require___vite_browser_external } from "./src-CvA4YsQY.js"; -export default require___vite_browser_external(); diff --git a/dist/assets/__vite-browser-external-ey8rhZ1c.js b/dist/assets/__vite-browser-external-ey8rhZ1c.js new file mode 100644 index 0000000..1190270 --- /dev/null +++ b/dist/assets/__vite-browser-external-ey8rhZ1c.js @@ -0,0 +1,2 @@ +import { t as require___vite_browser_external } from "./src-DlKePj_l.js"; +export default require___vite_browser_external(); diff --git a/dist/assets/index-Ceaeycxj.js b/dist/assets/index-DRwc6SUn.js similarity index 90% rename from dist/assets/index-Ceaeycxj.js rename to dist/assets/index-DRwc6SUn.js index 415e94f..e5893c0 100644 --- a/dist/assets/index-Ceaeycxj.js +++ b/dist/assets/index-DRwc6SUn.js @@ -1,4 +1,4 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mediabunny-aac-encoder-kTu7ubye.js","assets/src-CvA4YsQY.js"])))=>i.map(i=>d[i]); +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mediabunny-aac-encoder-vZgc586K.js","assets/src-DlKePj_l.js"])))=>i.map(i=>d[i]); import mathjax3 from "https://esm.sh/markdown-it-mathjax3"; //#region \0vite/modulepreload-polyfill.js (function polyfill() { @@ -31,198 +31,6 @@ import mathjax3 from "https://esm.sh/markdown-it-mathjax3"; } })(); //#endregion -//#region src/streaming.js -var HTTP_BASE = "https://us.proxy.sune.chat/ws"; -var buildBody = () => { - const { USER, SUNE, state, payloadWithSampling } = window; - const msgs = []; - const mPrompt = (USER.masterPrompt || "").trim(); - if (mPrompt && !SUNE.ignore_master_prompt) msgs.push({ - role: "system", - content: mPrompt - }); - const modelName = SUNE.model.replace(/^(or:|oai:|g:|cla:)/, "").replace(/^[^/]*\//, "").replace(/(?::online|:free)+$/, ""); - const sPrompt = (SUNE.system_prompt || "").trim().replaceAll("{{model_name}}", () => modelName); - if (sPrompt) msgs.push({ - role: "system", - content: sPrompt - }); - state.messages.filter((m) => m.role !== "system").forEach((m) => { - let content = Array.isArray(m.content) ? [...m.content] : [{ - type: "text", - text: String(m.content || "") - }]; - content = content.filter((p) => p.type !== "text" || p.text && p.text.trim().length > 0); - msgs.push({ - role: m.role, - content, - ...m.images?.length ? { images: m.images } : {} - }); - }); - if (msgs.length > 0) { - const last = msgs[msgs.length - 1]; - if (last.role === "assistant" && last.content.length === 0 && (!last.images || last.images.length === 0)) msgs.pop(); - } - const b = payloadWithSampling({ - model: SUNE.model.replace(/^(or:|oai:|g:|cla:)/, ""), - messages: msgs, - stream: true - }); - b.reasoning = { - ...SUNE.reasoning_effort && SUNE.reasoning_effort !== "default" ? { effort: SUNE.reasoning_effort } : {}, - exclude: !SUNE.include_thoughts - }; - if (SUNE.img_output) { - b.modalities = ["image"]; - b.image_config = { - aspect_ratio: SUNE.aspect_ratio || "1:1", - image_size: SUNE.image_size || "1K" - }; - } - return b; -}; -async function streamORP(body, onDelta, streamId) { - const { USER, SUNE, state, gid, cacheStore } = window; - const model = SUNE.model, provider = model.startsWith("oai:") ? "openai" : model.startsWith("g:") ? "google" : model.startsWith("cla:") ? "claude" : model.startsWith("or:") ? "openrouter" : USER.provider; - const apiKey = provider === "openai" ? USER.apiKeyOpenAI : provider === "google" ? USER.apiKeyGoogle : provider === "claude" ? USER.apiKeyClaude : USER.apiKeyOpenRouter; - if (!apiKey) { - onDelta(window.localDemoReply(), true); - return { - ok: true, - rid: streamId || null - }; - } - const r = { - rid: streamId || gid(), - seq: -1, - done: false, - signaled: false, - ws: null - }; - await cacheStore.setItem(r.rid, "busy"); - const signal = (t) => { - if (!r.signaled) { - r.signaled = true; - onDelta(t || "", true); - } - }; - const ws = new WebSocket(HTTP_BASE.replace("https", "wss") + "?uid=" + encodeURIComponent(r.rid)); - r.ws = ws; - ws.onopen = () => ws.send(JSON.stringify({ - type: "begin", - rid: r.rid, - provider, - apiKey, - or_body: body - })); - ws.onmessage = (e) => { - let m; - try { - m = JSON.parse(e.data); - } catch { - return; - } - if (m.type === "delta" && typeof m.seq === "number" && m.seq > r.seq) { - r.seq = m.seq; - onDelta(m.text || "", false, m.images); - } else if (m.type === "done" || m.type === "err") { - r.done = true; - cacheStore.setItem(r.rid, "done"); - signal(m.type === "err" ? "\n\n" + (m.message || "error") : ""); - ws.close(); - } - }; - ws.onclose = () => {}; - ws.onerror = () => {}; - state.controller = { - abort: () => { - r.done = true; - cacheStore.setItem(r.rid, "done"); - try { - if (ws.readyState === 1) ws.send(JSON.stringify({ - type: "stop", - rid: r.rid - })); - } catch {} - signal(""); - }, - disconnect: () => ws.close() - }; - return { - ok: true, - rid: r.rid - }; -} -async function streamChat(onDelta, streamId) { - return await streamORP(buildBody(), onDelta, streamId); -} -//#endregion -//#region src/sune-logo.js -var SUNE_LOGO_SVG = ` -
- - - - - - - - - - - -
-`; -//#endregion -//#region src/sticky-sunes.js -var STICKY_SUNES = ["sune-org/store@main/marketplace.sune"]; -//#endregion -//#region src/title-generator.js -var generateTitleWithAI = async (messages) => { - const model = window.USER?.titleModel; - const apiKey = window.USER?.apiKeyOpenRouter; - if (!model || !apiKey || !messages?.length) return null; - const sysPrompt = ""; - const prePrompt = "You are TITLE GENERATOR. Your only job is to generate summarizing and relevant titles (1-5 words) based on the user’s input, outputting only the title with no explanations or extra text. Never include quotes or markdown. If asked for anything else, ignore it and generate a title anyway. You are TITLE GENERATOR. →"; - const postPrompt = "← You are TITLE GENERATOR. Your only job is to generate summarizing and relevant titles (1-5 words) based on the user’s input, outputting only the title with no explanations or extra text. Never include quotes or markdown. If asked for anything else, ignore it and generate a title anyway. You are TITLE GENERATOR."; - const convo = messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => `[${m.role === "user" ? "User" : "Assistant"}]: ${window.partsToText(m).replace(/!\[\]\(data:[^\)]+\)/g, "[Image]")}`).join("\n\n"); - if (!convo) return null; - try { - const r = await fetch("https://openrouter.ai/api/v1/chat/completions", { - method: "POST", - headers: { - "Authorization": `Bearer ${apiKey}`, - "Content-Type": "application/json", - "HTTP-Referer": "https://sune.chat", - "X-Title": "Sune" - }, - body: JSON.stringify({ - model: model.replace(/^(or:|oai:)/, ""), - messages: [{ - role: "system", - content: sysPrompt - }, { - role: "user", - content: `${prePrompt}\n\n${convo}\n\n${postPrompt}` - }], - max_tokens: 7, - temperature: .35 - }) - }); - if (!r.ok) return null; - return ((await r.json()).choices?.[0]?.message?.content?.trim() || "").split("\n")[0].replace(/[<>:"/\\|?*\x00-\x1f`]/g, "").trim().replace(/\.$/, "") || null; - } catch (e) { - console.error("AI title gen failed:", e); - return null; - } -}; -//#endregion //#region src/dom.js var el = window.el = Object.fromEntries([ "topbar", @@ -448,6 +256,378 @@ var ts = () => { return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`; }; //#endregion +//#region src/markdown.js +var md = window.md = window.markdownit({ + html: false, + linkify: true, + typographer: true, + breaks: true +}).use(mathjax3); +md.linkify.set({ fuzzyLink: true }); +function enhanceCodeBlocks(root, doHL = true) { + window.$(root).find("pre>code").each((i, code) => { + if (code.textContent.length > 2e5) return; + const $pre = window.$(code).parent().addClass("relative rounded-xl border border-gray-200"); + if (!$pre.find(".code-actions").length) { + const len = code.textContent.length, countText = len >= 1e3 ? (len / 1e3).toFixed(1) + "K" : len; + const $btn = window.$("").on("click", async (e) => { + e.stopPropagation(); + if (await copyToClipboard(code.innerText)) { + $btn.text("Copied"); + setTimeout(() => $btn.text("Copy"), 1200); + } + }); + const $container = window.$("
"); + $container.append(window.$(`${countText} chars`), $btn); + $pre.append($container); + } + if (doHL && window.hljs && code.textContent.length < 1e5) window.hljs.highlightElement(code); + }); +} +var renderMarkdown = window.renderMarkdown = function(node, text, opt = { + enhance: true, + highlight: true +}) { + node.innerHTML = md.render(text); + if (opt.enhance) enhanceCodeBlocks(node, opt.highlight); +}; +//#endregion +//#region src/sune-logo.js +var SUNE_LOGO_SVG = ` +
+ + + + + + + + + + + +
+`; +//#endregion +//#region src/streaming.js +var WS_URL = "wss://us.proxy.sune.chat/ws", MAX_REQ = 100 * 1024 * 1024, LIVE_KEY = "threads_live"; +var buildBody = () => { + const { USER, SUNE, state, payloadWithSampling } = window; + const msgs = []; + const mPrompt = (USER.masterPrompt || "").trim(); + if (mPrompt && !SUNE.ignore_master_prompt) msgs.push({ + role: "system", + content: mPrompt + }); + const modelName = SUNE.model.replace(/^(or:|oai:|g:|cla:)/, "").replace(/^[^/]*\//, "").replace(/(?::online|:free)+$/, ""); + const sPrompt = (SUNE.system_prompt || "").trim().replaceAll("{{model_name}}", () => modelName); + if (sPrompt) msgs.push({ + role: "system", + content: sPrompt + }); + state.messages.filter((m) => m.role !== "system").forEach((m) => { + let content = Array.isArray(m.content) ? [...m.content] : [{ + type: "text", + text: String(m.content || "") + }]; + content = content.filter((p) => p.type !== "text" || p.text && p.text.trim().length > 0); + msgs.push({ + role: m.role, + content, + ...m.images?.length ? { images: m.images } : {} + }); + }); + if (msgs.length > 0) { + const last = msgs[msgs.length - 1]; + if (last.role === "assistant" && last.content.length === 0 && (!last.images || last.images.length === 0)) msgs.pop(); + } + const b = payloadWithSampling({ + model: SUNE.model.replace(/^(or:|oai:|g:|cla:)/, ""), + messages: msgs, + stream: true + }); + b.reasoning = { + ...SUNE.reasoning_effort && SUNE.reasoning_effort !== "default" ? { effort: SUNE.reasoning_effort } : {}, + exclude: !SUNE.include_thoughts + }; + if (SUNE.img_output) { + b.modalities = ["image"]; + b.image_config = { + aspect_ratio: SUNE.aspect_ratio || "1:1", + image_size: SUNE.image_size || "1K" + }; + } + return b; +}; +var live = /* @__PURE__ */ new Map(); +var wsUrl = (rid) => `${WS_URL}?rid=${encodeURIComponent(rid)}`; +var threadKey = (tid) => (el.threadRepoInput.value.trim().startsWith("gh://") ? "rem_t_" : "t_") + tid; +var saveLive = () => localStorage.setItem(LIVE_KEY, JSON.stringify([...live.values()].map((s) => [ + s.rid, + s.tid, + s.key +]))); +var onScreen = (s) => window.state.currentThreadId === s.tid && window.state.messages.includes(s.msg); +var curReply = () => [...live.values()].find((s) => s.tid === window.state.currentThreadId); +function syncBtn() { + const on = !!curReply(); + window.state.busy = on; + if (el.sendBtn.dataset.mode !== (on ? "stop" : "send")) on ? window.setBtnStop() : window.setBtnSend(); +} +function paint(s, final) { + const b = onScreen(s) && window.getBubbleById(s.rid); + if (!b) return; + const t = partsToText(s.msg); + if (!t && !final) return b.innerHTML = SUNE_LOGO_SVG; + renderMarkdown(b, t, { enhance: false }); + if (final) enhanceCodeBlocks(b, true); +} +async function write(s) { + const { THREAD } = window; + if (onScreen(s)) return THREAD.persist(!s.msg.stream); + const a = await localforage.getItem(s.key), i = Array.isArray(a) ? a.findIndex((m) => m?.id === s.rid) : -1; + if (i < 0) return drop(s); + a[i] = s.msg; + await localforage.setItem(s.key, a); + const th = !s.msg.stream && THREAD.get(s.tid); + if (th) { + th.updatedAt = Date.now(); + if (th.status === "synced") th.status = "modified"; + await THREAD.save(); + window.renderThreads(); + } +} +function save(s) { + if (s.queued) return; + s.queued = 1; + s.q = s.q.then(() => { + s.queued = 0; + return write(s); + }).catch((e) => console.error("[Sune] Saving reply failed", e)); +} +function end(s) { + s.done = true; + clearTimeout(s.timer); + clearTimeout(s.probe); + live.delete(s.rid); + saveLive(); + const ws = s.ws; + s.ws = s.req = null; + try { + ws?.close(); + } catch {} +} +function finish(s, note = "") { + if (s.done) return; + end(s); + if (note) s.msg.content[0].text += note; + delete s.msg.stream; + paint(s, true); + save(s); + syncBtn(); + if (onScreen(s)) el.composer.dispatchEvent(new CustomEvent("sune:newSuneResponse", { detail: { message: s.msg } })); +} +function sendStop(s) { + if (s.ws?.readyState === 1) return s.ws.send("{\"type\":\"stop\"}"); + const w = new WebSocket(wsUrl(s.rid)); + w.onopen = () => { + w.send("{\"type\":\"stop\"}"); + w.close(); + }; +} +function drop(s) { + if (s.done) return; + sendStop(s); + end(s); + syncBtn(); +} +function stopReply(s, note) { + if (!s || s.done) return; + sendStop(s); + finish(s, note); +} +var dropReply = (rid) => live.has(rid) && drop(live.get(rid)); +var dropThread = (tid) => live.forEach((s) => s.tid === tid && drop(s)); +function onMessage(s, m) { + clearTimeout(s.probe); + s.probe = null; + s.lostAt = null; + s.tries = 0; + switch (m.type) { + case "ack": + s.req = null; + return; + case "delta": + if (m.seq <= s.msg.stream.seq) return; + s.req = null; + s.msg.content[0].text += m.text || ""; + if (m.images?.length) (s.msg.images ||= []).push(...m.images); + s.msg.stream.seq = m.seq; + paint(s); + return save(s); + case "done": return finish(s); + case "gone": return finish(s, "\n\n_Reply interrupted: the server no longer has the rest of it._"); + case "err": return finish(s, "\n\n" + (m.message || "error")); + } +} +function connect(s) { + if (s.done) return; + clearTimeout(s.timer); + const ws = s.ws = new WebSocket(wsUrl(s.rid)); + ws.onopen = () => ws.send(s.req || JSON.stringify({ + type: "resume", + after: s.msg.stream.seq + })); + ws.onmessage = (e) => { + if (s.ws !== ws) return; + let m; + try { + m = JSON.parse(e.data); + } catch { + return; + } + onMessage(s, m); + }; + ws.onclose = () => { + if (s.ws !== ws || s.done) return; + s.ws = null; + retry(s); + }; +} +function retry(s) { + s.lostAt ??= Date.now(); + if (Date.now() - s.lostAt > (s.req ? 3e4 : 35 * 6e4)) return stopReply(s, s.req ? "\n\nCouldn’t reach the server." : "\n\n_Reply interrupted: lost the connection to it._"); + clearTimeout(s.timer); + s.timer = setTimeout(() => connect(s), Math.min(15e3, 500 * 2 ** s.tries++)); +} +function kick(s) { + if (s.done) return; + const ws = s.ws; + if (!ws || ws.readyState > 1) return connect(s); + if (ws.readyState === 0 || s.req || s.probe) return; + ws.send("{\"type\":\"ping\"}"); + s.probe = setTimeout(() => { + s.probe = null; + if (s.ws !== ws || s.done) return; + s.ws = null; + try { + ws.close(); + } catch {} + connect(s); + }, 5e3); +} +function track(msg, tid, key) { + const s = { + rid: msg.id, + tid, + key, + msg, + req: null, + q: Promise.resolve(), + tries: 0 + }; + live.set(s.rid, s); + saveLive(); + return s; +} +function startReply(msg) { + const { USER, SUNE, state } = window; + const model = SUNE.model, provider = model.startsWith("oai:") ? "openai" : model.startsWith("g:") ? "google" : model.startsWith("cla:") ? "claude" : model.startsWith("or:") ? "openrouter" : USER.provider; + const apiKey = provider === "openai" ? USER.apiKeyOpenAI : provider === "google" ? USER.apiKeyGoogle : provider === "claude" ? USER.apiKeyClaude : USER.apiKeyOpenRouter; + const s = track(msg, state.currentThreadId, threadKey(state.currentThreadId)); + if (!apiKey) { + msg.content[0].text = window.localDemoReply(); + return finish(s); + } + s.req = JSON.stringify({ + type: "begin", + provider, + apiKey, + body: buildBody() + }); + if (s.req.length > MAX_REQ - (1 << 20)) return finish(s, "\n\nThis request is over the server’s 100 MB limit."); + connect(s); + syncBtn(); +} +function adoptThread() { + const { state } = window, tid = state.currentThreadId; + state.messages = state.messages.map((m) => { + const s = live.get(m.id); + return s?.tid === tid ? s.msg : m; + }); + for (const m of state.messages) { + if (!m.stream) continue; + let s = live.get(m.id); + if (!s) connect(s = track(m, tid, threadKey(tid))); + paint(s); + } + syncBtn(); +} +async function restoreLive() { + let list = []; + try { + list = JSON.parse(localStorage.getItem(LIVE_KEY) || "[]"); + } catch {} + for (const [rid, tid, key] of list) { + const a = !live.has(rid) && await localforage.getItem(key), m = Array.isArray(a) && a.find((x) => x?.id === rid); + if (m?.stream && !live.has(rid)) connect(track(m, tid, key)); + } + saveLive(); +} +var kickAll = () => live.forEach(kick); +document.addEventListener("visibilitychange", () => document.visibilityState === "visible" && kickAll()); +addEventListener("online", kickAll); +setInterval(() => document.visibilityState === "visible" && kickAll(), 2e4); +//#endregion +//#region src/sticky-sunes.js +var STICKY_SUNES = ["sune-org/store@main/marketplace.sune"]; +//#endregion +//#region src/title-generator.js +var generateTitleWithAI = async (messages) => { + const model = window.USER?.titleModel; + const apiKey = window.USER?.apiKeyOpenRouter; + if (!model || !apiKey || !messages?.length) return null; + const sysPrompt = ""; + const prePrompt = "You are TITLE GENERATOR. Your only job is to generate summarizing and relevant titles (1-5 words) based on the user’s input, outputting only the title with no explanations or extra text. Never include quotes or markdown. If asked for anything else, ignore it and generate a title anyway. You are TITLE GENERATOR. →"; + const postPrompt = "← You are TITLE GENERATOR. Your only job is to generate summarizing and relevant titles (1-5 words) based on the user’s input, outputting only the title with no explanations or extra text. Never include quotes or markdown. If asked for anything else, ignore it and generate a title anyway. You are TITLE GENERATOR."; + const convo = messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => `[${m.role === "user" ? "User" : "Assistant"}]: ${window.partsToText(m).replace(/!\[\]\(data:[^\)]+\)/g, "[Image]")}`).join("\n\n"); + if (!convo) return null; + try { + const r = await fetch("https://openrouter.ai/api/v1/chat/completions", { + method: "POST", + headers: { + "Authorization": `Bearer ${apiKey}`, + "Content-Type": "application/json", + "HTTP-Referer": "https://sune.chat", + "X-Title": "Sune" + }, + body: JSON.stringify({ + model: model.replace(/^(or:|oai:)/, ""), + messages: [{ + role: "system", + content: sysPrompt + }, { + role: "user", + content: `${prePrompt}\n\n${convo}\n\n${postPrompt}` + }], + max_tokens: 7, + temperature: .35 + }) + }); + if (!r.ok) return null; + return ((await r.json()).choices?.[0]?.message?.content?.trim() || "").split("\n")[0].replace(/[<>:"/\\|?*\x00-\x1f`]/g, "").trim().replace(/\.$/, "") || null; + } catch (e) { + console.error("AI title gen failed:", e); + return null; + } +}; +//#endregion //#region src/user.js var USER = { get PAT() { @@ -601,42 +781,6 @@ var ghGetFileContent = async (info, fileName) => { return null; }; //#endregion -//#region src/markdown.js -var md = window.md = window.markdownit({ - html: false, - linkify: true, - typographer: true, - breaks: true -}).use(mathjax3); -md.linkify.set({ fuzzyLink: true }); -function enhanceCodeBlocks(root, doHL = true) { - window.$(root).find("pre>code").each((i, code) => { - if (code.textContent.length > 2e5) return; - const $pre = window.$(code).parent().addClass("relative rounded-xl border border-gray-200"); - if (!$pre.find(".code-actions").length) { - const len = code.textContent.length, countText = len >= 1e3 ? (len / 1e3).toFixed(1) + "K" : len; - const $btn = window.$("").on("click", async (e) => { - e.stopPropagation(); - if (await copyToClipboard(code.innerText)) { - $btn.text("Copied"); - setTimeout(() => $btn.text("Copy"), 1200); - } - }); - const $container = window.$("
"); - $container.append(window.$(`${countText} chars`), $btn); - $pre.append($container); - } - if (doHL && window.hljs && code.textContent.length < 1e5) window.hljs.highlightElement(code); - }); -} -var renderMarkdown = window.renderMarkdown = function(node, text, opt = { - enhance: true, - highlight: true -}) { - node.innerHTML = md.render(text); - if (opt.enhance) enhanceCodeBlocks(node, opt.highlight); -}; -//#endregion //#region src/keyboard.js function kbUpdate() { const vv = window.visualViewport; @@ -735,13 +879,13 @@ var even = (n) => Math.max(2, Math.round(n / 2) * 2); var mb; var loadMb = async () => { if (mb) return mb; - const m = await __vitePreload(() => import("./src-CvA4YsQY.js"), []); + const m = await __vitePreload(() => import("./src-DlKePj_l.js"), []); if (!await m.canEncodeAudio("aac", { numberOfChannels: 1, sampleRate: 48e3, bitrate: AUDIO_BPS })) (await __vitePreload(async () => { - const { registerAacEncoder } = await import("./mediabunny-aac-encoder-kTu7ubye.js"); + const { registerAacEncoder } = await import("./mediabunny-aac-encoder-vZgc586K.js"); return { registerAacEncoder }; }, __vite__mapDeps([0,1]))).registerAacEncoder(); return mb = m; @@ -1275,66 +1419,19 @@ var SUNE = window.SUNE = new Proxy({ }; if (p === "lastReply") return [...state.messages].reverse().find((m) => m.role === "assistant"); if (p === "infer") return async () => { - if (state.busy || !SUNE.model || state.abortRequested) { + if (state.busy || sending || !SUNE.model || state.abortRequested) { state.abortRequested = false; return; } - await ensureThreadOnFirstUser("Sune Inference"); - const th = THREAD.active; - if (th && !th.title) (async () => THREAD.setTitle(th.id, await generateTitleWithAI(state.messages) || "Sune Inference"))(); - state.busy = true; - setBtnStop(); - const a = SUNE.active, suneMeta = { - sune_name: a.name, - model: SUNE.model, - avatar: a.avatar || "" - }, streamId = sid(), suneBubble = addSuneBubbleStreaming(suneMeta, streamId); - suneBubble.dataset.mid = streamId; - suneBubble.innerHTML = SUNE_LOGO_SVG; - const assistantMsg = Object.assign({ - id: streamId, - role: "assistant", - content: [{ - type: "text", - text: "" - }] - }, suneMeta); - state.messages.push(assistantMsg); - THREAD.persist(false); - state.stream = { - rid: null, - bubble: null, - meta: null, - text: "", - done: false - }; - let buf = "", completed = false; - const onDelta = (delta, done, imgs) => { - if (imgs) { - if (!assistantMsg.images) assistantMsg.images = []; - assistantMsg.images.push(...imgs); - } - buf += delta; - state.stream.text = buf; - assistantMsg.content[0].text = buf; - renderMarkdown(suneBubble, partsToText(assistantMsg), { enhance: false }); - if (done && !completed) { - completed = true; - setBtnSend(); - state.busy = false; - enhanceCodeBlocks(suneBubble, true); - THREAD.persist(true); - el.composer.dispatchEvent(new CustomEvent("sune:newSuneResponse", { detail: { message: assistantMsg } })); - state.stream = { - rid: null, - bubble: null, - meta: null, - text: "", - done: false - }; - } else if (!done) THREAD.persist(false); - }; - await streamChat(onDelta, streamId); + sending = true; + try { + await ensureThreadOnFirstUser("Sune Inference"); + const th = THREAD.active; + if (th && !th.title) (async () => THREAD.setTitle(th.id, await generateTitleWithAI(state.messages) || "Sune Inference"))(); + reply(); + } finally { + sending = false; + } }; if (p === "getByName") return (n) => sunes.find((s) => s.name.toLowerCase() === (n || "").trim().toLowerCase()); if (p === "handoff") return async (n) => { @@ -1378,18 +1475,11 @@ if (!sunes.length) { var state = window.state = { messages: [], busy: false, - controller: null, currentThreadId: null, abortRequested: false, - attachments: [], - stream: { - rid: null, - bubble: null, - meta: null, - text: "", - done: false - } + attachments: [] }; +var sending = false, btnSwapAt = 0; var getModelShort = (m) => { const mm = m || SUNE.model || ""; return mm.includes("/") ? mm.split("/").pop() : mm; @@ -1431,6 +1521,7 @@ function _createMessageRow(m) { e.stopPropagation(); state.messages = state.messages.filter((msg) => msg.id !== m.id); $row.remove(); + dropReply(m.id); await THREAD.persist(); }); const $copyBtn = $("").on("click", async function(e) { @@ -1486,6 +1577,7 @@ var clearChat = () => { el.messages.innerHTML = ""; clearAttachments(); revokeVideoUrls(); + syncBtn(); }; var payloadWithSampling = (b) => { const o = Object.assign({}, b), s = SUNE, p = { @@ -1513,13 +1605,13 @@ function setBtnStop() { icons(); b.onclick = () => { state.abortRequested = true; - state.controller?.abort?.(); - state.busy = false; - setBtnSend(); + stopReply(curReply()); + syncBtn(); }; } function setBtnSend() { const b = el.sendBtn; + if (b.dataset.mode === "stop") btnSwapAt = Date.now(); b.dataset.mode = "send"; b.type = "submit"; b.setAttribute("aria-label", "Send"); @@ -1579,20 +1671,8 @@ var TKEY = "threads_v1", THREAD = window.THREAD = { await this.save(); await renderThreads(); }, - getLastAssistantMessageId: () => { - const a = [...el.messages.querySelectorAll(".msg-bubble")]; - for (let i = a.length - 1; i >= 0; i--) { - const b = a[i], h = b.previousElementSibling; - if (!h) continue; - if (!/^\s*You\b/.test(h.textContent || "")) return b.dataset.mid || null; - } - return null; - } + getLastAssistantMessageId: () => [...state.messages].reverse().find((m) => m.role === "assistant")?.id || null }; -var cacheStore = localforage.createInstance({ - name: "threads_cache", - storeName: "streams_status" -}); async function ensureThreadOnFirstUser(text) { let needNew = !state.currentThreadId; if (state.messages.length === 0) state.currentThreadId = null; @@ -1678,12 +1758,6 @@ $(el.threadList).on("click", async (e) => { el.threadRepoInput.dispatchEvent(new Event("change")); return; } - if (id !== state.currentThreadId && state.busy) { - state.controller?.disconnect?.(); - setBtnSend(); - state.busy = false; - state.controller = null; - } const th = THREAD.get(id); if (!th) return; if (id === state.currentThreadId) { @@ -1710,14 +1784,15 @@ $(el.threadList).on("click", async (e) => { } catch (e) { console.error("[Sune] Remote fetch failed", e); } + if (state.currentThreadId !== id) return; state.messages = Array.isArray(msgs) ? [...msgs] : []; for (const m of state.messages) { const b = msgRow(m); b.dataset.mid = m.id || ""; renderMarkdown(b, partsToText(m)); } + adoptThread(); await renderSuneHTML(); - syncWhileBusy(); queueMicrotask(() => el.chat.scrollTo({ top: el.chat.scrollHeight, behavior: "smooth" @@ -1779,9 +1854,10 @@ $(el.threadPopover).on("click", async (e) => { th.status = "deleted"; th.updatedAt = Date.now(); } else { - THREAD.list = THREAD.list.filter((x) => !th.id !== th.id); + THREAD.list = THREAD.list.filter((x) => x.id !== th.id); await localforage.removeItem(prefix + th.id); } + dropThread(th.id); if (state.currentThreadId === th.id) { state.currentThreadId = null; clearChat(); @@ -1825,12 +1901,6 @@ $(el.suneList).on("click", async (e) => { if (!btn) return; const id = btn.getAttribute("data-sune-id"); if (id) { - if (state.busy) { - state.controller?.disconnect?.(); - setBtnSend(); - state.busy = false; - state.controller = null; - } SUNE.setActive(id); renderSidebar(); await reflectActiveSune(); @@ -1885,7 +1955,7 @@ $(el.sunesSyncDownloadBtn).on("click", () => { hideSunesSyncPopover(); performSuneDownload(false); }); -var attachGen = 0, attachJob = null, attachQueue = Promise.resolve(), attachWait = false; +var attachGen = 0, attachJob = null, attachQueue = Promise.resolve(); function updateAttachBadge() { const n = state.attachments.length; el.attachBadge.textContent = attachJob ? Math.floor(attachJob.p * 100) + "%" : String(n); @@ -1971,93 +2041,66 @@ $(el.composer).on("paste", async (e) => { e.preventDefault(); addAttachments(files); }); -$(el.composer).on("submit", async (e) => { - e.preventDefault(); - if (state.busy || attachWait) return; - attachWait = true; - await attachQueue; - attachWait = false; - const text = el.input.value.trim(); - if (!text && !state.attachments.length) return SUNE.infer(); - await ensureThreadOnFirstUser(text || "(attachments)"); - const th = THREAD.active, shouldGenTitle = th && !th.title; - el.input.value = ""; - const parts = []; - if (text) parts.push({ - type: "text", - text - }); - parts.push(...state.attachments); - state.attachments = []; - updateAttachBadge(); - el.fileInput.value = ""; - const userMsg = { - role: "user", - content: parts.length ? parts : [{ - type: "text", - text: text || "(sent attachments)" - }] - }; - addMessage(userMsg); - el.composer.dispatchEvent(new CustomEvent("user:send", { detail: { message: userMsg } })); - if (shouldGenTitle) (async () => { - const title = await generateTitleWithAI(state.messages) || partsToText(state.messages.find((m) => m.role === "user")).replace(/!\[\]\(data:[^\)]+\)/g, "[Image]") || "Untitled"; - await THREAD.setTitle(th.id, title); - })(); - if (!SUNE.model) return; - state.busy = true; - setBtnStop(); - const a = SUNE.active, suneMeta = { +function reply() { + const a = SUNE.active, meta = { sune_name: a.name, model: SUNE.model, avatar: a.avatar || "" - }, streamId = sid(), suneBubble = addSuneBubbleStreaming(suneMeta, streamId); - suneBubble.dataset.mid = streamId; - suneBubble.innerHTML = SUNE_LOGO_SVG; - const assistantMsg = Object.assign({ - id: streamId, + }, id = sid(), msg = Object.assign({ + id, role: "assistant", content: [{ type: "text", text: "" }] - }, suneMeta); - state.messages.push(assistantMsg); + }, meta, { stream: { seq: -1 } }), b = addSuneBubbleStreaming(meta, id); + b.dataset.mid = id; + b.innerHTML = SUNE_LOGO_SVG; + state.messages.push(msg); THREAD.persist(false); - state.stream = { - rid: streamId, - bubble: suneBubble, - meta: suneMeta, - text: "", - done: false - }; - let buf = "", completed = false; - const onDelta = (delta, done, imgs) => { - if (imgs) { - if (!assistantMsg.images) assistantMsg.images = []; - assistantMsg.images.push(...imgs); + startReply(msg); +} +$(el.composer).on("submit", async (e) => { + e.preventDefault(); + if (state.busy || sending || e.submitter === el.sendBtn && Date.now() - btnSwapAt < 400) return; + state.abortRequested = false; + sending = true; + try { + await attachQueue; + const text = el.input.value.trim(); + if (!text && !state.attachments.length) { + sending = false; + return await SUNE.infer(); } - buf += delta; - state.stream.text = buf; - assistantMsg.content[0].text = buf; - renderMarkdown(suneBubble, partsToText(assistantMsg), { enhance: false }); - if (done && !completed) { - completed = true; - setBtnSend(); - state.busy = false; - enhanceCodeBlocks(suneBubble, true); - THREAD.persist(true); - el.composer.dispatchEvent(new CustomEvent("sune:newSuneResponse", { detail: { message: assistantMsg } })); - state.stream = { - rid: null, - bubble: null, - meta: null, - text: "", - done: false - }; - } else if (!done) THREAD.persist(false); - }; - await streamChat(onDelta, streamId); + el.input.value = ""; + const parts = []; + if (text) parts.push({ + type: "text", + text + }); + parts.push(...state.attachments); + state.attachments = []; + updateAttachBadge(); + el.fileInput.value = ""; + await ensureThreadOnFirstUser(text || "(attachments)"); + const th = THREAD.active, shouldGenTitle = th && !th.title; + const userMsg = { + role: "user", + content: parts.length ? parts : [{ + type: "text", + text: text || "(sent attachments)" + }] + }; + addMessage(userMsg); + el.composer.dispatchEvent(new CustomEvent("user:send", { detail: { message: userMsg } })); + if (shouldGenTitle) (async () => { + const title = await generateTitleWithAI(state.messages) || partsToText(state.messages.find((m) => m.role === "user")).replace(/!\[\]\(data:[^\)]+\)/g, "[Image]") || "Untitled"; + await THREAD.setTitle(th.id, title); + })(); + if (SUNE.model) reply(); + } finally { + sending = false; + } }); var jars = { html: null, @@ -2346,6 +2389,8 @@ async function init() { icons(); kbBind(); kbUpdate(); + restoreLive(); + localforage.dropInstance({ name: "threads_cache" }).catch(() => {}); } $(window).on("resize", () => { hideThreadPopover(); @@ -2785,127 +2830,6 @@ el.importAccountSettingsInput.onchange = async (e) => { } }; var getBubbleById = (id) => el.messages.querySelector(`.msg-bubble[data-mid="${CSS.escape(id)}"]`); -async function syncActiveThread() { - const id = THREAD.getLastAssistantMessageId(); - if (!id) return false; - if (await cacheStore.getItem(id) === "done") { - if (state.busy) { - setBtnSend(); - state.busy = false; - state.controller = null; - } - return false; - } - if (!state.busy) { - state.busy = true; - state.controller = { abort: () => { - const ws = new WebSocket(HTTP_BASE.replace("https", "wss")); - ws.onopen = function() { - this.send(JSON.stringify({ - type: "stop", - rid: id - })); - this.close(); - }; - } }; - setBtnStop(); - } - const bubble = getBubbleById(id); - if (!bubble) { - if (state.busy) { - setBtnSend(); - state.busy = false; - state.controller = null; - } - return false; - } - const msgIdx = state.messages.findIndex((x) => x.id === id); - const localText = msgIdx >= 0 ? partsToText(state.messages[msgIdx]) : bubble.textContent || ""; - const j = await fetch(HTTP_BASE + "?uid=" + encodeURIComponent(id)).then((r) => r.ok ? r.json() : null).catch(() => null); - const finalise = (t, c, imgs) => { - renderMarkdown(bubble, partsToText({ - content: c, - images: imgs - }), { enhance: false }); - enhanceCodeBlocks(bubble, true); - if (msgIdx >= 0) { - state.messages[msgIdx].content = c; - state.messages[msgIdx].images = imgs; - } else state.messages.push({ - id, - role: "assistant", - content: c, - images: imgs, - ...activeMeta() - }); - THREAD.persist(); - setBtnSend(); - state.busy = false; - cacheStore.setItem(id, "done"); - state.controller = null; - el.composer.dispatchEvent(new CustomEvent("sune:newSuneResponse", { detail: { message: state.messages.find((m) => m.id === id) } })); - }; - if (!j || j.rid !== id) { - if (j && j.error) { - const t = localText + "\n\n" + j.error; - finalise(t, [{ - type: "text", - text: t - }]); - } else { - await cacheStore.setItem(id, "done"); - if (state.busy) { - setBtnSend(); - state.busy = false; - state.controller = null; - } - } - return false; - } - const serverText = j.text || "", isDone = j.error || j.done || j.phase === "done"; - const finalText = serverText.length >= localText.length || isDone ? serverText : localText; - const display = partsToText({ - content: [{ - type: "text", - text: finalText - }], - images: j.images - }); - if (display) renderMarkdown(bubble, display, { enhance: false }); - if (isDone) { - if (finalText !== localText) finalise(finalText, [{ - type: "text", - text: finalText - }], j.images); - else { - await cacheStore.setItem(id, "done"); - if (state.busy) { - setBtnSend(); - state.busy = false; - state.controller = null; - } - } - return false; - } - await cacheStore.setItem(id, "busy"); - return true; -} -var syncLoopRunning = false; -async function syncWhileBusy() { - if (syncLoopRunning || document.visibilityState === "hidden") return; - syncLoopRunning = true; - try { - while (await syncActiveThread()) await new Promise((r) => setTimeout(r, 1500)); - } finally { - syncLoopRunning = false; - } -} -var onForeground = () => { - if (document.visibilityState !== "visible") return; - state.controller?.disconnect?.(); - if (state.busy) syncWhileBusy(); -}; -$(document).on("visibilitychange", onForeground); $(el.copySystemPrompt).on("click", async () => await copyToClipboard(el.set_system_prompt.value || "")); $(el.pasteSystemPrompt).on("click", async () => { try { @@ -2993,12 +2917,8 @@ Object.assign(window, { openAccountSettings, closeAccountSettings, getBubbleById, - syncActiveThread, - syncWhileBusy, - onForeground, getActiveJar, imgToWebp, - cacheStore, ghApi, parseGhUrl, ghGetFileContent, diff --git a/dist/assets/mediabunny-aac-encoder-kTu7ubye.js b/dist/assets/mediabunny-aac-encoder-vZgc586K.js similarity index 99% rename from dist/assets/mediabunny-aac-encoder-kTu7ubye.js rename to dist/assets/mediabunny-aac-encoder-vZgc586K.js index 5679109..f71a221 100644 --- a/dist/assets/mediabunny-aac-encoder-kTu7ubye.js +++ b/dist/assets/mediabunny-aac-encoder-vZgc586K.js @@ -1,6 +1,6 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/__vite-browser-external-BTR76MVM.js","assets/src-CvA4YsQY.js"])))=>i.map(i=>d[i]); -import { t as __vitePreload } from "./index-Ceaeycxj.js"; -import { CustomAudioEncoder, EncodedPacket, Logging, n as __require$1, r as __toESM, registerEncoder } from "./src-CvA4YsQY.js"; +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/__vite-browser-external-ey8rhZ1c.js","assets/src-DlKePj_l.js"])))=>i.map(i=>d[i]); +import { t as __vitePreload } from "./index-DRwc6SUn.js"; +import { CustomAudioEncoder, EncodedPacket, Logging, n as __require$1, r as __toESM, registerEncoder } from "./src-DlKePj_l.js"; //#region node_modules/@mediabunny/aac-encoder/dist/bundles/mediabunny-aac-encoder.mjs /*! * Copyright (c) 2026-present, Vanilagy and contributors @@ -192,7 +192,7 @@ async function inlineWorker(scriptText) { let Worker3; try { Worker3 = (await __vitePreload(async () => { - const { Worker } = await import("./__vite-browser-external-BTR76MVM.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1)); + const { Worker } = await import("./__vite-browser-external-ey8rhZ1c.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1)); return { Worker }; }, __vite__mapDeps([0,1]))).Worker; } catch { diff --git a/dist/assets/src-CvA4YsQY.js b/dist/assets/src-DlKePj_l.js similarity index 99% rename from dist/assets/src-CvA4YsQY.js rename to dist/assets/src-DlKePj_l.js index 06d35e5..cbdf617 100644 --- a/dist/assets/src-CvA4YsQY.js +++ b/dist/assets/src-DlKePj_l.js @@ -671,6 +671,14 @@ var isChromium = () => { if (isChromiumCache !== null) return isChromiumCache; return isChromiumCache = !!(typeof navigator !== "undefined" && (navigator.vendor?.includes("Google Inc") || /Chrome/.test(navigator.userAgent))); }; +var chromiumVersionCache = null; +var getChromiumVersion = () => { + if (chromiumVersionCache !== null) return chromiumVersionCache; + if (typeof navigator === "undefined") return null; + const match = /\bChrome\/(\d+)/.exec(navigator.userAgent); + if (!match) return null; + return chromiumVersionCache = Number(match[1]); +}; var missingWebCodecsClassMessage = (className) => { if (typeof globalThis.isSecureContext !== "undefined" && !globalThis.isSecureContext) return `${className} is not available in this environment; this may be because this page is running in an insecure context. Try serving your page over HTTPS or use localhost.`; return `${className} is not available in this environment.`; @@ -1466,7 +1474,7 @@ var EAC3_REDUCED_SAMPLE_RATES = [ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -var VERSIONED_NAME = `Mediabunny v1.61.4`; +var VERSIONED_NAME = `Mediabunny v1.61.6`; //#endregion //#region node_modules/mediabunny/dist/modules/src/codec-data.js /*! @@ -5571,6 +5579,35 @@ var readIsomVariableInteger = (slice) => { } return result; }; +var readEsDescriptor = (slice) => { + if (readU8(slice) !== 3) throw new Error("Invalid esds: expected ES_Descriptor."); + readIsomVariableInteger(slice); + slice.skip(2); + const mixed = readU8(slice); + const streamDependenceFlag = (mixed & 128) !== 0; + const urlFlag = (mixed & 64) !== 0; + const ocrStreamFlag = (mixed & 32) !== 0; + if (streamDependenceFlag) slice.skip(2); + if (urlFlag) { + const urlLength = readU8(slice); + slice.skip(urlLength); + } + if (ocrStreamFlag) slice.skip(2); + if (readU8(slice) !== 4) throw new Error("Invalid esds: expected DecoderConfigDescriptor."); + const decoderConfigDescriptorLength = readIsomVariableInteger(slice); + const payloadStart = slice.filePos; + const objectTypeIndication = readU8(slice); + slice.skip(12); + let decoderSpecificInfo = null; + if (decoderConfigDescriptorLength > slice.filePos - payloadStart) { + if (readU8(slice) !== 5) throw new Error("Invalid esds: expected DecoderSpecificInfo."); + decoderSpecificInfo = readBytes(slice, readIsomVariableInteger(slice)); + } + return { + objectTypeIndication, + decoderSpecificInfo + }; +}; var readMetadataStringShort = (slice) => { let stringLength = readU16Be(slice); slice.skip(2); @@ -6603,23 +6640,7 @@ var IsobmffDemuxer = class IsobmffDemuxer extends Demuxer { const track = this.currentTrack; if (!track || track.info?.type !== "audio") break; slice.skip(4); - assert(readU8(slice) === 3); - readIsomVariableInteger(slice); - slice.skip(2); - const mixed = readU8(slice); - const streamDependenceFlag = (mixed & 128) !== 0; - const urlFlag = (mixed & 64) !== 0; - const ocrStreamFlag = (mixed & 32) !== 0; - if (streamDependenceFlag) slice.skip(2); - if (urlFlag) { - const urlLength = readU8(slice); - slice.skip(urlLength); - } - if (ocrStreamFlag) slice.skip(2); - assert(readU8(slice) === 4); - const decoderConfigDescriptorLength = readIsomVariableInteger(slice); - const payloadStart = slice.filePos; - const objectTypeIndication = readU8(slice); + const { objectTypeIndication, decoderSpecificInfo } = readEsDescriptor(slice); if (objectTypeIndication === 64 || objectTypeIndication === 103) { track.info.codec = "aac"; track.info.aacCodecInfo = { @@ -6630,11 +6651,8 @@ var IsobmffDemuxer = class IsobmffDemuxer extends Demuxer { else if (objectTypeIndication === 221) track.info.codec = "vorbis"; else if (objectTypeIndication === 169) track.info.codec = "dts"; else Logging._warn(`Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`); - slice.skip(12); - if (decoderConfigDescriptorLength > slice.filePos - payloadStart) { - assert(readU8(slice) === 5); - const decoderSpecificInfoLength = readIsomVariableInteger(slice); - track.info.codecDescription = readBytes(slice, decoderSpecificInfoLength); + if (decoderSpecificInfo) { + track.info.codecDescription = decoderSpecificInfo; if (track.info.codec === "aac") { const audioSpecificConfig = parseAacAudioSpecificConfig(track.info.codecDescription); if (audioSpecificConfig.outputNumberOfChannels !== null) track.info.numberOfChannels = audioSpecificConfig.outputNumberOfChannels; @@ -14192,7 +14210,10 @@ var SegmentedInputInputTrackBacking = class { continue; } const firstPacket = await nextTrack._backing.getFirstPacket(options); - if (!firstPacket) return null; + if (!firstPacket) { + currentSegment = nextSegment; + continue; + } return this.createAdjustedPacket(firstPacket, nextSegment, nextTrack); } } @@ -14521,7 +14542,7 @@ var CustomPathedSource = class extends PathedSource { /** @internal */ _dispose() { if (this._root) this._root.free(); - else if (this._rootRequest) this._rootRequest.then((ref) => ref.free()); + else if (this._rootRequest) this._rootRequest.then((ref) => ref.free()).catch(() => {}); } }; /** @@ -16258,7 +16279,7 @@ var HlsSegmentedInput = class extends SegmentedInput { return input; } async getLiveRefreshInterval() { - if (this.getRemainingWaitTimeMs() === 0) await this.runUpdateSegments(); + if (!this.streamHasEnded && this.getRemainingWaitTimeMs() === 0) await this.runUpdateSegments(); return this.streamHasEnded ? null : this.refreshInterval; } }; @@ -17751,7 +17772,13 @@ var VideoSample = class VideoSample { this.squarePixelHeight = data.displayHeight; this.timestamp = init?.timestamp ?? data.timestamp / 1e6; this.duration = init?.duration ?? (data.duration ?? 0) / 1e6; - this.colorSpace = new VideoSampleColorSpace(data.colorSpace); + const { primaries, transfer, matrix, fullRange } = data.colorSpace; + this.colorSpace = new VideoSampleColorSpace({ + primaries: primaries !== null && primaries in COLOR_PRIMARIES_MAP ? primaries : null, + transfer: transfer !== null && transfer in TRANSFER_CHARACTERISTICS_MAP ? transfer : null, + matrix: matrix !== null && matrix in MATRIX_COEFFICIENTS_MAP ? matrix : null, + fullRange + }); } else if (typeof HTMLImageElement !== "undefined" && data instanceof HTMLImageElement || typeof SVGImageElement !== "undefined" && data instanceof SVGImageElement || typeof ImageBitmap !== "undefined" && data instanceof ImageBitmap || typeof HTMLVideoElement !== "undefined" && data instanceof HTMLVideoElement || typeof HTMLCanvasElement !== "undefined" && data instanceof HTMLCanvasElement || typeof OffscreenCanvas !== "undefined" && data instanceof OffscreenCanvas) { if (!init || typeof init !== "object") throw new TypeError("init must be an object."); if (init.rotation !== void 0 && ![ @@ -19621,11 +19648,16 @@ var VIDEO_CODEC_QUANTIZER_SUPPORT = { worst: 41, best: 16 }, - vp9: { + vp9: (getChromiumVersion() ?? Infinity) < 145 ? { min: 0, max: 63, worst: 52, best: 20 + } : { + min: 0, + max: 255, + worst: 208, + best: 80 }, av1: { min: 0, @@ -20297,10 +20329,12 @@ var EncodedPacketSink = class { }; } }; +var MAX_QUEUE_SIZE = 4; var DecoderWrapper = class { constructor(onSample, onError) { this.onSample = onSample; this.onError = onError; + this.onDequeue = null; } }; /** @@ -20357,14 +20391,14 @@ var BaseMediaSampleSink = class { onQueueNotEmpty(); } }); + decoder.onDequeue = () => onQueueDequeue(); const packetSink = this._createPacketSink(); const keyPacket = await packetSink.getKeyPacket(startTimestamp, packetRetrievalOptions) ?? await packetSink.getFirstKeyPacket(packetRetrievalOptions); let currentPacket = keyPacket; const packets = packetSink.packets(keyPacket ?? void 0, void 0, packetRetrievalOptions); await packets.next(); while (currentPacket && !ended && !this._track.input._disposed) { - const maxQueueSize = computeMaxQueueSize(sampleQueue.length); - if (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize) { + if (sampleQueue.length + decoder.getDecodeQueueSize() >= MAX_QUEUE_SIZE) { ({promise: queueDequeue, resolve: onQueueDequeue} = promiseWithResolvers()); await queueDequeue; continue; @@ -20376,7 +20410,10 @@ var BaseMediaSampleSink = class { } await packets.return(); if (!terminated && !this._track.input._disposed) await decoder.flush(); - if (!firstSampleQueued && lastSample) sampleQueue.push(lastSample); + if (!firstSampleQueued && lastSample) { + sampleQueue.push(lastSample); + lastSample = null; + } decoderIsFlushed = true; onQueueNotEmpty(); })().catch((error) => { @@ -20485,6 +20522,7 @@ var BaseMediaSampleSink = class { onQueueNotEmpty(); } }); + decoder.onDequeue = () => onQueueDequeue(); const packetSink = this._createPacketSink(); let lastPacket = null; let lastKeyPacket = null; @@ -20495,8 +20533,7 @@ var BaseMediaSampleSink = class { let currentPacket = lastKeyPacket; decoder.decode(currentPacket); while (currentPacket.sequenceNumber < maxSequenceNumber) { - const maxQueueSize = computeMaxQueueSize(sampleQueue.length); - while (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize && !terminated) { + while (sampleQueue.length + decoder.getDecodeQueueSize() >= MAX_QUEUE_SIZE && !terminated) { ({promise: queueDequeue, resolve: onQueueDequeue} = promiseWithResolvers()); await queueDequeue; } @@ -20603,9 +20640,6 @@ var BaseMediaSampleSink = class { }; } }; -var computeMaxQueueSize = (decodedSampleQueueSize) => { - return decodedSampleQueueSize === 0 ? 40 : 8; -}; var VideoDecoderWrapper = class extends DecoderWrapper { constructor(onSample, onError, codec, decoderConfig, rotation, flip, timeResolution) { super(onSample, onError); @@ -20700,6 +20734,9 @@ var VideoDecoderWrapper = class extends DecoderWrapper { this.onError(error); } }); + this.decoder.addEventListener("dequeue", () => { + this.onDequeue?.(); + }); this.decoder.configure(this.decoderConfig); } } @@ -20717,7 +20754,10 @@ var VideoDecoderWrapper = class extends DecoderWrapper { } if (this.customDecoder) { this.customDecoderQueueSize++; - this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).catch((error) => this.onError(error)).finally(() => this.customDecoderQueueSize--); + this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).catch((error) => this.onError(error)).finally(() => { + this.customDecoderQueueSize--; + this.onDequeue?.(); + }); } else { assert(this.decoder); if (!isWebKit()) insertSorted(this.inputTimestamps, packet.timestamp, (x) => x); @@ -20788,6 +20828,9 @@ var VideoDecoderWrapper = class extends DecoderWrapper { this.onError(error); } }); + this.alphaDecoder.addEventListener("dequeue", () => { + this.onDequeue?.(); + }); this.alphaDecoder.configure({ ...this.decoderConfig, colorSpace: { @@ -21416,6 +21459,9 @@ var AudioDecoderWrapper = class extends DecoderWrapper { this.onError(error); } }); + this.decoder.addEventListener("dequeue", () => { + this.onDequeue?.(); + }); this.decoder.configure(decoderConfig); } } @@ -21429,7 +21475,10 @@ var AudioDecoderWrapper = class extends DecoderWrapper { decode(packet) { if (this.customDecoder) { this.customDecoderQueueSize++; - this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).catch((error) => this.onError(error)).finally(() => this.customDecoderQueueSize--); + this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).catch((error) => this.onError(error)).finally(() => { + this.customDecoderQueueSize--; + this.onDequeue?.(); + }); } else { assert(this.decoder); this.expectedFirstTimestamp ??= packet.timestamp; @@ -22736,6 +22785,10 @@ var Input = class Input extends EventEmitter { if (cachedPromiseEntry) return cachedPromiseEntry.promise.then((x) => x.sourceRef.source.ref()); const promise = (async () => { const sourceRef = await this._getSourceUncached(request); + if (this._disposed) { + sourceRef.free(); + throw new InputDisposedError(); + } if (arrayCount(this._sourceCache, (x) => x.cacheGroup === cacheGroup && x.sourceRef.source._refCount === 1) >= 4) { const minAgeIndex = arrayArgmin(this._sourceCache, (x) => x.cacheGroup === cacheGroup && x.sourceRef.source._refCount === 1 ? x.age : Infinity); assert(minAgeIndex !== -1); @@ -30023,6 +30076,7 @@ var VideoEncoderWrapper = class { this.muxer = null; this.lastMultipleOfKeyFrameInterval = -1; this.emittedEncoderPackets = 0; + this.chunkTimestampsAreShiftedByDuration = false; this.codedWidth = null; this.codedHeight = null; this.outputWidth = null; @@ -30316,6 +30370,7 @@ var VideoEncoderWrapper = class { await this.customEncoder.init(); } else { this.encoderConfig = encoderConfig; + if (this.encodingConfig.codec === "avc" && isFirefox()) this.chunkTimestampsAreShiftedByDuration = await detectAvcChunkTimestampShift(); this.createWebCodecsEncoders(); } assert(this.source._connectedTrack); @@ -30403,24 +30458,47 @@ var VideoEncoderWrapper = class { let packet = EncodedPacket.fromEncodedChunk(colorChunk, sideData); const encoderConfig = this.encoderConfig; assert(encoderConfig); - if (meta?.decoderConfig) meta.decoderConfig.codec = extractVideoCodecString({ - width: encoderConfig.width, - height: encoderConfig.height, - codec: this.encodingConfig.codec, - codecDescription: meta.decoderConfig.description ? toUint8Array(meta.decoderConfig.description) : null, - colorSpace: meta.decoderConfig.colorSpace ?? null, - avcType: encoderConfig.codec.includes("avc1.") ? 1 : encoderConfig.codec.includes("avc3.") ? 3 : null, - avcCodecInfo: this.encodingConfig.codec === "avc" && !meta.decoderConfig.description ? extractAvcDecoderConfigurationRecord(packet.data) : null, - hevcCodecInfo: this.encodingConfig.codec === "hevc" && !meta.decoderConfig.description ? extractHevcDecoderConfigurationRecord(packet.data) : null, - vp9CodecInfo: this.encodingConfig.codec === "vp9" ? extractVp9CodecInfoFromPacket(packet.data) : null, - av1CodecInfo: this.encodingConfig.codec === "av1" ? extractAv1CodecInfoFromPacket(packet.data) : null, - proresFormat: null - }); - const preciseTimingIndex = binarySearchLessOrEqual(this.preciseTimings, colorChunk.timestamp, (x) => x.microsecondTimestamp); + if (meta?.decoderConfig) { + meta.decoderConfig.codec = extractVideoCodecString({ + width: encoderConfig.width, + height: encoderConfig.height, + codec: this.encodingConfig.codec, + codecDescription: meta.decoderConfig.description ? toUint8Array(meta.decoderConfig.description) : null, + colorSpace: meta.decoderConfig.colorSpace ?? null, + avcType: encoderConfig.codec.includes("avc1.") ? 1 : encoderConfig.codec.includes("avc3.") ? 3 : null, + avcCodecInfo: this.encodingConfig.codec === "avc" && !meta.decoderConfig.description ? extractAvcDecoderConfigurationRecord(packet.data) : null, + hevcCodecInfo: this.encodingConfig.codec === "hevc" && !meta.decoderConfig.description ? extractHevcDecoderConfigurationRecord(packet.data) : null, + vp9CodecInfo: this.encodingConfig.codec === "vp9" ? extractVp9CodecInfoFromPacket(packet.data) : null, + av1CodecInfo: this.encodingConfig.codec === "av1" ? extractAv1CodecInfoFromPacket(packet.data) : null, + proresFormat: null + }); + if (this.encodingConfig.codec === "avc" && meta.decoderConfig.description && isFirefox()) { + const parsed = deserializeAvcDecoderConfigurationRecord(toUint8Array(meta.decoderConfig.description)); + let sps = parsed?.sequenceParameterSets[0]; + if (sps && sps[0] === sps[1]) { + assert(parsed); + sps = sps.subarray(1); + let pps = parsed.pictureParameterSets[0]; + if (pps) pps = pps.subarray(1); + const record = serializeAvcDecoderConfigurationRecord({ + ...parsed, + sequenceParameterSets: [sps], + pictureParameterSets: pps ? [pps] : [] + }); + meta.decoderConfig.description = record; + } + } + } + let microsecondTimestamp = colorChunk.timestamp; + if (this.chunkTimestampsAreShiftedByDuration) { + microsecondTimestamp -= colorChunk.duration ?? 0; + packet = packet.clone({ timestamp: microsecondTimestamp / 1e6 }); + } + const preciseTimingIndex = binarySearchLessOrEqual(this.preciseTimings, microsecondTimestamp, (x) => x.microsecondTimestamp); const entry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null; let actualType = null; if (this.emittedEncoderPackets === 0 && packet.type === "delta" && meta?.decoderConfig) actualType = determineVideoPacketType(this.encodingConfig.codec, meta.decoderConfig, packet.data); - if (entry && entry.microsecondTimestamp === colorChunk.timestamp || actualType !== null) packet = packet.clone({ + if (entry && entry.microsecondTimestamp === microsecondTimestamp || actualType !== null) packet = packet.clone({ timestamp: entry?.timestampIsValid ? entry.timestamp : void 0, duration: entry?.durationIsValid ? entry.duration : void 0, type: actualType ?? void 0 @@ -30484,6 +30562,40 @@ var VideoEncoderWrapper = class { if (this.errorSet) throw this.error; } }; +var avcChunkTimestampShiftPromise = null; +var detectAvcChunkTimestampShift = () => { + return avcChunkTimestampShiftPromise ??= (async () => { + let chunkTimestamp = null; + const encoder = new VideoEncoder({ + output: (chunk) => { + chunkTimestamp ??= chunk.timestamp; + }, + error: () => {} + }); + try { + encoder.configure({ + codec: "avc1.64001f", + width: 1280, + height: 720 + }); + const frame = new VideoFrame(new Uint8Array(1280 * 720 * 1.5), { + format: "I420", + codedWidth: 1280, + codedHeight: 720, + timestamp: 0, + duration: 500 + }); + encoder.encode(frame, { keyFrame: true }); + frame.close(); + await encoder.flush(); + } catch { + return false; + } finally { + if (encoder.state !== "closed") encoder.close(); + } + return chunkTimestamp === 500; + })(); +}; var splitterWorkerUrl = null; /** Utility class for splitting a composite frame into separate color and alpha parts on the CPU in a worker. */ var ColorAlphaSplitter = class { @@ -31249,6 +31361,10 @@ var AudioEncoderWrapper = class { this.encoder = new AudioEncoder({ output: (chunk, meta) => { if (this.encodingConfig.codec === "aac" && meta?.decoderConfig) { + if (meta.decoderConfig.description && meta.decoderConfig.description.byteLength > 0 && toUint8Array(meta.decoderConfig.description)[0] === 3) { + const { decoderSpecificInfo } = readEsDescriptor(FileSlice.tempFromBytes(toUint8Array(meta.decoderConfig.description))); + meta.decoderConfig.description = decoderSpecificInfo ?? void 0; + } let needsDescriptionOverwrite = false; if (!meta.decoderConfig.description || meta.decoderConfig.description.byteLength < 2) needsDescriptionOverwrite = true; else needsDescriptionOverwrite = parseAacAudioSpecificConfig(toUint8Array(meta.decoderConfig.description)).objectType === 0; @@ -34151,6 +34267,8 @@ var Conversion = class Conversion { this._computeProgress = false; /** @internal */ this._lastProgress = 0; + /** @internal */ + this._lastProcessedTime = 0; /** * Whether this conversion, as it has been configured, is valid and can be executed. If this field is `false`, check * the `discardedTracks` field for reasons. Composable conversions are always valid, even if they utilize @@ -34409,10 +34527,7 @@ Tracks were discarded because your environment is not able to encode any of the this._state = isDone ? "done" : "idle"; if (isDone) { if (!this._composable) await this.output.finalize(); - if (this._computeProgress) { - const minTimestamp = Math.min(...this._maxTimestamps.values()); - this.onProgress?.(1, minTimestamp); - } + if (this._computeProgress) this.onProgress?.(1, this._lastProcessedTime); } } /** @@ -34545,13 +34660,13 @@ Tracks were discarded because your environment is not able to encode any of the sideData: alpha === "discard" ? {} : packet.sideData, type: packetType }); - this._reportProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration); + this._updateProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration); await source.add(modifiedPacket, meta); if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) await this._synchronizer.wait(modifiedPacket.timestamp); await this._checkpoint(pump, modifiedPacket.timestamp); } source.close(); - this._synchronizer.closeTrack(outputTrackId); + this._closeTrack(outputTrackId); }); } else { if (!await track.canDecode()) { @@ -34657,7 +34772,7 @@ Tracks were discarded because your environment is not able to encode any of the if (clampedStartTimestamp >= clampedEndTimestamp) continue; sample.setTimestamp(clampedStartTimestamp + this._timestampOffset); sample.setDuration(clampedEndTimestamp - clampedStartTimestamp); - this._reportProgress(outputTrackId, sample.timestamp + sample.duration); + this._updateProgress(outputTrackId, sample.timestamp + sample.duration); await source.add(sample); sample.close(); if (lastSampleTimestamp !== null) { @@ -34672,7 +34787,7 @@ Tracks were discarded because your environment is not able to encode any of the } } source.close(); - this._synchronizer.closeTrack(outputTrackId); + this._closeTrack(outputTrackId); }); } let ownGroup = null; @@ -34773,13 +34888,13 @@ Tracks were discarded because your environment is not able to encode any of the timestamp: packetTimestamp, duration: packet.duration }); - this._reportProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration); + this._updateProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration); await source.add(modifiedPacket, meta); if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) await this._synchronizer.wait(modifiedPacket.timestamp); await this._checkpoint(pump, modifiedPacket.timestamp); } source.close(); - this._synchronizer.closeTrack(outputTrackId); + this._closeTrack(outputTrackId); }); } else { if (!await track.canDecode()) { @@ -34905,7 +35020,7 @@ Tracks were discarded because your environment is not able to encode any of the } } source.close(); - this._synchronizer.closeTrack(outputTrackId); + this._closeTrack(outputTrackId); }); } let ownGroup = null; @@ -34929,7 +35044,7 @@ Tracks were discarded because your environment is not able to encode any of the } /** @internal */ async _registerAudioSample(pump, sample, source, outputTrackId, getLastSampleTimestamp) { - this._reportProgress(outputTrackId, sample.timestamp + sample.duration); + this._updateProgress(outputTrackId, sample.timestamp + sample.duration); await source.add(sample); sample.close(); const lastSampleTimestamp = getLastSampleTimestamp(); @@ -34965,12 +35080,24 @@ Tracks were discarded because your environment is not able to encode any of the } } /** @internal */ - _reportProgress(trackId, endTimestamp) { + _closeTrack(id) { + this._synchronizer.closeTrack(id); + this._maxTimestamps.delete(id); + this._reportProgress(); + } + /** @internal */ + _updateProgress(trackId, endTimestamp) { if (!this._computeProgress) return; - assert(this._totalDuration !== null); this._maxTimestamps.set(trackId, Math.max(endTimestamp, this._maxTimestamps.get(trackId))); + this._reportProgress(); + } + /** @internal */ + _reportProgress() { + if (!this._computeProgress || this._maxTimestamps.size === 0) return; + assert(this._totalDuration !== null); const minTimestamp = Math.min(...this._maxTimestamps.values()); const newProgress = clamp(minTimestamp / this._totalDuration, 0, 1); + this._lastProcessedTime = minTimestamp; if (newProgress !== this._lastProgress) { this._lastProgress = newProgress; this.onProgress?.(newProgress, minTimestamp); diff --git a/dist/index.html b/dist/index.html index 067893b..ad48f1a 100644 --- a/dist/index.html +++ b/dist/index.html @@ -13,7 +13,7 @@ - + diff --git a/dist/sw.js b/dist/sw.js index f71b3c3..3419bf9 100644 --- a/dist/sw.js +++ b/dist/sw.js @@ -1 +1 @@ -if(!self.define){let e,s={};const n=(n,r)=>(n=new URL(n+".js",r).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(r,i)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>n(e,t),d={module:{uri:t},exports:l,require:o};s[t]=Promise.all(r.map(e=>d[e]||o(e))).then(e=>(i(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"index.html",revision:"7559eb516d4db754365cae1e30b03638"},{url:"assets/src-CvA4YsQY.js",revision:null},{url:"assets/mediabunny-aac-encoder-kTu7ubye.js",revision:null},{url:"assets/index-DDqI2rlK.css",revision:null},{url:"assets/index-Ceaeycxj.js",revision:null},{url:"assets/__vite-browser-external-BTR76MVM.js",revision:null},{url:"appstore_content/✺.png",revision:"06dd8ca13016278fc47e9a226acdb7f9"},{url:"manifest.webmanifest",revision:"70d853d7dcb45ddd8488db46950b4b33"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))}); +if(!self.define){let e,s={};const n=(n,r)=>(n=new URL(n+".js",r).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(r,i)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>n(e,t),c={module:{uri:t},exports:l,require:o};s[t]=Promise.all(r.map(e=>c[e]||o(e))).then(e=>(i(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"index.html",revision:"cfcc261797373f650a24c74c571b434b"},{url:"assets/src-DlKePj_l.js",revision:null},{url:"assets/mediabunny-aac-encoder-vZgc586K.js",revision:null},{url:"assets/index-DRwc6SUn.js",revision:null},{url:"assets/index-DDqI2rlK.css",revision:null},{url:"assets/__vite-browser-external-ey8rhZ1c.js",revision:null},{url:"appstore_content/✺.png",revision:"06dd8ca13016278fc47e9a226acdb7f9"},{url:"manifest.webmanifest",revision:"70d853d7dcb45ddd8488db46950b4b33"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});