mirror of
https://github.com/sune-org/sune.git
synced 2026-04-07 07:52:13 +00:00
This build was committed by a bot.
This commit is contained in:
@@ -235,78 +235,7 @@ var generateTitleWithAI = async (messages) => {
|
||||
}
|
||||
};
|
||||
//#endregion
|
||||
//#region \0vite/preload-helper.js
|
||||
var scriptRel = "modulepreload";
|
||||
var assetsURL = function(dep) {
|
||||
return "/" + dep;
|
||||
};
|
||||
var seen = {};
|
||||
var __vitePreload = function preload(baseModule, deps, importerUrl) {
|
||||
let promise = Promise.resolve();
|
||||
if (deps && deps.length > 0) {
|
||||
const links = document.getElementsByTagName("link");
|
||||
const cspNonceMeta = document.querySelector("meta[property=csp-nonce]");
|
||||
const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
|
||||
function allSettled(promises) {
|
||||
return Promise.all(promises.map((p) => Promise.resolve(p).then((value) => ({
|
||||
status: "fulfilled",
|
||||
value
|
||||
}), (reason) => ({
|
||||
status: "rejected",
|
||||
reason
|
||||
}))));
|
||||
}
|
||||
promise = allSettled(deps.map((dep) => {
|
||||
dep = assetsURL(dep, importerUrl);
|
||||
if (dep in seen) return;
|
||||
seen[dep] = true;
|
||||
const isCss = dep.endsWith(".css");
|
||||
const cssSelector = isCss ? "[rel=\"stylesheet\"]" : "";
|
||||
if (!!importerUrl) for (let i = links.length - 1; i >= 0; i--) {
|
||||
const link = links[i];
|
||||
if (link.href === dep && (!isCss || link.rel === "stylesheet")) return;
|
||||
}
|
||||
else 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) {
|
||||
const e = new Event("vite:preloadError", { cancelable: true });
|
||||
e.payload = err;
|
||||
window.dispatchEvent(e);
|
||||
if (!e.defaultPrevented) throw err;
|
||||
}
|
||||
return promise.then((res) => {
|
||||
for (const item of res || []) {
|
||||
if (item.status !== "rejected") continue;
|
||||
handlePreloadError(item.reason);
|
||||
}
|
||||
return baseModule().catch(handlePreloadError);
|
||||
});
|
||||
};
|
||||
//#endregion
|
||||
//#region src/main.js
|
||||
(() => {
|
||||
let k, v = visualViewport;
|
||||
const f = () => {
|
||||
removeEventListener("popstate", f), document.activeElement?.blur();
|
||||
};
|
||||
v.onresize = () => {
|
||||
let o = v.height < innerHeight;
|
||||
o != k && ((k = o) ? (history.pushState({ k: 1 }, ""), addEventListener("popstate", f)) : (removeEventListener("popstate", f), history.state?.k && history.back()));
|
||||
};
|
||||
})();
|
||||
var DEFAULT_MODEL = "anthropic/claude-opus-4.6";
|
||||
//#region src/dom.js
|
||||
var el = window.el = Object.fromEntries([
|
||||
"topbar",
|
||||
"chat",
|
||||
@@ -411,17 +340,22 @@ var el = window.el = Object.fromEntries([
|
||||
"threadBackBtn",
|
||||
"threadFolderBtn",
|
||||
"threadSyncBtn"
|
||||
].map((id) => [id, $("#" + id)[0]]));
|
||||
var icons = () => window.lucide && lucide.createIcons();
|
||||
var haptic = () => /android/i.test(navigator.userAgent) && navigator.vibrate?.(1);
|
||||
var clamp = (v, min, max) => Math.max(min, Math.min(max, v)), num = (v, d) => v == null || v === "" || isNaN(+v) ? d : +v, int = (v, d) => v == null || v === "" || isNaN(parseInt(v)) ? d : parseInt(v), gid = () => Math.random().toString(36).slice(2, 9), esc = (s) => String(s).replace(/[&<>'"`]/g, (c) => ({
|
||||
].map((id) => [id, document.getElementById(id)]));
|
||||
//#endregion
|
||||
//#region src/utils.js
|
||||
var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
||||
var num = (v, d) => v == null || v === "" || isNaN(+v) ? d : +v;
|
||||
var int = (v, d) => v == null || v === "" || isNaN(parseInt(v)) ? d : parseInt(v);
|
||||
var gid = () => Math.random().toString(36).slice(2, 9);
|
||||
var esc = (s) => String(s).replace(/[&<>'"`]/g, (c) => ({
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"\"": """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
})[c]), positionPopover = (a, p) => {
|
||||
})[c]);
|
||||
var 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`;
|
||||
@@ -464,7 +398,129 @@ var imgToWebp = (f, D = 128, q = 80) => new Promise((r, j) => {
|
||||
i.src = URL.createObjectURL(f);
|
||||
});
|
||||
var b64 = (x) => x.split(",")[1] || "";
|
||||
var utob = (s) => btoa(unescape(encodeURIComponent(s))), btou = (s) => decodeURIComponent(escape(atob(s.replace(/\s/g, ""))));
|
||||
var utob = (s) => btoa(unescape(encodeURIComponent(s)));
|
||||
var btou = (s) => decodeURIComponent(escape(atob(s.replace(/\s/g, ""))));
|
||||
function partsToText(m) {
|
||||
if (!m) return "";
|
||||
const c = m.content, i = m.images;
|
||||
let t = Array.isArray(c) ? c.map((p) => p?.type === "text" ? p.text : p?.type === "image_url" ? `` : p?.type === "file" ? `[${p.file?.filename || "file"}]` : p?.type === "input_audio" ? `(audio:${p.input_audio?.format || ""})` : "").join("\n") : String(c || "");
|
||||
if (Array.isArray(i)) t += i.map((x) => `\n\n`).join("");
|
||||
return t;
|
||||
}
|
||||
function 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 = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
var 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())}`;
|
||||
};
|
||||
//#endregion
|
||||
//#region src/user.js
|
||||
var USER = {
|
||||
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"
|
||||
].includes(v) ? v : "openrouter");
|
||||
},
|
||||
get apiKeyOpenRouter() {
|
||||
return localStorage.getItem("openrouter_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.";
|
||||
},
|
||||
set masterPrompt(v) {
|
||||
localStorage.setItem("master_prompt", v || "");
|
||||
},
|
||||
get titleModel() {
|
||||
return localStorage.getItem("title_model") ?? "or:amazon/nova-micro-v1";
|
||||
},
|
||||
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) : "");
|
||||
}
|
||||
};
|
||||
//#endregion
|
||||
//#region src/github.js
|
||||
var ghApi = async (path, method = "GET", body = null) => {
|
||||
const t = USER.githubToken;
|
||||
if (!t) throw new Error("No GH token");
|
||||
@@ -490,6 +546,231 @@ var parseGhUrl = (u) => {
|
||||
apiPath: `${owner}/${repo}/contents${path ? "/" + path : ""}`
|
||||
};
|
||||
};
|
||||
//#endregion
|
||||
//#region src/markdown.js
|
||||
var md = window.md = window.markdownit({
|
||||
html: false,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
breaks: true
|
||||
}).use(mathjax3);
|
||||
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.$("<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 = window.$("<div class=\"code-actions absolute top-2 right-2 flex items-center gap-2\"></div>");
|
||||
$container.append(window.$(`<span class="text-xs text-gray-500">${countText} chars</span>`), $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;
|
||||
const overlap = vv ? Math.max(0, window.innerHeight - (vv.height + vv.offsetTop)) : 0;
|
||||
document.documentElement.style.setProperty("--kb", overlap + "px");
|
||||
const fh = el.footer.getBoundingClientRect().height;
|
||||
document.documentElement.style.setProperty("--footer-h", fh + "px");
|
||||
el.footer.style.transform = "translateY(" + -overlap + "px)";
|
||||
el.chat.style.scrollPaddingBottom = fh + overlap + 16 + "px";
|
||||
}
|
||||
function kbBind() {
|
||||
if (window.visualViewport) ["resize", "scroll"].forEach((ev) => window.visualViewport.addEventListener(ev, () => kbUpdate(), { passive: true }));
|
||||
window.$(window).on("resize orientationchange", () => setTimeout(kbUpdate, 50));
|
||||
window.$(el.input).on("focus click", () => {
|
||||
setTimeout(() => {
|
||||
kbUpdate();
|
||||
el.input.scrollIntoView({
|
||||
block: "nearest",
|
||||
behavior: "smooth"
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
//#region src/attachments.js
|
||||
async function toAttach(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)) return {
|
||||
type: "image_url",
|
||||
image_url: { url: mime === "image/webp" || /\.webp$/i.test(name) ? await asDataURL(file) : await imgToWebp(file, 2048, 94) }
|
||||
};
|
||||
if (mime === "application/pdf" || /\.pdf$/i.test(name)) {
|
||||
const bin = b64(await asDataURL(file));
|
||||
return {
|
||||
type: "file",
|
||||
file: {
|
||||
filename: name.endsWith(".pdf") ? name : name + ".pdf",
|
||||
file_data: bin
|
||||
}
|
||||
};
|
||||
}
|
||||
if (/^audio\//.test(mime) || /\.(wav|mp3)$/i.test(name)) return {
|
||||
type: "input_audio",
|
||||
input_audio: {
|
||||
data: b64(await asDataURL(file)),
|
||||
format: /mp3/.test(mime) || /\.mp3$/i.test(name) ? "mp3" : "wav"
|
||||
}
|
||||
};
|
||||
return {
|
||||
type: "file",
|
||||
file: {
|
||||
filename: name,
|
||||
file_data: b64(await asDataURL(file))
|
||||
}
|
||||
};
|
||||
}
|
||||
if (file && file.name == null && file.data) {
|
||||
const name = file.name || "file", mime = (file.mime || "application/octet-stream").toLowerCase();
|
||||
if (/^image\//.test(mime)) return {
|
||||
type: "image_url",
|
||||
image_url: { url: `data:${mime};base64,${file.data}` }
|
||||
};
|
||||
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;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/threads-utils.js
|
||||
var titleFrom = (t) => {
|
||||
if (!t) return "Untitled";
|
||||
return (typeof t === "string" ? t : Array.isArray(t) ? partsToText({ content: t }) : "Untitled").replace(/\s+/g, " ").trim().slice(0, 60) || "Untitled";
|
||||
};
|
||||
var serializeThreadName = (t) => {
|
||||
const s = (t.title || "Untitled").replace(/[^a-zA-Z0-9]/g, "_").slice(0, 150);
|
||||
return `${t.pinned ? "1" : "0"}-${t.updatedAt || Date.now()}-${t.id}-${s}.json`;
|
||||
};
|
||||
var deserializeThreadName = (n) => {
|
||||
const p = n.replace(".json", "").split("-");
|
||||
if (p.length < 4) return null;
|
||||
return {
|
||||
pinned: p[0] === "1",
|
||||
updatedAt: parseInt(p[1]),
|
||||
id: p[2],
|
||||
title: p.slice(3).join("-").replace(/_/g, " "),
|
||||
status: "synced",
|
||||
type: "thread"
|
||||
};
|
||||
};
|
||||
//#endregion
|
||||
//#region \0vite/preload-helper.js
|
||||
var scriptRel = "modulepreload";
|
||||
var assetsURL = function(dep) {
|
||||
return "/" + dep;
|
||||
};
|
||||
var seen = {};
|
||||
var __vitePreload = function preload(baseModule, deps, importerUrl) {
|
||||
let promise = Promise.resolve();
|
||||
if (deps && deps.length > 0) {
|
||||
const links = document.getElementsByTagName("link");
|
||||
const cspNonceMeta = document.querySelector("meta[property=csp-nonce]");
|
||||
const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
|
||||
function allSettled(promises) {
|
||||
return Promise.all(promises.map((p) => Promise.resolve(p).then((value) => ({
|
||||
status: "fulfilled",
|
||||
value
|
||||
}), (reason) => ({
|
||||
status: "rejected",
|
||||
reason
|
||||
}))));
|
||||
}
|
||||
promise = allSettled(deps.map((dep) => {
|
||||
dep = assetsURL(dep, importerUrl);
|
||||
if (dep in seen) return;
|
||||
seen[dep] = true;
|
||||
const isCss = dep.endsWith(".css");
|
||||
const cssSelector = isCss ? "[rel=\"stylesheet\"]" : "";
|
||||
if (!!importerUrl) for (let i = links.length - 1; i >= 0; i--) {
|
||||
const link = links[i];
|
||||
if (link.href === dep && (!isCss || link.rel === "stylesheet")) return;
|
||||
}
|
||||
else 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) {
|
||||
const e = new Event("vite:preloadError", { cancelable: true });
|
||||
e.payload = err;
|
||||
window.dispatchEvent(e);
|
||||
if (!e.defaultPrevented) throw err;
|
||||
}
|
||||
return promise.then((res) => {
|
||||
for (const item of res || []) {
|
||||
if (item.status !== "rejected") continue;
|
||||
handlePreloadError(item.reason);
|
||||
}
|
||||
return baseModule().catch(handlePreloadError);
|
||||
});
|
||||
};
|
||||
//#endregion
|
||||
//#region src/main.js
|
||||
(() => {
|
||||
let k, v = visualViewport;
|
||||
const f = () => {
|
||||
removeEventListener("popstate", f), document.activeElement?.blur();
|
||||
};
|
||||
v.onresize = () => {
|
||||
let o = v.height < innerHeight;
|
||||
o != k && ((k = o) ? (history.pushState({ k: 1 }, ""), addEventListener("popstate", f)) : (removeEventListener("popstate", f), history.state?.k && history.back()));
|
||||
};
|
||||
})();
|
||||
var DEFAULT_MODEL = "anthropic/claude-opus-4.6";
|
||||
var icons = () => window.lucide && lucide.createIcons();
|
||||
var haptic = () => /android/i.test(navigator.userAgent) && navigator.vibrate?.(1);
|
||||
var su = {
|
||||
key: "sunes_v1",
|
||||
activeKey: "active_sune_id",
|
||||
@@ -788,33 +1069,6 @@ var renderSidebar = window.renderSidebar = () => {
|
||||
el.suneList.innerHTML = list.map(suneRow).join("");
|
||||
icons();
|
||||
};
|
||||
function 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);
|
||||
});
|
||||
}
|
||||
var md = window.markdownit({
|
||||
html: false,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
breaks: true
|
||||
}).use(mathjax3);
|
||||
var getSuneLabel = (m) => {
|
||||
return `${m && m.sune_name || SUNE.name} · ${getModelShort(m && m.model)}`;
|
||||
};
|
||||
@@ -858,20 +1112,6 @@ function msgRow(m) {
|
||||
});
|
||||
return $row.find(".msg-bubble")[0];
|
||||
}
|
||||
var renderMarkdown = window.renderMarkdown = function(node, text, opt = {
|
||||
enhance: true,
|
||||
highlight: true
|
||||
}) {
|
||||
node.innerHTML = md.render(text);
|
||||
if (opt.enhance) enhanceCodeBlocks(node, opt.highlight);
|
||||
};
|
||||
function partsToText(m) {
|
||||
if (!m) return "";
|
||||
const c = m.content, i = m.images;
|
||||
let t = Array.isArray(c) ? c.map((p) => p?.type === "text" ? p.text : p?.type === "image_url" ? `` : p?.type === "file" ? `[${p.file?.filename || "file"}]` : p?.type === "input_audio" ? `(audio:${p.input_audio?.format || ""})` : "").join("\n") : String(c || "");
|
||||
if (Array.isArray(i)) t += i.map((x) => `\n\n`).join("");
|
||||
return t;
|
||||
}
|
||||
var addMessage = window.addMessage = function(m, track = true) {
|
||||
m.id = m.id || gid();
|
||||
if (!Array.isArray(m.content) && m.content != null) m.content = [{
|
||||
@@ -939,26 +1179,6 @@ function setBtnSend() {
|
||||
function localDemoReply() {
|
||||
return "Tip: open the sidebar → Account & Backup to set your API key.";
|
||||
}
|
||||
var titleFrom = (t) => {
|
||||
if (!t) return "Untitled";
|
||||
return (typeof t === "string" ? t : Array.isArray(t) ? partsToText({ content: t }) : "Untitled").replace(/\s+/g, " ").trim().slice(0, 60) || "Untitled";
|
||||
};
|
||||
var serializeThreadName = (t) => {
|
||||
const s = (t.title || "Untitled").replace(/[^a-zA-Z0-9]/g, "_").slice(0, 150);
|
||||
return `${t.pinned ? "1" : "0"}-${t.updatedAt || Date.now()}-${t.id}-${s}.json`;
|
||||
};
|
||||
var deserializeThreadName = (n) => {
|
||||
const p = n.replace(".json", "").split("-");
|
||||
if (p.length < 4) return null;
|
||||
return {
|
||||
pinned: p[0] === "1",
|
||||
updatedAt: parseInt(p[1]),
|
||||
id: p[2],
|
||||
title: p.slice(3).join("-").replace(/_/g, " "),
|
||||
status: "synced",
|
||||
type: "thread"
|
||||
};
|
||||
};
|
||||
var TKEY = "threads_v1", THREAD = window.THREAD = {
|
||||
list: [],
|
||||
load: async function() {
|
||||
@@ -1302,72 +1522,6 @@ function updateAttachBadge() {
|
||||
el.attachBadge.textContent = String(n);
|
||||
el.attachBadge.classList.toggle("hidden", n === 0);
|
||||
}
|
||||
async function toAttach(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)) return {
|
||||
type: "image_url",
|
||||
image_url: { url: mime === "image/webp" || /\.webp$/i.test(name) ? await asDataURL(file) : await imgToWebp(file, 2048, 94) }
|
||||
};
|
||||
if (mime === "application/pdf" || /\.pdf$/i.test(name)) {
|
||||
const bin = b64(await asDataURL(file));
|
||||
return {
|
||||
type: "file",
|
||||
file: {
|
||||
filename: name.endsWith(".pdf") ? name : name + ".pdf",
|
||||
file_data: bin
|
||||
}
|
||||
};
|
||||
}
|
||||
if (/^audio\//.test(mime) || /\.(wav|mp3)$/i.test(name)) return {
|
||||
type: "input_audio",
|
||||
input_audio: {
|
||||
data: b64(await asDataURL(file)),
|
||||
format: /mp3/.test(mime) || /\.mp3$/i.test(name) ? "mp3" : "wav"
|
||||
}
|
||||
};
|
||||
return {
|
||||
type: "file",
|
||||
file: {
|
||||
filename: name,
|
||||
file_data: b64(await asDataURL(file))
|
||||
}
|
||||
};
|
||||
}
|
||||
if (file && file.name == null && file.data) {
|
||||
const name = file.name || "file", mime = (file.mime || "application/octet-stream").toLowerCase();
|
||||
if (/^image\//.test(mime)) return {
|
||||
type: "image_url",
|
||||
image_url: { url: `data:${mime};base64,${file.data}` }
|
||||
};
|
||||
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;
|
||||
}
|
||||
$(el.attachBtn).on("click", () => {
|
||||
if (state.busy) return;
|
||||
if (state.attachments.length) {
|
||||
@@ -1599,19 +1753,6 @@ $(el.newSuneBtn).on("click", async () => {
|
||||
document.getElementById("sidebarLeft").classList.add("-translate-x-full");
|
||||
document.getElementById("sidebarOverlayLeft").classList.add("hidden");
|
||||
});
|
||||
function 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);
|
||||
}
|
||||
var 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())}`;
|
||||
};
|
||||
var importMode = null;
|
||||
$(el.sunesExportOption).on("click", () => {
|
||||
dl(`sunes-${ts()}.sune`, {
|
||||
@@ -1698,28 +1839,6 @@ $(el.importInput).on("change", async () => {
|
||||
importMode = null;
|
||||
}
|
||||
});
|
||||
function 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.footer.getBoundingClientRect().height;
|
||||
document.documentElement.style.setProperty("--footer-h", fh + "px");
|
||||
el.footer.style.transform = "translateY(" + -overlap + "px)";
|
||||
el.chat.style.scrollPaddingBottom = fh + overlap + 16 + "px";
|
||||
}
|
||||
function kbBind() {
|
||||
if (window.visualViewport) ["resize", "scroll"].forEach((ev) => visualViewport.addEventListener(ev, () => kbUpdate(), { passive: true }));
|
||||
$(window).on("resize orientationchange", () => setTimeout(kbUpdate, 50));
|
||||
$(el.input).on("focus click", () => {
|
||||
setTimeout(() => {
|
||||
kbUpdate();
|
||||
el.input.scrollIntoView({
|
||||
block: "nearest",
|
||||
behavior: "smooth"
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
function activeMeta() {
|
||||
return {
|
||||
sune_name: SUNE.name,
|
||||
@@ -1727,8 +1846,8 @@ function activeMeta() {
|
||||
avatar: SUNE.avatar
|
||||
};
|
||||
}
|
||||
var USER = window.USER = {
|
||||
log: async (s) => {
|
||||
window.USER = USER;
|
||||
USER.log = async (s) => {
|
||||
const t = String(s ?? "").trim();
|
||||
if (!t) return;
|
||||
await ensureThreadOnFirstUser(t);
|
||||
@@ -1740,8 +1859,8 @@ var USER = window.USER = {
|
||||
}]
|
||||
});
|
||||
await THREAD.persist();
|
||||
},
|
||||
logMany: async (msgs) => {
|
||||
};
|
||||
USER.logMany = async (msgs) => {
|
||||
if (!Array.isArray(msgs) || !msgs.length) return;
|
||||
const clean = msgs.map((s) => String(s ?? "").trim()).filter(Boolean);
|
||||
if (!clean.length) return;
|
||||
@@ -1778,102 +1897,6 @@ var USER = window.USER = {
|
||||
icons();
|
||||
});
|
||||
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"
|
||||
].includes(v) ? v : "openrouter");
|
||||
},
|
||||
get apiKeyOpenRouter() {
|
||||
return localStorage.getItem("openrouter_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.";
|
||||
},
|
||||
set masterPrompt(v) {
|
||||
localStorage.setItem("master_prompt", v || "");
|
||||
},
|
||||
get titleModel() {
|
||||
return localStorage.getItem("title_model") ?? "or:amazon/nova-micro-v1";
|
||||
},
|
||||
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) : "");
|
||||
}
|
||||
};
|
||||
async function init() {
|
||||
const u = localStorage.getItem("thread_repo_url") || "";
|
||||
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -14,7 +14,7 @@
|
||||
<script defer src="https://c.planetrenox.com/tracker.js"></script>
|
||||
|
||||
|
||||
<script type="module" crossorigin src="/assets/index-uVgFAg1W.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-6iOL7W2F.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CLEI5Rwr.css">
|
||||
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
||||
<body class="bg-white text-gray-900 selection:bg-black/10" x-data @click.window="if($event.target.closest('button')) haptic(); if(!document.getElementById('threadPopover').contains($event.target)&&!$event.target.closest('[data-thread-menu]')) hideThreadPopover(); if(!document.getElementById('sunePopover').contains($event.target)&&!$event.target.closest('[data-sune-menu]')) hideSunePopover(); if(!document.getElementById('userMenu').contains($event.target)&&!document.getElementById('userMenuBtn').contains($event.target)) document.getElementById('userMenu').classList.add('hidden')">
|
||||
|
||||
2
dist/sw.js
vendored
2
dist/sw.js
vendored
@@ -1 +1 @@
|
||||
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let o={};const d=e=>i(e,t),l={module:{uri:t},exports:o,require:d};s[t]=Promise.all(n.map(e=>l[e]||d(e))).then(e=>(r(...e),o))}}define(["./workbox-8c29f6e4"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"index.html",revision:"f88bfd9d7b620afd81de89ec21767e69"},{url:"assets/index-uVgFAg1W.js",revision:null},{url:"assets/index-CLEI5Rwr.css",revision:null},{url:"manifest.webmanifest",revision:"7a6c5c6ab9cb5d3605d21df44c6b17a2"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
|
||||
if(!self.define){let e,i={};const s=(s,n)=>(s=new URL(s+".js",n).href,i[s]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=s,e.onload=i,document.head.appendChild(e)}else e=s,importScripts(s),i()}).then(()=>{let e=i[s];if(!e)throw new Error(`Module ${s} didn’t register its module`);return e}));self.define=(n,r)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(i[t])return;let o={};const d=e=>s(e,t),l={module:{uri:t},exports:o,require:d};i[t]=Promise.all(n.map(e=>l[e]||d(e))).then(e=>(r(...e),o))}}define(["./workbox-8c29f6e4"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"index.html",revision:"ad949321931259a665ded77f3e344165"},{url:"assets/index-CLEI5Rwr.css",revision:null},{url:"assets/index-6iOL7W2F.js",revision:null},{url:"manifest.webmanifest",revision:"7a6c5c6ab9cb5d3605d21df44c6b17a2"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
|
||||
|
||||
Reference in New Issue
Block a user