fix:优化当前的项目

This commit is contained in:
Daniel
2026-04-28 18:36:38 +08:00
parent 04f26bdaaf
commit f47453a656
22 changed files with 3671 additions and 89 deletions

View File

@@ -18,6 +18,8 @@ const imBtn = $("imBtn");
const coverUploadBtn = $("coverUploadBtn");
const coverUrlUploadBtn = $("coverUrlUploadBtn");
const coverGenerateBtn = $("coverGenerateBtn");
const saveCoverImageModelBtn = $("saveCoverImageModelBtn");
const coverImageModelInput = $("coverImageModel");
const coverModeManualBtn = $("coverModeManualBtn");
const coverModeAiBtn = $("coverModeAiBtn");
const coverManualSection = $("coverManualSection");
@@ -26,11 +28,13 @@ const coverAutoAfterRewrite = $("coverAutoAfterRewrite");
const coverPreview = $("coverPreview");
const coverPreviewWrap = $("coverPreviewWrap");
const logoutBtn = $("logoutBtn");
const clearDraftBtn = $("clearDraftBtn");
const targetBodyCharsInput = $("targetBodyChars");
const posterGenerateBtn = $("posterGenerateBtn");
const posterPreviewList = $("posterPreviewList");
const posterHint = $("posterHint");
const posterAutoInclude = $("posterAutoInclude");
const DRAFT_STORAGE_KEY = "aifagao:index:draft:v1";
let posterState = {
signature: "",
@@ -125,6 +129,123 @@ function setStatus(msg, danger = false) {
statusEl.textContent = msg;
}
function saveDraftState() {
try {
const data = {
sourceText: ($("sourceText") && $("sourceText").value) || "",
titleHint: ($("titleHint") && $("titleHint").value) || "",
audienceExtra: ($("audienceExtra") && $("audienceExtra").value) || "",
toneExtra: ($("toneExtra") && $("toneExtra").value) || "",
avoidWords: ($("avoidWords") && $("avoidWords").value) || "",
keepPoints: ($("keepPoints") && $("keepPoints").value) || "",
targetBodyChars: ($("targetBodyChars") && $("targetBodyChars").value) || "500",
title: ($("title") && $("title").value) || "",
summary: ($("summary") && $("summary").value) || "",
body: ($("body") && $("body").value) || "",
thumbMediaId: ($("thumbMediaId") && $("thumbMediaId").value) || "",
coverStyleHint: ($("coverStyleHint") && $("coverStyleHint").value) || "",
coverImageModel: (coverImageModelInput && coverImageModelInput.value) || "",
coverAutoAfterRewrite: Boolean(coverAutoAfterRewrite && coverAutoAfterRewrite.checked),
posterAutoInclude: Boolean(posterAutoInclude && posterAutoInclude.checked),
audienceChipValues: Array.from(document.querySelectorAll('input[name="audienceChip"]:checked')).map((n) => n.value),
toneChipValues: Array.from(document.querySelectorAll('input[name="toneChip"]:checked')).map((n) => n.value),
};
window.localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(data));
} catch {
// ignore
}
}
function restoreDraftState() {
try {
const raw = window.localStorage.getItem(DRAFT_STORAGE_KEY);
if (!raw) return;
const data = JSON.parse(raw);
if (!data || typeof data !== "object") return;
const setVal = (id, val) => {
const el = $(id);
if (!el || typeof val !== "string") return;
el.value = val;
};
setVal("sourceText", data.sourceText || "");
setVal("titleHint", data.titleHint || "");
setVal("audienceExtra", data.audienceExtra || "");
setVal("toneExtra", data.toneExtra || "");
setVal("avoidWords", data.avoidWords || "");
setVal("keepPoints", data.keepPoints || "");
setVal("targetBodyChars", String(data.targetBodyChars || "500"));
setVal("title", data.title || "");
setVal("summary", data.summary || "");
setVal("body", data.body || "");
setVal("thumbMediaId", data.thumbMediaId || "");
setVal("coverStyleHint", data.coverStyleHint || "");
if (coverImageModelInput && typeof data.coverImageModel === "string" && data.coverImageModel.trim()) {
coverImageModelInput.value = data.coverImageModel;
}
if (coverAutoAfterRewrite) coverAutoAfterRewrite.checked = Boolean(data.coverAutoAfterRewrite);
if (posterAutoInclude) posterAutoInclude.checked = Boolean(data.posterAutoInclude);
const audienceSet = new Set(Array.isArray(data.audienceChipValues) ? data.audienceChipValues : []);
document.querySelectorAll('input[name="audienceChip"]').forEach((el) => {
el.checked = audienceSet.size ? audienceSet.has(el.value) : el.checked;
});
const toneSet = new Set(Array.isArray(data.toneChipValues) ? data.toneChipValues : []);
document.querySelectorAll('input[name="toneChip"]').forEach((el) => {
el.checked = toneSet.size ? toneSet.has(el.value) : el.checked;
});
} catch {
// ignore
}
}
function clearDraftState() {
try {
window.localStorage.removeItem(DRAFT_STORAGE_KEY);
} catch {
// ignore
}
const clearIds = [
"sourceText",
"titleHint",
"audienceExtra",
"toneExtra",
"avoidWords",
"keepPoints",
"title",
"summary",
"body",
"thumbMediaId",
"coverStyleHint",
"coverUrl",
];
clearIds.forEach((id) => {
const el = $(id);
if (!el) return;
el.value = "";
});
if ($("targetBodyChars")) $("targetBodyChars").value = "500";
if (coverAutoAfterRewrite) coverAutoAfterRewrite.checked = false;
if (posterAutoInclude) posterAutoInclude.checked = false;
if (coverImageModelInput) coverImageModelInput.value = "";
document.querySelectorAll('input[name="audienceChip"]').forEach((el) => {
el.checked = false;
});
document.querySelectorAll('input[name="toneChip"]').forEach((el) => {
el.checked = false;
});
if (coverPreviewWrap) coverPreviewWrap.hidden = true;
if (coverPreview) coverPreview.src = "";
if ($("coverFile")) $("coverFile").value = "";
posterState = { signature: "", bodyMarkdownWithPosters: "", posters: [] };
renderPosterPreview([]);
updateCounters();
syncTargetCharChips();
initMultiDropdowns();
initImageModelStatus();
if (posterHint) posterHint.textContent = "默认不生成海报,点击“生成段落海报”后再插入发布。";
setStatus("草稿已清除。");
}
function buildPosterSignature() {
const title = ($("title") && $("title").value.trim()) || "";
const summary = ($("summary") && $("summary").value.trim()) || "";
@@ -183,7 +304,7 @@ function renderPosterPreview(posters) {
function markPosterStaleIfNeeded() {
if (!posterState.signature || !posterHint) return;
if (posterState.signature !== buildPosterSignature()) {
posterHint.textContent = "正文已修改,发布前会自动重建段落海报。";
posterHint.textContent = "正文已修改,如需海报请手动点击“生成段落海报。";
}
}
@@ -194,11 +315,26 @@ async function postJSON(url, body) {
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || "请求失败");
if (!res.ok) {
const err = new Error(data.detail || "请求失败");
err.payload = data;
throw err;
}
data._requestId = res.headers.get("X-Request-ID") || "";
return data;
}
function handleUpgradeRequired(err) {
const data = (err && err.payload) || {};
const msg = (err && err.message) || data.detail || "";
if (!data.upgrade_required && !msg.includes("免费额度已用完") && !msg.includes("余额不足")) return false;
setStatus("免费额度已用完,请前往升级页充值或升级 VIP 用户。", true);
window.setTimeout(() => {
window.location.href = "/upgrade";
}, 800);
return true;
}
async function generatePosterMaterials({ silent = false } = {}) {
const bodyMarkdown = (($("body") && $("body").value) || "").trim();
if (bodyMarkdown.length < 20) {
@@ -212,9 +348,14 @@ async function generatePosterMaterials({ silent = false } = {}) {
title: $("title").value,
summary: $("summary").value,
body_markdown: $("body").value,
image_model: (coverImageModelInput && coverImageModelInput.value.trim()) || "",
upload_to_wechat: true,
});
if (!data.ok) throw new Error(data.detail || "海报生成失败");
if (!data.ok) {
const err = new Error(data.detail || "海报生成失败");
err.payload = data;
throw err;
}
posterState = {
signature: buildPosterSignature(),
@@ -252,9 +393,14 @@ async function generateWechatCover({ silent = false } = {}) {
title,
summary: (($("summary") && $("summary").value) || "").trim(),
style_hint: (($("coverStyleHint") && $("coverStyleHint").value) || "").trim(),
image_model: (coverImageModelInput && coverImageModelInput.value.trim()) || "",
upload_to_wechat: true,
});
if (!data.ok) throw new Error(data.detail || "封面生成失败");
if (!data.ok) {
const err = new Error(data.detail || "封面生成失败");
err.payload = data;
throw err;
}
const mid = data.thumb_media_id || "";
if (mid && $("thumbMediaId")) $("thumbMediaId").value = mid;
if (data.preview_data_url && coverPreview && coverPreviewWrap) {
@@ -353,6 +499,20 @@ async function initWechatAccountSwitch() {
if (me) renderWechatAccountSelect(me);
}
async function initImageModelStatus() {
try {
const me = await fetch("/api/auth/me").then((r) => r.json());
const active = me && me.active_ai_model ? me.active_ai_model : null;
const imageModel = active && active.image_model ? String(active.image_model).trim() : "";
if (coverImageModelInput) {
const current = (coverImageModelInput.value || "").trim();
if (!current) coverImageModelInput.value = imageModel || "wanx2.0-t2i-turbo";
}
} catch {
if (coverImageModelInput && !(coverImageModelInput.value || "").trim()) coverImageModelInput.value = "wanx2.0-t2i-turbo";
}
}
async function logoutAndGoAuth() {
try {
await postJSON("/api/auth/logout", {});
@@ -369,6 +529,12 @@ if (logoutBtn) {
});
}
if (clearDraftBtn) {
clearDraftBtn.addEventListener("click", () => {
clearDraftState();
});
}
if (coverModeManualBtn) {
coverModeManualBtn.addEventListener("click", () => setCoverMode("manual"));
}
@@ -419,6 +585,7 @@ $("rewriteBtn").addEventListener("click", async () => {
$("summary").value = data.summary || "";
$("body").value = data.body_markdown || "";
updateCounters();
saveDraftState();
const tr = data.trace || {};
if (data.mode === "fallback") {
const note = (data.quality_notes || [])[0] || "当前为保底改写稿";
@@ -429,23 +596,19 @@ $("rewriteBtn").addEventListener("click", async () => {
} else {
setStatus("改写完成。");
}
try {
setStatus("改写完成,正在生成段落海报...");
await generatePosterMaterials({ silent: true });
setStatus("改写与段落海报生成完成。");
} catch (posterErr) {
setStatus(`改写完成,段落海报未生成:${posterErr.message}`, true);
}
if (posterHint) posterHint.textContent = "改写完成。默认不自动生成海报,可手动点击“生成段落海报”。";
if (coverAutoAfterRewrite && coverAutoAfterRewrite.checked) {
try {
setStatus("改写完成,正在按输出标题生成封面...");
await generateWechatCover({ silent: true });
setStatus("改写、封面与段落海报生成完成。");
} catch (coverErr) {
if (handleUpgradeRequired(coverErr)) return;
setStatus(`改写完成,封面未生成:${coverErr.message}`, true);
}
}
} catch (e) {
if (handleUpgradeRequired(e)) return;
setStatus(`改写失败: ${e.message}`, true);
} finally {
setLoading(rewriteBtn, false, "改写并排版", "改写中...");
@@ -460,15 +623,10 @@ $("wechatBtn").addEventListener("click", async () => {
const autoInclude = Boolean(posterAutoInclude && posterAutoInclude.checked);
if (autoInclude) {
const stale = posterState.signature !== buildPosterSignature() || !posterState.bodyMarkdownWithPosters;
if (stale) {
try {
await generatePosterMaterials({ silent: true });
} catch (posterErr) {
setStatus(`海报生成失败,本次仅发布文字:${posterErr.message}`, true);
}
}
if (posterState.bodyMarkdownWithPosters) {
if (!stale && posterState.bodyMarkdownWithPosters) {
bodyForPublish = posterState.bodyMarkdownWithPosters;
} else {
setStatus("未检测到可用海报,本次仅发布文字;如需海报请先手动生成。", true);
}
}
const data = await postJSON("/api/publish/wechat", {
@@ -495,7 +653,9 @@ if (coverGenerateBtn) {
coverGenerateBtn.addEventListener("click", async () => {
try {
await generateWechatCover({ silent: false });
saveDraftState();
} catch (e) {
if (handleUpgradeRequired(e)) return;
const hint = $("coverHint");
if (hint) hint.textContent = "AI 封面生成失败,请检查标题、模型或公众号配置。";
setStatus(`封面生成失败: ${e.message}`, true);
@@ -508,6 +668,30 @@ if (coverGenerateBtn) {
});
}
if (saveCoverImageModelBtn) {
saveCoverImageModelBtn.addEventListener("click", async () => {
const value = (coverImageModelInput && coverImageModelInput.value.trim()) || "";
if (!value) {
setStatus("请先填写文生图模型", true);
return;
}
setLoading(saveCoverImageModelBtn, true, "保存模型", "保存中...");
try {
const out = await postJSON("/api/auth/ai-models/image-model/update", { image_model: value });
if (!out.ok) {
setStatus(out.detail || "保存失败", true);
return;
}
setStatus("文生图模型已保存。");
saveDraftState();
} catch (e) {
setStatus(e.message || "保存失败", true);
} finally {
setLoading(saveCoverImageModelBtn, false, "保存模型", "保存中...");
}
});
}
if (coverUploadBtn) {
coverUploadBtn.addEventListener("click", async () => {
const fileInput = $("coverFile");
@@ -529,6 +713,7 @@ if (coverUploadBtn) {
if ($("thumbMediaId")) $("thumbMediaId").value = mid;
if (hint) hint.textContent = `封面上传成功,已绑定 media_id${mid}`;
setStatus("封面上传成功,发布时将优先使用该封面。");
saveDraftState();
} catch (e) {
if (hint) hint.textContent = "封面上传失败,请看状态提示。";
setStatus(`封面上传失败: ${e.message}`, true);
@@ -561,6 +746,7 @@ if (coverUrlUploadBtn) {
if (hint) hint.textContent = `URL 封面上传成功,已绑定 media_id${mid}`;
setStatus("URL 封面上传成功,发布时将优先使用该封面。");
if ($("coverUrl")) $("coverUrl").value = "";
saveDraftState();
} catch (e) {
if (hint) hint.textContent = "URL 封面上传失败,请看状态提示。";
setStatus(`URL 封面上传失败: ${e.message}`, true);
@@ -579,7 +765,9 @@ if (posterGenerateBtn) {
posterGenerateBtn.addEventListener("click", async () => {
try {
await generatePosterMaterials({ silent: false });
saveDraftState();
} catch (e) {
if (handleUpgradeRequired(e)) return;
setStatus(`海报生成失败: ${e.message}`, true);
if (posterHint) posterHint.textContent = "海报生成失败,请检查配置后重试。";
if ((e.message || "").includes("请先登录")) {
@@ -610,13 +798,30 @@ $("imBtn").addEventListener("click", async () => {
["sourceText", "title", "summary", "body"].forEach((id) => {
$(id).addEventListener("input", updateCounters);
$(id).addEventListener("input", saveDraftState);
if (id !== "sourceText") $(id).addEventListener("input", markPosterStaleIfNeeded);
});
["titleHint", "audienceExtra", "toneExtra", "avoidWords", "keepPoints", "targetBodyChars", "thumbMediaId", "coverStyleHint"].forEach(
(id) => {
const el = $(id);
if (el) el.addEventListener("input", saveDraftState);
},
);
document.querySelectorAll('input[name="audienceChip"],input[name="toneChip"]').forEach((el) => {
el.addEventListener("change", saveDraftState);
});
if (coverAutoAfterRewrite) coverAutoAfterRewrite.addEventListener("change", saveDraftState);
if (posterAutoInclude) posterAutoInclude.addEventListener("change", saveDraftState);
if (coverImageModelInput) coverImageModelInput.addEventListener("input", saveDraftState);
restoreDraftState();
updateCounters();
initMultiDropdowns();
initWechatAccountSwitch();
syncTargetCharChips();
renderPosterPreview([]);
setCoverMode("manual");
initImageModelStatus();
window.addEventListener("beforeunload", saveDraftState);
window.addEventListener("load", () => setCoverMode("manual"));