feat: GEO/SEO structured data + search ranking tracker
This commit is contained in:
@@ -9,7 +9,9 @@ from ..database import get_db
|
||||
from ..models import Topic, TopicField, TopicConfigField, Article, ContentMetrics
|
||||
from ..schemas import (
|
||||
TopicCreate, TopicUpdate, TopicResponse, TopicScoreRequest,
|
||||
TopicAnalyzeRequest, TopicAnalyzeResponse,
|
||||
)
|
||||
from ..core.nvidia_client import call_llm
|
||||
from .auth import get_current_user, org_filter
|
||||
|
||||
router = APIRouter(prefix="/api/topics", tags=["topics"], dependencies=[Depends(get_current_user)])
|
||||
@@ -93,6 +95,65 @@ def topic_stats(
|
||||
}
|
||||
|
||||
|
||||
@router.post("/analyze", response_model=TopicAnalyzeResponse)
|
||||
def analyze_topic(
|
||||
data: TopicAnalyzeRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
"""AI 分析用户输入的选题思路,返回结构化的选题信息"""
|
||||
import json, re
|
||||
from sqlalchemy import text as sa_text
|
||||
|
||||
refs = "\n".join(f"- {link}" for link in data.reference_links) if data.reference_links else "无"
|
||||
prompt_text = f"""你是一个专业的内容策略师。用户提供了一个选题思路,请分析并提炼为结构化的选题信息。
|
||||
|
||||
用户输入的原始内容:
|
||||
{data.raw_input}
|
||||
|
||||
参考链接(如有):
|
||||
{refs}
|
||||
|
||||
请输出 JSON(只输出 JSON,不要其他文字):
|
||||
{{
|
||||
"title": "优化后的选题标题(20字内,含核心关键词,有吸引力)",
|
||||
"format": "内容形式(趋势洞察/实操指南/对比分析/案例解读/观点讨论)",
|
||||
"core_concept": "核心观点(一句话说清独特价值,20字内)",
|
||||
"audience_pain": "受众痛点(目标读者的真实困惑或需求,20字内)",
|
||||
"unique_angle": "差异化切入点(与常见文章不同的视角,20字内)",
|
||||
"tags": ["标签1", "标签2", "标签3", "标签4", "标签5"]
|
||||
}}"""
|
||||
|
||||
try:
|
||||
resp = call_llm(prompt_text, temperature=0.6, max_tokens=1500)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=503, detail=f"AI 分析失败: {str(e)}")
|
||||
|
||||
# Parse JSON from response
|
||||
resp = resp.strip()
|
||||
# Remove markdown code fences if present
|
||||
resp = re.sub(r'^```(?:json)?\s*', '', resp)
|
||||
resp = re.sub(r'\s*```$', '', resp)
|
||||
parsed = json.loads(resp)
|
||||
|
||||
field_name = None
|
||||
if data.field_id:
|
||||
field = db.query(TopicField).filter(TopicField.id == data.field_id).first()
|
||||
if field:
|
||||
field_name = field.name
|
||||
|
||||
return TopicAnalyzeResponse(
|
||||
title=parsed.get("title", ""),
|
||||
format=parsed.get("format", ""),
|
||||
core_concept=parsed.get("core_concept", ""),
|
||||
audience_pain=parsed.get("audience_pain", ""),
|
||||
unique_angle=parsed.get("unique_angle", ""),
|
||||
field_name=field_name,
|
||||
tags=parsed.get("tags", []),
|
||||
priority="中",
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=TopicResponse)
|
||||
def create_topic(
|
||||
data: TopicCreate,
|
||||
@@ -111,7 +172,7 @@ def create_topic(
|
||||
else:
|
||||
topic_id = f"T{datetime.now().strftime('%m%d%H%M')}"
|
||||
|
||||
field_name = None
|
||||
field_name = "未分类"
|
||||
if data.field_id:
|
||||
field = db.query(TopicField).filter(TopicField.id == data.field_id).first()
|
||||
if field:
|
||||
@@ -164,7 +225,7 @@ def update_topic(
|
||||
topic.field_id = data.field_id
|
||||
if data.field_id:
|
||||
field = db.query(TopicField).filter(TopicField.id == data.field_id).first()
|
||||
topic.field_name = field.name if field else None
|
||||
topic.field_name = field.name if field else '未分类'
|
||||
|
||||
for k, v in data.model_dump(exclude_unset=True, exclude={"field_id"}).items():
|
||||
if k == "tags" or k == "custom_data" or k == "scoring_data":
|
||||
|
||||
Reference in New Issue
Block a user