diff --git a/backend/app/api/articles.py b/backend/app/api/articles.py
index 5ee78f3..a6d0782 100644
--- a/backend/app/api/articles.py
+++ b/backend/app/api/articles.py
@@ -127,6 +127,10 @@ async def list_articles(
published_at=art.published_at,
fetched_at=art.fetched_at,
image_url=art.image_url,
+ # 列表预览钩子:分类 + LLM 点评 + AI 插图 缩略图
+ commentary=art.commentary,
+ commentary_status=art.commentary_status,
+ image_ai_url=art.image_ai_url,
is_starred=art.id in starred_ids,
)
items.append(item)
diff --git a/backend/app/schemas/article.py b/backend/app/schemas/article.py
index 66c048e..339d2b8 100644
--- a/backend/app/schemas/article.py
+++ b/backend/app/schemas/article.py
@@ -16,7 +16,7 @@ class SourceBrief(BaseModel):
class ArticleListItem(BaseModel):
- """列表项:精简字段。"""
+ """列表项:精简字段(首页只露钩子,详细阅读进详情页)。"""
model_config = ConfigDict(from_attributes=True)
@@ -31,6 +31,10 @@ class ArticleListItem(BaseModel):
published_at: datetime | None = None
fetched_at: datetime
image_url: str | None = None
+ # === 列表预览钩子:点击进详情前的"诱导点" ===
+ commentary: str | None = None # LLM 点评(列表里截断显示)
+ commentary_status: str | None = None # ok/failed/pending/n/a
+ image_ai_url: str | None = None # AI 插图(列表里缩略图)
is_starred: bool = False
diff --git a/frontend/src/api/articles.ts b/frontend/src/api/articles.ts
index c5085aa..10bf8e9 100644
--- a/frontend/src/api/articles.ts
+++ b/frontend/src/api/articles.ts
@@ -31,6 +31,10 @@ export interface ArticleListItem {
published_at?: string | null
fetched_at: string
image_url?: string | null
+ // 列表预览钩子(首页展示用,详情页看完整版)
+ commentary?: string | null
+ commentary_status?: string | null
+ image_ai_url?: string | null
is_starred: boolean
}
diff --git a/frontend/src/views/Feed.vue b/frontend/src/views/Feed.vue
index 55b322a..a67cec3 100644
--- a/frontend/src/views/Feed.vue
+++ b/frontend/src/views/Feed.vue
@@ -68,6 +68,26 @@ function fmtTime(s?: string | null) {
return dayjs(s).fromNow()
}
+// category 是逗号分隔字符串(LLM 输出),拆成多个 tag
+function splitCategory(c?: string | null): string[] {
+ if (!c) return []
+ return c.split(',').map((s) => s.trim()).filter(Boolean)
+}
+
+// 评论预览:长文截断,带状态点
+function previewCommentary(c?: string | null, max = 120): string {
+ if (!c) return ''
+ const trimmed = c.replace(/\s+/g, ' ').trim()
+ return trimmed.length > max ? trimmed.slice(0, max) + '…' : trimmed
+}
+
+function commentaryStatusType(s?: string | null): 'success' | 'warning' | 'error' | 'default' {
+ if (s === 'ok') return 'success'
+ if (s === 'failed') return 'error'
+ if (s === 'pending') return 'warning'
+ return 'default'
+}
+
onMounted(async () => {
await loadSources()
await load()
@@ -112,6 +132,15 @@ onMounted(async () => {