移动端拍照上传、奥数区、学段约束解题与 AI 模型配置。
- 手机/平板响应式布局,支持拍照与相册上传 - 学生详情新增奥数区,按初/高中学段生成解法并禁止超纲 - 系统设置可配置 Ollama 或 OpenAI 兼容 API - 更新 frontend/dist 与使用说明
This commit is contained in:
@@ -8,10 +8,10 @@ from sqlalchemy.orm import Session, joinedload
|
||||
from app.core.config import settings
|
||||
from app.core.database import SessionLocal, get_db
|
||||
from app.core.deps import get_current_user
|
||||
from app.models.user import Subject, User, WrongQuestion, WrongQuestionStatus
|
||||
from app.schemas import WrongQuestionOut, WrongQuestionUpdate
|
||||
from app.models.user import Subject, User, WrongQuestion, WrongQuestionCategory, WrongQuestionStatus
|
||||
from app.schemas import WrongQuestionCategoryEnum, WrongQuestionOut, WrongQuestionUpdate
|
||||
from app.services import llm as llm_service
|
||||
from app.services import ocr as ocr_service
|
||||
from app.services import ollama as ollama_service
|
||||
from app.services.student_access import get_student_for_user
|
||||
|
||||
router = APIRouter(tags=["wrong_questions"])
|
||||
@@ -23,6 +23,7 @@ def _wq_to_out(wq: WrongQuestion) -> WrongQuestionOut:
|
||||
student_id=wq.student_id,
|
||||
subject_id=wq.subject_id,
|
||||
subject_name=wq.subject.name if wq.subject else None,
|
||||
category=wq.category,
|
||||
image_path=wq.image_path,
|
||||
ocr_raw_text=wq.ocr_raw_text,
|
||||
question_text=wq.question_text,
|
||||
@@ -60,16 +61,25 @@ def _process_wrong_question(question_id: uuid.UUID):
|
||||
|
||||
subject_name = wq.subject.name if wq.subject else "综合"
|
||||
school_level = wq.student.school_level if wq.student else None
|
||||
olympiad = wq.category == WrongQuestionCategory.olympiad
|
||||
ai_cfg = llm_service.load_ai_config(db)
|
||||
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
question_text = loop.run_until_complete(
|
||||
ollama_service.format_question(subject_name, ocr_text, school_level)
|
||||
llm_service.format_question(ai_cfg, subject_name, ocr_text, school_level)
|
||||
)
|
||||
solution_text = loop.run_until_complete(
|
||||
ollama_service.generate_solution(subject_name, question_text, school_level)
|
||||
llm_service.generate_solution(
|
||||
ai_cfg,
|
||||
subject_name,
|
||||
question_text,
|
||||
school_level,
|
||||
olympiad=olympiad,
|
||||
)
|
||||
)
|
||||
wq.question_text = question_text
|
||||
wq.solution_text = solution_text
|
||||
@@ -88,6 +98,7 @@ def _process_wrong_question(question_id: uuid.UUID):
|
||||
def list_wrong_questions(
|
||||
student_id: uuid.UUID,
|
||||
subject_id: int | None = Query(None),
|
||||
category: WrongQuestionCategoryEnum | None = Query(None),
|
||||
q: str | None = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
@@ -100,6 +111,8 @@ def list_wrong_questions(
|
||||
)
|
||||
if subject_id is not None:
|
||||
query = query.filter(WrongQuestion.subject_id == subject_id)
|
||||
if category is not None:
|
||||
query = query.filter(WrongQuestion.category == category.value)
|
||||
if q:
|
||||
pattern = f"%{q}%"
|
||||
query = query.filter(
|
||||
@@ -121,6 +134,7 @@ async def upload_wrong_question(
|
||||
background_tasks: BackgroundTasks,
|
||||
subject_id: int = Form(...),
|
||||
file: UploadFile = File(...),
|
||||
category: WrongQuestionCategoryEnum = Form(WrongQuestionCategoryEnum.regular),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
@@ -137,6 +151,7 @@ async def upload_wrong_question(
|
||||
student_id=student_id,
|
||||
subject_id=subject_id,
|
||||
image_path="",
|
||||
category=WrongQuestionCategory(category.value),
|
||||
status=WrongQuestionStatus.pending,
|
||||
)
|
||||
db.add(wq)
|
||||
@@ -270,21 +285,27 @@ async def regenerate_solution(
|
||||
|
||||
subject_name = wq.subject.name if wq.subject else "综合"
|
||||
school_level = wq.student.school_level if wq.student else None
|
||||
olympiad = wq.category == WrongQuestionCategory.olympiad
|
||||
question_text = wq.question_text or wq.ocr_raw_text or ""
|
||||
ai_cfg = llm_service.load_ai_config(db)
|
||||
|
||||
try:
|
||||
if not wq.question_text and wq.ocr_raw_text:
|
||||
wq.question_text = await ollama_service.format_question(
|
||||
subject_name, wq.ocr_raw_text, school_level
|
||||
wq.question_text = await llm_service.format_question(
|
||||
ai_cfg, subject_name, wq.ocr_raw_text, school_level
|
||||
)
|
||||
question_text = wq.question_text
|
||||
wq.solution_text = await ollama_service.generate_solution(
|
||||
subject_name, question_text, school_level
|
||||
wq.solution_text = await llm_service.generate_solution(
|
||||
ai_cfg,
|
||||
subject_name,
|
||||
question_text,
|
||||
school_level,
|
||||
olympiad=olympiad,
|
||||
)
|
||||
wq.status = WrongQuestionStatus.solved
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY, detail=f"Ollama 调用失败: {exc}"
|
||||
status_code=status.HTTP_502_BAD_GATEWAY, detail=f"AI 调用失败: {exc}"
|
||||
) from exc
|
||||
|
||||
db.commit()
|
||||
|
||||
Reference in New Issue
Block a user