import { GoogleGenAI, Modality } from '@google/genai'; import { VoiceName } from '../types'; import { normalizeUrl } from '../utils/url'; const getAiClient = () => { const apiKey = import.meta.env.VITE_API_KEY; if (!apiKey) { throw new Error( "Gemini API key is missing. Set VITE_API_KEY in your .env.local file (e.g., VITE_API_KEY=your_key_here)." ); } return new GoogleGenAI({ apiKey }); }; /** * List of CORS proxies to try in order. * This improves reliability if one service is down or blocked. */ const PROXY_PROVIDERS = [ // AllOrigins: Generally the most reliable for raw text (url: string) => `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`, // CodeTabs: Good fallback, handles redirects well (url: string) => `https://api.codetabs.com/v1/proxy?quest=${encodeURIComponent(url)}`, // CORSProxy.io: Fast but sometimes has strict CORS headers (url: string) => `https://corsproxy.io/?${encodeURIComponent(url)}`, // ThingProxy: Another fallback (url: string) => `https://thingproxy.freeboard.io/fetch/${url}` ]; /** * Cleans raw HTML by removing scripts, styles, and non-content elements. * This acts like a dedicated "Reader Mode" pre-processor. */ function cleanAndMinifyHtml(rawHtml: string): string { try { const parser = new DOMParser(); const doc = parser.parseFromString(rawHtml, 'text/html'); // 1. Remove heavy technical tags // We remove these because they consume tokens and provide no semantic value for text extraction. const technicalTags = ['script', 'style', 'noscript', 'iframe', 'svg', 'link', 'meta', 'button', 'input', 'form', 'img', 'picture', 'video']; technicalTags.forEach(tag => { const elements = doc.querySelectorAll(tag); elements.forEach(el => el.remove()); }); // NOTE: We intentionally DO NOT remove semantic tags like