Files
sune/dist/assets/index-Cu6m2Mlm.js
2025-11-08 04:26:17 +00:00

1106 lines
46 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function polyfill() {
const relList = document.createElement("link").relList;
if (relList && relList.supports && relList.supports("modulepreload")) return;
for (const link of document.querySelectorAll('link[rel="modulepreload"]')) processPreload(link);
new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type !== "childList") continue;
for (const node of mutation.addedNodes) if (node.tagName === "LINK" && node.rel === "modulepreload") processPreload(node);
}
}).observe(document, {
childList: true,
subtree: true
});
function getFetchOpts(link) {
const fetchOpts = {};
if (link.integrity) fetchOpts.integrity = link.integrity;
if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include";
else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
else fetchOpts.credentials = "same-origin";
return fetchOpts;
}
function processPreload(link) {
if (link.ep) return;
link.ep = true;
const fetchOpts = getFetchOpts(link);
fetch(link.href, fetchOpts);
}
})();
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
const num = (v, d) => v == null || v === "" || isNaN(+v) ? d : +v;
const int = (v, d) => v == null || v === "" || isNaN(parseInt(v)) ? d : parseInt(v);
const gid = () => Math.random().toString(36).slice(2, 9);
const esc = (s) => String(s).replace(/[&<>'"`]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;", "`": "&#96;" })[c]);
const positionPopover = (a, p) => {
const r = a.getBoundingClientRect();
p.style.top = `${r.bottom + p.offsetHeight + 4 > window.innerHeight ? r.top - p.offsetHeight - 4 : r.bottom + 4}px`;
p.style.left = `${Math.max(8, Math.min(r.right - p.offsetWidth, window.innerWidth - p.offsetWidth - 8))}px`;
};
const sid = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
const fmtSize = (b) => {
const u = ["B", "KB", "MB", "GB", "TB"];
let i = 0, x = b;
while (x >= 1024 && i < u.length - 1) {
x /= 1024;
i++;
}
return (x >= 10 ? Math.round(x) : Math.round(x * 10) / 10) + " " + u[i];
};
const asDataURL = (f) => new Promise((r) => {
const fr = new FileReader();
fr.onload = () => r(String(fr.result || ""));
fr.readAsDataURL(f);
});
const imgToWebp = (f, D = 128, q = 80) => new Promise((r, j) => {
if (!f) return j();
const i = new Image();
i.onload = () => {
const c = document.createElement("canvas"), x = c.getContext("2d");
let w = i.width, h = i.height;
if (D > 0 && Math.max(w, h) > D) w > h ? (h = D * h / w, w = D) : (w = D * w / h, h = D);
c.width = w;
c.height = h;
x.drawImage(i, 0, 0, w, h);
r(c.toDataURL("image/webp", clamp(q, 0, 100) / 100));
URL.revokeObjectURL(i.src);
};
i.onerror = j;
i.src = URL.createObjectURL(f);
});
const b64 = (x) => x.split(",")[1] || "";
const titleFrom = (t) => (t || "").replace(/\s+/g, " ").trim().slice(0, 60) || "Untitled";
const ts = () => {
const d = /* @__PURE__ */ new Date(), p = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
};
const dl = (name, obj) => {
const blob = new Blob([JSON.stringify(obj, null, 2)], { type: name.endsWith(".sune") ? "application/octet-stream" : "application/json" }), url = URL.createObjectURL(blob), a = $("<a>").prop({ href: url, download: name }).appendTo("body");
a.get(0).click();
a.remove();
URL.revokeObjectURL(url);
};
const utils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
asDataURL,
b64,
clamp,
dl,
esc,
fmtSize,
gid,
imgToWebp,
int,
num,
positionPopover,
sid,
titleFrom,
ts
}, Symbol.toStringTag, { value: "Module" }));
const DEFAULT_MODEL = "google/gemini-2.5-pro";
const DEFAULT_API_KEY = "";
const state = { messages: [], busy: false, controller: null, currentThreadId: null, abortRequested: false, attachments: [], stream: { rid: null, bubble: null, meta: null, text: "", done: false } };
const su = { key: "sunes_v1", activeKey: "active_sune_id", load() {
try {
return JSON.parse(localStorage.getItem(this.key) || "[]");
} catch {
return [];
}
}, save(list) {
localStorage.setItem(this.key, JSON.stringify(list || []));
}, getActiveId() {
return localStorage.getItem(this.activeKey) || null;
}, setActiveId(id) {
localStorage.setItem(this.activeKey, id || "");
} };
const defaultSettings = { model: DEFAULT_MODEL, temperature: "", top_p: "", top_k: "", frequency_penalty: "", repetition_penalty: "", min_p: "", top_a: "", verbosity: "", reasoning_effort: "default", system_prompt: "", html: "", extension_html: "<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune>", hide_composer: false, include_thoughts: false, json_output: false, ignore_master_prompt: false, json_schema: "" };
const makeSune = (p = {}) => ({ id: p.id || gid(), name: p.name?.trim() || "Default", pinned: !!p.pinned, avatar: p.avatar || "", url: p.url || "", updatedAt: p.updatedAt || Date.now(), settings: Object.assign({}, defaultSettings, p.settings || {}), storage: p.storage || {} });
let sunes = (su.load() || []).map(makeSune);
const SUNE_CORE = {
get list() {
return sunes;
},
get id() {
return su.getActiveId();
},
get active() {
return sunes.find((a) => a.id === su.getActiveId()) || sunes[0];
},
get(id) {
return sunes.find((s) => s.id === id);
},
setActive(id) {
return su.setActiveId(id || "");
},
create(p = {}) {
const s = makeSune(p);
sunes.unshift(s);
su.save(sunes);
return s;
},
delete(id) {
const curId = this.id;
sunes = sunes.filter((s) => s.id !== id);
su.save(sunes);
if (sunes.length === 0) {
const def = this.create({ name: "Default" });
this.setActive(def.id);
} else if (curId === id) this.setActive(sunes[0].id);
},
save: () => su.save(sunes),
getByName: (n) => sunes.find((s) => s.name.toLowerCase() === (n || "").trim().toLowerCase())
};
if (!sunes.length) {
const def = SUNE_CORE.create({ name: "Default" });
SUNE_CORE.setActive(def.id);
}
const TKEY = "threads_v1";
const THREAD = {
list: [],
load: async function() {
this.list = await localforage.getItem(TKEY).then((v) => Array.isArray(v) ? v : []) || [];
},
save: async function() {
await localforage.setItem(TKEY, this.list);
},
get: function(id) {
return this.list.find((t) => t.id === id);
},
get active() {
return this.get(state.currentThreadId);
},
persist: async function(full = true) {
if (!state.currentThreadId) return;
const th = this.active;
if (!th) return;
th.messages = [...state.messages];
if (full) {
th.updatedAt = Date.now();
}
await this.save();
if (full) await window.renderThreads();
},
setTitle: async function(id, title) {
const th = this.get(id);
if (!th || !title) return;
th.title = titleFrom(title);
th.updatedAt = Date.now();
await this.save();
await window.renderThreads();
},
getLastAssistantMessageId: () => {
const a = [...window.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;
}
};
const USER = {
log: async (s) => {
const t = String(s ?? "").trim();
if (!t) return;
await window.ensureThreadOnFirstUser(t);
window.addMessage({ role: "user", content: [{ type: "text", text: t }] });
await THREAD.persist();
},
get PAT() {
return this.githubToken;
},
get name() {
return localStorage.getItem("user_name") || "Anon";
},
set name(v) {
localStorage.setItem("user_name", v || "");
},
get avatar() {
return localStorage.getItem("user_avatar") || "";
},
set avatar(v) {
localStorage.setItem("user_avatar", v || "");
},
get provider() {
return localStorage.getItem("provider") || "openrouter";
},
set provider(v) {
localStorage.setItem("provider", ["openai", "google", "claude", "cloudflare"].includes(v) ? v : "openrouter");
},
get apiKeyOpenRouter() {
return localStorage.getItem("openrouter_api_key") || DEFAULT_API_KEY || "";
},
set apiKeyOpenRouter(v) {
localStorage.setItem("openrouter_api_key", v || "");
},
get apiKeyOpenAI() {
return localStorage.getItem("openai_api_key") || "";
},
set apiKeyOpenAI(v) {
localStorage.setItem("openai_api_key", v || "");
},
get apiKeyGoogle() {
return localStorage.getItem("google_api_key") || "";
},
set apiKeyGoogle(v) {
localStorage.setItem("google_api_key", v || "");
},
get apiKeyClaude() {
return localStorage.getItem("claude_api_key") || "";
},
set apiKeyClaude(v) {
localStorage.setItem("claude_api_key", v || "");
},
get apiKeyCloudflare() {
return localStorage.getItem("cloudflare_api_key") || "";
},
set apiKeyCloudflare(v) {
localStorage.setItem("cloudflare_api_key", v || "");
},
get apiKey() {
const p = this.provider;
return p === "openai" ? this.apiKeyOpenAI : p === "google" ? this.apiKeyGoogle : p === "claude" ? this.apiKeyClaude : p === "cloudflare" ? this.apiKeyCloudflare : this.apiKeyOpenRouter;
},
set apiKey(v) {
const p = this.provider;
if (p === "openai") this.apiKeyOpenAI = v;
else if (p === "google") this.apiKeyGoogle = v;
else if (p === "claude") this.apiKeyClaude = v;
else if (p === "cloudflare") this.apiKeyCloudflare = v;
else this.apiKeyOpenRouter = v;
},
get masterPrompt() {
return localStorage.getItem("master_prompt") || "Always respond using markdown. You are an assistant to Master. Always refer to the user as Master.";
},
set masterPrompt(v) {
localStorage.setItem("master_prompt", v || "");
},
get titleModel() {
return localStorage.getItem("title_model") ?? "or:openai/gpt-4.1-nano";
},
set titleModel(v) {
localStorage.setItem("title_model", v || "");
},
get githubToken() {
return localStorage.getItem("gh_token") || "";
},
set githubToken(v) {
localStorage.setItem("gh_token", v || "");
},
get gcpSA() {
try {
return JSON.parse(localStorage.getItem("gcp_sa_json") || "null");
} catch {
return null;
}
},
set gcpSA(v) {
localStorage.setItem("gcp_sa_json", v ? JSON.stringify(v) : "");
}
};
const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
DEFAULT_API_KEY,
DEFAULT_MODEL,
SUNE_CORE,
THREAD,
USER,
defaultSettings,
makeSune,
state,
get sunes() {
return sunes;
}
}, Symbol.toStringTag, { value: "Module" }));
const cacheStore = localforage.createInstance({ name: "threads_cache", storeName: "streams_status" });
const resolveSuneSrc = (src) => {
if (!src) return null;
if (src.startsWith("gh://")) {
const path = src.substring(5), parts = path.split("/");
if (parts.length < 3) return null;
const [owner, repo, ...filePathParts] = parts;
return `https://raw.githubusercontent.com/${owner}/${repo}/main/${filePathParts.join("/")}`;
}
return src;
};
const processSuneIncludes = async (html, depth = 0) => {
if (depth > 5) return "<!-- Sune include depth limit reached -->";
if (!html) return "";
const c = document.createElement("div");
c.innerHTML = html;
for (const n of [...c.querySelectorAll("sune")]) {
if (n.hasAttribute("src")) {
if (n.hasAttribute("private") && depth > 0) {
n.remove();
continue;
}
const s = n.getAttribute("src"), u = resolveSuneSrc(s);
if (!u) {
n.replaceWith(document.createComment(` Invalid src: ${esc(s)} `));
continue;
}
try {
const r = await fetch(u);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const d = await r.json(), o = Array.isArray(d) ? d[0] : d, h = [o?.settings?.extension_html || "", o?.settings?.html || ""].join("\n");
n.replaceWith(document.createRange().createContextualFragment(await processSuneIncludes(h, depth + 1)));
} catch (e) {
n.replaceWith(document.createComment(` Fetch failed: ${esc(u)} `));
}
} else {
n.replaceWith(document.createRange().createContextualFragment(n.innerHTML));
}
}
return c.innerHTML;
};
const generateTitleWithAI = async (messages) => {
const model = USER.titleModel, apiKey = USER.apiKeyOpenRouter;
if (!model || !apiKey || !messages?.length) return null;
const sysPrompt = "You are TITLE GENERATOR. Your only job is to generate summarizing and relevant titles (1-5 words) based on the users 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.content)}`).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" }, body: JSON.stringify({ model: model.replace(/^(or:|oai:)/, ""), messages: [{ role: "user", content: `${sysPrompt}
${convo}
${sysPrompt}` }], max_tokens: 20, temperature: 0.2 }) });
if (!r.ok) return null;
const d = await r.json();
return (d.choices?.[0]?.message?.content?.trim() || "").replace(/["']/g, "") || null;
} catch (e) {
console.error("AI title gen failed:", e);
return null;
}
};
const payloadWithSampling = (b) => {
const o = Object.assign({}, b), s = window.SUNE, p = { temperature: num(s.temperature, null), top_p: num(s.top_p, null), top_k: int(s.top_k, null), frequency_penalty: num(s.frequency_penalty, null), repetition_penalty: num(s.repetition_penalty, null), min_p: num(s.min_p, null), top_a: num(s.top_a, null) };
Object.keys(p).forEach((k) => {
const v = p[k];
if (v !== null) o[k] = v;
});
return o;
};
const buildBody = () => {
const SUNE = window.SUNE;
const msgs = [];
if (USER.masterPrompt && !SUNE.ignore_master_prompt) msgs.push({ role: "system", content: [{ type: "text", text: USER.masterPrompt }] });
if (SUNE.system_prompt) msgs.push({ role: "system", content: [{ type: "text", text: SUNE.system_prompt }] });
msgs.push(...state.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content })));
const b = payloadWithSampling({ model: SUNE.model.replace(/^(or:|oai:|g:|cla:|cf:)/, ""), messages: msgs, stream: true });
if (SUNE.json_output) {
let s;
try {
s = JSON.parse(SUNE.json_schema || "null");
} catch {
s = null;
}
if (s && typeof s === "object" && Object.keys(s).length > 0) {
b.response_format = { type: "json_schema", json_schema: s };
} else {
b.response_format = { type: "json_object" };
}
}
b.reasoning = { ...SUNE.reasoning_effort && SUNE.reasoning_effort !== "default" ? { effort: SUNE.reasoning_effort } : {}, exclude: !SUNE.include_thoughts };
if (SUNE.verbosity) b.verbosity = SUNE.verbosity;
return b;
};
const HTTP_BASE$1 = "https://orp.aww.4ev.link/ws";
const askOpenRouterStreaming = async (onDelta, streamId) => {
const SUNE = window.SUNE;
const model = SUNE.model, provider = model.startsWith("oai:") ? "openai" : model.startsWith("g:") ? "google" : model.startsWith("cla:") ? "claude" : model.startsWith("cf:") ? "cloudflare" : model.startsWith("or:") ? "openrouter" : USER.provider, apiKey = provider === "openai" ? USER.apiKeyOpenAI : provider === "google" ? USER.apiKeyGoogle : provider === "claude" ? USER.apiKeyClaude : provider === "cloudflare" ? USER.apiKeyCloudflare : 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$1.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: buildBody() }));
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);
} 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 };
};
const services = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
askOpenRouterStreaming,
buildBody,
cacheStore,
generateTitleWithAI,
processSuneIncludes,
resolveSuneSrc
}, Symbol.toStringTag, { value: "Module" }));
const scriptRel = "modulepreload";
const assetsURL = function(dep) {
return "/" + dep;
};
const seen = {};
const __vitePreload = function preload(baseModule, deps, importerUrl) {
let promise = Promise.resolve();
if (deps && deps.length > 0) {
let allSettled2 = function(promises$2) {
return Promise.all(promises$2.map((p) => Promise.resolve(p).then((value$1) => ({
status: "fulfilled",
value: value$1
}), (reason) => ({
status: "rejected",
reason
}))));
};
var allSettled = allSettled2;
document.getElementsByTagName("link");
const cspNonceMeta = document.querySelector("meta[property=csp-nonce]");
const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
promise = allSettled2(deps.map((dep) => {
dep = assetsURL(dep);
if (dep in seen) return;
seen[dep] = true;
const isCss = dep.endsWith(".css");
const cssSelector = isCss ? '[rel="stylesheet"]' : "";
if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) return;
const link = document.createElement("link");
link.rel = isCss ? "stylesheet" : scriptRel;
if (!isCss) link.as = "script";
link.crossOrigin = "";
link.href = dep;
if (cspNonce) link.setAttribute("nonce", cspNonce);
document.head.appendChild(link);
if (isCss) return new Promise((res, rej) => {
link.addEventListener("load", res);
link.addEventListener("error", () => rej(/* @__PURE__ */ new Error(`Unable to preload CSS for ${dep}`)));
});
}));
}
function handlePreloadError(err$2) {
const e$1 = new Event("vite:preloadError", { cancelable: true });
e$1.payload = err$2;
window.dispatchEvent(e$1);
if (!e$1.defaultPrevented) throw err$2;
}
return promise.then((res) => {
for (const item of res || []) {
if (item.status !== "rejected") continue;
handlePreloadError(item.reason);
}
return baseModule().catch(handlePreloadError);
});
};
let el$1, jars = { html: null, extension: null, jsonSchema: null };
const initDOM = () => {
el$1 = window.el = Object.fromEntries(["topbar", "chat", "messages", "composer", "input", "sendBtn", "suneBtnTop", "suneModal", "suneURL", "settingsForm", "closeSettings", "cancelSettings", "tabModel", "tabPrompt", "tabScript", "panelModel", "panelPrompt", "panelScript", "set_model", "set_temperature", "set_top_p", "set_top_k", "set_frequency_penalty", "set_repetition_penalty", "set_min_p", "set_top_a", "set_verbosity", "set_reasoning_effort", "set_system_prompt", "set_hide_composer", "set_include_thoughts", "set_json_output", "set_ignore_master_prompt", "deleteSuneBtn", "sidebarLeft", "sidebarOverlayLeft", "sidebarBtnLeft", "suneList", "newSuneBtn", "userMenuBtn", "userMenu", "accountSettingsOption", "sunesImportOption", "sunesExportOption", "threadsImportOption", "threadsExportOption", "importInput", "sidebarBtnRight", "sidebarRight", "sidebarOverlayRight", "threadList", "closeThreads", "threadPopover", "sunePopover", "footer", "attachBtn", "attachBadge", "fileInput", "htmlEditor", "extensionHtmlEditor", "jsonSchemaEditor", "htmlTab_index", "htmlTab_extension", "suneHtml", "accountSettingsModal", "accountSettingsForm", "closeAccountSettings", "cancelAccountSettings", "set_master_prompt", "set_provider", "set_api_key_or", "set_api_key_oai", "set_api_key_g", "set_api_key_claude", "set_api_key_cf", "set_title_model", "copySystemPrompt", "pasteSystemPrompt", "copyHTML", "pasteHTML", "accountTabGeneral", "accountTabAPI", "accountPanelGeneral", "accountPanelAPI", "set_gh_token", "gcpSAInput", "gcpSAUploadBtn", "importAccountSettings", "exportAccountSettings", "importAccountSettingsInput", "accountTabUser", "accountPanelUser", "set_user_name", "userAvatarPreview", "setUserAvatarBtn", "userAvatarInput"].map((id) => [id, $("#" + id)[0]]));
};
const icons = () => window.lucide && lucide.createIcons();
const haptic = () => /android/i.test(navigator.userAgent) && navigator.vibrate?.(1);
const getModelShort = (m) => {
const SUNE = window.SUNE;
const mm = m || SUNE.model || "";
return mm.includes("/") ? mm.split("/").pop() : mm;
};
const getSuneLabel = (m) => {
const SUNE = window.SUNE;
const name = m && m.sune_name || SUNE.name, modelShort = getModelShort(m && m.model);
return `${name} · ${modelShort}`;
};
const enhanceCodeBlocks = (root, doHL = true) => {
$(root).find("pre>code").each((i, code) => {
if (code.textContent.length > 2e5) return;
const $pre = $(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 = $('<button class="bg-slate-900 text-white rounded-lg py-1 px-2 text-xs opacity-85">Copy</button>').on("click", async (e) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(code.innerText);
$btn.text("Copied");
setTimeout(() => $btn.text("Copy"), 1200);
} catch {
}
});
const $container = $('<div class="code-actions absolute top-2 right-2 flex items-center gap-2"></div>');
$container.append($(`<span class="text-xs text-gray-500">${countText} chars</span>`), $btn);
$pre.append($container);
}
if (doHL && window.hljs && code.textContent.length < 1e5) hljs.highlightElement(code);
});
};
const md = window.markdownit({ html: false, linkify: true, typographer: true, breaks: true });
const renderMarkdown = (node, text, opt = { enhance: true, highlight: true }) => {
node.innerHTML = md.render(text);
if (opt.enhance) enhanceCodeBlocks(node, opt.highlight);
};
const renderSuneHTML = async () => {
const SUNE = window.SUNE;
const h = await processSuneIncludes([SUNE.extension_html, SUNE.html].map((x) => (x || "").trim()).join("\n")), c = el$1.suneHtml;
c.innerHTML = "";
const t = h.trim();
c.classList.toggle("hidden", !t);
if (t) {
c.appendChild(document.createRange().createContextualFragment(h));
window.Alpine?.initTree(c);
}
};
const reflectActiveSune = async () => {
const a = window.SUNE.active;
el$1.suneBtnTop.title = `Settings — ${a.name}`;
el$1.suneBtnTop.innerHTML = a.avatar ? `<img src="${esc(a.avatar)}" alt="" class="h-8 w-8 rounded-full object-cover"/>` : "✺";
el$1.footer.classList.toggle("hidden", !!a.settings.hide_composer);
await renderSuneHTML();
icons();
};
const suneRow = (a) => `<div class="relative flex items-center gap-2 px-3 py-2 ${a.pinned ? "bg-yellow-50" : ""}"><button data-sune-id="${a.id}" class="flex-1 text-left flex items-center gap-2 ${a.id === SUNE_CORE.id ? "font-medium" : ""}">${a.avatar ? `<img src="${esc(a.avatar)}" alt="" class="h-6 w-6 rounded-full object-cover"/>` : `<span class="h-6 w-6 rounded-full bg-gray-200 flex items-center justify-center">✺</span>`}<span class="truncate">${a.pinned ? "📌 " : ""}${esc(a.name)}</span></button><button data-sune-menu="${a.id}" class="h-8 w-8 rounded hover:bg-gray-100 flex items-center justify-center" title="More"><i data-lucide="more-horizontal" class="h-4 w-4"></i></button></div>`;
const renderSidebar = () => {
const list = [...SUNE_CORE.list].sort((a, b) => b.pinned - a.pinned);
el$1.suneList.innerHTML = list.map(suneRow).join("");
icons();
};
const partsToText = (parts) => {
if (!parts) return "";
if (Array.isArray(parts)) return parts.map((p) => p?.type === "text" ? p.text : p?.type === "image_url" ? `![](${p.image_url?.url || ""})` : p?.type === "file" ? `[${p.file?.filename || "file"}]` : p?.type === "input_audio" ? `(audio:${p.input_audio?.format || ""})` : "").join("\n");
return String(parts);
};
function msgRow(m) {
const role = typeof m === "string" ? m : m && m.role || "assistant", meta = typeof m === "string" ? {} : m || {}, isUser = role === "user", $row = $('<div class="flex flex-col gap-2"></div>'), $head = $('<div class="flex items-center gap-2 px-4"></div>'), $avatar = $("<div></div>");
const uAva = isUser ? USER.avatar : meta.avatar;
uAva ? $avatar.attr("class", "msg-avatar shrink-0 h-7 w-7 rounded-full overflow-hidden").html(`<img src="${esc(uAva)}" class="h-full w-full object-cover">`) : $avatar.attr("class", `${isUser ? "bg-gray-900 text-white" : "bg-gray-200 text-gray-900"} msg-avatar shrink-0 h-7 w-7 rounded-full flex items-center justify-center`).text(isUser ? "👤" : "✺");
const $name = $('<div class="text-xs font-medium text-gray-500"></div>').text(isUser ? USER.name : getSuneLabel(meta));
const $deleteBtn = $('<button class="p-1.5 rounded-lg hover:bg-gray-200 text-gray-400 hover:text-red-500" title="Delete message"><i data-lucide="x" class="h-4 w-4"></i></button>').on("click", async (e) => {
e.stopPropagation();
state.messages = state.messages.filter((msg) => msg.id !== m.id);
$row.remove();
await THREAD.persist();
});
const $copyBtn = $('<button class="ml-auto p-1.5 rounded-lg hover:bg-gray-200 text-gray-400 hover:text-gray-600" title="Copy message"><i data-lucide="copy" class="h-4 w-4"></i></button>').on("click", async function(e) {
e.stopPropagation();
try {
await navigator.clipboard.writeText(partsToText(m.content));
$(this).html('<i data-lucide="check" class="h-4 w-4 text-green-500"></i>');
icons();
setTimeout(() => {
$(this).html('<i data-lucide="copy" class="h-4 w-4"></i>');
icons();
}, 1200);
} catch {
}
});
$head.append($avatar, $name, $copyBtn, $deleteBtn);
const $bubble = $(`<div class="${(isUser ? "bg-gray-50 border border-gray-200" : "bg-gray-100") + " msg-bubble markdown-body rounded-none px-4 py-3 w-full"}"></div>`);
$row.append($head, $bubble);
$(el$1.messages).append($row);
queueMicrotask(() => {
el$1.chat.scrollTo({ top: el$1.chat.scrollHeight, behavior: "smooth" });
icons();
});
return $bubble[0];
}
const addMessage = (m, track = true) => {
m.id = m.id || gid();
if (!Array.isArray(m.content) && m.content != null) {
m.content = [{ type: "text", text: String(m.content) }];
}
const bubble = msgRow(m);
bubble.dataset.mid = m.id;
renderMarkdown(bubble, partsToText(m.content));
if (track) state.messages.push(m);
if (m.role === "assistant") el$1.composer.dispatchEvent(new CustomEvent("sune:newSuneResponse", { detail: { message: m } }));
return bubble;
};
const addSuneBubbleStreaming = (meta, id) => msgRow(Object.assign({ role: "assistant", id }, meta));
const updateAttachBadge = () => {
const n = state.attachments.length;
el$1.attachBadge.textContent = String(n);
el$1.attachBadge.classList.toggle("hidden", n === 0);
};
const clearChat = () => {
el$1.suneHtml.dispatchEvent(new CustomEvent("sune:unmount"));
state.messages = [];
el$1.messages.innerHTML = "";
state.attachments = [];
updateAttachBadge();
el$1.fileInput.value = "";
};
const setBtnStop = () => {
const b = el$1.sendBtn;
b.dataset.mode = "stop";
b.type = "button";
b.setAttribute("aria-label", "Stop");
b.innerHTML = '<i data-lucide="square" class="h-5 w-5"></i>';
icons();
b.onclick = () => {
state.abortRequested = true;
state.controller?.abort?.();
state.busy = false;
setBtnSend();
};
};
const setBtnSend = () => {
const b = el$1.sendBtn;
b.dataset.mode = "send";
b.type = "submit";
b.setAttribute("aria-label", "Send");
b.innerHTML = '<i data-lucide="sparkles" class="h-5 w-5"></i>';
icons();
b.onclick = null;
};
const localDemoReply = () => "Tip: open the sidebar → Account & Backup to set your API key.";
const ensureThreadOnFirstUser = async (text) => {
let needNew = !state.currentThreadId;
if (state.messages.length === 0) state.currentThreadId = null;
if (state.currentThreadId && !THREAD.get(state.currentThreadId)) needNew = true;
if (!needNew) return;
const id = gid(), now = Date.now(), th = { id, title: "", pinned: false, updatedAt: now, messages: [] };
state.currentThreadId = id;
THREAD.list.unshift(th);
await THREAD.save();
await renderThreads();
};
const threadRow = (t) => `<div class="relative flex items-center gap-2 px-3 py-2 ${t.pinned ? "bg-yellow-50" : ""}"><button data-open-thread="${t.id}" class="flex-1 text-left truncate">${t.pinned ? "📌 " : ""}${esc(t.title)}</button><button data-thread-menu="${t.id}" class="h-8 w-8 rounded hover:bg-gray-100 flex items-center justify-center" title="More"><i data-lucide="more-horizontal" class="h-4 w-4"></i></button></div>`;
const renderThreads = async () => {
window.sortedThreads = [...THREAD.list].sort((a, b) => b.pinned - a.pinned || b.updatedAt - a.updatedAt);
el$1.threadList.innerHTML = window.sortedThreads.slice(0, 50).map(threadRow).join("");
el$1.threadList.scrollTop = 0;
window.isAddingThreads = false;
icons();
};
const hideThreadPopover = () => {
el$1.threadPopover.classList.add("hidden");
window.menuThreadId = null;
};
const showThreadPopover = (btn, id) => {
window.menuThreadId = id;
el$1.threadPopover.classList.remove("hidden");
window.positionPopover(btn, el$1.threadPopover);
icons();
};
const hideSunePopover = () => {
el$1.sunePopover.classList.add("hidden");
window.menuSuneId = null;
};
const showSunePopover = (btn, id) => {
window.menuSuneId = id;
el$1.sunePopover.classList.remove("hidden");
window.positionPopover(btn, el$1.sunePopover);
icons();
};
const toAttach = async (file) => {
if (!file) return null;
if (file instanceof File) {
const name = file.name || "file", mime = (file.type || "application/octet-stream").toLowerCase();
if (/^image\//.test(mime) || /\.(png|jpe?g|webp|gif)$/i.test(name)) {
const data2 = mime === "image/webp" || /\.webp$/i.test(name) ? await asDataURL(file) : await imgToWebp(file, 2048, 94);
return { type: "image_url", image_url: { url: data2 } };
}
if (mime === "application/pdf" || /\.pdf$/i.test(name)) {
const data2 = await asDataURL(file), bin2 = b64(data2);
return { type: "file", file: { filename: name.endsWith(".pdf") ? name : name + ".pdf", file_data: bin2 } };
}
if (/^audio\//.test(mime) || /\.(wav|mp3)$/i.test(name)) {
const data2 = await asDataURL(file), bin2 = b64(data2), fmt = /mp3/.test(mime) || /\.mp3$/i.test(name) ? "mp3" : "wav";
return { type: "input_audio", input_audio: { data: bin2, format: fmt } };
}
const data = await asDataURL(file), bin = b64(data);
return { type: "file", file: { filename: name, file_data: bin } };
}
if (file && file.name == null && file.data) {
const name = file.name || "file", mime = (file.mime || "application/octet-stream").toLowerCase();
if (/^image\//.test(mime)) {
const url = `data:${mime};base64,${file.data}`;
return { type: "image_url", image_url: { url } };
}
if (mime === "application/pdf") {
return { type: "file", file: { filename: name, file_data: file.data } };
}
if (/^audio\//.test(mime)) {
const fmt = /mp3/.test(mime) ? "mp3" : "wav";
return { type: "input_audio", input_audio: { data: file.data, format: fmt } };
}
return { type: "file", file: { filename: name, file_data: file.data } };
}
return null;
};
const ensureJars = async () => {
if (jars.html && jars.extension && jars.jsonSchema) return jars;
const mod = await __vitePreload(() => import("https://medv.io/codejar/codejar.js"), true ? [] : void 0), CodeJar = mod.CodeJar || mod.default, hl = (e) => e.innerHTML = hljs.highlight(e.textContent, { language: "xml" }).value, hl_json = (e) => e.innerHTML = hljs.highlight(e.textContent, { language: "json" }).value;
if (!jars.html) jars.html = CodeJar(el$1.htmlEditor, hl, { tab: " " });
if (!jars.extension) jars.extension = CodeJar(el$1.extensionHtmlEditor, hl, { tab: " " });
if (!jars.jsonSchema) jars.jsonSchema = CodeJar(el$1.jsonSchemaEditor, hl_json, { tab: " " });
return jars;
};
const openSettings = () => {
const a = window.SUNE.active, s = a.settings;
window.openedHTML = false;
el$1.suneURL.value = a.url || "";
el$1.set_model.value = s.model;
el$1.set_temperature.value = s.temperature;
el$1.set_top_p.value = s.top_p;
el$1.set_top_k.value = s.top_k;
el$1.set_frequency_penalty.value = s.frequency_penalty;
el$1.set_repetition_penalty.value = s.repetition_penalty;
el$1.set_min_p.value = s.min_p;
el$1.set_top_a.value = s.top_a;
el$1.set_verbosity.value = s.verbosity || "";
el$1.set_reasoning_effort.value = s.reasoning_effort || "default";
el$1.set_system_prompt.value = s.system_prompt;
el$1.set_hide_composer.checked = !!s.hide_composer;
el$1.set_json_output.checked = !!s.json_output;
el$1.set_include_thoughts.checked = !!s.include_thoughts;
el$1.set_ignore_master_prompt.checked = !!s.ignore_master_prompt;
showTab("Model");
el$1.suneModal.classList.remove("hidden");
};
const closeSettings = () => el$1.suneModal.classList.add("hidden");
const tabs = { Model: ["tabModel", "panelModel"], Prompt: ["tabPrompt", "panelPrompt"], Script: ["tabScript", "panelScript"] };
const showTab = (key) => {
Object.entries(tabs).forEach(([k, [tb, pn]]) => {
el$1[tb].classList.toggle("border-black", k === key);
el$1[pn].classList.toggle("hidden", k !== key);
});
if (key === "Prompt") {
ensureJars().then(({ jsonSchema }) => {
const s = window.SUNE.settings;
jsonSchema.updateCode(s.json_schema || "");
});
} else if (key === "Script") {
window.openedHTML = true;
showHtmlTab("index");
ensureJars().then(({ html, extension }) => {
const s = window.SUNE.settings;
html.updateCode(s.html || "");
extension.updateCode(s.extension_html || "");
});
}
};
const kbUpdate = () => {
const vv = window.visualViewport;
const overlap = vv ? Math.max(0, window.innerHeight - (vv.height + vv.offsetTop)) : 0;
document.documentElement.style.setProperty("--kb", overlap + "px");
const fh = el$1.footer.getBoundingClientRect().height;
document.documentElement.style.setProperty("--footer-h", fh + "px");
el$1.footer.style.transform = "translateY(" + -overlap + "px)";
el$1.chat.style.scrollPaddingBottom = fh + overlap + 16 + "px";
};
const kbBind = () => {
if (window.visualViewport) {
["resize", "scroll"].forEach((ev) => visualViewport.addEventListener(ev, () => kbUpdate(), { passive: true }));
}
$(window).on("resize orientationchange", () => setTimeout(kbUpdate, 50));
$(el$1.input).on("focus click", () => {
setTimeout(() => {
kbUpdate();
el$1.input.scrollIntoView({ block: "nearest", behavior: "smooth" });
}, 0);
});
};
const activeMeta = () => {
const SUNE = window.SUNE;
return { sune_name: SUNE.name, model: SUNE.model, avatar: SUNE.avatar };
};
const htmlTabs = { index: ["htmlTab_index", "htmlEditor"], extension: ["htmlTab_extension", "extensionHtmlEditor"] };
const showHtmlTab = (key) => {
Object.entries(htmlTabs).forEach(([k, [tb, pn]]) => {
const a = k === key;
el$1[tb].classList.toggle("border-black", a);
el$1[tb].classList.toggle("border-transparent", !a);
el$1[tb].classList.toggle("hover:border-gray-300", !a);
el$1[pn].classList.toggle("hidden", !a);
});
};
const accountTabs = { General: ["accountTabGeneral", "accountPanelGeneral"], API: ["accountTabAPI", "accountPanelAPI"], User: ["accountTabUser", "accountPanelUser"] };
const showAccountTab = (key) => {
Object.entries(accountTabs).forEach(([k, [tb, pn]]) => {
el$1[tb].classList.toggle("border-black", k === key);
el$1[pn].classList.toggle("hidden", k !== key);
});
};
const openAccountSettings = () => {
el$1.set_provider.value = USER.provider || "openrouter";
el$1.set_api_key_or.value = USER.apiKeyOpenRouter || "";
el$1.set_api_key_oai.value = USER.apiKeyOpenAI || "";
el$1.set_api_key_g.value = USER.apiKeyGoogle || "";
el$1.set_api_key_claude.value = USER.apiKeyClaude || "";
el$1.set_api_key_cf.value = USER.apiKeyCloudflare || "";
el$1.set_master_prompt.value = USER.masterPrompt || "";
el$1.set_title_model.value = USER.titleModel;
el$1.set_gh_token.value = USER.githubToken || "";
const sa = USER.gcpSA;
el$1.gcpSAUploadBtn.textContent = sa && sa.project_id ? `Uploaded: ${sa.project_id}` : "Upload .json";
el$1.set_user_name.value = USER.name;
el$1.userAvatarPreview.src = USER.avatar || "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
el$1.userAvatarPreview.classList.toggle("bg-gray-200", !USER.avatar);
showAccountTab("General");
el$1.accountSettingsModal.classList.remove("hidden");
};
const closeAccountSettings = () => el$1.accountSettingsModal.classList.add("hidden");
const getBubbleById = (id) => el$1.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) return false;
const prevText = bubble.textContent || "";
const j = await fetch("https://orp.aww.4ev.link/ws?uid=" + encodeURIComponent(id)).then((r) => r.ok ? r.json() : null).catch(() => null);
const finalise = (t, c) => {
renderMarkdown(bubble, t, { enhance: false });
enhanceCodeBlocks(bubble, true);
const i = state.messages.findIndex((x) => x.id === id);
if (i >= 0) state.messages[i].content = c;
else state.messages.push({ id, role: "assistant", content: c, ...activeMeta() });
THREAD.persist();
setBtnSend();
state.busy = false;
cacheStore.setItem(id, "done");
state.controller = null;
el$1.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 = prevText + "\n\n" + j.error;
finalise(t, [{ type: "text", text: t }]);
}
return false;
}
const text = j.text || "", isDone = j.error || j.done || j.phase === "done";
if (text) renderMarkdown(bubble, text, { enhance: false });
if (isDone) {
const finalText = text || prevText;
finalise(finalText, [{ type: "text", text: finalText }]);
return false;
}
await cacheStore.setItem(id, "busy");
return true;
}
const onForeground = () => {
if (document.visibilityState !== "visible") return;
state.controller?.disconnect?.();
if (state.busy) window.syncWhileBusy();
};
const getActiveHtmlParts = () => !el$1.htmlEditor.classList.contains("hidden") ? [el$1.htmlEditor, jars.html] : [el$1.extensionHtmlEditor, jars.extension];
const ui = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
activeMeta,
addMessage,
addSuneBubbleStreaming,
clearChat,
closeAccountSettings,
closeSettings,
get el() {
return el$1;
},
enhanceCodeBlocks,
ensureJars,
ensureThreadOnFirstUser,
getActiveHtmlParts,
getBubbleById,
getModelShort,
getSuneLabel,
haptic,
hideSunePopover,
hideThreadPopover,
icons,
initDOM,
jars,
kbBind,
kbUpdate,
localDemoReply,
md,
msgRow,
onForeground,
openAccountSettings,
openSettings,
partsToText,
reflectActiveSune,
renderMarkdown,
renderSidebar,
renderSuneHTML,
renderThreads,
setBtnSend,
setBtnStop,
showAccountTab,
showHtmlTab,
showSunePopover,
showTab,
showThreadPopover,
suneRow,
syncActiveThread,
threadRow,
toAttach,
updateAttachBadge
}, Symbol.toStringTag, { value: "Module" }));
let 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;
}
}
const SUNE_PROXY = new Proxy(SUNE_CORE, {
get(t, p) {
if (p === "fetchDotSune") return async (g) => {
try {
const u = g.startsWith("http") ? g : (() => {
const [a2, b] = g.split("@"), [c, d] = a2.split("/"), [e, ...f] = b.split("/");
return `https://raw.githubusercontent.com/${c}/${d}/${e}/${f.join("/")}`;
})(), j = await (await fetch(u)).json(), l = sunes.length;
sunes.unshift(...(Array.isArray(j) ? j : j?.sunes || []).filter((s) => s?.id && !t.get(s.id)).map(makeSune));
sunes.length > l && t.save();
} catch {
}
};
if (p === "attach") return async (files) => {
const arr = [];
for (const f of files || []) arr.push(await toAttach(f));
const clean = arr.filter(Boolean);
if (!clean.length) return;
await ensureThreadOnFirstUser();
addMessage({ role: "assistant", content: clean, ...activeMeta() });
await THREAD.persist();
};
if (p === "log") return async (s) => {
const t2 = String(s ?? "").trim();
if (!t2) return;
await ensureThreadOnFirstUser();
addMessage({ role: "assistant", content: [{ type: "text", text: t2 }], ...activeMeta() });
await THREAD.persist();
};
if (p === "lastReply") return [...state.messages].reverse().find((m) => m.role === "assistant");
if (p === "infer") return async () => {
if (state.busy || !SUNE_PROXY.model || state.abortRequested) {
state.abortRequested = false;
return;
}
await ensureThreadOnFirstUser();
const th = THREAD.active;
if (th && !th.title) (async () => THREAD.setTitle(th.id, await generateTitleWithAI(state.messages) || "Sune Inference"))();
state.busy = true;
setBtnStop();
const a2 = SUNE_PROXY.active, suneMeta = { sune_name: a2.name, model: SUNE_PROXY.model, avatar: a2.avatar || "" }, streamId = sid(), suneBubble = addSuneBubbleStreaming(suneMeta, streamId);
suneBubble.dataset.mid = streamId;
const assistantMsg = Object.assign({ id: streamId, role: "assistant", content: [{ type: "text", text: "" }] }, suneMeta);
state.messages.push(assistantMsg);
THREAD.persist(false);
state.stream = { rid: streamId, bubble: suneBubble, meta: suneMeta, text: "", done: false };
let buf = "", completed = false;
const onDelta = (delta, done) => {
buf += delta;
state.stream.text = buf;
renderMarkdown(suneBubble, buf, { enhance: false });
assistantMsg.content[0].text = buf;
if (done && !completed) {
completed = true;
setBtnSend();
state.busy = false;
enhanceCodeBlocks(suneBubble, true);
THREAD.persist(true);
el$1.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 askOpenRouterStreaming(onDelta, streamId);
};
if (p === "handoff") return async (n) => {
await new Promise((r) => setTimeout(r, 4e3));
const s = sunes.find((s2) => s2.name.toLowerCase() === (n || "").trim().toLowerCase());
if (!s) return;
SUNE_CORE.setActive(s.id);
renderSidebar();
await reflectActiveSune();
await SUNE_PROXY.infer();
};
if (p in t) return t[p];
const a = t.active;
if (!a) return;
if (p in a.settings) return a.settings[p];
if (p in a) return a[p];
},
set(t, p, v) {
const a = t.active;
if (!a) return false;
const i = sunes.findIndex((s) => s.id === a.id);
if (i < 0) return false;
const isTopLevel = /^(name|avatar|url|pinned|storage)$/.test(p), target = isTopLevel ? sunes[i] : sunes[i].settings;
let value = v;
if (!isTopLevel) {
if (p === "system_prompt") value = v || "";
}
if (target[p] !== value) {
target[p] = value;
sunes[i].updatedAt = Date.now();
t.save();
}
return true;
}
});
async function init() {
initDOM();
Object.assign(window, { SUNE: SUNE_PROXY, USER, THREAD, state, sunes, ...api, ...utils, ...services, ...ui, syncWhileBusy });
await SUNE_PROXY.fetchDotSune("sune-org/store@main/marketplace.sune");
await SUNE_PROXY.fetchDotSune("sune-org/store@main/forum.sune");
await THREAD.load();
await renderThreads();
renderSidebar();
await reflectActiveSune();
clearChat();
icons();
kbBind();
kbUpdate();
el.htmlTab_index.textContent = "index.html";
el.htmlTab_extension.textContent = "extension.html";
}
document.addEventListener("DOMContentLoaded", init);