/** * Cloud model providers the user can bring their own key for. Every one of * them speaks the OpenAI Chat Completions shape (Anthropic ships an official * compatibility layer at the same path), so one adapter covers them all — * plus a "custom" entry for any other OpenAI-compatible endpoint. * * Shared by the client (key inputs, pickers) and the server (routing): no * secrets live here. */ export type ProviderInfo = { id: string; label: string; /** OpenAI-compatible API base. Empty for the custom provider. */ baseUrl: string; /** Placeholder shown in the key input. */ keyHint: string; /** Where to create a key. */ docs: string; /** * Models offered before the live listing arrives (and on providers whose * /models needs a paid tier). Free-tier ids first where they exist. */ models: string[]; }; export const PROVIDERS: ProviderInfo[] = [ { id: "openai", label: "OpenAI", baseUrl: "https://api.openai.com/v1", keyHint: "sk-…", docs: "https://platform.openai.com/api-keys", models: ["gpt-4o-mini", "gpt-4o", "o4-mini"], }, { id: "anthropic", label: "Anthropic (Claude)", baseUrl: "https://api.anthropic.com/v1", keyHint: "sk-ant-…", docs: "https://console.anthropic.com/settings/keys", models: ["claude-sonnet-4-5", "claude-haiku-4-5", "claude-3-5-haiku-latest"], }, { id: "deepseek", label: "DeepSeek", baseUrl: "https://api.deepseek.com/v1", keyHint: "sk-…", docs: "https://platform.deepseek.com/api_keys", models: ["deepseek-chat", "deepseek-reasoner"], }, { id: "groq", label: "Groq", baseUrl: "https://api.groq.com/openai/v1", keyHint: "gsk_…", docs: "https://console.groq.com/keys", models: ["llama-3.3-70b-versatile", "llama-3.1-8b-instant", "openai/gpt-oss-120b"], }, { id: "openrouter", label: "OpenRouter", baseUrl: "https://openrouter.ai/api/v1", keyHint: "sk-or-…", docs: "https://openrouter.ai/keys", models: [ "meta-llama/llama-3.3-70b-instruct:free", "deepseek/deepseek-chat-v3-0324:free", "qwen/qwen3-235b-a22b:free", ], }, { id: "gemini", label: "Google Gemini", baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai", keyHint: "AIza…", docs: "https://aistudio.google.com/app/apikey", models: ["gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash"], }, { id: "custom", label: "Custom (OpenAI-compatible)", baseUrl: "", keyHint: "key…", docs: "", models: [], }, ]; /** * Text-to-speech for the podcast player. Only OpenAI-compatible * `/audio/speech` endpoints are wired (OpenAI, Groq and any local server the * custom provider points at); the rest simply do not offer TTS. Voice ids come * from each provider's catalogue — the male ones lead because a podcast host * was the ask. */ export type TtsVoice = { id: string; label: string; male?: boolean }; export type TtsProfile = { /** Default model for /audio/speech. */ model: string; voices: TtsVoice[]; /** Response format to request (Groq speaks WAV, everything else MP3). */ format: "mp3" | "wav"; }; export const TTS_PROFILES: Record = { openai: { model: "gpt-4o-mini-tts", format: "mp3", voices: [ { id: "onyx", label: "Onyx", male: true }, { id: "echo", label: "Echo", male: true }, { id: "ash", label: "Ash", male: true }, { id: "fable", label: "Fable" }, { id: "nova", label: "Nova" }, { id: "coral", label: "Coral" }, { id: "shimmer", label: "Shimmer" }, ], }, groq: { model: "playai-tts", format: "wav", voices: [ { id: "Fritz-PlayAI", label: "Fritz", male: true }, { id: "Thunder-PlayAI", label: "Thunder", male: true }, { id: "Mason-PlayAI", label: "Mason", male: true }, { id: "Celeste-PlayAI", label: "Celeste" }, { id: "Cheyenne-PlayAI", label: "Cheyenne" }, ], }, custom: { model: "tts-1", format: "mp3", voices: [ { id: "onyx", label: "Onyx", male: true }, { id: "echo", label: "Echo", male: true }, { id: "alloy", label: "Alloy" }, { id: "nova", label: "Nova" }, ], }, }; export function ttsProfile(providerId: string): TtsProfile | null { return TTS_PROFILES[providerId] ?? null; } export function providerInfo(id: string): ProviderInfo | undefined { return PROVIDERS.find((provider) => provider.id === id); } /** Headers the browser sends so the server can reach the picked provider. */ export const PROVIDER_HEADER = "x-freezmo-provider"; export const PROVIDER_KEY_HEADER = "x-freezmo-provider-key"; export const PROVIDER_BASE_HEADER = "x-freezmo-provider-base";