智教助手平台:完整初始化

- 前端: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:
Zhang Jing Xuan
2026-07-29 16:29:10 +08:00
commit 0841b1a103
193 changed files with 44995 additions and 0 deletions
+301
View File
@@ -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,
}