mirror of
https://github.com/handsomezhuzhu/QQuiz.git
synced 2026-02-20 20:10:14 +00:00
主要功能: - 🎯 新增 Google Gemini AI 提供商支持 - 原生 PDF 理解能力(最多1000页) - 完整保留图片、表格、公式等内容 - 支持自定义 Base URL(用于代理/中转服务) - 🤖 实现 AI 参考答案自动生成 - 当题目缺少答案时自动调用 AI 生成参考答案 - 支持单选、多选、判断、简答等所有题型 - 答案标记为"AI参考答案:"便于识别 - 🔧 优化文档解析功能 - 改进中文 Prompt 提高识别准确度 - 自动修复 JSON 中的控制字符(换行符等) - 智能题目类型验证和自动转换(proof→short等) - 增加超时时间和重试机制 - 🎨 完善管理后台配置界面 - 新增 Gemini 配置区域 - 突出显示 PDF 原生支持特性 - 为其他提供商添加"仅文本"警告 - 支持 Gemini Base URL 自定义 技术改进: - 添加 google-genai 依赖 - 实现异步 API 调用适配 - 完善错误处理和日志输出 - 统一配置管理和数据库存储 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
185 lines
4.2 KiB
Python
185 lines
4.2 KiB
Python
"""
|
|
Pydantic Schemas for Request/Response Validation
|
|
"""
|
|
from pydantic import BaseModel, Field, validator
|
|
from typing import Optional, List
|
|
from datetime import datetime
|
|
from models import ExamStatus, QuestionType
|
|
|
|
|
|
# ============ Auth Schemas ============
|
|
class UserCreate(BaseModel):
|
|
username: str = Field(..., min_length=3, max_length=50)
|
|
password: str = Field(..., min_length=6)
|
|
|
|
@validator('username')
|
|
def username_alphanumeric(cls, v):
|
|
if not v.replace('_', '').replace('-', '').isalnum():
|
|
raise ValueError('Username must be alphanumeric (allows _ and -)')
|
|
return v
|
|
|
|
|
|
class UserLogin(BaseModel):
|
|
username: str
|
|
password: str
|
|
|
|
|
|
class Token(BaseModel):
|
|
access_token: str
|
|
token_type: str = "bearer"
|
|
|
|
|
|
class UserResponse(BaseModel):
|
|
id: int
|
|
username: str
|
|
is_admin: bool
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ============ System Config Schemas ============
|
|
class SystemConfigUpdate(BaseModel):
|
|
allow_registration: Optional[bool] = None
|
|
max_upload_size_mb: Optional[int] = None
|
|
max_daily_uploads: Optional[int] = None
|
|
ai_provider: Optional[str] = None
|
|
# API Configuration
|
|
openai_api_key: Optional[str] = None
|
|
openai_base_url: Optional[str] = None
|
|
openai_model: Optional[str] = None
|
|
anthropic_api_key: Optional[str] = None
|
|
anthropic_model: Optional[str] = None
|
|
qwen_api_key: Optional[str] = None
|
|
qwen_base_url: Optional[str] = None
|
|
qwen_model: Optional[str] = None
|
|
gemini_api_key: Optional[str] = None
|
|
gemini_base_url: Optional[str] = None
|
|
gemini_model: Optional[str] = None
|
|
|
|
|
|
class SystemConfigResponse(BaseModel):
|
|
allow_registration: bool
|
|
max_upload_size_mb: int
|
|
max_daily_uploads: int
|
|
ai_provider: str
|
|
# API Configuration
|
|
openai_api_key: Optional[str] = None
|
|
openai_base_url: Optional[str] = None
|
|
openai_model: Optional[str] = None
|
|
anthropic_api_key: Optional[str] = None
|
|
anthropic_model: Optional[str] = None
|
|
qwen_api_key: Optional[str] = None
|
|
qwen_base_url: Optional[str] = None
|
|
qwen_model: Optional[str] = None
|
|
gemini_api_key: Optional[str] = None
|
|
gemini_base_url: Optional[str] = None
|
|
gemini_model: Optional[str] = None
|
|
|
|
|
|
# ============ Exam Schemas ============
|
|
class ExamCreate(BaseModel):
|
|
title: str = Field(..., min_length=1, max_length=200)
|
|
|
|
|
|
class ExamResponse(BaseModel):
|
|
id: int
|
|
user_id: int
|
|
title: str
|
|
status: ExamStatus
|
|
current_index: int
|
|
total_questions: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class ExamListResponse(BaseModel):
|
|
exams: List[ExamResponse]
|
|
total: int
|
|
|
|
|
|
class ExamUploadResponse(BaseModel):
|
|
exam_id: int
|
|
title: str
|
|
status: str
|
|
message: str
|
|
|
|
|
|
class ParseResult(BaseModel):
|
|
"""Result from file parsing"""
|
|
total_parsed: int
|
|
duplicates_removed: int
|
|
new_added: int
|
|
message: str
|
|
|
|
|
|
# ============ Question Schemas ============
|
|
class QuestionBase(BaseModel):
|
|
content: str
|
|
type: QuestionType
|
|
options: Optional[List[str]] = None
|
|
answer: str
|
|
analysis: Optional[str] = None
|
|
|
|
|
|
class QuestionCreate(QuestionBase):
|
|
exam_id: int
|
|
|
|
|
|
class QuestionResponse(QuestionBase):
|
|
id: int
|
|
exam_id: int
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class QuestionListResponse(BaseModel):
|
|
questions: List[QuestionResponse]
|
|
total: int
|
|
|
|
|
|
# ============ Quiz Schemas ============
|
|
class AnswerSubmit(BaseModel):
|
|
question_id: int
|
|
user_answer: str
|
|
|
|
|
|
class AnswerCheckResponse(BaseModel):
|
|
correct: bool
|
|
user_answer: str
|
|
correct_answer: str
|
|
analysis: Optional[str] = None
|
|
ai_score: Optional[float] = None # For short answer questions
|
|
ai_feedback: Optional[str] = None # For short answer questions
|
|
|
|
|
|
class QuizProgressUpdate(BaseModel):
|
|
current_index: int
|
|
|
|
|
|
# ============ Mistake Schemas ============
|
|
class MistakeAdd(BaseModel):
|
|
question_id: int
|
|
|
|
|
|
class MistakeResponse(BaseModel):
|
|
id: int
|
|
user_id: int
|
|
question_id: int
|
|
question: QuestionResponse
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class MistakeListResponse(BaseModel):
|
|
mistakes: List[MistakeResponse]
|
|
total: int
|