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();