Files
agentic/frontend/js/ui.js
T
2026-06-10 08:20:27 +02:00

199 lines
7.2 KiB
JavaScript

// Main UI management
const UI = {
async init() {
// Tab switching
$$(".tab").forEach(tab => {
tab.addEventListener("click", () => {
const target = tab.dataset.tab;
this.switchTab(target);
// Load tab data
if (target === "search") this.initSearch();
if (target === "memories") this.loadMemories();
if (target === "findings") this.loadFindings();
if (target === "pipeline" && STORE.currentSessionId) this.loadPipelineSession(STORE.currentSessionId);
});
});
// Pipeline mode toggle
const pipelineCheck = $("#pipeline-mode");
const pipelineModeSelect = $("#pipeline-output-mode");
if (pipelineCheck) {
pipelineCheck.addEventListener("change", () => {
const on = pipelineCheck.checked;
pipelineModeSelect.style.display = on ? "inline-block" : "none";
const toggles = document.querySelectorAll("#skill-toggles .skill-toggle");
toggles.forEach(t => {
const cb = t.querySelector("input");
if (cb) cb.disabled = on;
});
});
}
// Rebuild pipeline button
const rebuildBtn = $("#rebuild-pipeline");
if (rebuildBtn && STORE.currentSessionId) {
rebuildBtn.addEventListener("click", () => UI.loadPipelineSession(STORE.currentSessionId));
}
// Search
$("#execute-search").addEventListener("click", () => this.executeSearch());
$("#search-query").addEventListener("keydown", (e) => {
if (e.key === "Enter") this.executeSearch();
});
// Check models
$("#check-models").addEventListener("click", () => this.checkModels());
// Load state
await loadInitialState();
// Init sub-modules
DOC_VIEWER.init();
RESEARCH_UI.init();
DOC_UI.init();
},
switchTab(tabName) {
$$(".tab").forEach(t => t.classList.remove("active"));
$$(".tab-pane").forEach(p => p.classList.remove("active"));
document.querySelector(`[data-tab="${tabName}"]`).classList.add("active");
$(`#tab-${tabName}`).classList.add("active");
},
initSearch() {
// Search already bound in HTML
},
async executeSearch() {
const query = $("#search-query").value.trim();
if (!query) return showToast("Enter a search query", "error");
const type = $("#search-type").value;
const resultsEl = $("#search-results");
resultsEl.innerHTML = '<div class="spinner"></div> <span class="typing">Searching...</span>';
try {
let results;
if (type === "semantic") {
results = await RESEARCH_UI.runSemanticSearch(query, STORE.currentDocId);
} else {
results = await RESEARCH_UI.runTextSearch(query, STORE.currentDocId);
}
if (!results.length) {
resultsEl.innerHTML = '<div style="padding:20px;color:var(--text-muted);text-align:center">No results found</div>';
return;
}
resultsEl.innerHTML = results.map(r => {
const score = r.similarity ?? r.rank ?? 0;
const page = r.page_num != null ? `p${r.page_num}` : "";
const truncated = r.content ? r.content.substring(0, 200) : "";
// Highlight matching text
let content = truncated.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
if (query) {
const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
content = content.replace(regex, '<mark>$1</mark>');
}
return `
<div class="search-result-item" data-chunk-id="${r.id}">
<div class="result-score">Score: ${score.toFixed(3)}</div>
<div class="result-content">${content}${truncated.length >= 200 ? '...' : ''}</div>
<div class="result-meta">${page} · ${r.chunk_type || 'text'}</div>
</div>`;
}).join('');
// Click to show chunk in viewer
resultsEl.querySelectorAll(".search-result-item").forEach(item => {
item.addEventListener("click", () => {
const chunkId = item.dataset.chunkId;
if (STORE.currentDocId) {
DOC_VIEWER.showChunkInfo(chunkId);
}
});
});
} catch (e) {
resultsEl.innerHTML = `<p style="color:var(--accent-red);padding:20px">Search error: ${e.message}</p>`;
}
},
async loadMemories() {
const list = $("#memories-list");
list.innerHTML = '<div class="spinner"></div>';
try {
if (STORE.currentSessionId) {
const res = await API.get(`/api/research/memories?session_id=${STORE.currentSessionId}`);
list.innerHTML = (res.memories || []).map(m => `
<div class="memory-item">
<div class="memory-type">${m.memory_type || "fact"} (imp: ${m.importance || "?"})</div>
<div class="memory-content">${m.content ? m.content.substring(0, 300) : "(empty)"}</div>
</div>`).join('') || '<div style="color:var(--text-muted);padding:12px">No memories</div>';
} else {
list.innerHTML = '<div style="color:var(--text-muted);padding:12px">No active session</div>';
}
} catch (e) {
list.innerHTML = `<div style="color:var(--accent-red);padding:12px">${e.message}</div>`;
}
},
async loadFindings() {
const list = $("#findings-list");
list.innerHTML = '<div class="spinner"></div>';
try {
const findings = await RESEARCH_UI.getFindings(STORE.currentSessionId);
list.innerHTML = findings.length ? findings.map(f => `
<div class="finding-item">
<div class="finding-agent">${f.agent_name || "unknown"}</div>
<div class="finding-query">${f.question || "N/A"}</div>
<div class="finding-answer">${(f.answer || f.summary || "").substring(0, 400)}</div>
</div>`).join('') : '<div style="color:var(--text-muted);padding:12px">No findings yet</div>';
} catch (e) {
list.innerHTML = `<div style="color:var(--accent-red);padding:12px">${e.message}</div>`;
}
},
async checkModels() {
const list = $("#model-list");
list.innerHTML = '<div class="spinner"></div>';
try {
const res = await API.get("/api/ollama/models");
const models = res.models || [];
list.innerHTML = models.length ? models.map(m => `
<div style="font-size:11px;padding:3px 8px;color:var(--accent-green)">
${m.name || m.model}
</div>`).join('') : '<div style="font-size:11px;padding:3px 8px;color:var(--text-muted)">None found</div>';
} catch (e) {
list.innerHTML = `<div style="font-size:11px;padding:3px 8px;color:var(--accent-red)">Off: ${e.message}</div>`;
}
},
updateHealth(health) {
const dot = $("#health-indicator .health-dot");
const text = $("#health-indicator");
if (dot) {
dot.className = health.status === "ok" ? "health-dot healthy" : "health-dot";
text.textContent = health.status === "ok" ? "All systems operational" : "Systems degraded";
}
},
renderModels(models) {
if (!models?.models?.length) return;
const list = $("#model-list");
if (list) {
list.innerHTML = models.models.map(m => `
<div style="font-size:11px;padding:3px 8px;color:var(--accent-green)">
${m.name || m.model}
</div>`).join('');
}
},
};