diff --git a/app/services/ai_rewriter.py b/app/services/ai_rewriter.py index cead49b..61e3bc3 100644 --- a/app/services/ai_rewriter.py +++ b/app/services/ai_rewriter.py @@ -56,7 +56,9 @@ SYSTEM_PROMPT = """ 3) 只输出合法 JSON:title, summary, body_markdown; 4) **body_markdown 约束**:恰好 **5 个自然段**;段与段之间用一个空行分隔;**不要**使用 # / ## 标题符号;全文(正文)总字数 **不超过 500 字**(含标点); 5) title、summary 也要短:标题约 8~18 字;摘要约 40~80 字; -6) JSON 字符串内引号请用「」或『』,勿用未转义的英文 "。 +6) 正文每段需首行缩进(建议段首使用两个全角空格「  」),避免顶格; +7) 关键观点需要加粗:请用 Markdown `**加粗**` 标出 2~4 个重点短语; +8) JSON 字符串内引号请用「」或『』,勿用未转义的英文 "。 """.strip() @@ -72,6 +74,8 @@ body_markdown 写法: - 必须且只能有 **5 段**:每段若干完整句子,段之间 **\\n\\n**(空一行); - **禁止** markdown 标题(不要用 #); - 正文总长 **≤500 字**,宁可短而清楚,不要写满废话; +- 每段段首请保留首行缩进(两个全角空格「  」); +- 请用 `**...**` 加粗 2~4 个关键观点词; - 内容顺序建议:第 1 段交代在说什么;中间 3 段展开关键信息;最后 1 段收束或提醒(均须紧扣原文,勿乱发挥)。 """.strip() @@ -759,6 +763,15 @@ class AIRewriter: if re.search(r"(?m)^#+\s", body): issues.append("正文请勿使用 # 标题符号,只用自然段") + if "**" not in body: + issues.append("关键观点未加粗(建议 2~4 处)") + + paragraphs = [p.strip() for p in re.split(r"\n\s*\n", body) if p.strip()] + if paragraphs: + no_indent = sum(1 for p in paragraphs if not p.startswith("  ")) + if no_indent >= max(2, len(paragraphs) // 2): + issues.append("正文缺少首行缩进(建议每段段首使用两个全角空格)") + if self._looks_like_raw_copy(source, body, lenient=lenient): issues.append("改写与原文相似度过高,疑似未充分重写") @@ -799,4 +812,29 @@ class AIRewriter: def _format_markdown(self, text: str) -> str: body = text.replace("\r\n", "\n").strip() body = re.sub(r"\n{3,}", "\n\n", body) - return body.strip() + "\n" + paragraphs = [p.strip() for p in re.split(r"\n\s*\n", body) if p.strip()] + if not paragraphs: + return body.strip() + "\n" + + # 若模型未加粗,兜底给第一段的核心短语加粗一次 + merged = "\n\n".join(paragraphs) + if "**" not in merged: + first = paragraphs[0] + first_plain = first.lstrip("  ").strip() + phrase = re.split(r"[,。;:,:]", first_plain, maxsplit=1)[0].strip() + phrase = phrase[:14] + if len(phrase) >= 4 and phrase in first: + paragraphs[0] = first.replace(phrase, f"**{phrase}**", 1) + + # 段首全角缩进:保持阅读习惯,避免顶格 + out: list[str] = [] + for p in paragraphs: + seg = p.strip() + if not seg: + continue + if seg.startswith("  "): + out.append(seg) + else: + out.append("  " + seg.lstrip()) + + return "\n\n".join(out).strip() + "\n" diff --git a/app/static/app.js b/app/static/app.js index 00bae9b..caf027c 100644 --- a/app/static/app.js +++ b/app/static/app.js @@ -58,8 +58,8 @@ $("rewriteBtn").addEventListener("click", async () => { return; } - setStatus("AI 改写中..."); - setLoading(rewriteBtn, true, "AI 改写并排版", "AI 改写中..."); + setStatus("正在改写..."); + setLoading(rewriteBtn, true, "改写并排版", "改写中..."); try { const data = await postJSON("/api/rewrite", { source_text: sourceText, @@ -74,25 +74,19 @@ $("rewriteBtn").addEventListener("click", async () => { $("body").value = data.body_markdown || ""; updateCounters(); 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 - ); + setStatus(`改写完成(保底模式):${note}`, true); } else if (tr.quality_soft_accept) { - setStatus( - `改写完成(AI,质检提示):${(data.quality_notes || []).join(";") || "见 quality_notes"} · ${modelLine || "AI"}` - ); + setStatus(`改写完成(有提示):${(data.quality_notes || []).join(";") || "请检查正文"}`); statusEl.style.color = "#9a3412"; } else { - setStatus(`改写完成(AI 洗稿)${modelLine ? ` · ${modelLine}` : ""}`); + setStatus("改写完成。"); } } catch (e) { setStatus(`改写失败: ${e.message}`, true); } finally { - setLoading(rewriteBtn, false, "AI 改写并排版", "AI 改写中..."); + setLoading(rewriteBtn, false, "改写并排版", "改写中..."); } }); diff --git a/app/static/style.css b/app/static/style.css index ec5bb8e..60c78c6 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -1,12 +1,12 @@ :root { - --bg: #ecfeff; + --bg: #f8fafc; --panel: #ffffff; - --line: #d9eef2; - --text: #164e63; - --muted: #457386; - --accent: #0891b2; - --accent-2: #0e7490; - --accent-soft: #f0fdff; + --line: #e2e8f0; + --text: #1e293b; + --muted: #64748b; + --accent: #2563eb; + --accent-2: #1d4ed8; + --accent-soft: #eff6ff; } * { @@ -46,8 +46,8 @@ body { font-size: 12px; font-weight: 700; color: #fafafa; - background: var(--accent-2); - border: 1px solid var(--accent-2); + background: #334155; + border: 1px solid #334155; padding: 5px 10px; border-radius: 999px; } @@ -68,7 +68,7 @@ body { border: 1px solid var(--line); border-radius: 12px; padding: 14px; - box-shadow: 0 6px 24px rgba(8, 145, 178, 0.08); + box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05); min-height: 0; overflow: hidden; } @@ -130,8 +130,8 @@ textarea { input:focus, textarea:focus { outline: none; - border-color: #7dd3e7; - box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.15); + border-color: #93c5fd; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12); } button { @@ -166,7 +166,7 @@ button.secondary:hover { .subtle-btn { background: #fff; - border-color: #a5dae6; + border-color: #cbd5e1; color: var(--accent-2); } diff --git a/app/templates/index.html b/app/templates/index.html index 9a80677..bc1e123 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -11,9 +11,9 @@

{{ app_name }}

-

从原文到公众号草稿,一页完成改写、封面和发布。

+

从原文到公众号草稿,一页完成编辑、封面和发布。

-
Studio
+
编辑台
@@ -53,7 +53,7 @@ - +