智教助手平台:完整初始化
- 前端:Vue3 + TS + Element Plus,24 个页面路由(课件/组题/教案/动画/思维导图/作文批改/命题/课堂/资源/社区等) - 后端:FastAPI + SQLAlchemy + SQLite,17 个路由模块,AI 服务层含降级模板 - AI:glm-5.x 推理模型已禁用思维链,确保输出真实内容 - 修复:ai_service 两处请求体注入 enable_thinking/thinking disabled - 测试账号:13900999999 / test1234
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database import get_db
|
||||
from models.animation import Animation
|
||||
from models.classroom import ClassroomActivity
|
||||
from models.community import Post
|
||||
from models.courseware import Courseware
|
||||
from models.exam import Exam
|
||||
from models.exercise import Exercise
|
||||
from models.lesson_plan import LessonPlan
|
||||
from models.material import Material
|
||||
from models.mindmap import MindMap
|
||||
from models.resource import Resource
|
||||
from models.user import User
|
||||
from schemas.search import SearchResponse
|
||||
from services.auth import get_optional_current_user
|
||||
|
||||
router = APIRouter(prefix="/api/search", tags=["全局搜索"])
|
||||
|
||||
|
||||
def _has_keyword(*values, keyword: str) -> bool:
|
||||
if not keyword:
|
||||
return True
|
||||
haystack = " ".join(str(value or "") for value in values).lower()
|
||||
return keyword.lower() in haystack
|
||||
|
||||
|
||||
def _tag_list(value) -> list[str]:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [str(item) for item in value if str(item).strip()][:6]
|
||||
|
||||
|
||||
def _item(
|
||||
*,
|
||||
id,
|
||||
source: str,
|
||||
source_label: str,
|
||||
title: str,
|
||||
route: str,
|
||||
description: str = "",
|
||||
subject: str = "",
|
||||
grade: str = "",
|
||||
tags=None,
|
||||
action_route: str = "",
|
||||
is_public: bool = False,
|
||||
updated_at=None,
|
||||
) -> dict:
|
||||
return {
|
||||
"id": id,
|
||||
"source": source,
|
||||
"source_label": source_label,
|
||||
"title": title or "未命名",
|
||||
"description": description or "",
|
||||
"subject": subject or "",
|
||||
"grade": grade or "",
|
||||
"tags": _tag_list(tags),
|
||||
"route": route,
|
||||
"action_route": action_route,
|
||||
"is_public": bool(is_public),
|
||||
"updated_at": updated_at,
|
||||
}
|
||||
|
||||
|
||||
def _sort_key(item: dict):
|
||||
return item["updated_at"] or ""
|
||||
|
||||
|
||||
def _resource_query(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
||||
query = db.query(Resource).filter(Resource.status == "active")
|
||||
if current_user:
|
||||
query = query.filter((Resource.user_id == current_user.id) | (Resource.is_public == 1))
|
||||
else:
|
||||
query = query.filter(Resource.is_public == 1)
|
||||
if keyword:
|
||||
query = query.filter(or_(
|
||||
Resource.title.contains(keyword),
|
||||
Resource.description.contains(keyword),
|
||||
Resource.subject.contains(keyword),
|
||||
Resource.grade.contains(keyword),
|
||||
))
|
||||
return [
|
||||
_item(
|
||||
id=res.id,
|
||||
source="resource",
|
||||
source_label="资源",
|
||||
title=res.title,
|
||||
description=res.description,
|
||||
subject=res.subject,
|
||||
grade=res.grade,
|
||||
tags=res.tags,
|
||||
route=f"/resources/{res.id}",
|
||||
action_route=f"/resources/{res.id}",
|
||||
is_public=bool(res.is_public),
|
||||
updated_at=res.updated_at,
|
||||
)
|
||||
for res in query.order_by(Resource.updated_at.desc()).limit(limit).all()
|
||||
]
|
||||
|
||||
|
||||
def _community_query(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
||||
query = db.query(Post)
|
||||
if keyword:
|
||||
query = query.filter(or_(Post.title.contains(keyword), Post.content.contains(keyword)))
|
||||
return [
|
||||
_item(
|
||||
id=post.id,
|
||||
source="community",
|
||||
source_label="模板社区",
|
||||
title=post.title,
|
||||
description=post.content,
|
||||
tags=post.tags,
|
||||
route=f"/community/{post.id}",
|
||||
action_route=f"/community/{post.id}",
|
||||
is_public=True,
|
||||
updated_at=post.updated_at,
|
||||
)
|
||||
for post in query.order_by(Post.updated_at.desc()).limit(limit).all()
|
||||
]
|
||||
|
||||
|
||||
def _my_work_queries(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
||||
if not current_user:
|
||||
return []
|
||||
user_id = current_user.id
|
||||
items = []
|
||||
|
||||
coursewares = db.query(Courseware).filter(Courseware.user_id == user_id, Courseware.status != "archived").all()
|
||||
for work in coursewares:
|
||||
if _has_keyword(work.title, work.description, work.subject, work.grade, " ".join(_tag_list(work.tags)), keyword=keyword):
|
||||
items.append(_item(
|
||||
id=work.id,
|
||||
source="courseware",
|
||||
source_label="我的课件",
|
||||
title=work.title,
|
||||
description=work.description,
|
||||
subject=work.subject,
|
||||
grade=work.grade,
|
||||
tags=work.tags,
|
||||
route=f"/courseware/{work.id}",
|
||||
action_route=f"/courseware/{work.id}",
|
||||
updated_at=work.updated_at,
|
||||
))
|
||||
|
||||
animations = db.query(Animation).filter(Animation.user_id == user_id).all()
|
||||
for work in animations:
|
||||
if _has_keyword(work.title, work.description, work.anim_type, keyword=keyword):
|
||||
items.append(_item(
|
||||
id=work.id,
|
||||
source="animation",
|
||||
source_label="我的动画",
|
||||
title=work.title,
|
||||
description=work.description,
|
||||
subject=str(work.anim_type or ""),
|
||||
route=f"/animation?open={work.id}",
|
||||
action_route=f"/animation?open={work.id}",
|
||||
updated_at=work.updated_at,
|
||||
))
|
||||
|
||||
exercises = db.query(Exercise).filter(Exercise.user_id == user_id).all()
|
||||
for work in exercises:
|
||||
if _has_keyword(work.title, work.subject, " ".join(_tag_list(work.knowledge_points)), keyword=keyword):
|
||||
items.append(_item(
|
||||
id=work.id,
|
||||
source="exercise",
|
||||
source_label="我的练习",
|
||||
title=work.title,
|
||||
subject=work.subject,
|
||||
tags=work.knowledge_points,
|
||||
route=f"/exercise?open={work.id}",
|
||||
action_route=f"/exercise?open={work.id}",
|
||||
updated_at=work.updated_at,
|
||||
))
|
||||
|
||||
lesson_plans = db.query(LessonPlan).filter(LessonPlan.user_id == user_id).all()
|
||||
for work in lesson_plans:
|
||||
if _has_keyword(work.title, work.subject, work.grade, " ".join(_tag_list(work.objectives)), keyword=keyword):
|
||||
items.append(_item(
|
||||
id=work.id,
|
||||
source="lesson_plan",
|
||||
source_label="我的教案",
|
||||
title=work.title,
|
||||
subject=work.subject,
|
||||
grade=work.grade,
|
||||
tags=work.objectives,
|
||||
route=f"/lesson-plan?open={work.id}",
|
||||
action_route=f"/lesson-plan?open={work.id}",
|
||||
updated_at=work.updated_at,
|
||||
))
|
||||
|
||||
mind_maps = db.query(MindMap).filter(MindMap.user_id == user_id).all()
|
||||
for work in mind_maps:
|
||||
if _has_keyword(work.title, work.subject, keyword=keyword):
|
||||
items.append(_item(
|
||||
id=work.id,
|
||||
source="mindmap",
|
||||
source_label="我的导图",
|
||||
title=work.title,
|
||||
subject=work.subject,
|
||||
route=f"/mindmap?open={work.id}",
|
||||
action_route=f"/mindmap?open={work.id}",
|
||||
updated_at=work.updated_at,
|
||||
))
|
||||
|
||||
exams = db.query(Exam).filter(Exam.user_id == user_id).all()
|
||||
for work in exams:
|
||||
if _has_keyword(work.title, work.subject, work.grade, " ".join(_tag_list(work.knowledge_points)), keyword=keyword):
|
||||
items.append(_item(
|
||||
id=work.id,
|
||||
source="exam",
|
||||
source_label="我的试卷",
|
||||
title=work.title,
|
||||
subject=work.subject,
|
||||
grade=work.grade,
|
||||
tags=work.knowledge_points,
|
||||
route=f"/exam?open={work.id}",
|
||||
action_route=f"/exam?open={work.id}",
|
||||
updated_at=work.updated_at,
|
||||
))
|
||||
|
||||
items.sort(key=_sort_key, reverse=True)
|
||||
return items[:limit]
|
||||
|
||||
|
||||
def _material_query(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
||||
if not current_user:
|
||||
return []
|
||||
query = db.query(Material).filter(Material.user_id == current_user.id, Material.status == "active")
|
||||
if keyword:
|
||||
query = query.filter(or_(
|
||||
Material.title.contains(keyword),
|
||||
Material.filename.contains(keyword),
|
||||
Material.summary.contains(keyword),
|
||||
Material.subject.contains(keyword),
|
||||
Material.grade.contains(keyword),
|
||||
))
|
||||
return [
|
||||
_item(
|
||||
id=item.id,
|
||||
source="material",
|
||||
source_label="素材",
|
||||
title=item.title,
|
||||
description=item.summary,
|
||||
subject=item.subject,
|
||||
grade=item.grade,
|
||||
tags=item.tags,
|
||||
route="/materials",
|
||||
action_route=f"/courseware/create",
|
||||
updated_at=item.updated_at,
|
||||
)
|
||||
for item in query.order_by(Material.updated_at.desc()).limit(limit).all()
|
||||
]
|
||||
|
||||
|
||||
def _classroom_query(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
||||
if not current_user:
|
||||
return []
|
||||
activities = db.query(ClassroomActivity).filter(ClassroomActivity.user_id == current_user.id).all()
|
||||
items = []
|
||||
for activity in activities:
|
||||
config = activity.config or {}
|
||||
if _has_keyword(activity.title, activity.description, config.get("question"), activity.activity_type, keyword=keyword):
|
||||
items.append(_item(
|
||||
id=activity.id,
|
||||
source="classroom",
|
||||
source_label="课堂活动",
|
||||
title=activity.title,
|
||||
description=activity.description or config.get("question") or "",
|
||||
subject=activity.activity_type or "",
|
||||
route=f"/classroom?open={activity.id}",
|
||||
action_route=f"/classroom?open={activity.id}",
|
||||
updated_at=activity.updated_at,
|
||||
))
|
||||
items.sort(key=_sort_key, reverse=True)
|
||||
return items[:limit]
|
||||
|
||||
|
||||
@router.get("", response_model=SearchResponse)
|
||||
def global_search(
|
||||
keyword: str = Query("", max_length=100),
|
||||
limit: int = Query(6, ge=1, le=20),
|
||||
current_user: User | None = Depends(get_optional_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
keyword = keyword.strip()
|
||||
resources = _resource_query(keyword, current_user, db, limit)
|
||||
community = _community_query(keyword, current_user, db, limit)
|
||||
works = _my_work_queries(keyword, current_user, db, limit)
|
||||
materials = _material_query(keyword, current_user, db, limit)
|
||||
classroom = _classroom_query(keyword, current_user, db, limit)
|
||||
return {
|
||||
"keyword": keyword,
|
||||
"total": len(resources) + len(community) + len(works) + len(materials) + len(classroom),
|
||||
"resources": resources,
|
||||
"community": community,
|
||||
"works": works,
|
||||
"materials": materials,
|
||||
"classroom": classroom,
|
||||
}
|
||||
Reference in New Issue
Block a user