${entry.content.replace(/
const healthButton = document.getElementById("healthButton"); const ingestButton = document.getElementById("ingestButton"); const bootstrapButton = document.getElementById("bootstrapButton"); const replaceBootstrapButton = document.getElementById("replaceBootstrapButton"); const clearBootstrapButton = document.getElementById("clearBootstrapButton"); const sendChatButton = document.getElementById("sendChatButton"); const clearChatButton = document.getElementById("clearChatButton"); const manualLogButton = document.getElementById("manualLogButton"); const presetDocs = document.getElementById("presetDocs"); const presetRagDocs = document.getElementById("presetRagDocs"); const presetCode = document.getElementById("presetCode"); const healthResult = document.getElementById("healthResult"); const ingestResult = document.getElementById("ingestResult"); const bootstrapResult = document.getElementById("bootstrapResult"); const mainResult = document.getElementById("mainResult"); const compareResult = document.getElementById("compareResult"); const bootstrapContextResult = document.getElementById("bootstrapContextResult"); const chatMessages = document.getElementById("chatMessages"); const contextIndicator = document.getElementById("contextIndicator"); const contextStatusText = document.getElementById("contextStatusText"); const contextScopeText = document.getElementById("contextScopeText"); const logsResult = document.getElementById("logsResult"); const logCounterValue = document.getElementById("logCounterValue"); const ingestSourceType = document.getElementById("ingestSourceType"); const ingestScopeMode = document.getElementById("ingestScopeMode"); const ingestSourceIdWrapper = document.getElementById("ingestSourceIdWrapper"); const ingestSourceId = document.getElementById("ingestSourceId"); const ingestSourceRef = document.getElementById("ingestSourceRef"); const ingestUploadFile = document.getElementById("ingestUploadFile"); const ingestMode = document.getElementById("ingestMode"); const ingestTags = document.getElementById("ingestTags"); const ingestModeHint = document.getElementById("ingestModeHint"); const bootstrapQuery = document.getElementById("bootstrapQuery"); const bootstrapMode = document.getElementById("bootstrapMode"); const answerModel = document.getElementById("answerModel"); const useModelInRetrieve = document.getElementById("useModelInRetrieve"); const reuseBootstrapContext = document.getElementById("reuseBootstrapContext"); const allowAdditionalRetrieve = document.getElementById("allowAdditionalRetrieve"); const scopePresetSelect = document.getElementById("scopePresetSelect"); const selectedSourceId = document.getElementById("selectedSourceId"); const scopeSourceRef = document.getElementById("scopeSourceRef"); const scopeTags = document.getElementById("scopeTags"); const scopeEditMode = document.getElementById("scopeEditMode"); const compareWithoutRag = document.getElementById("compareWithoutRag"); const chatMode = document.getElementById("chatMode"); const chatScopeInfo = document.getElementById("chatScopeInfo"); const chatInput = document.getElementById("chatInput"); const manualLogNote = document.getElementById("manualLogNote"); let lastBootstrapContext = null; let lastBootstrapMeta = null; let chatHistory = []; let availableScopes = []; let lastInteraction = null; function format(value) { return JSON.stringify(value, null, 2); } function splitTags(value) { return value.split(",").map((entry) => entry.trim()).filter(Boolean); } function buildScopeLabel(scope) { const modes = scope.chunkModes.join(", ") || "sin modo"; return `${scope.sourceRef} [${modes}]`; } function updateIngestUiState() { const hasUpload = Boolean(ingestUploadFile.files && ingestUploadFile.files[0]); ingestSourceType.value = hasUpload ? "file" : ingestSourceType.value; ingestSourceType.disabled = hasUpload; ingestSourceRef.disabled = hasUpload; ingestSourceIdWrapper.style.display = ingestScopeMode.value === "custom" ? "block" : "none"; if (hasUpload) { ingestModeHint.textContent = `Upload directo activo: se ingerira el archivo local "${ingestUploadFile.files[0].name}" y se ignorara la ruta manual.`; ingestModeHint.classList.add("strong"); } else { ingestModeHint.textContent = "Si seleccionas un archivo local, el playground usara upload directo y podras aislarlo con un `sourceId` propio para no mezclarlo con otros scopes."; ingestModeHint.classList.remove("strong"); } } function applySelectedScope(scope) { selectedSourceId.value = scope?.sourceId || ""; scopeSourceRef.value = scope?.sourceRef || ""; scopeTags.value = (scope?.tags || []).join(", "); chatScopeInfo.value = [scope?.sourceId, scope?.sourceRef].filter(Boolean).join(" | "); if (scope?.chunkModes?.includes("codigo")) { bootstrapMode.value = "codigo"; chatMode.value = "codigo"; } else if (scope?.chunkModes?.includes("documental")) { bootstrapMode.value = "documental"; chatMode.value = "documental"; } } function updateScopeEditState() { const locked = scopeEditMode.value !== "manual"; scopeSourceRef.readOnly = locked; scopeTags.readOnly = locked; } function request(url, payload, method = "POST") { return fetch(url, { method, headers: { "Content-Type": "application/json" }, body: payload ? JSON.stringify(payload) : undefined }).then(async (response) => { const data = await response.json(); if (!response.ok) { throw new Error(data.error || `HTTP ${response.status}`); } return data; }); } function renderBootstrapContext() { if (!lastBootstrapContext) { bootstrapContextResult.textContent = "Aun no hay bootstrap cargado."; contextIndicator.className = "indicator indicator-off"; contextStatusText.textContent = "Sin contexto cargado"; contextScopeText.textContent = "No hay bootstrap activo."; chatScopeInfo.value = [selectedSourceId.value, scopeSourceRef.value].filter(Boolean).join(" | ") || "Sin scope seleccionado"; return; } bootstrapContextResult.textContent = format(lastBootstrapContext); contextIndicator.className = "indicator indicator-on"; contextStatusText.textContent = "Contexto bootstrap activo"; contextScopeText.textContent = lastBootstrapContext.scope?.sourceRef || "Scope no especificado"; chatScopeInfo.value = [lastBootstrapContext.scope?.sourceId, lastBootstrapContext.scope?.sourceRef].filter(Boolean).join(" | "); } function renderChatHistory() { if (chatHistory.length === 0) { chatMessages.innerHTML = '
Aun no hay conversacion. Carga un bootstrap y empieza a preguntar.
'; return; } chatMessages.innerHTML = chatHistory.map((entry) => ` `).join(""); chatMessages.scrollTop = chatMessages.scrollHeight; } function buildScopeFromInputs() { if (scopeEditMode.value !== "manual" && scopePresetSelect.value) { const scope = JSON.parse(scopePresetSelect.value); return { sourceId: scope.sourceId, sourceRef: scope.sourceRef, tags: scope.tags || [] }; } return { sourceId: selectedSourceId.value || undefined, sourceRef: scopeSourceRef.value, tags: splitTags(scopeTags.value) }; } function applyPreset(mode, query, sourceRef, useRetrieveModel = false) { bootstrapMode.value = mode; chatMode.value = mode; bootstrapQuery.value = query; scopeSourceRef.value = sourceRef; useModelInRetrieve.checked = useRetrieveModel; } async function loadScopes() { try { const scopes = await fetch("/sources").then((response) => response.json()); availableScopes = scopes; scopePresetSelect.innerHTML = ""; const placeholder = document.createElement("option"); placeholder.value = ""; placeholder.textContent = "Selecciona un scope disponible"; scopePresetSelect.appendChild(placeholder); for (const scope of scopes) { const option = document.createElement("option"); option.value = JSON.stringify(scope); option.textContent = buildScopeLabel(scope); scopePresetSelect.appendChild(option); } if (scopes.length === 0) { placeholder.textContent = "No hay scopes detectados"; } else if (!scopePresetSelect.value) { scopePresetSelect.value = JSON.stringify(scopes[0]); applySelectedScope(scopes[0]); } } catch (error) { scopePresetSelect.innerHTML = ``; } } async function loadAnswerModels() { try { const payload = await fetch("/models/answer").then((response) => response.json()); answerModel.innerHTML = ""; for (const model of payload.models || []) { const option = document.createElement("option"); option.value = model; option.textContent = model; answerModel.appendChild(option); } if (payload.defaultModel) { answerModel.value = payload.defaultModel; } } catch { answerModel.innerHTML = ''; } } async function loadRecentLogs() { try { const logs = await fetch("/logs/recent").then((response) => response.json()); logsResult.textContent = format(logs); logCounterValue.textContent = Array.isArray(logs) ? String(logs.length) : "0"; } catch (error) { logsResult.textContent = String(error); logCounterValue.textContent = "0"; } } document.querySelectorAll(".tab-button").forEach((button) => { button.addEventListener("click", () => { document.querySelectorAll(".tab-button").forEach((entry) => entry.classList.remove("active")); document.querySelectorAll(".tab-panel").forEach((panel) => panel.classList.remove("active")); button.classList.add("active"); document.getElementById(`tab-${button.dataset.tab}`).classList.add("active"); }); }); scopePresetSelect.addEventListener("change", () => { if (!scopePresetSelect.value) { return; } const scope = JSON.parse(scopePresetSelect.value); applySelectedScope(scope); }); ingestUploadFile.addEventListener("change", updateIngestUiState); ingestScopeMode.addEventListener("change", updateIngestUiState); scopeEditMode.addEventListener("change", updateScopeEditState); healthButton.addEventListener("click", async () => { healthResult.textContent = "Comprobando..."; try { const data = await fetch("/health").then((response) => response.json()); healthResult.textContent = format(data); } catch (error) { healthResult.textContent = String(error); } }); ingestButton.addEventListener("click", async () => { ingestResult.textContent = "Ejecutando ingesta..."; try { let data; if (ingestUploadFile.files && ingestUploadFile.files[0]) { const formData = new FormData(); formData.append("file", ingestUploadFile.files[0]); formData.append("mode", ingestMode.value); formData.append("tags", splitTags(ingestTags.value).join(",")); if (ingestScopeMode.value === "custom" && ingestSourceId.value.trim()) { formData.append("sourceId", ingestSourceId.value.trim()); } const response = await fetch("/ingest/upload", { method: "POST", body: formData }); data = await response.json(); if (!response.ok) { throw new Error(data.error || `HTTP ${response.status}`); } } else { data = await request("/ingest", { sourceId: ingestScopeMode.value === "custom" ? (ingestSourceId.value.trim() || undefined) : undefined, sourceType: ingestSourceType.value, sourceRef: ingestSourceRef.value, mode: ingestMode.value, tags: splitTags(ingestTags.value) }); } ingestResult.textContent = format(data); await loadScopes(); await loadRecentLogs(); updateIngestUiState(); } catch (error) { ingestResult.textContent = String(error); } }); async function executeBootstrap() { bootstrapResult.textContent = "Cargando bootstrap..."; try { const data = await request("/retrieve", { mode: bootstrapMode.value, intent: "bootstrap", query: bootstrapQuery.value, model: answerModel.value, useModelInRetrieve: useModelInRetrieve.checked, scope: buildScopeFromInputs() }); lastBootstrapContext = data; lastBootstrapMeta = { query: bootstrapQuery.value, mode: bootstrapMode.value, scope: buildScopeFromInputs(), model: answerModel.value, usedModelSummary: useModelInRetrieve.checked }; bootstrapResult.textContent = format(data); renderBootstrapContext(); lastInteraction = { operation: "retrieve", query: bootstrapQuery.value, mode: bootstrapMode.value, intent: "bootstrap", model: answerModel.value, scope: buildScopeFromInputs(), usedBootstrapContext: false, bootstrapMeta: lastBootstrapMeta, usedAdditionalRetrieve: useModelInRetrieve.checked, responseSummary: data.modelSummary || data.summary, retrievedItems: data.items || [] }; await loadRecentLogs(); } catch (error) { bootstrapResult.textContent = String(error); } } bootstrapButton.addEventListener("click", executeBootstrap); replaceBootstrapButton.addEventListener("click", executeBootstrap); clearBootstrapButton.addEventListener("click", () => { lastBootstrapContext = null; lastBootstrapMeta = null; renderBootstrapContext(); }); sendChatButton.addEventListener("click", async () => { const message = chatInput.value.trim(); if (!message) { return; } chatHistory.push({ role: "user", content: message }); renderChatHistory(); mainResult.textContent = "Consultando..."; compareResult.textContent = compareWithoutRag.checked ? "Comparando..." : "Desactivada."; try { const response = await request("/chat", { message, history: chatHistory, mode: chatMode.value, model: answerModel.value, preloadedContext: reuseBootstrapContext.checked && lastBootstrapContext ? (lastBootstrapContext.modelSummary || lastBootstrapContext.summary || "") : undefined, bootstrapMeta: reuseBootstrapContext.checked ? lastBootstrapMeta : undefined, allowAdditionalRetrieve: allowAdditionalRetrieve.checked, scope: buildScopeFromInputs() }); chatHistory.push({ role: "assistant", content: response.answer }); renderChatHistory(); mainResult.textContent = format(response); lastInteraction = { operation: "chat", query: message, mode: chatMode.value, intent: "specific", model: answerModel.value, scope: buildScopeFromInputs(), usedBootstrapContext: response.usedBootstrapContext, bootstrapMeta: reuseBootstrapContext.checked ? lastBootstrapMeta : undefined, usedAdditionalRetrieve: response.usedAdditionalRetrieve, responseSummary: response.answer, retrievedItems: response.retrieved?.items || [] }; await loadRecentLogs(); if (compareWithoutRag.checked) { const comparison = await request("/answer/direct", { query: message, model: answerModel.value, preloadedContext: reuseBootstrapContext.checked && lastBootstrapContext ? (lastBootstrapContext.modelSummary || lastBootstrapContext.summary || "") : undefined, bootstrapMeta: reuseBootstrapContext.checked ? lastBootstrapMeta : undefined }); compareResult.textContent = format(comparison); } chatInput.value = ""; } catch (error) { mainResult.textContent = String(error); compareResult.textContent = compareWithoutRag.checked ? String(error) : "Desactivada."; } }); clearChatButton.addEventListener("click", () => { chatHistory = []; renderChatHistory(); mainResult.textContent = "Sin ejecutar aun."; compareResult.textContent = "Desactivada."; }); manualLogButton.addEventListener("click", async () => { if (!lastInteraction) { mainResult.textContent = "No hay una consulta previa para registrar en logs."; return; } try { const entry = await request("/logs/manual", { ...lastInteraction, reason: "manual_review_requested", note: manualLogNote.value.trim() || undefined }); logsResult.textContent = format(entry); await loadRecentLogs(); } catch (error) { logsResult.textContent = String(error); } }); presetDocs.addEventListener("click", () => { applyPreset( "documental", "dame un mapa inicial del workspace, sus lineas de trabajo principales, reglas, documentacion base y puntos importantes a tener presentes", "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs", true ); }); presetRagDocs.addEventListener("click", () => { applyPreset( "documental", "dame un mapa inicial del modulo RAG, su arquitectura, decisiones, estado actual y documentos clave", "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs", true ); }); presetCode.addEventListener("click", () => { applyPreset( "codigo", "dame un mapa inicial del codigo del modulo RAG, sus modulos principales, flujo interno y piezas clave", "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src", true ); }); loadScopes(); loadAnswerModels(); loadRecentLogs(); renderBootstrapContext(); renderChatHistory(); updateIngestUiState(); updateScopeEditState();