diff --git a/.omo/run-continuation/ses_1dede8a7affeG6DxtfPUOviXrp.json b/.omo/run-continuation/ses_1dede8a7affeG6DxtfPUOviXrp.json index 173fa2d..fc13147 100644 --- a/.omo/run-continuation/ses_1dede8a7affeG6DxtfPUOviXrp.json +++ b/.omo/run-continuation/ses_1dede8a7affeG6DxtfPUOviXrp.json @@ -1,11 +1,10 @@ { "sessionID": "ses_1dede8a7affeG6DxtfPUOviXrp", - "updatedAt": "2026-06-17T08:18:52.213Z", + "updatedAt": "2026-06-23T00:31:03.722Z", "sources": { "background-task": { - "state": "active", - "reason": "2 background task(s) active", - "updatedAt": "2026-06-17T08:18:52.213Z" + "state": "idle", + "updatedAt": "2026-06-23T00:31:03.722Z" } } } \ No newline at end of file diff --git a/platform/backend/app/api/topics.py b/platform/backend/app/api/topics.py index a32a96b..e1c1581 100644 --- a/platform/backend/app/api/topics.py +++ b/platform/backend/app/api/topics.py @@ -66,7 +66,63 @@ def list_topics( else: query = query.order_by(sort_col.asc()) - return query.offset(offset).limit(limit).all() + topics = query.offset(offset).limit(limit).all() + # Enrich with article count and previews + from ..models import Article + topic_ids = [t.id for t in topics] + if topic_ids: + article_rows = ( + db.query(Article.topic_id, Article.platform, Article.title, Article.word_count, Article.status) + .filter(Article.topic_id.in_(topic_ids)) + .all() + ) + articles_by_topic: Dict[str, list] = {} + for ar in article_rows: + articles_by_topic.setdefault(ar.topic_id, []).append({ + "platform": ar.platform, + "title": ar.title, + "word_count": ar.word_count, + "status": ar.status, + }) + else: + articles_by_topic = {} + + result = [] + for t in topics: + t_dict = { + "id": t.id, + "field_id": t.field_id, + "field_name": t.field_name, + "org_id": t.org_id, + "title": t.title, + "format": t.format, + "core_concept": t.core_concept, + "audience_pain": t.audience_pain, + "unique_angle": t.unique_angle, + "priority": t.priority, + "priority_score": t.priority_score, + "total_score": t.total_score, + "status": t.status, + "tags": t.tags or [], + "custom_data": t.custom_data or {}, + "scoring_data": t.scoring_data or {}, + "lock_by": t.lock_by, + "lock_at": t.lock_at, + "series": t.series, + "created_at": t.created_at, + "updated_at": t.updated_at, + "generated_at": t.generated_at, + "reviewed_at": t.reviewed_at, + "ready_at": t.ready_at, + "published_at": t.published_at, + "compliance_score": t.compliance_score, + "platform_urls": t.platform_urls or {}, + "cases": [], + "article_count": len(articles_by_topic.get(t.id, [])), + "articles": articles_by_topic.get(t.id, []), + } + result.append(t_dict) + return result @router.get("/stats") diff --git a/platform/backend/app/core/generator.py b/platform/backend/app/core/generator.py index 09423fa..1387924 100644 --- a/platform/backend/app/core/generator.py +++ b/platform/backend/app/core/generator.py @@ -84,8 +84,9 @@ def run_creator_blocking(topic_id: str = None, timeout: int = 1800): existing = db.query(Article).filter(Article.id == article_id).first() if existing: existing.html_content = html + existing.word_count = len(html) else: - db.add(Article(id=article_id, topic_id=topic_id, platform=platform_dir, file_path=f"db:{article_id}", html_content=html, status="draft")) + db.add(Article(id=article_id, topic_id=topic_id, platform=platform_dir, file_path=f"db:{article_id}", html_content=html, word_count=len(html), status="draft")) hf.unlink() for platform_dir in ["zhihu", "wechat", "xiaohongshu"]: pdir = dd / platform_dir diff --git a/platform/backend/app/database.py b/platform/backend/app/database.py index 675c897..5d914ee 100644 --- a/platform/backend/app/database.py +++ b/platform/backend/app/database.py @@ -131,6 +131,7 @@ def init_db(): ("search_rankings", "org_id", "VARCHAR DEFAULT 'default'"), ("geo_readiness_scores", "org_id", "VARCHAR DEFAULT 'default'"), ("articles", "org_id", "VARCHAR DEFAULT 'default'"), + ("content_tasks", "org_id", "VARCHAR DEFAULT 'default'"), ("topics", "series", "VARCHAR"), ]: try: diff --git a/platform/backend/app/schemas.py b/platform/backend/app/schemas.py index cb44cd8..e0fb4b5 100644 --- a/platform/backend/app/schemas.py +++ b/platform/backend/app/schemas.py @@ -138,6 +138,8 @@ class TopicResponse(TopicBase): compliance_score: Optional[int] = None platform_urls: Dict[str, str] = {} cases: List[Any] = [] + article_count: int = 0 + articles: List[Dict[str, Any]] = [] model_config = ConfigDict(from_attributes=True) diff --git a/platform/frontend/topics.html b/platform/frontend/topics.html index 08531c4..b11c43c 100644 --- a/platform/frontend/topics.html +++ b/platform/frontend/topics.html @@ -109,6 +109,17 @@ + + + @@ -145,6 +156,10 @@
{{ topic.field }} 合规{{ topic.compliance_score }} + + {{ platformShort(a.platform) }}{{ a.word_count ? ' ' + fmtWordCount(a.word_count) : '' }} + + 无文章
创建: {{ formatDate(topic.created_at) }}
@@ -534,6 +549,20 @@ const TopicsApp = { }); }, platformName(platform) { return { zhihu: '知乎', wechat: '微信公众号', xiaohongshu: '小红书' }[platform] || platform; }, + platformShort(platform) { return { zhihu: '知', wechat: '公', xiaohongshu: '红' }[platform] || platform; }, + fmtWordCount(wc) { if (!wc) return ''; if (wc >= 1000) return (wc / 1000).toFixed(wc >= 10000 ? 0 : 1) + 'k'; return wc + ''; }, + openTopicArticlePreview(topic, article) { + this.previewTopic = topic; + this.previewPlatform = article.platform; + this.previewVisible = true; + this.platformContents = {}; + this.previewImages = {}; + const token = this.getToken(); + if (!token) return; + fetch(`/api/articles/${topic.id}/preview?platform=${article.platform}`, { headers: { 'Authorization': 'Bearer ' + token } }) + .then(r => r.ok ? r.json() : null).then(d => { if (d && d.html) { this.platformContents[article.platform] = d.html; if (d.images) this.previewImages[article.platform] = d.images; } }) + .catch(e => console.error(`加载${article.platform}预览失败:`, e)); + }, copyContent(platform) { const html = this.platformContents[platform]; if (!html) { this.$message.warning('暂无内容可复制'); return; } diff --git a/scripts/db_helper.py b/scripts/db_helper.py index 798ca59..921e27a 100644 --- a/scripts/db_helper.py +++ b/scripts/db_helper.py @@ -246,12 +246,14 @@ def save_article(topic_id: str, platform: str, html_content: str, *, title: str from app.models import Article article_id = f"{platform}_{topic_id}" existing = db.query(Article).filter(Article.id == article_id).first() + word_count = len(content) if content else (len(html_content) if html_content else 0) if existing: existing.html_content = html_content if title: existing.title = title if content: existing.content = content + existing.word_count = word_count else: article = Article( id=article_id, @@ -261,6 +263,7 @@ def save_article(topic_id: str, platform: str, html_content: str, *, title: str title=title, content=content, html_content=html_content, + word_count=word_count, status="draft", compliance_score=None ) diff --git a/scripts/trends.py b/scripts/trends.py index b6d0543..6deac7b 100644 --- a/scripts/trends.py +++ b/scripts/trends.py @@ -193,7 +193,8 @@ def fetch_baidu_hot() -> List[Dict]: word = item.get("query", "").strip() or item.get("word", "").strip() if not word: continue - hot_score = item.get("hotScore", 0) or item.get("heat", 0) + hot_score_raw = item.get("hotScore", 0) or item.get("heat", 0) + hot_score = int(hot_score_raw) if hot_score_raw else 0 desc = item.get("desc", "") results.append({ "domain": _guess_domain(word, desc), diff --git a/scripts/writer.py b/scripts/writer.py index ab75eba..2abcf1d 100644 --- a/scripts/writer.py +++ b/scripts/writer.py @@ -557,7 +557,7 @@ class Writer: line = line.strip() if not line: continue - line = re.sub(r'^\d+[.、)\s]+', '', line) + line = re.sub(r'^\d+[.、))]\s*', '', line) line = line.strip('*#- \t"\'"''"') # 跳过思考/建议类输出(如"不如:"、"或者:"、"建议方案"等) if re.match(r'^(不如|或者|建议|推荐|参考|方案[一二三]|第[一二三]种|以[下是]|标题[一二三]|选项)', line):