DictionaryService.swift 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import Foundation
  2. protocol DictionaryServicing {
  3. func lookUp(word: String) async throws -> DictionaryEntry
  4. }
  5. struct DictionaryService: DictionaryServicing {
  6. private let openAI: OpenAIChatServicing
  7. init(openAI: OpenAIChatServicing = OpenAIChatService()) {
  8. self.openAI = openAI
  9. }
  10. func lookUp(word: String) async throws -> DictionaryEntry {
  11. let trimmed = word.trimmingCharacters(in: .whitespacesAndNewlines)
  12. guard !trimmed.isEmpty else {
  13. throw DictionaryServiceError.emptyInput
  14. }
  15. do {
  16. let response: DictionaryAPIResponse = try await openAI.completeJSON(
  17. systemPrompt: """
  18. You are an English dictionary. Define the requested word. Respond with JSON only using this schema:
  19. {
  20. "word": "the word",
  21. "phonetic": "/phonetic transcription/ or null",
  22. "meanings": [
  23. {
  24. "partOfSpeech": "noun",
  25. "definitions": [
  26. {
  27. "text": "definition text",
  28. "example": "example sentence or null"
  29. }
  30. ],
  31. "synonyms": ["synonym"],
  32. "antonyms": ["antonym"]
  33. }
  34. ]
  35. }
  36. If the word is not a valid English word, return an empty meanings array.
  37. """,
  38. userPrompt: trimmed,
  39. as: DictionaryAPIResponse.self
  40. )
  41. guard !response.meanings.isEmpty else {
  42. throw DictionaryServiceError.wordNotFound
  43. }
  44. return DictionaryEntry(
  45. word: response.word,
  46. phonetic: response.phonetic,
  47. audioURL: nil,
  48. meanings: response.meanings.map { meaning in
  49. DictionaryMeaning(
  50. partOfSpeech: meaning.partOfSpeech,
  51. definitions: meaning.definitions.map {
  52. DictionaryDefinition(text: $0.text, example: $0.example)
  53. },
  54. synonyms: meaning.synonyms,
  55. antonyms: meaning.antonyms
  56. )
  57. }
  58. )
  59. } catch let error as OpenAIServiceError {
  60. throw DictionaryServiceError.fromOpenAI(error)
  61. } catch let error as DictionaryServiceError {
  62. throw error
  63. } catch {
  64. throw DictionaryServiceError.invalidResponse
  65. }
  66. }
  67. }
  68. private struct DictionaryAPIResponse: Decodable {
  69. struct Meaning: Decodable {
  70. struct Definition: Decodable {
  71. let text: String
  72. let example: String?
  73. }
  74. let partOfSpeech: String
  75. let definitions: [Definition]
  76. let synonyms: [String]
  77. let antonyms: [String]
  78. }
  79. let word: String
  80. let phonetic: String?
  81. let meanings: [Meaning]
  82. }
  83. private extension DictionaryServiceError {
  84. static func fromOpenAI(_ error: OpenAIServiceError) -> DictionaryServiceError {
  85. switch error {
  86. case .missingAPIKey:
  87. .missingAPIKey
  88. case .invalidResponse:
  89. .invalidResponse
  90. case .apiError(let message):
  91. .networkError(message)
  92. }
  93. }
  94. }