fix: content quality, image format, task monitor, calendar data source, search UI & sort

This commit is contained in:
Yuzhiran Dev
2026-05-26 11:26:10 +08:00
parent b2d043b231
commit ac8644d752
36 changed files with 2294 additions and 873 deletions
+75 -38
View File
@@ -14,6 +14,7 @@ sys.path.insert(0, str(PROJECT_ROOT / 'platform' / 'backend'))
from db_helper import get_topic_by_id, update_topic_status, save_article
from content_cleaner import strip_thinking, strip_ai_preface, clean_markdown_content, clean_html_content
from prompt_loader import get_prompt, get_prompt_params
from image_generator import insert_lead_image
try:
from app.core.nvidia_client import call_llm
HAVE_LLM = True
@@ -206,6 +207,15 @@ class Writer:
expanded = self._expand_section(sec)
parts.append(expanded + "\n")
full_md = "\n".join(parts).strip()
# 收集所有引用来源,统一添加到文末
refs = set()
for m in re.finditer(r'(来源:([^]+)', full_md):
refs.add(m.group(1).strip())
if refs:
# 去掉已有参考资料区,重新生成统一的
full_md = re.sub(r'\n---\n\*\*参考资料\*\*[\s\S]*$', '', full_md).strip()
ref_lines = "\n".join(f"- {r}" for r in sorted(refs))
full_md += f"\n\n---\n\n**参考资料**\n{ref_lines}"
return full_md
def _adapt_for_platform(self, markdown: str, platform: str) -> str:
@@ -213,27 +223,71 @@ class Writer:
max_c = cfg['max_chars']
lines = markdown.split('\n')
if platform == "zhihu":
result = []
in_list = False
for line in lines:
stripped = line.strip()
# 数据类行 → 引用格式(知乎文章中引用数据能增强可信度)
if any(stripped.startswith(p) for p in ('据统计', '调研显示', '数据显示', '报告指出', '根据', '数据显示')):
line = f"> {line}"
# 列表保持原样(知乎支持 markdown 列表)
if stripped.startswith('- ') or stripped.startswith('* '):
if not in_list:
result.append('')
in_list = True
else:
in_list = False
result.append(line)
adapted = '\n'.join(result)
# 末尾加讨论引导(知乎算法权重:互动率)
if not any(kw in adapted for kw in ('你觉得', '你怎么看', '欢迎在评论区', '说说你的')):
adapted += "\n\n---\n\n你觉得这个观点有道理吗?你在工作中有没有类似的经验?欢迎在评论区聊聊。"
return adapted
if platform == "xiaohongshu":
result = []
char_count = 0
last_was_heading = False
for line in lines:
if char_count >= max_c:
break
stripped = line.strip()
if line.startswith('## '):
if not last_was_heading and result:
result.append('')
char_count += 1
line = f"## ✨ {line[3:]}"
last_was_heading = True
elif line.startswith('### '):
if not last_was_heading and result:
result.append('')
char_count += 1
line = f"### 💡 {line[4:]}"
last_was_heading = True
else:
last_was_heading = False
# 超长段落后拆行 + 每段前加点缀
if stripped and len(stripped) > 60:
sentences = [s.strip() for s in stripped.replace('', '\n').split('\n') if s.strip()]
for s in sentences:
if s and char_count < max_c:
result.append(s)
char_count += len(s)
continue
result.append(line)
char_count += len(line)
adapted = '\n'.join(result)
if adapted.count('#') == 0:
adapted = f"# {self.topic['title']}\n\n{adapted}"
# 结尾加收藏引导(小红书算法权重:收藏率)
if '收藏' not in adapted:
adapted += "\n\n✨ 觉得有用的话点个收藏吧,下次需要的时候随时翻出来看~"
return adapted
if platform == "wechat":
result = []
for line in lines:
line = line.replace('', '')
# 人称统一:我们→我,你们→你
line = line.replace('我们', '').replace('你们', '').replace('', '')
if line.startswith('### '):
result.append(f"\n**{line[4:]}**\n")
elif line.startswith('## '):
@@ -338,12 +392,20 @@ class Writer:
if not line:
continue
line = re.sub(r'^\d+[.、)\s]+', '', line)
line = line.strip('*#- \t')
line = line.strip('*#- \t"\'"''"')
# 跳过思考/建议类输出(如"不如:"、"或者:"、"建议方案"等)
if re.match(r'^(不如|或者|建议|推荐|参考|方案[一二三]|第[一二三]种|以[下是]|标题[一二三]|选项)', line):
continue
if line:
titles.append(line)
if titles:
logger.info(f"标题优化 [{platform}]: {titles[0][:50]}...")
return titles[0]
best = titles[0][:80]
# 如果优化后标题与原文毫无关联或过短,回退原题
if len(best) < 4 or (len(set(best) & set(original)) < 2 and len(original) > 4):
logger.warning(f"标题优化结果异常「{best}」,回退原文")
return original
logger.info(f"标题优化 [{platform}]: {best}")
return best
except Exception as e:
logger.warning(f"标题优化失败: {e}")
return original
@@ -361,43 +423,18 @@ class Writer:
html = template.replace("{{TITLE}}", title).replace("{{DATE}}", TODAY).replace("{{GEN_TIME}}", GEN_TIME)
html_content = _md_parser(adapted)
# WeChat: insert topic-relevant image at start of body
if platform == "wechat":
import base64
topic_title = self.topic.get('title', title)
topic_field = self.topic.get('field', '')
safe_title = topic_title.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&apos;')
safe_field = topic_field.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
lines = []
chars_per_line = 24
for i in range(0, len(safe_title), chars_per_line):
lines.append(safe_title[i:i+chars_per_line])
if not lines:
lines = ['配图']
line_y = 220 - (len(lines) - 1) * 20
title_texts = ''.join(f'<text x="540" y="{line_y + i*55}" font-size="36" fill="#1a1a1a" font-weight="bold">{l}</text>' for i, l in enumerate(lines))
field_text = f'<text x="540" y="{line_y + len(lines)*55 + 30}" font-size="20" fill="#98a2b3">{safe_field}</text>' if safe_field else ''
img_svg = f'''<svg xmlns="http://www.w3.org/2000/svg" width="1080" height="600" viewBox="0 0 1080 600" style="width:100%;max-width:1080px;border-radius:8px;background:linear-gradient(135deg,#f0f4ff,#e8f0fe)">
<rect width="1080" height="600" fill="url(#bg)"/>
<defs><linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#f0f4ff"/><stop offset="100%" style="stop-color:#e8f0fe"/></linearGradient></defs>
<g transform="translate(540,300)" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Microsoft YaHei,sans-serif">
<rect x="-60" y="-100" width="120" height="4" rx="2" fill="#409eff"/>
{title_texts}
{field_text}
<text y="100" font-size="14" fill="#c0c4cc">宇之然 · 配图(可替换)</text>
</g></svg>'''
img_b64 = 'data:image/svg+xml;base64,' + base64.b64encode(img_svg.encode('utf-8')).decode('ascii')
img_tag = f'<p><img src="{img_b64}" alt="{safe_title}" style="width:100%;max-width:1080px;border-radius:8px;"></p>\n'
h1_end = html_content.find('</h1>')
if h1_end != -1:
html_content = html_content[:h1_end + 5] + '\n' + img_tag + html_content[h1_end + 5:]
else:
html_content = img_tag + html_content
# 仅插入头图(每个平台一篇一张,不过度)
html_content = insert_lead_image(
html_content, platform,
title=self.topic.get('title', title),
field=self.topic.get('field', ''),
)
html = html.replace("<!-- CONTENT -->", html_content)
# 防御:清理可能在 LLM 输出中混入的 markdown 代码围栏和文件头
html = re.sub(r'^```+\w*\s*\n?', '', html)
html = re.sub(r'\n?```+\s*$', '', html)
html = html.strip()
tags_html = self._get_platform_tags(platform)