Add AI-generated short stories with tappable words and comprehension quiz
Generate one-paragraph Spanish stories on-device using Foundation Models, matched to user's level and enabled tenses. Every word is tappable — pre-annotated words show instantly, others get a quick on-device AI lookup with caching. English translation hidden by default behind a toggle. Comprehension quiz with 3 multiple-choice questions. Stories saved to cloud container for sync and persistence across resets. Closes #9 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
67
Conjuga/SharedModels/Sources/SharedModels/Story.swift
Normal file
67
Conjuga/SharedModels/Sources/SharedModels/Story.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
@Model
|
||||
public final class Story {
|
||||
public var id: String = ""
|
||||
public var title: String = ""
|
||||
public var bodyES: String = ""
|
||||
public var bodyEN: String = ""
|
||||
public var level: String = ""
|
||||
public var wordAnnotations: String = "[]"
|
||||
public var quizQuestions: String = "[]"
|
||||
public var createdDate: Date = Date()
|
||||
|
||||
public init(title: String, bodyES: String, bodyEN: String, level: String, wordAnnotations: String, quizQuestions: String) {
|
||||
self.id = UUID().uuidString
|
||||
self.title = title
|
||||
self.bodyES = bodyES
|
||||
self.bodyEN = bodyEN
|
||||
self.level = level
|
||||
self.wordAnnotations = wordAnnotations
|
||||
self.quizQuestions = quizQuestions
|
||||
self.createdDate = Date()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - JSON Helpers
|
||||
|
||||
public struct WordAnnotation: Codable, Identifiable, Hashable {
|
||||
public var id: String { word }
|
||||
public let word: String
|
||||
public let baseForm: String
|
||||
public let english: String
|
||||
public let partOfSpeech: String
|
||||
|
||||
public init(word: String, baseForm: String, english: String, partOfSpeech: String) {
|
||||
self.word = word
|
||||
self.baseForm = baseForm
|
||||
self.english = english
|
||||
self.partOfSpeech = partOfSpeech
|
||||
}
|
||||
}
|
||||
|
||||
public struct QuizQuestion: Codable, Identifiable, Hashable {
|
||||
public var id: String { question }
|
||||
public let question: String
|
||||
public let options: [String]
|
||||
public let correctIndex: Int
|
||||
|
||||
public init(question: String, options: [String], correctIndex: Int) {
|
||||
self.question = question
|
||||
self.options = options
|
||||
self.correctIndex = correctIndex
|
||||
}
|
||||
}
|
||||
|
||||
extension Story {
|
||||
public var decodedAnnotations: [WordAnnotation] {
|
||||
guard let data = wordAnnotations.data(using: .utf8) else { return [] }
|
||||
return (try? JSONDecoder().decode([WordAnnotation].self, from: data)) ?? []
|
||||
}
|
||||
|
||||
public var decodedQuestions: [QuizQuestion] {
|
||||
guard let data = quizQuestions.data(using: .utf8) else { return [] }
|
||||
return (try? JSONDecoder().decode([QuizQuestion].self, from: data)) ?? []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user