Auto backup \2026-\04-\20
This commit is contained in:
@@ -1,12 +1,6 @@
|
||||
{
|
||||
"date": "2026-06-22",
|
||||
"fulfilled_slots": [
|
||||
"08:00"
|
||||
],
|
||||
"published_ids": [
|
||||
4011
|
||||
],
|
||||
"used_columns": [
|
||||
"糖友"
|
||||
]
|
||||
"date": "2026-07-01",
|
||||
"fulfilled_slots": [],
|
||||
"published_ids": [],
|
||||
"used_columns": []
|
||||
}
|
||||
@@ -198,6 +198,74 @@ def update_article_content(
|
||||
return affected > 0
|
||||
|
||||
|
||||
def _cleanup_cover_file(cover_image: str):
|
||||
"""
|
||||
删除封面图文件及对应缩略图(与后台 file_utils.cleanup_file_by_path 逻辑对齐)。
|
||||
|
||||
Args:
|
||||
cover_image: 封面图路径(/storage/admin/images/... 或相对路径)
|
||||
"""
|
||||
if not cover_image or cover_image.startswith(("http://", "https://")):
|
||||
return
|
||||
|
||||
upload_dir = os.path.join(PROJECT_ROOT, "storage", "admin", "images")
|
||||
thumbnail_dir = os.path.join(upload_dir, "thumbnails")
|
||||
|
||||
# 兼容两种路径格式
|
||||
if cover_image.startswith("/storage/admin/images/"):
|
||||
rel_path = cover_image[len("/storage/admin/images/"):]
|
||||
else:
|
||||
rel_path = cover_image.lstrip("/")
|
||||
|
||||
full_path = os.path.join(upload_dir, rel_path)
|
||||
|
||||
# 删除原文件
|
||||
try:
|
||||
if os.path.isfile(full_path):
|
||||
os.remove(full_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# 同步删除缩略图(与原图同名,扩展名 .jpg)
|
||||
try:
|
||||
stem = os.path.splitext(os.path.basename(rel_path))[0]
|
||||
thumb_path = os.path.join(thumbnail_dir, f"{stem}.jpg")
|
||||
if os.path.isfile(thumb_path):
|
||||
os.remove(thumb_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def delete_article(article_id: int) -> bool:
|
||||
"""
|
||||
删除文章(物理删除),同步清理封面图片文件及缩略图。
|
||||
与后台 routers/articles.py 的 delete_article 行为对齐。
|
||||
|
||||
Args:
|
||||
article_id: 文章 ID
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
conn = _get_conn()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 先查 cover_image 以便删除后清理文件
|
||||
cursor.execute("SELECT cover_image FROM articles WHERE id = ?", (article_id,))
|
||||
row = cursor.fetchone()
|
||||
cover_image = row["cover_image"] if row else None
|
||||
|
||||
cursor.execute("DELETE FROM articles WHERE id = ?", (article_id,))
|
||||
conn.commit()
|
||||
affected = cursor.rowcount
|
||||
conn.close()
|
||||
|
||||
if affected > 0 and cover_image:
|
||||
_cleanup_cover_file(cover_image)
|
||||
|
||||
return affected > 0
|
||||
|
||||
|
||||
def count_drafts() -> int:
|
||||
"""统计草稿文章数量"""
|
||||
conn = _get_conn()
|
||||
|
||||
@@ -33,7 +33,7 @@ BAOYU_DIR = "/home/ubuntu/.nanobot/workspace/skills/baoyu-post-to-wechat"
|
||||
sys.path.insert(0, BAOYU_DIR)
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
|
||||
from cms_ops import get_article_full, update_article_content
|
||||
from cms_ops import get_article_full, update_article_content, delete_article
|
||||
from baoyu_optimized import WeChatPublisher
|
||||
|
||||
# ---------- 配置 ----------
|
||||
@@ -76,6 +76,8 @@ IMAGE_PROMPT_SYSTEM = """你是一个图片提示词专家。根据下面给出
|
||||
HUNYUAN_API_KEY = "sk-qgPNJwuOGji7I7ixqNbbkWaTIvF1MrYTRWQnEZ3ZRVU4u7y3"
|
||||
HUNYUAN_API_BASE = "https://tokenhub.tencentmaas.com/v1/api/image"
|
||||
HUNYUAN_MODEL = "hy-image-v3.0"
|
||||
HUNYUAN_LITE_MODEL = "hy-image-lite"
|
||||
HUNYUAN_LITE_ENDPOINT = "https://tokenhub.tencentmaas.com/v1/api/image/lite"
|
||||
|
||||
# Seedream(降级)
|
||||
SEEDREAM_API_KEY = "83a29955-c633-449f-97f1-f49b62295cf6"
|
||||
@@ -297,10 +299,10 @@ def _strip_images(markdown: str) -> str:
|
||||
return re.sub(r'!\[.*?\]\([^)]+\)', '', markdown)
|
||||
|
||||
|
||||
def _check_original_content(content: str) -> int:
|
||||
def _check_original_content(content: str, article_id: int) -> int:
|
||||
"""
|
||||
检查原文是否有足够的文字内容。
|
||||
返回纯文本字符数;低于 _MIN_TEXT_LENGTH 抛出 ValueError。
|
||||
返回纯文本字符数;低于 _MIN_TEXT_LENGTH 删除文章并抛出 ValueError。
|
||||
"""
|
||||
text = _strip_images(content)
|
||||
# 也移除 markdown 链接、HTML 标签等
|
||||
@@ -309,27 +311,33 @@ def _check_original_content(content: str) -> int:
|
||||
text = re.sub(r'\s+', ' ', text).strip()
|
||||
text_len = len(text)
|
||||
if text_len < _MIN_TEXT_LENGTH:
|
||||
delete_article(article_id)
|
||||
print(f" 🗑️ 已删除短文章 ID={article_id}({text_len} 字符)", file=sys.stderr)
|
||||
raise ValueError(
|
||||
f"原文文字内容不足({text_len} 字符 < {_MIN_TEXT_LENGTH}),"
|
||||
f"可能全部为图片,跳过改写"
|
||||
f"可能全部为图片,已删除"
|
||||
)
|
||||
return text_len
|
||||
|
||||
|
||||
def _check_llm_response(content: str) -> None:
|
||||
def _check_llm_response(content: str, article_id: int) -> None:
|
||||
"""
|
||||
检查 LLM 返回是否是无意义的占位回复。
|
||||
如果是占位回复则抛出 ValueError。
|
||||
"""
|
||||
for pattern in _LLM_PLACEHOLDER_PATTERNS:
|
||||
if re.search(pattern, content):
|
||||
delete_article(article_id)
|
||||
print(f" 🗑️ 已删除占位回复文章 ID={article_id}", file=sys.stderr)
|
||||
raise ValueError(
|
||||
f"LLM 返回疑似占位回复(匹配模式: {pattern}),不发布"
|
||||
f"LLM 返回疑似占位回复(匹配模式: {pattern}),已删除"
|
||||
)
|
||||
# 额外检查:改写后内容过短(< 200 字)也视为无效
|
||||
if len(content) < _MIN_TEXT_LENGTH:
|
||||
delete_article(article_id)
|
||||
print(f" 🗑️ 已删除改写后过短文章 ID={article_id}({len(content)} 字符)", file=sys.stderr)
|
||||
raise ValueError(
|
||||
f"改写后内容过短({len(content)} 字符 < {_MIN_TEXT_LENGTH}),不发布"
|
||||
f"改写后内容过短({len(content)} 字符 < {_MIN_TEXT_LENGTH}),已删除"
|
||||
)
|
||||
|
||||
|
||||
@@ -374,12 +382,73 @@ def _make_image_prompt(context_text: str, api_key: str) -> str:
|
||||
return "温暖的医疗健康主题插图,简洁专业的扁平设计风格"
|
||||
|
||||
|
||||
def _hunyuan_lite_image(prompt: str, output_path: str,
|
||||
resolution: str = "1024:1024") -> bool:
|
||||
"""腾讯混元生图 hy-image-lite (轻量同步版)
|
||||
|
||||
POST 一次直接返回图片 URL,无需轮询。价格更低。
|
||||
注意:Lite 固定输出 1024×1024,不支持自定义分辨率。
|
||||
"""
|
||||
import requests
|
||||
|
||||
if not HUNYUAN_API_KEY:
|
||||
return False
|
||||
|
||||
try:
|
||||
payload = {
|
||||
"model": HUNYUAN_LITE_MODEL,
|
||||
"prompt": prompt,
|
||||
"rsp_img_type": "url",
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {HUNYUAN_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
resp = requests.post(HUNYUAN_LITE_ENDPOINT, json=payload,
|
||||
headers=headers, timeout=60)
|
||||
resp.raise_for_status()
|
||||
result = resp.json()
|
||||
|
||||
data = result.get("data", [])
|
||||
if not data:
|
||||
print(f" ⚠️ 混元 Lite 无数据: {result}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
img_url = data[0].get("url", "")
|
||||
if not img_url:
|
||||
print(f" ⚠️ 混元 Lite 无 URL: {data[0]}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
r = requests.get(img_url, timeout=60)
|
||||
r.raise_for_status()
|
||||
img_data = r.content
|
||||
|
||||
os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(img_data)
|
||||
print(f" ✅ 混元 Lite: {os.path.basename(output_path)} ({len(img_data)} bytes)", file=sys.stderr)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 混元 Lite 异常: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def _generate_image(prompt: str, output_path: str,
|
||||
resolution: str = "1024:1024") -> bool:
|
||||
"""调用混元生图生成图片,失败则降级到 Seedream"""
|
||||
"""生成封面配图:优先混元 Lite → 混元 V3.0 → Seedream"""
|
||||
# 1) 混元 Lite (轻量版,更便宜,同步返回)
|
||||
success = _hunyuan_lite_image(prompt, output_path, resolution)
|
||||
if success:
|
||||
return True
|
||||
# 2) 混元 V3.0 (标准版)
|
||||
print(" ↪ 降级到混元 V3.0...", file=sys.stderr)
|
||||
success = _hunyuan_image(prompt, output_path, resolution)
|
||||
if success:
|
||||
return True
|
||||
# 3) Seedream
|
||||
print(" ↪ 降级到 Seedream...", file=sys.stderr)
|
||||
return _seedream_image(prompt, output_path, resolution)
|
||||
|
||||
@@ -806,13 +875,13 @@ def run_pipeline(
|
||||
new_content = rewritten_content
|
||||
else:
|
||||
# 自动改写前先检查原文内容质量
|
||||
text_len = _check_original_content(original)
|
||||
text_len = _check_original_content(original, article_id)
|
||||
print(f" 原文有效文字: {text_len} chars", file=sys.stderr)
|
||||
print(" 模式: 自动改写(DeepSeek)", file=sys.stderr)
|
||||
api_key = load_deepseek_key()
|
||||
new_content = call_deepseek(original, api_key)
|
||||
# 检查 LLM 返回是否有效
|
||||
_check_llm_response(new_content)
|
||||
_check_llm_response(new_content, article_id)
|
||||
|
||||
print(f" 改写后字数: {len(new_content)} chars", file=sys.stderr)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user