New features: - Offline Dictionary: reverse index of 175K verb forms + 200 common words, cached to disk, powers instant word lookups in Stories - Vocab SRS Review: spaced repetition for course vocabulary cards with due count badge and Again/Hard/Good/Easy rating - Cloze Practice: fill-in-the-blank using SentenceQuizEngine with distractor generation from vocabulary pool - Grammar Exercises: interactive quizzes for 5 grammar topics (ser/estar, por/para, preterite/imperfect, subjunctive, personal a) with "Practice This" button on grammar note detail - Listening Practice: listen-and-type + pronunciation check modes using Speech framework with word-by-word match scoring - Conversational Practice: AI chat partner via Foundation Models with 10 scenario types, saved to cloud container Other changes: - Add Conversation model to SharedModels and cloud container - Add Info.plist keys for speech recognition and microphone - Skip speech auth on simulator to prevent crash - Fix preparing data screen to only show during seed/migration - Extract courseDataVersion to static property on DataLoader - Add "How Features Work" reference page in Settings Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
49 lines
1.4 KiB
Swift
49 lines
1.4 KiB
Swift
import SwiftData
|
|
import Foundation
|
|
|
|
@Model
|
|
public final class Conversation {
|
|
public var id: String = ""
|
|
public var scenario: String = ""
|
|
public var level: String = ""
|
|
public var messages: String = "[]"
|
|
public var createdDate: Date = Date()
|
|
|
|
public init(scenario: String, level: String) {
|
|
self.id = UUID().uuidString
|
|
self.scenario = scenario
|
|
self.level = level
|
|
self.messages = "[]"
|
|
self.createdDate = Date()
|
|
}
|
|
}
|
|
|
|
public struct ChatMessage: Codable, Identifiable, Hashable {
|
|
public var id: String
|
|
public let role: String // "assistant" or "user"
|
|
public let content: String
|
|
public let correction: String?
|
|
|
|
public init(role: String, content: String, correction: String? = nil) {
|
|
self.id = UUID().uuidString
|
|
self.role = role
|
|
self.content = content
|
|
self.correction = correction
|
|
}
|
|
}
|
|
|
|
extension Conversation {
|
|
public var decodedMessages: [ChatMessage] {
|
|
guard let data = messages.data(using: .utf8) else { return [] }
|
|
return (try? JSONDecoder().decode([ChatMessage].self, from: data)) ?? []
|
|
}
|
|
|
|
public func appendMessage(_ message: ChatMessage) {
|
|
var msgs = decodedMessages
|
|
msgs.append(message)
|
|
if let data = try? JSONEncoder().encode(msgs), let str = String(data: data, encoding: .utf8) {
|
|
messages = str
|
|
}
|
|
}
|
|
}
|