Spaces:
Running
Running
| // js/iaConfigModule.js | |
| const defaultConfig = { | |
| llm: { | |
| provider: "deepseek", | |
| apiKeys: { deepseek: "", openai: "" }, | |
| model: "deepseek-chat" | |
| }, | |
| transcription: { | |
| provider: "openai", | |
| apiKeys: { openai: "", deepgram: "" }, | |
| models: { openai: "whisper-1", deepgram: "nova-2" } | |
| } | |
| }; | |
| // CORRECCI脫N: Nombres de modelos de OpenAI actualizados con tus nuevas solicitudes. | |
| export const llmProviders = [ | |
| { name: "OpenAI", value: "openai", models: ["gpt-4o", "gpt-4o-mini", "gpt-5-mini", "gpt-5", "gpt-5-nano"], url: "https://api.openai.com" }, | |
| { name: "DeepSeek", value: "deepseek", models: ["deepseek-chat", "deepseek-reasoner"], url: "https://api.deepseek.com" } | |
| ]; | |
| export const transcriptionProviders = [ | |
| { name: "OpenAI Whisper", value: "openai", models: ["whisper-1"], url: "https://api.openai.com" }, | |
| { name: "Deepgram", value: "deepgram", models: ["nova-2", "whisper-large"], url: "https://api.deepgram.com" } | |
| ]; | |
| function saveConfig(config) { | |
| try { | |
| localStorage.setItem("iaConfig", JSON.stringify(config)); | |
| } catch (e) { | |
| console.error("[iaConfigModule] Error guardando la configuraci贸n:", e); | |
| } | |
| } | |
| function clone(obj) { | |
| // structuredClone es moderno y robusto, con un fallback seguro. | |
| try { | |
| return structuredClone(obj); | |
| } catch (e) { | |
| return JSON.parse(JSON.stringify(obj)); | |
| } | |
| } | |
| function loadConfig() { | |
| let config; | |
| try { | |
| const storedConfig = localStorage.getItem("iaConfig"); | |
| config = storedConfig ? | |
| JSON.parse(storedConfig) : clone(defaultConfig); | |
| } catch (error) { | |
| console.error("[iaConfigModule] Error al parsear config, usando defaults.", error); | |
| config = clone(defaultConfig); | |
| } | |
| let needsSave = false; | |
| if (config.transcription.apiKey !== undefined) { | |
| console.log("[iaConfigModule] Migrando config antigua de transcripci贸n..."); | |
| const oldKey = config.transcription.apiKey; | |
| const oldModel = config.transcription.model; | |
| const oldProvider = config.transcription.provider; | |
| // Se usa el spread operator para no perder las keys de otros proveedores. | |
| config.transcription.apiKeys = { ...defaultConfig.transcription.apiKeys, [oldProvider]: oldKey }; | |
| config.transcription.models = { ...defaultConfig.transcription.models, [oldProvider]: oldModel }; | |
| delete config.transcription.apiKey; | |
| delete config.transcription.model; | |
| needsSave = true; | |
| } | |
| if (config.llm.apiKey !== undefined) { | |
| console.log("[iaConfigModule] Migrando config antigua de LLM..."); | |
| const old = config.llm.apiKey; | |
| config.llm.apiKeys = { ...defaultConfig.llm.apiKeys, [config.llm.provider]: old }; | |
| delete config.llm.apiKey; | |
| needsSave = true; | |
| } | |
| if (config.llm.provider === 'deepseek' && (config.llm.model === 'deepseek-v3' || config.llm.model === 'deepseek-llm')) { | |
| config.llm.model = 'deepseek-chat'; | |
| needsSave = true; | |
| } | |
| if (needsSave) { | |
| saveConfig(config); | |
| } | |
| return config; | |
| } | |
| export function getIaConfig() { | |
| return loadConfig(); | |
| } | |
| export function renderIaConfigForm(containerId) { | |
| let config = loadConfig(); | |
| const container = document.getElementById(containerId); | |
| if (!container) { | |
| console.error(`[iaConfigModule] No se encontr贸 el contenedor '${containerId}'`); | |
| document.body.insertAdjacentHTML('beforeend', `<div style='color:red; padding: 1rem;'>[Error] No se encontr贸 el contenedor '${containerId}' para la configuraci贸n IA.</div>`); | |
| return; | |
| } | |
| function maskApiKey(key) { | |
| if (!key) return ''; | |
| if (key.length <= 8) return '*'.repeat(key.length); | |
| return `${key.substring(0, 3)}-****-${key.slice(-4)}`; | |
| } | |
| container.innerHTML = ` | |
| <div class="flex justify-between items-center mb-6 border-b pb-2 border-blue-100"> | |
| <h2 class="text-xl font-bold text-blue-700 flex items-center"><i class='fas fa-cogs mr-2'></i>Configurar Proveedores IA</h2> | |
| <button id="btnCloseConfig" type="button" class="text-gray-500 hover:text-blue-600 text-2xl focus:outline-none" aria-label="Cerrar"><i class="fas fa-times"></i></button> | |
| </div> | |
| <form id="iaConfigForm" class="space-y-6"> | |
| <div class="bg-blue-50 p-4 rounded-lg border border-blue-100"> | |
| <label class="block font-semibold text-blue-800 mb-2">Proveedor LLM</label> | |
| <select id="llmProvider" class="w-full mb-3 p-2 rounded border border-gray-300 focus:ring-2 focus:ring-blue-300">${llmProviders.map(p => `<option value="${p.value}">${p.name}</option>`).join("")}</select> | |
| <div class="flex items-center mb-3"> | |
| <input type="password" id="llmApiKey" class="flex-1 p-2 rounded border border-gray-300 mr-2 bg-gray-100" placeholder="API Key LLM" autocomplete="off"> | |
| <button class="text-blue-700 hover:text-blue-900 px-3 py-2 rounded focus:outline-none border border-blue-200 bg-white" type="button" id="toggleLlmApiKey"><i class="fas fa-eye"></i></button> | |
| </div> | |
| <select id="llmModel" class="w-full p-2 rounded border border-gray-300 focus:ring-2 focus:ring-blue-300"></select> | |
| </div> | |
| <div class="bg-purple-50 p-4 rounded-lg border border-purple-100"> | |
| <label class="block font-semibold text-purple-800 mb-2">Proveedor Transcripci贸n</label> | |
| <select id="transProvider" class="w-full mb-3 p-2 rounded border border-gray-300 focus:ring-2 focus:ring-purple-300">${transcriptionProviders.map(p => `<option value="${p.value}">${p.name}</option>`).join("")}</select> | |
| <div class="flex items-center mb-3"> | |
| <input type="password" id="transApiKey" class="flex-1 p-2 rounded border border-gray-300 mr-2 bg-gray-100" placeholder="API Key Transcripci贸n" autocomplete="off"> | |
| <button class="text-purple-700 hover:text-purple-900 px-3 py-2 rounded focus:outline-none border border-purple-200 bg-white" type="button" id="toggleTransApiKey"><i class="fas fa-eye"></i></button> | |
| </div> | |
| <select id="transModel" class="w-full p-2 rounded border border-gray-300 focus:ring-2 focus:ring-purple-300"></select> | |
| </div> | |
| <button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 rounded-lg shadow transition-colors flex items-center justify-center text-lg"><i class="fas fa-save mr-2"></i>Guardar configuraci贸n</button> | |
| </form> | |
| `; | |
| // Elementos del DOM | |
| const llmProviderSelect = document.getElementById("llmProvider"); | |
| const llmModelSelect = document.getElementById("llmModel"); | |
| const llmApiKeyInput = document.getElementById("llmApiKey"); | |
| const transProviderSelect = document.getElementById("transProvider"); | |
| const transModelSelect = document.getElementById("transModel"); | |
| const transApiKeyInput = document.getElementById("transApiKey"); | |
| // Listeners | |
| document.getElementById("btnCloseConfig")?.addEventListener("click", () => document.getElementById("configModal")?.classList.remove("active")); | |
| document.getElementById("toggleLlmApiKey").addEventListener("click", () => { llmApiKeyInput.type = llmApiKeyInput.type === "password" ? "text" : "password"; }); | |
| document.getElementById("toggleTransApiKey").addEventListener("click", () => { transApiKeyInput.type = transApiKeyInput.type === "password" ? "text" : "password"; }); | |
| function updateLlmUI() { | |
| const selectedProvider = llmProviderSelect.value; | |
| const providerObj = llmProviders.find(p => p.value === selectedProvider); | |
| if (!providerObj) return; | |
| llmModelSelect.innerHTML = providerObj.models.map(m => `<option value="${m}">${m}</option>`).join(""); | |
| let modelToSelect = providerObj.models[0]; | |
| if (selectedProvider === config.llm.provider) { | |
| if (providerObj.models.includes(config.llm.model)) { | |
| modelToSelect = config.llm.model; | |
| } | |
| } | |
| llmModelSelect.value = modelToSelect; | |
| llmApiKeyInput.value = maskApiKey(config.llm.apiKeys[selectedProvider] || ''); | |
| } | |
| function updateTransUI() { | |
| const selectedProvider = transProviderSelect.value; | |
| const providerObj = transcriptionProviders.find(p => p.value === selectedProvider); | |
| if (!providerObj) return; | |
| transModelSelect.innerHTML = providerObj.models.map(m => `<option value="${m}">${m}</option>`).join(""); | |
| transModelSelect.value = config.transcription.models[selectedProvider] || providerObj.models[0]; | |
| transApiKeyInput.value = maskApiKey(config.transcription.apiKeys[selectedProvider] || ''); | |
| } | |
| llmProviderSelect.addEventListener("change", updateLlmUI); | |
| transProviderSelect.addEventListener("change", updateTransUI); | |
| // Estado inicial del formulario | |
| llmProviderSelect.value = config.llm.provider; | |
| transProviderSelect.value = config.transcription.provider; | |
| updateLlmUI(); | |
| updateTransUI(); | |
| // Guardar configuraci贸n | |
| document.getElementById("iaConfigForm").addEventListener("submit", e => { | |
| e.preventDefault(); | |
| const prevConfig = config; | |
| const newConfig = clone(prevConfig); | |
| const llmProv = llmProviderSelect.value; | |
| const rawLlmKey = llmApiKeyInput.value; | |
| const oldLlmKey = prevConfig.llm.apiKeys[llmProv] || ''; | |
| const actualLlmKey = rawLlmKey === maskApiKey(oldLlmKey) ? oldLlmKey : rawLlmKey; | |
| newConfig.llm = { | |
| ...prevConfig.llm, | |
| provider: llmProv, | |
| model: llmModelSelect.value, | |
| apiKeys: { ...prevConfig.llm.apiKeys, [llmProv]: actualLlmKey } | |
| }; | |
| const transProv = transProviderSelect.value; | |
| const rawTransKey = transApiKeyInput.value; | |
| const oldTransKey = prevConfig.transcription.apiKeys[transProv] || ''; | |
| const actualTransKey = rawTransKey === maskApiKey(oldTransKey) ? oldTransKey : rawTransKey; | |
| newConfig.transcription = { | |
| ...prevConfig.transcription, | |
| provider: transProv, | |
| models: { ...prevConfig.transcription.models, [transProv]: transModelSelect.value }, | |
| apiKeys: { ...prevConfig.transcription.apiKeys, [transProv]: actualTransKey } | |
| }; | |
| saveConfig(newConfig); | |
| config = newConfig; | |
| document.dispatchEvent(new CustomEvent('iaConfigChanged')); | |
| llmApiKeyInput.value = maskApiKey(newConfig.llm.apiKeys[newConfig.llm.provider] || ''); | |
| transApiKeyInput.value = maskApiKey(newConfig.transcription.apiKeys[newConfig.transcription.provider] || ''); | |
| llmApiKeyInput.type = "password"; | |
| transApiKeyInput.type = "password"; | |
| let msg = document.getElementById('iaConfigSavedMsg'); | |
| if (!msg) { | |
| msg = document.createElement('div'); | |
| msg.id = 'iaConfigSavedMsg'; | |
| msg.className = 'fixed left-1/2 top-6 -translate-x-1/2 bg-green-500 text-white px-6 py-3 rounded shadow-lg text-lg z-50'; | |
| msg.innerHTML = '<i class="fas fa-check-circle mr-2"></i>隆Configuraci贸n guardada!'; | |
| document.body.appendChild(msg); | |
| } | |
| msg.style.display = 'block'; | |
| setTimeout(() => { msg.style.display = 'none'; }, 2000); | |
| document.getElementById("configModal")?.classList.remove("active"); | |
| }); | |
| } |