Files
AIcreat/app/static/app.js
2026-04-06 15:28:15 +08:00

225 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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");
function countText(v) {
return (v || "").trim().length;
}
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;
}
function renderTrace(trace, headerRid) {
const wrap = $("traceWrap");
const pre = $("traceJson");
const badge = $("traceBadge");
if (!pre || !wrap) return;
if (!trace || Object.keys(trace).length === 0) {
pre.textContent = headerRid
? JSON.stringify({ request_id: headerRid, note: "响应中无 trace 字段" }, null, 2)
: "尚无数据完成一次「AI 改写」后,这里会显示请求 ID、耗时、质检与降级原因。";
if (badge) badge.textContent = "";
return;
}
const merged = { ...trace };
if (headerRid && !merged.request_id) merged.request_id = headerRid;
pre.textContent = JSON.stringify(merged, null, 2);
const mode = merged.mode || "";
if (badge) {
badge.textContent = mode === "ai" ? "AI" : mode === "fallback" ? "保底" : "";
badge.className = "trace-badge " + (mode === "ai" ? "is-ai" : mode === "fallback" ? "is-fallback" : "");
}
wrap.open = true;
}
$("rewriteBtn").addEventListener("click", async () => {
const sourceText = $("sourceText").value.trim();
if (sourceText.length < 20) {
setStatus("原始内容太短,至少 20 个字符", true);
return;
}
setStatus("AI 改写中...");
setLoading(rewriteBtn, true, "AI 改写并排版", "AI 改写中...");
try {
const data = await postJSON("/api/rewrite", {
source_text: sourceText,
title_hint: $("titleHint").value,
tone: $("tone").value,
audience: $("audience").value,
keep_points: $("keepPoints").value,
avoid_words: $("avoidWords").value,
});
$("title").value = data.title || "";
$("summary").value = data.summary || "";
$("body").value = data.body_markdown || "";
updateCounters();
renderTrace(data.trace, data._requestId);
const tr = data.trace || {};
const modelLine = tr.model ? `模型 ${tr.model}` : "";
if (data.mode === "fallback") {
const note = (data.quality_notes || [])[0] || "当前为保底改写稿";
setStatus(
`改写完成(保底模式,未使用或未通过千问长文):${note}${modelLine ? ` · ${modelLine}` : ""}`,
true
);
} else if (tr.quality_soft_accept) {
setStatus(
`改写完成AI质检提示${(data.quality_notes || []).join("") || "见 quality_notes"} · ${modelLine || "AI"}`
);
statusEl.style.color = "#9a3412";
} else {
setStatus(`改写完成AI 洗稿)${modelLine ? ` · ${modelLine}` : ""}`);
}
} catch (e) {
setStatus(`改写失败: ${e.message}`, true);
} finally {
setLoading(rewriteBtn, false, "AI 改写并排版", "AI 改写中...");
}
});
$("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);
} 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);
} 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);
});
async function loadBackendConfig() {
const el = $("backendConfig");
if (!el) return;
try {
const res = await fetch("/api/config");
const c = await res.json();
if (!c.openai_configured) {
el.textContent =
"后端未配置 OPENAI_API_KEY改写将使用本地保底稿千问不会参与。请在 .env 中配置并重启容器。";
el.style.color = "#b42318";
return;
}
const name =
c.provider === "dashscope"
? "通义千问DashScope 兼容接口)"
: "OpenAI 兼容接口";
const host = c.base_url_host ? ` · ${c.base_url_host}` : "";
const to = c.openai_timeout_sec != null ? ` · 单轮最长等待 ${c.openai_timeout_sec}s` : "";
el.textContent = `已接入:${c.openai_model} · ${name}${host}${to}`;
el.style.color = "";
} catch (e) {
el.textContent = "无法读取 /api/config请确认服务已启动";
el.style.color = "#b42318";
}
}
loadBackendConfig();
updateCounters();
renderTrace(null, "");