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>
68 lines
2.1 KiB
Swift
68 lines
2.1 KiB
Swift
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)) ?? []
|
|
}
|
|
}
|