analize
This commit is contained in:
+231
@@ -14,6 +14,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"changeme/antigravity"
|
||||
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"google.golang.org/api/option"
|
||||
@@ -121,6 +123,20 @@ func NewQuizService() *QuizService {
|
||||
log.Fatalf("Failed to create QuizExplain table: %v", err)
|
||||
}
|
||||
|
||||
// Create StudyElements table if it doesn't exist
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS StudyElements (
|
||||
question_id INTEGER NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
content TEXT,
|
||||
PRIMARY KEY (source, question_id),
|
||||
FOREIGN KEY (source, question_id) REFERENCES QuizQuestions (source, id) ON DELETE CASCADE
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create StudyElements table: %v", err)
|
||||
}
|
||||
|
||||
// Create Users table if it doesn't exist
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS Users (
|
||||
@@ -1377,3 +1393,218 @@ func (s *QuizService) UpdateUserInfoForce(username, nickname, phone, password st
|
||||
}
|
||||
}
|
||||
|
||||
type StudyElementDB struct {
|
||||
QuestionID int `json:"questionId"`
|
||||
Source string `json:"source"`
|
||||
QuestionText string `json:"questionText"`
|
||||
Content string `json:"content"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
// GetStudyElements retrieves all extracted study elements for a category.
|
||||
func (s *QuizService) GetStudyElements(category string) ([]StudyElementDB, error) {
|
||||
query := `
|
||||
SELECT se.question_id, se.source, se.content, q.question_text, q.category
|
||||
FROM StudyElements se
|
||||
JOIN QuizQuestions q ON se.source = q.source AND se.question_id = q.id
|
||||
WHERE q.category = ?
|
||||
ORDER BY se.question_id ASC
|
||||
`
|
||||
rows, err := s.db.Query(query, category)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var list []StudyElementDB
|
||||
for rows.Next() {
|
||||
var se StudyElementDB
|
||||
err := rows.Scan(&se.QuestionID, &se.Source, &se.Content, &se.QuestionText, &se.Category)
|
||||
if err == nil {
|
||||
list = append(list, se)
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ExtractStudyElementsForCategory extracts study elements for all questions in the category.
|
||||
// It skips questions that already have an extracted study element.
|
||||
func (s *QuizService) ExtractStudyElementsForCategory(category string) (string, error) {
|
||||
// 1. Get questions in this category that don't have study elements yet.
|
||||
query := `
|
||||
SELECT q.id, q.source, q.question_text, q.choice1, q.choice2, q.choice3, q.choice4, q.correct_answer_str, q.explanation
|
||||
FROM QuizQuestions q
|
||||
LEFT JOIN StudyElements se ON q.source = se.source AND q.id = se.question_id
|
||||
WHERE q.category = ? AND se.question_id IS NULL
|
||||
`
|
||||
rows, err := s.db.Query(query, category)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type Task struct {
|
||||
ID int
|
||||
Source string
|
||||
QuestionText string
|
||||
Choice1 string
|
||||
Choice2 string
|
||||
Choice3 string
|
||||
Choice4 string
|
||||
CorrectAnswerStr string
|
||||
Explanation string
|
||||
}
|
||||
|
||||
var tasks []Task
|
||||
for rows.Next() {
|
||||
var t Task
|
||||
err := rows.Scan(&t.ID, &t.Source, &t.QuestionText, &t.Choice1, &t.Choice2, &t.Choice3, &t.Choice4, &t.CorrectAnswerStr, &t.Explanation)
|
||||
if err == nil {
|
||||
tasks = append(tasks, t)
|
||||
}
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
return "이미 모든 문제의 학습요소가 추출되어 있습니다.", nil
|
||||
}
|
||||
|
||||
// 2. Start a background goroutine to process with limited concurrency.
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
sem := make(chan struct{}, 3) // limit to 3 concurrent agy calls
|
||||
total := len(tasks)
|
||||
|
||||
for i, t := range tasks {
|
||||
sem <- struct{}{}
|
||||
go func(idx int, task Task) {
|
||||
defer func() { <-sem }()
|
||||
|
||||
// Update progress
|
||||
s.emitEvent("study:progress", map[string]interface{}{
|
||||
"current": idx + 1,
|
||||
"total": total,
|
||||
"message": fmt.Sprintf("문제 %d 추출 중...", task.ID),
|
||||
})
|
||||
|
||||
content, err := antigravity.ExtractStudyElement(
|
||||
ctx,
|
||||
task.QuestionText,
|
||||
task.Choice1,
|
||||
task.Choice2,
|
||||
task.Choice3,
|
||||
task.Choice4,
|
||||
task.CorrectAnswerStr,
|
||||
task.Explanation,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to extract study element for question %d: %v", task.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Save to DB
|
||||
_, err = s.db.Exec(`
|
||||
INSERT INTO StudyElements (question_id, source, content)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(source, question_id) DO UPDATE SET content=excluded.content
|
||||
`, task.ID, task.Source, content)
|
||||
if err != nil {
|
||||
log.Printf("Failed to save study element to DB: %v", err)
|
||||
}
|
||||
}(i, t)
|
||||
}
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
for i := 0; i < 3; i++ {
|
||||
sem <- struct{}{}
|
||||
}
|
||||
|
||||
s.emitEvent("study:done", map[string]interface{}{
|
||||
"category": category,
|
||||
"message": "학습요소 추출이 완료되었습니다.",
|
||||
})
|
||||
}()
|
||||
|
||||
return "학습요소 추출을 시작합니다.", nil
|
||||
}
|
||||
|
||||
// GenerateGoPracticalExams creates new exam questions based on existing ones.
|
||||
func (s *QuizService) GenerateGoPracticalExams(count int) (string, error) {
|
||||
if count <= 0 {
|
||||
count = 5
|
||||
}
|
||||
|
||||
// 1. Fetch sample real exams as references for the AI
|
||||
rows, err := s.db.Query(`
|
||||
SELECT question_text, explanation
|
||||
FROM QuizQuestions
|
||||
WHERE category = '정보보안기사 실기' AND explanation != ''
|
||||
LIMIT 10
|
||||
`)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var references []string
|
||||
for rows.Next() {
|
||||
var qText, explanation string
|
||||
if err := rows.Scan(&qText, &explanation); err == nil {
|
||||
references = append(references, fmt.Sprintf("질문:\n%s\n\n모범답안/해설:\n%s", qText, explanation))
|
||||
}
|
||||
}
|
||||
|
||||
if len(references) == 0 {
|
||||
references = []string{
|
||||
"질문:\n다음 지문을 읽고 빈칸 A, B에 들어갈 적절한 용어를 쓰시오.\n- ( A )은 네트워크 상의 패킷을 가로채어 분석하는 행위 또는 도구를 말한다.\n- ( B )은 IP 주소를 스푸핑하여 정상적인 호스트인 것처럼 위장하는 공격이다.\n\n모범답안/해설:\nA: 스니핑(Sniffing)\nB: IP 스푸핑(IP Spoofing)",
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Start a background goroutine to generate questions
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
category := "정보보호기사 실기(Go)"
|
||||
source := "qple"
|
||||
difficulty := "실기(Go)"
|
||||
subject := "만든 자료"
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
s.emitEvent("exam:progress", map[string]interface{}{
|
||||
"current": i + 1,
|
||||
"total": count,
|
||||
"message": fmt.Sprintf("%d번째 실기 문제 생성 중...", i+1),
|
||||
})
|
||||
|
||||
gq, err := antigravity.GenerateDescriptiveQuestion(ctx, references)
|
||||
if err != nil {
|
||||
log.Printf("Failed to generate exam question %d: %v", i+1, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate next ID
|
||||
var nextID int
|
||||
err = s.db.QueryRow("SELECT COALESCE(MAX(id), 0) + 1 FROM QuizQuestions WHERE source = ?", source).Scan(&nextID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get next ID: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Insert into QuizQuestions
|
||||
_, err = s.db.Exec(`
|
||||
INSERT INTO QuizQuestions (id, source, category, question_text, choice1, choice2, choice3, choice4, correct_answer, explanation, difficulty, subject)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, nextID, source, category, gq.QuestionText, gq.Choice1, gq.Choice2, gq.Choice3, gq.Choice4, gq.CorrectAnswer, gq.Explanation, difficulty, subject)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to insert generated question: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
s.emitEvent("exam:done", map[string]interface{}{
|
||||
"message": fmt.Sprintf("%d개의 실기 문제가 생성 및 저장되었습니다.", count),
|
||||
})
|
||||
}()
|
||||
|
||||
return "실기 문제 생성을 시작합니다.", nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user