310 lines
10 KiB
JavaScript
310 lines
10 KiB
JavaScript
const $ = (id) => document.getElementById(id);
|
||
|
||
function renderBodyPreview() {
|
||
const raw = ($("body") && $("body").value) || "";
|
||
const el = $("bodyPreview");
|
||
if (!el) return;
|
||
if (typeof marked !== "undefined" && marked.parse) {
|
||
el.innerHTML = marked.parse(raw, { breaks: true });
|
||
} else {
|
||
el.textContent = raw;
|
||
}
|
||
}
|
||
|
||
const statusEl = $("status");
|
||
const rewriteBtn = $("rewriteBtn");
|
||
const wechatBtn = $("wechatBtn");
|
||
const imBtn = $("imBtn");
|
||
const coverUploadBtn = $("coverUploadBtn");
|
||
const logoutBtn = $("logoutBtn");
|
||
|
||
function countText(v) {
|
||
return (v || "").trim().length;
|
||
}
|
||
|
||
/** 多选子项用顿号拼接,可选补充用分号接在末尾 */
|
||
function buildMultiPrompt(nameAttr, extraId) {
|
||
const boxes = document.querySelectorAll(`input[name="${nameAttr}"]:checked`);
|
||
const parts = Array.from(boxes).map((b) => (b.value || "").trim()).filter(Boolean);
|
||
const extraEl = extraId ? $(extraId) : null;
|
||
const extra = extraEl ? (extraEl.value || "").trim() : "";
|
||
let s = parts.join("、");
|
||
if (extra) s = s ? `${s};${extra}` : extra;
|
||
return s;
|
||
}
|
||
|
||
function updateMultiDropdownSummary(nameAttr, summaryId, emptyHint) {
|
||
const el = $(summaryId);
|
||
if (!el) return;
|
||
const parts = Array.from(document.querySelectorAll(`input[name="${nameAttr}"]:checked`)).map((b) =>
|
||
(b.value || "").trim(),
|
||
);
|
||
el.textContent = parts.length ? parts.join("、") : emptyHint;
|
||
}
|
||
|
||
function initMultiDropdowns() {
|
||
const pairs = [
|
||
{ name: "audienceChip", summary: "audienceSummary", empty: "点击展开,选择目标读者…" },
|
||
{ name: "toneChip", summary: "toneSummary", empty: "点击展开,选择语气风格…" },
|
||
];
|
||
pairs.forEach(({ name, summary, empty }) => {
|
||
updateMultiDropdownSummary(name, summary, empty);
|
||
document.querySelectorAll(`input[name="${name}"]`).forEach((cb) => {
|
||
cb.addEventListener("change", () => updateMultiDropdownSummary(name, summary, empty));
|
||
});
|
||
});
|
||
}
|
||
|
||
function updateCounters() {
|
||
$("sourceCount").textContent = `${countText($("sourceText").value)} 字`;
|
||
$("summaryCount").textContent = `${countText($("summary").value)} 字`;
|
||
$("bodyCount").textContent = `${countText($("body").value)} 字`;
|
||
renderBodyPreview();
|
||
}
|
||
|
||
function setLoading(button, loading, idleText, loadingText) {
|
||
if (!button) return;
|
||
button.disabled = loading;
|
||
button.textContent = loading ? loadingText : idleText;
|
||
}
|
||
|
||
function setStatus(msg, danger = false) {
|
||
statusEl.style.color = danger ? "#b42318" : "#0f5f3d";
|
||
statusEl.textContent = msg;
|
||
}
|
||
|
||
async function postJSON(url, body) {
|
||
const res = await fetch(url, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(body),
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok) throw new Error(data.detail || "请求失败");
|
||
data._requestId = res.headers.get("X-Request-ID") || "";
|
||
return data;
|
||
}
|
||
|
||
async function fetchAuthMe() {
|
||
const res = await fetch("/api/auth/me");
|
||
const data = await res.json();
|
||
if (!data.ok || !data.logged_in) {
|
||
window.location.href = "/auth?next=/";
|
||
return null;
|
||
}
|
||
return data;
|
||
}
|
||
|
||
function renderWechatAccountSelect(me) {
|
||
const sel = $("wechatAccountSelect");
|
||
const hint = $("wechatAccountStatus");
|
||
if (!sel) return;
|
||
const list = Array.isArray(me.wechat_accounts) ? me.wechat_accounts : [];
|
||
const activeId =
|
||
me.active_wechat_account && me.active_wechat_account.id
|
||
? Number(me.active_wechat_account.id)
|
||
: 0;
|
||
sel.innerHTML = "";
|
||
if (!list.length) {
|
||
const opt = document.createElement("option");
|
||
opt.value = "";
|
||
opt.textContent = "暂无公众号";
|
||
sel.appendChild(opt);
|
||
sel.disabled = true;
|
||
if (hint) hint.textContent = "请先在「公众号设置」绑定";
|
||
return;
|
||
}
|
||
sel.disabled = false;
|
||
list.forEach((a) => {
|
||
const opt = document.createElement("option");
|
||
opt.value = String(a.id);
|
||
const name = (a.account_name || "未命名").trim();
|
||
const appid = (a.appid || "").trim();
|
||
opt.textContent = appid ? `${name} (${appid})` : name;
|
||
if ((activeId && Number(a.id) === activeId) || a.active) opt.selected = true;
|
||
sel.appendChild(opt);
|
||
});
|
||
}
|
||
|
||
function flashWechatAccountHint(msg, clearMs = 2600) {
|
||
const hint = $("wechatAccountStatus");
|
||
if (!hint) return;
|
||
hint.textContent = msg;
|
||
if (clearMs > 0) {
|
||
window.clearTimeout(flashWechatAccountHint._t);
|
||
flashWechatAccountHint._t = window.setTimeout(() => {
|
||
hint.textContent = "";
|
||
}, clearMs);
|
||
}
|
||
}
|
||
|
||
const wechatAccountSelect = $("wechatAccountSelect");
|
||
if (wechatAccountSelect) {
|
||
wechatAccountSelect.addEventListener("change", async () => {
|
||
const id = Number(wechatAccountSelect.value || 0);
|
||
if (!id) return;
|
||
try {
|
||
const out = await postJSON("/api/auth/wechat/switch", { account_id: id });
|
||
if (!out.ok) {
|
||
flashWechatAccountHint(out.detail || "切换失败", 4000);
|
||
const me = await fetchAuthMe();
|
||
if (me) renderWechatAccountSelect(me);
|
||
return;
|
||
}
|
||
const me = await fetchAuthMe();
|
||
if (me) renderWechatAccountSelect(me);
|
||
flashWechatAccountHint("已切换");
|
||
} catch (e) {
|
||
flashWechatAccountHint(e.message || "切换失败", 4000);
|
||
const me = await fetchAuthMe();
|
||
if (me) renderWechatAccountSelect(me);
|
||
}
|
||
});
|
||
}
|
||
|
||
async function initWechatAccountSwitch() {
|
||
const me = await fetchAuthMe();
|
||
if (me) renderWechatAccountSelect(me);
|
||
}
|
||
|
||
async function logoutAndGoAuth() {
|
||
try {
|
||
await postJSON("/api/auth/logout", {});
|
||
} catch {
|
||
// 忽略退出接口异常,直接跳转认证页
|
||
}
|
||
window.location.href = "/auth?next=/";
|
||
}
|
||
|
||
if (logoutBtn) {
|
||
logoutBtn.addEventListener("click", async () => {
|
||
setLoading(logoutBtn, true, "退出登录", "退出中...");
|
||
await logoutAndGoAuth();
|
||
});
|
||
}
|
||
|
||
$("rewriteBtn").addEventListener("click", async () => {
|
||
const sourceText = $("sourceText").value.trim();
|
||
if (sourceText.length < 20) {
|
||
setStatus("原始内容太短,至少 20 个字符", true);
|
||
return;
|
||
}
|
||
|
||
setStatus("正在改写...");
|
||
setLoading(rewriteBtn, true, "改写并排版", "改写中...");
|
||
try {
|
||
const audience = buildMultiPrompt("audienceChip", "audienceExtra") || "公众号读者";
|
||
const tone = buildMultiPrompt("toneChip", "toneExtra") || "专业、可信、可读性强";
|
||
const data = await postJSON("/api/rewrite", {
|
||
source_text: sourceText,
|
||
title_hint: $("titleHint").value,
|
||
tone,
|
||
audience,
|
||
keep_points: $("keepPoints").value,
|
||
avoid_words: $("avoidWords").value,
|
||
});
|
||
$("title").value = data.title || "";
|
||
$("summary").value = data.summary || "";
|
||
$("body").value = data.body_markdown || "";
|
||
updateCounters();
|
||
const tr = data.trace || {};
|
||
if (data.mode === "fallback") {
|
||
const note = (data.quality_notes || [])[0] || "当前为保底改写稿";
|
||
setStatus(`改写完成(保底模式):${note}`, true);
|
||
} else if (tr.quality_soft_accept) {
|
||
setStatus(`改写完成(有提示):${(data.quality_notes || []).join(";") || "请检查正文"}`);
|
||
statusEl.style.color = "#9a3412";
|
||
} else {
|
||
setStatus("改写完成。");
|
||
}
|
||
} catch (e) {
|
||
setStatus(`改写失败: ${e.message}`, true);
|
||
} finally {
|
||
setLoading(rewriteBtn, false, "改写并排版", "改写中...");
|
||
}
|
||
});
|
||
|
||
$("wechatBtn").addEventListener("click", async () => {
|
||
setStatus("正在发布到公众号草稿箱...");
|
||
setLoading(wechatBtn, true, "发布到公众号草稿箱", "发布中...");
|
||
try {
|
||
const data = await postJSON("/api/publish/wechat", {
|
||
title: $("title").value,
|
||
summary: $("summary").value,
|
||
body_markdown: $("body").value,
|
||
thumb_media_id: $("thumbMediaId") ? $("thumbMediaId").value.trim() : "",
|
||
});
|
||
if (!data.ok) throw new Error(data.detail);
|
||
setStatus("公众号草稿发布成功");
|
||
} catch (e) {
|
||
setStatus(`公众号发布失败: ${e.message}`, true);
|
||
if ((e.message || "").includes("请先登录")) {
|
||
window.location.href = "/auth?next=/";
|
||
} else if ((e.message || "").includes("未绑定公众号")) {
|
||
window.location.href = "/settings";
|
||
}
|
||
} finally {
|
||
setLoading(wechatBtn, false, "发布到公众号草稿箱", "发布中...");
|
||
}
|
||
});
|
||
|
||
if (coverUploadBtn) {
|
||
coverUploadBtn.addEventListener("click", async () => {
|
||
const fileInput = $("coverFile");
|
||
const hint = $("coverHint");
|
||
const file = fileInput && fileInput.files && fileInput.files[0];
|
||
if (!file) {
|
||
setStatus("请先选择封面图片再上传", true);
|
||
return;
|
||
}
|
||
if (hint) hint.textContent = "正在上传封面...";
|
||
setLoading(coverUploadBtn, true, "上传封面并绑定", "上传中...");
|
||
try {
|
||
const fd = new FormData();
|
||
fd.append("file", file);
|
||
const res = await fetch("/api/wechat/cover/upload", { method: "POST", body: fd });
|
||
const data = await res.json();
|
||
if (!res.ok || !data.ok) throw new Error(data.detail || "封面上传失败");
|
||
const mid = data.data && data.data.thumb_media_id ? data.data.thumb_media_id : "";
|
||
if ($("thumbMediaId")) $("thumbMediaId").value = mid;
|
||
if (hint) hint.textContent = `封面上传成功,已绑定 media_id:${mid}`;
|
||
setStatus("封面上传成功,发布时将优先使用该封面。");
|
||
} catch (e) {
|
||
if (hint) hint.textContent = "封面上传失败,请看状态提示。";
|
||
setStatus(`封面上传失败: ${e.message}`, true);
|
||
if ((e.message || "").includes("请先登录")) {
|
||
window.location.href = "/auth?next=/";
|
||
} else if ((e.message || "").includes("未绑定公众号")) {
|
||
window.location.href = "/settings";
|
||
}
|
||
} finally {
|
||
setLoading(coverUploadBtn, false, "上传封面并绑定", "上传中...");
|
||
}
|
||
});
|
||
}
|
||
|
||
$("imBtn").addEventListener("click", async () => {
|
||
setStatus("正在发送到 IM...");
|
||
setLoading(imBtn, true, "发送到 IM", "发送中...");
|
||
try {
|
||
const data = await postJSON("/api/publish/im", {
|
||
title: $("title").value,
|
||
body_markdown: $("body").value,
|
||
});
|
||
if (!data.ok) throw new Error(data.detail);
|
||
setStatus("IM 发送成功");
|
||
} catch (e) {
|
||
setStatus(`IM 发送失败: ${e.message}`, true);
|
||
} finally {
|
||
setLoading(imBtn, false, "发送到 IM", "发送中...");
|
||
}
|
||
});
|
||
|
||
["sourceText", "summary", "body"].forEach((id) => {
|
||
$(id).addEventListener("input", updateCounters);
|
||
});
|
||
|
||
updateCounters();
|
||
initMultiDropdowns();
|
||
initWechatAccountSwitch();
|