Files
Spanish/Conjuga/Conjuga/Services/ConversationService.swift
Trey t a663bc03cd Add 6 new practice features, offline dictionary, and feature reference
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>
2026-04-13 16:12:36 -05:00

79 lines
3.1 KiB
Swift

import Foundation
import FoundationModels
import SharedModels
@MainActor
@Observable
final class ConversationService {
var isResponding = false
private var session: LanguageModelSession?
static let scenarios = [
"Ordering at a restaurant",
"Asking for directions",
"Shopping at a market",
"Checking into a hotel",
"Making plans with a friend",
"At the doctor's office",
"Job interview",
"Renting an apartment",
"At the airport",
"Meeting someone new",
]
func startConversation(scenario: String, level: String) -> String {
session = LanguageModelSession(instructions: """
You are a friendly Spanish conversation partner. The scenario is: \(scenario).
The student's level is: \(level).
Rules:
- Respond ONLY in Spanish appropriate for the student's level.
- Keep responses to 1-3 sentences.
- If the student makes a grammar mistake, gently correct it in parentheses \
at the end of your response, like: (Pequeña corrección: "fuiste" en vez de "fue")
- Stay in character for the scenario.
- Be encouraging and natural.
""")
// Return the opening message based on scenario
switch scenario {
case "Ordering at a restaurant":
return "¡Bienvenido! Soy su mesero. ¿Ya sabe qué le gustaría ordenar?"
case "Asking for directions":
return "¡Hola! ¿En qué le puedo ayudar? ¿Está buscando algún lugar?"
case "Shopping at a market":
return "¡Buenos días! Tenemos frutas muy frescas hoy. ¿Qué le gustaría comprar?"
case "Checking into a hotel":
return "Buenas tardes, bienvenido al Hotel Sol. ¿Tiene una reservación?"
case "Making plans with a friend":
return "¡Oye! ¿Qué quieres hacer este fin de semana? Estoy libre el sábado."
case "At the doctor's office":
return "Buenos días. Soy el doctor García. ¿Cómo se siente hoy? ¿Qué le pasa?"
case "Job interview":
return "Buenos días, gracias por venir. Cuénteme un poco sobre usted."
case "Renting an apartment":
return "¡Hola! Gracias por su interés en el apartamento. ¿Qué preguntas tiene?"
case "At the airport":
return "Buenas tardes, pasajero. ¿Me puede mostrar su pasaporte y su boleto?"
case "Meeting someone new":
return "¡Hola! Me llamo Carlos. ¿Cómo te llamas? ¿De dónde eres?"
default:
return "¡Hola! ¿Cómo estás? Vamos a practicar español juntos."
}
}
func respond(to userMessage: String) async throws -> String {
guard let session else { return "Error: no session" }
isResponding = true
defer { isResponding = false }
let response = try await session.respond(to: userMessage)
return response.content
}
static var isAvailable: Bool {
SystemLanguageModel.default.availability == .available
}
}