' +
- '
' +
- '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '' +
"
" +
'
' +
- '
' +
+ '
' +
'
' +
"
" +
'
';
@@ -86,6 +106,7 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
let busy = false;
let muted = true;
let currentThemeId = "";
+ let themesExpanded = true;
function findSettingsTrigger() {
const buttons = [...document.querySelectorAll('button[aria-haspopup="dialog"]')];
@@ -150,12 +171,27 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
: data.media === "image"
? "图片"
: "无背景";
- statusEl.textContent = label;
- if (data.atmosphere === "gallery") currentThemeId = "builtin-gallery";
+ const sourceLabel =
+ data.sourceMode === "local"
+ ? "本地引用"
+ : data.sourceMode === "managed"
+ ? "托管副本"
+ : "";
+ if (typeof data.themeId === "string" && data.themeId) {
+ currentThemeId = data.themeId;
+ } else if (data.atmosphere === "gallery") {
+ currentThemeId = "builtin-gallery";
+ } else {
+ currentThemeId = "";
+ }
muted = data.muted !== false;
soundBtn.classList.toggle("on", !muted);
- soundBtn.textContent = muted ? "声音" : "声音开";
+ soundBtn.textContent = muted ? "声音已关" : "声音已开";
const themes = Array.isArray(data.themes) ? data.themes : [];
+ const selected = themes.find((theme) => theme.id === currentThemeId);
+ const currentLabel = selected?.name || label;
+ const compactSource = sourceLabel === "本地引用" ? "本地" : sourceLabel === "托管副本" ? "托管" : "已应用";
+ statusEl.textContent = `${currentLabel} / ${compactSource}`;
if (themes.length === 0) {
themesBox.hidden = true;
themeList.innerHTML = "";
@@ -165,13 +201,20 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
}
themesBox.hidden = false;
themeList.innerHTML = themes
- .map(
- (theme) =>
- `
`,
- )
+ .map((theme) => {
+ const del =
+ theme.bundled === true
+ ? ""
+ : `
`;
+ const source =
+ theme.sourceMode === "local" ? "本地" : theme.bundled ? "内置" : "托管";
+ const current = theme.id === currentThemeId ? ' aria-current="true"' : "";
+ return `
${del}
`;
+ })
.join("");
- const selected = themes.find((theme) => theme.id === currentThemeId);
- themeToggle.textContent = selected ? selected.name : "已保存主题";
+ themeToggle.innerHTML = `
SAVED / ${String(themes.length).padStart(2, "0")}${themesExpanded ? "−" : "+"}`;
+ themeList.hidden = !themesExpanded;
+ themeToggle.setAttribute("aria-expanded", themesExpanded ? "true" : "false");
}
function escapeText(value) {
@@ -185,18 +228,36 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
return escapeText(value).replaceAll('"', """);
}
- async function request(path, init) {
- const response = await fetch(path, {
- ...init,
- headers: {
- ...(init?.headers || {}),
- },
- });
- const body = await response.json().catch(() => null);
- if (!response.ok || body?.ok === false) {
- throw new Error(body?.error || `请求失败(${response.status})`);
+ async function request(path, init, options = {}) {
+ const timeoutMs = options.timeoutMs === 0 ? 0 : options.timeoutMs || 45_000;
+ const controller = timeoutMs > 0 ? new AbortController() : null;
+ const timer = controller
+ ? setTimeout(() => controller.abort(new Error("background_request_timeout")), timeoutMs)
+ : null;
+ try {
+ const response = await fetch(path, {
+ ...init,
+ ...(controller ? { signal: controller.signal } : {}),
+ headers: {
+ ...(init?.headers || {}),
+ },
+ });
+ const body = await response.json().catch(() => null);
+ if (!response.ok || body?.ok === false) {
+ const error = new Error(body?.error || `请求失败(${response.status})`);
+ error.status = response.status;
+ error.code = body?.code || "";
+ throw error;
+ }
+ return body;
+ } catch (error) {
+ if (controller?.signal.aborted) {
+ throw new Error("背景操作超时,控件已恢复。原背景保持不变,请重试。");
+ }
+ throw error;
+ } finally {
+ if (timer) clearTimeout(timer);
}
- return body;
}
async function refresh() {
@@ -210,18 +271,146 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
async function run(task) {
if (busy) return;
busy = true;
- for (const button of pop.querySelectorAll(".bc-btn, .bc-theme-toggle, .bc-theme-item")) button.disabled = true;
- showMessage("");
+ pop.dataset.busy = "true";
+ let afterRun = null;
+ for (const button of pop.querySelectorAll(".bc-btn, .bc-theme-toggle, .bc-theme-item, .bc-theme-del")) button.disabled = true;
+ showMessage("正在处理,请稍候…");
try {
const result = await task();
- if (result?.message) showMessage(result.message);
+ if (typeof result?.afterRun === "function") afterRun = result.afterRun;
+ if (result?.theme?.id) currentThemeId = result.theme.id;
+ if (result?.message) {
+ const source =
+ result.sourceMode === "local"
+ ? "本地引用,未复制主媒体"
+ : result.sourceMode === "managed"
+ ? "托管副本"
+ : "";
+ const totalMs = Number(result.importTimings?.applyAndSaveMs ?? result.timings?.totalMs);
+ const duration = Number.isFinite(totalMs) ? `${Math.round(totalMs)} ms` : "";
+ showMessage([result.message, source, duration].filter(Boolean).join(" · "));
+ } else {
+ showMessage("");
+ }
await refresh();
} catch (error) {
showMessage(error instanceof Error ? error.message : String(error));
} finally {
busy = false;
- for (const button of pop.querySelectorAll(".bc-btn, .bc-theme-toggle, .bc-theme-item")) button.disabled = false;
+ delete pop.dataset.busy;
+ for (const button of pop.querySelectorAll(".bc-btn, .bc-theme-toggle, .bc-theme-item, .bc-theme-del")) button.disabled = false;
+ }
+ if (afterRun) queueMicrotask(afterRun);
+ }
+
+ function validateThemeName(value) {
+ const name = String(value || "").trim();
+ if (!name) return "主题名不能为空。";
+ if (name.length > 80) return "主题名不能超过 80 个字符。";
+ if (/[<>:"/\\|?*]/.test(name) || /[\u0000-\u001f]/.test(name)) {
+ return '主题名不能包含 < > : " / \\ | ? * 或控制字符。';
+ }
+ return "";
+ }
+
+ function defaultThemeName(fileName) {
+ const suggested = String(fileName || "")
+ .replace(/\.[^.]+$/, "")
+ .trim()
+ .slice(0, 80);
+ return suggested || "新主题";
+ }
+
+ function askThemeName(fileName, suggestedName, options = {}) {
+ return new Promise((resolve) => {
+ const dialog = document.createElement("div");
+ dialog.id = "beauticode-name-dialog";
+ dialog.innerHTML =
+ '
' +
+ '
给主题取个名字
' +
+ `
${escapeText(fileName)}
` +
+ (options.compatibilityUpload
+ ? '
原生选择器不可用,兼容模式会复制媒体文件。
'
+ : "") +
+ `
` +
+ '
' +
+ '
' +
+ "
";
+ document.body.append(dialog);
+ const input = dialog.querySelector('input[aria-label="主题名称"]');
+ const errorEl = dialog.querySelector(".bc-name-error");
+ let closed = false;
+
+ const close = (value) => {
+ if (closed) return;
+ closed = true;
+ dialog.remove();
+ resolve(value);
+ };
+ const confirm = () => {
+ const error = validateThemeName(input.value);
+ if (error) {
+ errorEl.hidden = false;
+ errorEl.textContent = error;
+ input.focus();
+ return;
+ }
+ close(input.value.trim());
+ };
+ dialog.querySelector('[data-name="confirm"]').addEventListener("click", confirm);
+ dialog.querySelector('[data-name="cancel"]').addEventListener("click", () => close(null));
+ input.addEventListener("input", () => {
+ errorEl.hidden = true;
+ errorEl.textContent = "";
+ });
+ input.addEventListener("keydown", (event) => {
+ if (event.key === "Enter") confirm();
+ if (event.key === "Escape") close(null);
+ });
+ dialog.addEventListener("click", (event) => {
+ if (event.target === dialog) close(null);
+ });
+ input.focus();
+ input.select();
+ });
+ }
+
+ async function pickAndImport(kind) {
+ let picked;
+ try {
+ picked = await request(
+ "/__beauticode/ui/pick",
+ {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ kind }),
+ },
+ // The user controls how long the native dialog stays open. Import and
+ // theme switching still use the bounded request timeout above.
+ { timeoutMs: 0 },
+ );
+ } catch (error) {
+ if (error?.code !== "native_picker_unavailable") throw error;
+ return {
+ ok: true,
+ afterRun: () => {
+ fileInput.accept = kind === "video" ? VIDEO_ACCEPT : IMAGE_ACCEPT;
+ fileInput.dataset.compatibilityUpload = "true";
+ fileInput.click();
+ },
+ };
}
+ if (picked.cancelled) return { ok: true };
+ const themeName = await askThemeName(
+ picked.name,
+ picked.suggestedThemeName,
+ );
+ if (!themeName) return { ok: true };
+ return request("/__beauticode/ui/import-selected", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ selectionId: picked.selectionId, themeName }),
+ });
}
trigger.addEventListener("click", (event) => {
@@ -229,16 +418,26 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
setOpen(pop.hidden);
});
pop.querySelector('[data-act="image"]').addEventListener("click", () => {
- fileInput.accept = IMAGE_ACCEPT;
- fileInput.click();
+ void run(() => pickAndImport("image"));
});
pop.querySelector('[data-act="video"]').addEventListener("click", () => {
- fileInput.accept = VIDEO_ACCEPT;
- fileInput.click();
+ void run(() => pickAndImport("video"));
+ });
+ pop.querySelector('[data-act="gallery"]').addEventListener("click", (event) => {
+ event.stopPropagation();
+ setOpen(false);
+ if (window.BeauticodeGallery) {
+ void window.BeauticodeGallery.open();
+ return;
+ }
+ showMessage("皮肤中心脚本尚未加载。");
});
pop.querySelector('[data-act="clear"]').addEventListener("click", () => {
- currentThemeId = "";
- void run(() => request("/__beauticode/ui/clear", { method: "POST" }));
+ void run(async () => {
+ const result = await request("/__beauticode/ui/clear", { method: "POST" });
+ currentThemeId = "";
+ return result;
+ });
});
soundBtn.addEventListener("click", () => {
void run(() =>
@@ -251,39 +450,68 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
});
themeToggle.addEventListener("click", (event) => {
event.stopPropagation();
- const open = themeList.hidden;
- themeList.hidden = !open;
- themeToggle.setAttribute("aria-expanded", open ? "true" : "false");
+ themesExpanded = !themesExpanded;
+ themeList.hidden = !themesExpanded;
+ themeToggle.querySelector("span:last-child").textContent = themesExpanded ? "−" : "+";
+ themeToggle.setAttribute("aria-expanded", themesExpanded ? "true" : "false");
});
themeList.addEventListener("click", (event) => {
+ const del = event.target.closest("[data-theme-delete]");
+ if (del) {
+ event.stopPropagation();
+ const id = del.getAttribute("data-theme-delete") || "";
+ const name = del.getAttribute("data-theme-name") || "主题";
+ if (!id) return;
+ if (!window.confirm(`确定删除主题「${name}」?`)) return;
+ void run(async () => {
+ const result = await request("/__beauticode/ui/theme/delete", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ id }),
+ });
+ if (currentThemeId === id) currentThemeId = "";
+ return result;
+ });
+ return;
+ }
const item = event.target.closest("[data-theme-id]");
if (!item) return;
- currentThemeId = item.getAttribute("data-theme-id") || "";
+ const targetThemeId = item.getAttribute("data-theme-id") || "";
themeList.hidden = true;
themeToggle.setAttribute("aria-expanded", "false");
- globalThis.BeauticodeAtmosphere?.setWindowMode?.(
- currentThemeId === "builtin-gallery" ? "on" : "closed",
- );
- void run(() =>
- request("/__beauticode/ui/theme/use", {
+ void run(async () => {
+ const result = await request("/__beauticode/ui/theme/use", {
method: "POST",
headers: { "content-type": "application/json" },
- body: JSON.stringify({ id: currentThemeId }),
- }),
- );
+ body: JSON.stringify({ id: targetThemeId }),
+ });
+ currentThemeId = targetThemeId;
+ globalThis.BeauticodeAtmosphere?.setWindowMode?.(
+ currentThemeId === "builtin-gallery" ? "on" : "closed",
+ );
+ return result;
+ });
});
fileInput.addEventListener("change", () => {
const file = fileInput.files?.[0];
+ const compatibilityUpload = fileInput.dataset.compatibilityUpload === "true";
fileInput.value = "";
+ delete fileInput.dataset.compatibilityUpload;
if (!file) return;
- currentThemeId = "";
- void run(() =>
- request("/__beauticode/ui/import", {
+ void run(async () => {
+ const themeName = await askThemeName(file.name, defaultThemeName(file.name), {
+ compatibilityUpload,
+ });
+ if (!themeName) return { ok: true };
+ return request("/__beauticode/ui/import", {
method: "POST",
- headers: { "x-beauticode-filename": encodeURIComponent(file.name) },
+ headers: {
+ "x-beauticode-filename": encodeURIComponent(file.name),
+ "x-beauticode-theme-name": encodeURIComponent(themeName),
+ },
body: file,
- }),
- );
+ });
+ });
});
document.addEventListener("click", (event) => {
@@ -294,6 +522,9 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && !pop.hidden) setOpen(false);
});
+ document.addEventListener("beauticode-gallery-installed", () => {
+ void refresh();
+ });
const observer = new MutationObserver(() => place());
observer.observe(document.documentElement, { childList: true, subtree: true });
diff --git a/integrations/deepseek-harness/control-client.mjs b/integrations/deepseek-harness/control-client.mjs
index 1eae13c..8d410eb 100644
--- a/integrations/deepseek-harness/control-client.mjs
+++ b/integrations/deepseek-harness/control-client.mjs
@@ -90,6 +90,47 @@ export function isPidAlive(pid) {
}
}
+let livenessModulePromise;
+
+function loadCoreLiveness() {
+ if (!livenessModulePromise) {
+ livenessModulePromise = (async () => {
+ const candidates = [
+ "@beauticode/core",
+ new URL("./vendor/core/index.js", import.meta.url).href,
+ new URL("../../packages/core/dist/index.js", import.meta.url).href,
+ ];
+ for (const specifier of candidates) {
+ try {
+ const mod = await import(specifier);
+ if (typeof mod.isRecordedPidLive === "function") return mod;
+ } catch {
+ /* try the next resolution path */
+ }
+ }
+ return null;
+ })();
+ }
+ return livenessModulePromise;
+}
+
+async function isLiveRecordedPid(pid, startedAt, mtimeMs) {
+ if (!isPidAlive(pid)) return false;
+ const recorded =
+ typeof startedAt === "string" && startedAt
+ ? startedAt
+ : Number.isFinite(mtimeMs)
+ ? new Date(mtimeMs).toISOString()
+ : null;
+ try {
+ const core = await loadCoreLiveness();
+ if (core) return await core.isRecordedPidLive(pid, recorded);
+ } catch {
+ /* fall back to the cheap PID check */
+ }
+ return true;
+}
+
export function stripPathQuotes(value) {
const text = String(value ?? "").trim();
if (text.length >= 2) {
@@ -195,6 +236,7 @@ export async function writeDshControlFile(opts) {
pid,
url: new URL(url).origin,
token,
+ startedAt: new Date().toISOString(),
});
}
@@ -219,6 +261,7 @@ export async function writeSessionHostFile(opts) {
pid,
url: new URL(url).origin,
token,
+ startedAt: new Date().toISOString(),
});
}
@@ -253,8 +296,11 @@ export async function removeDshControlFile(opts) {
export async function readDshControlFile(dataRoot, opts = {}) {
const file = controlFilePath(dataRoot);
let raw;
+ let mtimeMs = 0;
try {
- raw = await fs.readFile(file, "utf8");
+ const [text, stat] = await Promise.all([fs.readFile(file, "utf8"), fs.stat(file)]);
+ raw = text;
+ mtimeMs = stat.mtimeMs;
} catch (error) {
if (error && typeof error === "object" && error.code === "ENOENT") return null;
throw error;
@@ -277,13 +323,19 @@ export async function readDshControlFile(dataRoot, opts = {}) {
) {
return null;
}
- if (!opts.allowDead && !isPidAlive(parsed.pid)) return null;
+ if (
+ !opts.allowDead &&
+ !(await isLiveRecordedPid(parsed.pid, parsed.startedAt, mtimeMs))
+ ) {
+ return null;
+ }
return {
schema: CONTROL_SCHEMA,
host: "dsh",
pid: parsed.pid,
url: new URL(parsed.url).origin,
token: parsed.token,
+ startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : null,
};
}
@@ -305,8 +357,11 @@ export async function removeSessionHostFile(opts) {
export async function readSessionHostFile(dataRoot, opts = {}) {
const file = sessionHostFilePath(dataRoot);
let raw;
+ let mtimeMs = 0;
try {
- raw = await fs.readFile(file, "utf8");
+ const [text, stat] = await Promise.all([fs.readFile(file, "utf8"), fs.stat(file)]);
+ raw = text;
+ mtimeMs = stat.mtimeMs;
} catch (error) {
if (error && typeof error === "object" && error.code === "ENOENT") return null;
throw error;
@@ -329,21 +384,30 @@ export async function readSessionHostFile(dataRoot, opts = {}) {
) {
return null;
}
- if (!opts.allowDead && !isPidAlive(parsed.pid)) return null;
+ if (
+ !opts.allowDead &&
+ !(await isLiveRecordedPid(parsed.pid, parsed.startedAt, mtimeMs))
+ ) {
+ return null;
+ }
return {
schema: SESSION_HOST_SCHEMA,
host: parsed.host,
pid: parsed.pid,
url: new URL(parsed.url).origin,
token: parsed.token,
+ startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : null,
};
}
export async function readTrayClaim(dataRoot, opts = {}) {
const file = trayClaimFilePath(dataRoot);
let raw;
+ let mtimeMs = 0;
try {
- raw = await fs.readFile(file, "utf8");
+ const [text, stat] = await Promise.all([fs.readFile(file, "utf8"), fs.stat(file)]);
+ raw = text;
+ mtimeMs = stat.mtimeMs;
} catch (error) {
if (error && typeof error === "object" && error.code === "ENOENT") return null;
throw error;
@@ -362,7 +426,12 @@ export async function readTrayClaim(dataRoot, opts = {}) {
) {
return null;
}
- if (!opts.allowDead && !isPidAlive(parsed.pid)) return null;
+ if (
+ !opts.allowDead &&
+ !(await isLiveRecordedPid(parsed.pid, parsed.startedAt, mtimeMs))
+ ) {
+ return null;
+ }
return {
schema: TRAY_CLAIM_SCHEMA,
pid: parsed.pid,
@@ -439,6 +508,8 @@ export async function callDshControl(dataRoot, spec) {
const error = new Error(message);
error.statusCode = response.status;
error.payload = payload;
+ if (payload?.sourceMode != null) error.sourceMode = payload.sourceMode;
+ if (payload?.timings != null) error.timings = payload.timings;
throw error;
}
return payload;
diff --git a/integrations/deepseek-harness/cordis.patch.yml b/integrations/deepseek-harness/cordis.patch.yml
index 4b0da46..4c5c12a 100644
--- a/integrations/deepseek-harness/cordis.patch.yml
+++ b/integrations/deepseek-harness/cordis.patch.yml
@@ -1,4 +1,4 @@
- insert:
- id: beauticode-bridge
- name: '@beauticode/dsh-plugin'
+ name: beauticode-dsh
inject: [webServer]
diff --git a/integrations/deepseek-harness/gallery-host.mjs b/integrations/deepseek-harness/gallery-host.mjs
new file mode 100644
index 0000000..cd3f1a8
--- /dev/null
+++ b/integrations/deepseek-harness/gallery-host.mjs
@@ -0,0 +1,253 @@
+import fs from "node:fs";
+import fsp from "node:fs/promises";
+import path from "node:path";
+import crypto from "node:crypto";
+import { Readable } from "node:stream";
+import { Transform } from "node:stream";
+import { pipeline } from "node:stream/promises";
+import { fileURLToPath } from "node:url";
+
+const here = path.dirname(fileURLToPath(import.meta.url));
+const SKIN_ID = /^skin-[a-z0-9]{8,40}$/;
+const MAX_IMAGE_BYTES = 18 * 1024 * 1024;
+const MAX_VIDEO_BYTES = 800 * 1024 * 1024;
+const INSTALL_TIMEOUT_MS = 30 * 60 * 1000;
+const LOOPBACK = new Set(["127.0.0.1", "localhost", "::1"]);
+
+export function isSafeSkinId(id) {
+ return typeof id === "string" && SKIN_ID.test(id);
+}
+
+export function normalizeSkinCenterUrl(value) {
+ if (typeof value !== "string") return null;
+ const trimmed = value.trim();
+ if (!trimmed) return null;
+ try {
+ const url = new URL(trimmed);
+ if (url.username || url.password || url.hash) return null;
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
+ if (url.protocol === "http:" && !LOOPBACK.has(url.hostname.toLowerCase())) return null;
+ const pathname = url.pathname === "/" ? "" : url.pathname.replace(/\/+$/, "");
+ return `${url.origin}${pathname}`;
+ } catch {
+ return null;
+ }
+}
+
+export async function readBundledSkinCenterUrl() {
+ try {
+ const raw = JSON.parse(await fsp.readFile(path.join(here, "skin-center.json"), "utf8"));
+ return normalizeSkinCenterUrl(raw.url);
+ } catch {
+ return null;
+ }
+}
+
+export async function resolveConfiguredSkinCenterUrl() {
+ return (
+ normalizeSkinCenterUrl(process.env.BEAUTICODE_SKIN_CENTER) ??
+ (await readBundledSkinCenterUrl())
+ );
+}
+
+export function skinUrl(center, id, part = "") {
+ const origin = normalizeSkinCenterUrl(center);
+ if (!origin || !isSafeSkinId(id)) {
+ throw new Error("Skin center is not configured.");
+ }
+ return part ? `${origin}/api/skins/${id}/${part}` : `${origin}/api/skins/${id}`;
+}
+
+export async function downloadToFile(url, dest, { maxBytes, expectedOrigin, onProgress } = {}) {
+ const expected = new URL(url);
+ if (expectedOrigin && expected.origin !== expectedOrigin) {
+ throw new Error("Skin media download host mismatch.");
+ }
+ const response = await fetch(url, { redirect: "follow" });
+ if (!response.ok || !response.body) {
+ throw new Error("Skin media download failed.");
+ }
+ const finalUrl = new URL(response.url);
+ if (finalUrl.origin !== expected.origin) {
+ throw new Error("Skin media download host mismatch.");
+ }
+ const length = Number(response.headers.get("content-length"));
+ if (Number.isFinite(length) && length > maxBytes) {
+ throw new Error("Skin media download exceeded the size limit.");
+ }
+ await fsp.mkdir(path.dirname(dest), { recursive: true });
+ let size = 0;
+ const limiter = new Transform({
+ transform(chunk, _enc, callback) {
+ size += chunk.length;
+ if (size > maxBytes) {
+ callback(new Error("Skin media download exceeded the size limit."));
+ return;
+ }
+ onProgress?.(size, Number.isFinite(length) ? length : 0);
+ callback(null, chunk);
+ },
+ });
+ await pipeline(Readable.fromWeb(response.body), limiter, fs.createWriteStream(dest));
+ return { bytes: size };
+}
+
+function extensionOf(url, fallback) {
+ try {
+ const ext = path.extname(new URL(url).pathname).toLowerCase();
+ if (ext) return ext;
+ } catch {
+ /* use fallback */
+ }
+ return fallback;
+}
+
+export function createGalleryHandlers({ dataRoot, actions }) {
+ async function importTheme(input) {
+ if (typeof actions.importTheme === "function") {
+ return actions.importTheme(input);
+ }
+ throw new Error("当前引擎不支持导入皮肤。");
+ }
+
+ return {
+ async config(req, res, sendJson, isSameOrigin) {
+ if (req.method !== "GET") {
+ res.writeHead(405).end();
+ return;
+ }
+ if (!isSameOrigin(req)) {
+ res.writeHead(403).end();
+ return;
+ }
+ const url = await resolveConfiguredSkinCenterUrl();
+ sendJson(res, 200, { ok: true, url, enabled: Boolean(url) });
+ },
+
+ async catalog(req, res, sendJson, isSameOrigin) {
+ if (req.method !== "GET") {
+ res.writeHead(405).end();
+ return;
+ }
+ if (!isSameOrigin(req)) {
+ res.writeHead(403).end();
+ return;
+ }
+ const center = await resolveConfiguredSkinCenterUrl();
+ if (!center) {
+ sendJson(res, 200, {
+ ok: false,
+ error: "尚未配置皮肤中心地址。",
+ skins: [],
+ });
+ return;
+ }
+ const incoming = new URL(req.url || "/", "http://127.0.0.1");
+ const target = new URL("/api/catalog", `${center}/`);
+ target.search = incoming.search;
+ const response = await fetch(target, { headers: { accept: "application/json" } });
+ const body = await response.json().catch(() => null);
+ if (!response.ok || !body || body.ok === false) {
+ sendJson(res, 422, {
+ ok: false,
+ error: body?.error || "无法读取皮肤目录。",
+ skins: [],
+ });
+ return;
+ }
+ sendJson(res, 200, {
+ ok: true,
+ skins: Array.isArray(body.skins) ? body.skins : [],
+ nextCursor: body.nextCursor ?? null,
+ url: center,
+ });
+ },
+
+ async install(req, res, sendJson, isSameOrigin, readJson) {
+ if (req.method !== "POST") {
+ res.writeHead(405).end();
+ return;
+ }
+ if (!isSameOrigin(req)) {
+ res.writeHead(403).end();
+ return;
+ }
+ const body = await readJson(req);
+ const id = String(body.id ?? "").trim();
+ if (!isSafeSkinId(id)) {
+ sendJson(res, 400, { ok: false, error: "皮肤 ID 无效。" });
+ return;
+ }
+ const center = await resolveConfiguredSkinCenterUrl();
+ if (!center) {
+ sendJson(res, 422, { ok: false, error: "尚未配置皮肤中心地址。" });
+ return;
+ }
+ const origin = new URL(center).origin;
+ res.writeHead(200, {
+ "content-type": "application/x-ndjson; charset=utf-8",
+ "cache-control": "no-store",
+ });
+ const write = (payload) => {
+ if (!res.writableEnded) res.write(`${JSON.stringify(payload)}\n`);
+ };
+ const tmpDir = path.join(dataRoot, "tmp", "gallery", `${Date.now()}-${crypto.randomBytes(4).toString("hex")}`);
+ try {
+ write({ phase: "fetch" });
+ const metaRes = await fetch(skinUrl(center, id), { headers: { accept: "application/json" } });
+ const meta = await metaRes.json().catch(() => null);
+ const skin = meta?.skin;
+ if (!metaRes.ok || !skin || skin.status && skin.status !== "approved") {
+ throw new Error("Skin is not available for download.");
+ }
+ await fsp.mkdir(tmpDir, { recursive: true });
+ const imageUrl = skinUrl(center, id, "image");
+ const imagePath = path.join(tmpDir, `image${extensionOf(imageUrl, ".png")}`);
+ write({ phase: "download", part: "image" });
+ await downloadToFile(imageUrl, imagePath, {
+ maxBytes: MAX_IMAGE_BYTES,
+ expectedOrigin: origin,
+ onProgress: (done, total) => write({ phase: "download", part: "image", done, total }),
+ });
+ let videoPath;
+ if (skin.type === "video") {
+ const videoUrl = skinUrl(center, id, "video");
+ videoPath = path.join(tmpDir, "background.mp4");
+ write({ phase: "download", part: "video" });
+ await downloadToFile(videoUrl, videoPath, {
+ maxBytes: MAX_VIDEO_BYTES,
+ expectedOrigin: origin,
+ onProgress: (done, total) => write({ phase: "download", part: "video", done, total }),
+ });
+ }
+ write({ phase: "import" });
+ const imported = await importTheme({
+ name: String(skin.name || id).slice(0, 80),
+ imagePath,
+ ...(videoPath ? { videoPath } : {}),
+ ...(skin.effects ? { effects: skin.effects } : {}),
+ source: { kind: "skin-center", skinId: id, centerUrl: center },
+ });
+ write({ phase: "apply" });
+ const applied = await actions.useTheme(imported.theme?.id || imported.id, undefined);
+ fetch(skinUrl(center, id, "download"), { method: "POST" }).catch(() => {});
+ write({
+ ok: true,
+ phase: "done",
+ theme: imported.theme || imported,
+ message: applied.message || `已安装并应用「${skin.name}」。`,
+ });
+ } catch (error) {
+ write({
+ ok: false,
+ error: error instanceof Error ? error.message : String(error),
+ });
+ } finally {
+ await fsp.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
+ res.end();
+ }
+ },
+ };
+}
+
+export const GALLERY_INSTALL_TIMEOUT_MS = INSTALL_TIMEOUT_MS;
diff --git a/integrations/deepseek-harness/gallery.js b/integrations/deepseek-harness/gallery.js
new file mode 100644
index 0000000..64ac3b9
--- /dev/null
+++ b/integrations/deepseek-harness/gallery.js
@@ -0,0 +1,178 @@
+(() => {
+ "use strict";
+ if (window.__beauticodeGalleryLoaded) return;
+ window.__beauticodeGalleryLoaded = true;
+
+ const style = document.createElement("style");
+ style.textContent = `
+#beauticode-gallery{position:fixed;inset:0;z-index:3000;display:flex;align-items:center;justify-content:center;background:rgba(11,13,18,.62)}
+#beauticode-gallery[hidden]{display:none}
+#beauticode-gallery .bcg-panel{width:min(880px,calc(100vw - 32px));height:min(640px,calc(100vh - 32px));display:flex;flex-direction:column;border:1px solid rgba(255,255,255,.08);border-radius:18px;background:#2c323c;color:#e8eaed;box-shadow:0 16px 48px rgba(0,0,0,.4);overflow:hidden}
+body:not([data-ds-dark-theme]) #beauticode-gallery .bcg-panel{background:#fff;color:#1b1f24;border-color:rgba(0,0,0,.08)}
+#beauticode-gallery .bcg-head{display:flex;align-items:center;gap:10px;padding:12px 14px;border-bottom:1px solid rgba(255,255,255,.08)}
+#beauticode-gallery .bcg-head h2{margin:0;font-size:15px;font-weight:600}
+#beauticode-gallery .bcg-head input,#beauticode-gallery .bcg-head select{height:32px;border:1px solid rgba(255,255,255,.1);border-radius:10px;background:rgba(255,255,255,.06);color:inherit;padding:0 8px}
+#beauticode-gallery .bcg-grid{flex:1;overflow:auto;padding:12px;display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;align-content:start}
+#beauticode-gallery .bcg-card{display:block;border:none;padding:0;border-radius:12px;overflow:hidden;background:#232830;color:inherit;text-align:left;cursor:pointer}
+#beauticode-gallery .bcg-card img{width:100%;aspect-ratio:16/10;object-fit:cover;display:block;background:#111}
+#beauticode-gallery .bcg-card span{display:block;padding:8px 10px;font-size:13px}
+#beauticode-gallery .bcg-msg,#beauticode-gallery .bcg-foot{padding:0 14px 12px;color:#9aa3ad;font-size:12px}
+#beauticode-gallery .bcg-close{margin-left:auto}
+#beauticode-gallery .bcg-btn{height:32px;padding:0 10px;border:1px solid rgba(255,255,255,.1);border-radius:10px;background:rgba(255,255,255,.06);color:inherit;cursor:pointer}
+#beauticode-gallery .bcg-btn.primary{background:#4d6bfe;border-color:transparent}
+ `;
+ document.head.append(style);
+
+ const host = document.createElement("div");
+ host.id = "beauticode-gallery";
+ host.hidden = true;
+ host.innerHTML =
+ '
' +
+ '
' +
+ '
皮肤中心
' +
+ '' +
+ '' +
+ '' +
+ "" +
+ '
' +
+ '
' +
+ '' +
+ "
";
+ document.body.append(host);
+
+ const grid = host.querySelector(".bcg-grid");
+ const msg = host.querySelector(".bcg-msg");
+ const foot = host.querySelector(".bcg-foot");
+ const queryInput = host.querySelector(".bcg-q");
+ const typeSelect = host.querySelector(".bcg-type");
+ let centerUrl = "";
+ let busy = false;
+
+ function escapeText(value) {
+ return String(value ?? "")
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">");
+ }
+
+ async function request(path, init) {
+ const response = await fetch(path, init);
+ if ((response.headers.get("content-type") || "").includes("ndjson")) {
+ return readNdjson(response);
+ }
+ const body = await response.json().catch(() => null);
+ if (!response.ok || body?.ok === false) {
+ throw new Error(body?.error || `请求失败(${response.status})`);
+ }
+ return body;
+ }
+
+ async function readNdjson(response) {
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let buffer = "";
+ let last = null;
+ while (true) {
+ const { value, done } = await reader.read();
+ buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
+ const lines = buffer.split("\n");
+ buffer = lines.pop() || "";
+ for (const line of lines) {
+ if (!line.trim()) continue;
+ last = JSON.parse(line);
+ if (last.phase === "download" && last.total) {
+ const pct = Math.round((last.done / last.total) * 100);
+ msg.textContent = `正在下载${last.part === "video" ? "视频" : "图片"} ${pct}%`;
+ } else if (last.phase === "import") {
+ msg.textContent = "正在写入本机主题…";
+ } else if (last.phase === "apply") {
+ msg.textContent = "正在应用到当前窗口…";
+ }
+ if (last.ok === false) throw new Error(last.error || "安装失败。");
+ }
+ if (done) break;
+ }
+ if (last?.ok) return last;
+ throw new Error(last?.error || "安装失败。");
+ }
+
+ async function load() {
+ msg.textContent = "正在读取目录…";
+ const params = new URLSearchParams();
+ if (queryInput.value.trim()) params.set("q", queryInput.value.trim());
+ if (typeSelect.value) params.set("type", typeSelect.value);
+ const data = await request(`/__beauticode/ui/gallery/catalog?${params}`);
+ centerUrl = data.url || centerUrl;
+ grid.innerHTML = (data.skins || [])
+ .map(
+ (skin) =>
+ `
`,
+ )
+ .join("");
+ msg.textContent = data.skins?.length ? "" : "目录是空的。";
+ foot.innerHTML = centerUrl
+ ? `上传与审核在
皮肤中心网站。安装会下载到本机后再应用。`
+ : "未配置皮肤中心地址。在插件的 skin-center.json 或环境变量 BEAUTICODE_SKIN_CENTER 里填入你的域名。";
+ }
+
+ async function open() {
+ host.hidden = false;
+ const config = await request("/__beauticode/ui/gallery/config");
+ centerUrl = config.url || "";
+ if (!config.enabled) {
+ grid.innerHTML = "";
+ msg.textContent = "尚未配置皮肤中心地址。";
+ foot.textContent = "设置 BEAUTICODE_SKIN_CENTER,或在 skin-center.json 填写站点 URL。";
+ return;
+ }
+ await load();
+ }
+
+ function close() {
+ host.hidden = true;
+ }
+
+ host.querySelector(".bcg-close").addEventListener("click", close);
+ host.addEventListener("click", (event) => {
+ if (event.target === host) close();
+ });
+ queryInput.addEventListener("keydown", (event) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ void load();
+ }
+ });
+ typeSelect.addEventListener("change", () => void load());
+ grid.addEventListener("click", (event) => {
+ const card = event.target.closest("[data-id]");
+ if (!card || busy) return;
+ const id = card.getAttribute("data-id");
+ busy = true;
+ msg.textContent = "开始安装…";
+ request("/__beauticode/ui/gallery/install", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ id }),
+ })
+ .then((result) => {
+ msg.textContent = result.message || "已安装。";
+ document.dispatchEvent(new CustomEvent("beauticode-gallery-installed"));
+ })
+ .catch((error) => {
+ msg.textContent = error instanceof Error ? error.message : String(error);
+ })
+ .finally(() => {
+ busy = false;
+ });
+ });
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && !host.hidden) {
+ event.stopPropagation();
+ close();
+ }
+ });
+
+ window.BeauticodeGallery = { open, close };
+})();
diff --git a/integrations/deepseek-harness/host-apply.mjs b/integrations/deepseek-harness/host-apply.mjs
index 008a7e5..987b4e0 100644
--- a/integrations/deepseek-harness/host-apply.mjs
+++ b/integrations/deepseek-harness/host-apply.mjs
@@ -8,6 +8,7 @@ import {
import { canvasImagePath } from "./presets.mjs";
const sessions = new Map();
+const DSH_VERIFY_DEADLINE_MS = 10_000;
export const ENGINE_MISSING_MESSAGE =
"beautiCode 插件未能加载本机导入引擎。请执行 npx beauticode-dsh,或从完整安装目录加载桥接。";
@@ -92,7 +93,7 @@ export async function ensureInProcessSession(options) {
const session = new adapter.DshSession({
dataRoot,
baseUrl,
- verifyDeadlineMs: 30_000,
+ verifyDeadlineMs: DSH_VERIFY_DEADLINE_MS,
bundledGalleryImagePath: canvasImagePath() || undefined,
});
try {
diff --git a/integrations/deepseek-harness/index.mjs b/integrations/deepseek-harness/index.mjs
index 2284179..55e4a21 100644
--- a/integrations/deepseek-harness/index.mjs
+++ b/integrations/deepseek-harness/index.mjs
@@ -168,12 +168,30 @@ function publicStatus(current, modes, clients, clientStates) {
const playback =
activeAcks.find((ack) => ack.ok === true && ack.playback?.hasVideo === true)
?.playback ?? null;
+ // Only an explicit renderer error is terminal. Older clients may send a
+ // transient ok:false heartbeat while a large video is still warming up.
+ const failedAcks = activeAcks.filter(
+ (ack) => ack.ok === false && typeof ack.error === "string" && ack.error,
+ );
+ const readyAcks = activeAcks.filter((ack) => ack.ok === true);
+ // Poster-first video commits ack ok while the first frame still settles.
+ const videoPendingAcks = readyAcks.filter(
+ (ack) => ack.media === "video" && ack.videoReady === false,
+ );
return {
ok: true,
connectedClients: clients.size,
current,
- readyClients: activeAcks.filter((ack) => ack.ok === true).length,
- failedClients: activeAcks.filter((ack) => ack.ok !== true).length,
+ readyClients: readyAcks.length,
+ failedClients: failedAcks.length,
+ videoReadyClients: readyAcks.length - videoPendingAcks.length,
+ videoPendingClients: videoPendingAcks.length,
+ lastVideoError:
+ videoPendingAcks.find((ack) => typeof ack.error === "string" && ack.error)?.error ??
+ null,
+ lastRenderError:
+ failedAcks.find((ack) => typeof ack.error === "string" && ack.error)?.error ??
+ null,
visibleClients: activeAcks.filter((ack) => ack.ok === true && ack.visible === true).length,
modeReadyClients: modeReady.length,
blockedClients: modeReady.filter((ack) => ack.blocked === true).length,
@@ -198,6 +216,10 @@ export function apply(ctx, config = {}) {
sendJson,
isSameOrigin,
readJson,
+ pickMedia: config.pickMedia,
+ allowManagedUpload: config.allowManagedUpload,
+ now: config.now,
+ selectionTtlMs: config.selectionTtlMs,
});
const broadcast = (payload) => {
@@ -211,7 +233,8 @@ export function apply(ctx, config = {}) {
const script =
'' +
'' +
- '';
+ '' +
+ '';
return html.includes("