All files / core/infrastructure/repositories GeminiVocabularyRepository.ts

96.87% Statements 31/32
77.77% Branches 7/9
100% Functions 2/2
96.87% Lines 31/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 781x 1x             9x       9x 1x   8x             7x   7x               7x   7x 1x       6x 6x 1x   6x     6x 1x   6x     6x 6x   1x     5x 1x     4x 4x 13x 1x   12x     3x 1x     2x      
import { GoogleGenAI } from '@google/genai'
import { VocabularyItem, isVocabularyItem } from '@/core/domain/VocabularyItem'
import { IVocabularyRepository } from '@/core/application/interfaces/IVocabularyRepository'
 
/**
 * GeminiVocabularyRepository
 * Infrastructure implementation of IVocabularyRepository using Google's Gemini AI.
 */
export class GeminiVocabularyRepository implements IVocabularyRepository {
  private readonly ai: GoogleGenAI
 
  constructor(apiKey: string) {
    if (!apiKey) {
      throw new Error('GEMINI_API_KEY is required')
    }
    this.ai = new GoogleGenAI({ apiKey })
  }
 
  /**
   * Fetches vocabulary items from Gemini AI
   */
  async getVocabularyItems(count: number = 5): Promise<VocabularyItem[]> {
    const prompt = `Elabore ${count} palavras distintas, comuns e não comuns e seus respectivos significados e um exemplo de uso. Retorne um JSON estrito no formato [{"word": "", "description": "", "useCase": ""}]. Seja criativo na escolha das palavras. Não adicione quebras de linhas ou crases de markdown. \`word\` em inglês, \`description\` em português e \`useCase\` em português`
 
    const response = await this.ai.models.generateContent({
      model: 'gemini-3-flash-preview',
      contents: prompt,
      config: {
        systemInstruction: 'Responda somente questões relacionadas ao ensino de inglês. Retorne apenas JSON válido.',
      },
    })
 
    const text = response.text
    // console.log(text)
    if (!text) {
      throw new Error('Empty response from Gemini AI')
    }
 
    // Clean the response text (remove potential markdown code blocks)
    let cleanedText = text.trim()
    if (cleanedText.startsWith('```json')) {
      cleanedText = cleanedText.slice(7)
    }
    Iif (cleanedText.startsWith('```')) {
      cleanedText = cleanedText.slice(3)
    }
    if (cleanedText.endsWith('```')) {
      cleanedText = cleanedText.slice(0, -3)
    }
    cleanedText = cleanedText.trim()
 
    let parsed: unknown
    try {
      parsed = JSON.parse(cleanedText)
    } catch {
      throw new Error('Failed to parse Gemini response as JSON')
    }
 
    if (!Array.isArray(parsed)) {
      throw new Error('Gemini response is not an array')
    }
 
    const items: VocabularyItem[] = []
    for (const item of parsed) {
      if (!isVocabularyItem(item)) {
        throw new Error('Invalid vocabulary item structure in response')
      }
      items.push(item)
    }
 
    if (items.length !== count) {
      throw new Error(`Expected ${count} items but received ${items.length}`)
    }
 
    return items
  }
}