File size: 11,424 Bytes
f1602bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2be59fa
 
 
 
 
 
f1602bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// 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" }
  }
};

// Lista de modelos actualizada (2024)
export const llmProviders = [
  { name: "OpenAI", value: "openai", models: ["gpt-4o-mini-2024-07-18","chatgpt-4o-latest","o1-mini-2024-09-12","o4-mini-2025-04-16"], url: "https://api.openai.com" },
  // { name: "Gemini", value: "gemini", models: [
  //   "gemini-2.5-flash-preview-04-17",    // Versi贸n preliminar de Gemini 2.5 Flash 04-17
  //   "gemini-2.0-flash",                  // Gemini 2.0 Flash
  //   "gemini-2.0-flash-lite",             // Gemini 2.0 Flash-Lite
  //   "gemini-1.5-flash"                   // Gemini 1.5 Flash
  // ], url: "https://generativelanguage.googleapis.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) {
  localStorage.setItem("iaConfig", JSON.stringify(config));
}
function loadConfig() {
  const config = JSON.parse(localStorage.getItem("iaConfig")) || defaultConfig;
  // Migrar configuraci贸n antigua de transcription
  if (config.transcription.apiKey !== undefined) {
    const oldKey = config.transcription.apiKey;
    const oldModel = config.transcription.model;
    config.transcription.apiKeys = { [config.transcription.provider]: oldKey, deepgram: "" };
    config.transcription.models = { [config.transcription.provider]: oldModel, deepgram: "nova-2" };
    delete config.transcription.apiKey;
    delete config.transcription.model;
    saveConfig(config);
  }
  // Migrar configuraci贸n antigua de LLM apiKey a apiKeys
  if (config.llm.apiKey !== undefined) {
    const old = config.llm.apiKey;
    config.llm.apiKeys = { ...defaultConfig.llm.apiKeys, [config.llm.provider]: old };
    delete config.llm.apiKey;
    saveConfig(config);
  }
  // Migrar modelos obsoletos de DeepSeek a 'deepseek-chat'
  if (config.llm.provider === 'deepseek' && (config.llm.model === 'deepseek-v3' || config.llm.model === 'deepseek-llm')) {
    config.llm.model = 'deepseek-chat';
    console.log('[iaConfigModule] Migrado modelo DeepSeek a deepseek-chat');
    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'>[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 mb-2">
        <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 mb-2">
        <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>
  `;

  // Bot贸n de cerrar modal
  const closeBtn = document.getElementById("btnCloseConfig");
  if (closeBtn) {
    closeBtn.addEventListener("click", () => {
      const modal = document.getElementById("configModal");
      if (modal) modal.classList.remove("active");
    });
  }

  // Set initial values
  document.getElementById("llmProvider").value = config.llm.provider;
  document.getElementById("llmApiKey").value = maskApiKey(config.llm.apiKeys[config.llm.provider] || '');
  document.getElementById("transProvider").value = config.transcription.provider;
  document.getElementById("transApiKey").value = maskApiKey(config.transcription.apiKeys[config.transcription.provider] || '');

  // API Key toggle (ver/ocultar)
  document.getElementById("toggleLlmApiKey").addEventListener("click", () => {
    const input = document.getElementById("llmApiKey");
    input.type = input.type === "password" ? "text" : "password";
  });
  document.getElementById("toggleTransApiKey").addEventListener("click", () => {
    const input = document.getElementById("transApiKey");
    input.type = input.type === "password" ? "text" : "password";
  });

  // Populate models
  function updateLlmModels() {
    const prov = document.getElementById("llmProvider").value;
    const providerObj = llmProviders.find(p => p.value === prov);
    const models = providerObj.models;
    const sel = document.getElementById("llmModel");
    sel.innerHTML = models.map(m => `<option value="${m}">${m}</option>`).join("");
    sel.value = config.llm.model;
  }
  function updateTransModels() {
    const prov = document.getElementById("transProvider").value;
    const providerObj = transcriptionProviders.find(p => p.value === prov);
    const models = providerObj.models;
    const sel = document.getElementById("transModel");
    sel.innerHTML = models.map(m => `<option value="${m}">${m}</option>`).join("");
    sel.value = config.transcription.models[prov] || models[0];
    // Actualizar API Key input al cambiar proveedor
    document.getElementById("transApiKey").value = maskApiKey(config.transcription.apiKeys[prov] || '');
  }
  document.getElementById("llmProvider").addEventListener("change", () => {
    const p = document.getElementById("llmProvider").value;
    updateLlmModels();
    // Refrescar config con todas las llaves y mostrar la del proveedor seleccionado
    const fresh = loadConfig();
    const keyEl = document.getElementById("llmApiKey");
    if (keyEl) keyEl.value = maskApiKey(fresh.llm.apiKeys[p] || '');
  });
  document.getElementById("transProvider").addEventListener("change", updateTransModels);
  updateLlmModels();
  updateTransModels();

  // Save on submit
  document.getElementById("iaConfigForm").addEventListener("submit", e => {
    e.preventDefault();
    // Persistir configuraci贸n por proveedor
    const prev = config;
    const newConfig = { ...prev };
    // LLM: provider, apiKeys y model
    const llProv = document.getElementById("llmProvider").value;
    const rawKey = document.getElementById("llmApiKey").value;
    const oldKey = prev.llm.apiKeys[llProv] || '';
    const newKey = rawKey === maskApiKey(oldKey) ? oldKey : rawKey;
    newConfig.llm = { ...prev.llm, provider: llProv, model: document.getElementById("llmModel").value };
    newConfig.llm.apiKeys = { ...prev.llm.apiKeys, [llProv]: newKey };
    // Transcription: provider, apiKeys y models por proveedor
    const tp = document.getElementById("transProvider").value;
    const rawKeyTrans = document.getElementById("transApiKey").value;
    const existingKeyTrans = prev.transcription.apiKeys[tp] || '';
    const actualKeyTrans = rawKeyTrans === maskApiKey(existingKeyTrans) ? existingKeyTrans : rawKeyTrans;
    newConfig.transcription.provider = tp;
    newConfig.transcription.apiKeys = { ...prev.transcription.apiKeys, [tp]: actualKeyTrans };
    newConfig.transcription.models = { ...prev.transcription.models, [tp]: document.getElementById("transModel").value };
    console.log('[iaConfigModule] Configuraci贸n guardada:', newConfig);
    saveConfig(newConfig);
    // Actualizar config local despu茅s de guardar
    config = newConfig;
    // Actualizar labels de modelo en la UI
    document.dispatchEvent(new CustomEvent('iaConfigChanged'));
    // Ofusca los campos API Key
    document.getElementById("llmApiKey").value = maskApiKey(newConfig.llm.apiKeys[newConfig.llm.provider] || '');
    document.getElementById("transApiKey").value = maskApiKey(newConfig.transcription.apiKeys[tp] || '');
    document.getElementById("llmApiKey").type = "password";
    document.getElementById("transApiKey").type = "password";
    // Mensaje de 茅xito discreto
    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 text-lg z-50';
      msg.innerHTML = '<i class="fas fa-check-circle mr-2"></i>隆Configuraci贸n guardada!';
      document.body.appendChild(msg);
    } else {
      msg.style.display = 'block';
    }
    setTimeout(() => { msg.style.display = 'none'; }, 2000);
    // Cierra el modal
    const modal = document.getElementById("configModal");
    if (modal) modal.classList.remove("active");
  });
}