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:
Trey t
2026-04-13 11:31:58 -05:00
parent 0848675016
commit 451866e988
8 changed files with 827 additions and 3 deletions

View 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)) ?? []
}
}