AppConsulta360 / js /iaConfigModule.js
aarnal80's picture
Update js/iaConfigModule.js
e56e25e verified
// 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");
});
}