ocr
This commit is contained in:
+65
-24
@@ -56,14 +56,15 @@ type Question struct {
|
||||
|
||||
|
||||
type DescriptiveGradingRecord struct {
|
||||
ID int `json:"id"`
|
||||
QuestionID int `json:"questionId"`
|
||||
Source string `json:"source"`
|
||||
UserAnswer string `json:"userAnswer"`
|
||||
Score int `json:"score"`
|
||||
Evaluation string `json:"evaluation"`
|
||||
Username string `json:"username"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
ID int `json:"id"`
|
||||
QuestionID int `json:"questionId"`
|
||||
Source string `json:"source"`
|
||||
UserAnswer string `json:"userAnswer"`
|
||||
Score int `json:"score"`
|
||||
Evaluation string `json:"evaluation"`
|
||||
Username string `json:"username"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
OcrImageUrl string `json:"ocrImageUrl"`
|
||||
}
|
||||
|
||||
|
||||
@@ -227,6 +228,9 @@ func NewQuizService() *QuizService {
|
||||
log.Fatalf("Failed to create DescriptiveGrading table: %v", err)
|
||||
}
|
||||
|
||||
// Alter DescriptiveGrading to add ocr_image_url if it doesn't exist
|
||||
_, _ = db.Exec(`ALTER TABLE DescriptiveGrading ADD COLUMN ocr_image_url TEXT DEFAULT ''`)
|
||||
|
||||
// Create GeminiKeys table if it doesn't exist
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS GeminiKeys (
|
||||
@@ -838,7 +842,7 @@ func (s *QuizService) attachQuestionAssets(parts []genai.Part, source string, id
|
||||
|
||||
func (s *QuizService) GetDescriptiveGradingHistory(username string, source string, questionID int) []DescriptiveGradingRecord {
|
||||
query := `
|
||||
SELECT id, question_id, source, user_answer, score, evaluation, username, strftime('%Y-%m-%d %H:%M:%S', created_at, 'localtime') as created_at
|
||||
SELECT id, question_id, source, user_answer, score, evaluation, username, strftime('%Y-%m-%d %H:%M:%S', created_at, 'localtime') as created_at, COALESCE(ocr_image_url, '') as ocr_image_url
|
||||
FROM DescriptiveGrading
|
||||
WHERE username = ? AND source = ? AND question_id = ?
|
||||
ORDER BY created_at DESC
|
||||
@@ -853,7 +857,7 @@ func (s *QuizService) GetDescriptiveGradingHistory(username string, source strin
|
||||
var records []DescriptiveGradingRecord
|
||||
for rows.Next() {
|
||||
var r DescriptiveGradingRecord
|
||||
err := rows.Scan(&r.ID, &r.QuestionID, &r.Source, &r.UserAnswer, &r.Score, &r.Evaluation, &r.Username, &r.CreatedAt)
|
||||
err := rows.Scan(&r.ID, &r.QuestionID, &r.Source, &r.UserAnswer, &r.Score, &r.Evaluation, &r.Username, &r.CreatedAt, &r.OcrImageUrl)
|
||||
if err != nil {
|
||||
log.Printf("Scan error in GetDescriptiveGradingHistory: %v", err)
|
||||
continue
|
||||
@@ -863,7 +867,7 @@ func (s *QuizService) GetDescriptiveGradingHistory(username string, source strin
|
||||
return records
|
||||
}
|
||||
|
||||
func (s *QuizService) GradeDescriptiveAnswer(username string, source string, questionID int, userAnswer string) (*DescriptiveGradingRecord, error) {
|
||||
func (s *QuizService) GradeDescriptiveAnswer(username string, source string, questionID int, userAnswer string, ocrImageBase64 string) (*DescriptiveGradingRecord, error) {
|
||||
var qText, passage, modelAnswer string
|
||||
queryQ := `SELECT question_text, passage, explanation FROM QuizQuestions WHERE source = ? AND id = ?`
|
||||
err := s.db.QueryRow(queryQ, source, questionID).Scan(&qText, &passage, &modelAnswer)
|
||||
@@ -879,6 +883,37 @@ func (s *QuizService) GradeDescriptiveAnswer(username string, source string, que
|
||||
}
|
||||
}
|
||||
|
||||
var ocrImageUrl string
|
||||
if ocrImageBase64 != "" {
|
||||
base64Data := ocrImageBase64
|
||||
ext := "png"
|
||||
if idx := strings.Index(base64Data, ";base64,"); idx != -1 {
|
||||
header := base64Data[:idx]
|
||||
if strings.Contains(header, "image/jpeg") || strings.Contains(header, "image/jpg") {
|
||||
ext = "jpg"
|
||||
} else if strings.Contains(header, "image/webp") {
|
||||
ext = "webp"
|
||||
}
|
||||
base64Data = base64Data[idx+8:]
|
||||
}
|
||||
|
||||
dec, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err == nil {
|
||||
os.MkdirAll("assets/ocr_uploads", 0755)
|
||||
safeSource := sanitizeFilename(source)
|
||||
filename := fmt.Sprintf("ocr_%s_%s_%d_%d.%s", username, safeSource, questionID, time.Now().UnixNano(), ext)
|
||||
localPath := filepath.Join("assets", "ocr_uploads", filename)
|
||||
err = os.WriteFile(localPath, dec, 0644)
|
||||
if err == nil {
|
||||
ocrImageUrl = "/assets/ocr_uploads/" + filename
|
||||
} else {
|
||||
log.Printf("Failed to write OCR image file: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Failed to decode base64 OCR image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Start background goroutine for Gemini grading
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
@@ -950,8 +985,8 @@ func (s *QuizService) GradeDescriptiveAnswer(username string, source string, que
|
||||
}
|
||||
|
||||
stmt, err := s.db.Prepare(`
|
||||
INSERT INTO DescriptiveGrading (source, question_id, user_answer, score, evaluation, username)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO DescriptiveGrading (source, question_id, user_answer, score, evaluation, username, ocr_image_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
if err != nil {
|
||||
log.Printf("Failed to prepare INSERT statement: %v", err)
|
||||
@@ -964,7 +999,7 @@ func (s *QuizService) GradeDescriptiveAnswer(username string, source string, que
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
res, err := stmt.Exec(source, questionID, userAnswer, result.Score, result.Evaluation, username)
|
||||
res, err := stmt.Exec(source, questionID, userAnswer, result.Score, result.Evaluation, username, ocrImageUrl)
|
||||
if err != nil {
|
||||
log.Printf("Failed to execute INSERT statement: %v", err)
|
||||
s.emitEvent("ai:grading:done", map[string]interface{}{
|
||||
@@ -988,10 +1023,10 @@ func (s *QuizService) GradeDescriptiveAnswer(username string, source string, que
|
||||
|
||||
var record DescriptiveGradingRecord
|
||||
err = s.db.QueryRow(`
|
||||
SELECT id, question_id, source, user_answer, score, evaluation, username, strftime('%Y-%m-%d %H:%M:%S', created_at, 'localtime')
|
||||
SELECT id, question_id, source, user_answer, score, evaluation, username, strftime('%Y-%m-%d %H:%M:%S', created_at, 'localtime'), COALESCE(ocr_image_url, '') as ocr_image_url
|
||||
FROM DescriptiveGrading
|
||||
WHERE id = ?
|
||||
`, lastID).Scan(&record.ID, &record.QuestionID, &record.Source, &record.UserAnswer, &record.Score, &record.Evaluation, &record.Username, &record.CreatedAt)
|
||||
`, lastID).Scan(&record.ID, &record.QuestionID, &record.Source, &record.UserAnswer, &record.Score, &record.Evaluation, &record.Username, &record.CreatedAt, &record.OcrImageUrl)
|
||||
if err != nil {
|
||||
log.Printf("Failed to scan grading record: %v", err)
|
||||
s.emitEvent("ai:grading:done", map[string]interface{}{
|
||||
@@ -1013,17 +1048,23 @@ func (s *QuizService) GradeDescriptiveAnswer(username string, source string, que
|
||||
|
||||
// Return a temporary processing record immediately
|
||||
return &DescriptiveGradingRecord{
|
||||
ID: -1,
|
||||
QuestionID: questionID,
|
||||
Source: source,
|
||||
UserAnswer: userAnswer,
|
||||
Score: -1, // -1 means in progress
|
||||
Evaluation: "채점 진행 중...",
|
||||
Username: username,
|
||||
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||
ID: -1,
|
||||
QuestionID: questionID,
|
||||
Source: source,
|
||||
UserAnswer: userAnswer,
|
||||
Score: -1, // -1 means in progress
|
||||
Evaluation: "채점 진행 중...",
|
||||
Username: username,
|
||||
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||
OcrImageUrl: ocrImageUrl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sanitizeFilename(s string) string {
|
||||
r := strings.NewReplacer("/", "_", "\\", "_", ":", "_", "*", "_", "?", "_", "\"", "_", "<", "_", ">", "_", "|", "_", " ", "_")
|
||||
return r.Replace(s)
|
||||
}
|
||||
|
||||
// Login authenticates a user
|
||||
func (s *QuizService) Login(username, password string) (*User, error) {
|
||||
var u User
|
||||
|
||||
Reference in New Issue
Block a user