// Research orchestration UI const RESEARCH_UI = { async init() { $("#run-research").addEventListener("click", () => this.runResearch()); $("#query-input").addEventListener("keydown", (e) => { if (e.key === "Enter" && e.ctrlKey) this.runResearch(); }); }, async runResearch() { const query = $("#query-input").value.trim(); if (!query) return showToast("Enter a research query", "error"); // Get selected skills const checked = [...document.querySelectorAll('.skill-toggle input:checked')].map(c => c.value); const output = $("#research-output"); output.innerHTML = ""; showSpinner(output); // Create session let sessionId = STORE.currentSessionId; if (!sessionId) { try { const session = await API.post("/api/research/session", { query }); STORE.currentSessionId = session.session_id; sessionId = session.session_id; } catch (e) { showToast("Failed to create session", "error"); output.innerHTML = `
Session error: ${e.message}
`; return; } } // Run research try { const results = await API.post("/api/research/run", { query, doc_id: STORE.currentDocId, skills: checked.length ? checked : undefined, }); output.innerHTML = ""; // Session info const info = document.createElement("div"); info.className = "research-section"; info.innerHTML = `Query: ${query}
Error: ${e.message}
`; showToast("Research failed", "error"); } }, async runSemanticSearch(query, docId = null) { const results = await API.post("/api/research/semantic", { query, doc_id: docId, limit: 20 }); return results.results || []; }, async runTextSearch(query, docId = null) { const results = await API.post("/api/research/text-search", { query, doc_id: docId, limit: 20 }); return results.results || []; }, async getFindings(sessionId) { if (!sessionId) return []; try { const res = await API.get(`/api/research/findings?session_id=${sessionId}`); return res.findings || []; } catch { return []; } }, async saveMemory(sessionId, content, type = "fact", importance = 3) { if (!sessionId) return; try { await API.post("/api/memories/save", { session_id: sessionId, content, memory_type: type, importance, source_doc_id: STORE.currentDocId, }); showToast("Memory saved", "info"); } catch (e) { showToast("Save failed: " + e.message, "error"); } }, async runPipeline() { const query = $("#query-input").value.trim(); if (!query) return showToast("Enter a research query", "error"); const mode = $("#pipeline-output-mode").value || ""; // Create session let sessionId = STORE.currentSessionId; if (!sessionId) { try { const session = await API.post("/api/research/session", { query }); sessionId = session.session_id; STORE.currentSessionId = sessionId; } catch (e) { showToast("Failed to create session", "error"); return; } } const pipelineEl = $("#pipeline-output"); pipelineEl.innerHTML = ' Running: triage → evidence extraction → synthesis...'; // Run the pipeline try { const results = await API.post("/api/research/pipeline", { query, doc_ids: STORE.currentDocId ? [STORE.currentDocId] : undefined, output_mode: mode || undefined, }); // Save session to the first doc or temp session STORE.currentSessionId = sessionId; // Render pipeline sections // First render: get structured sections from results const sections = RESEARCH_UI.renderPipelineSections(results); pipelineEl.innerHTML = sections.map(s => RESEARCH_UI.renderSectionHtml(s)).join(''); // Wire up collapse toggles pipelineEl.querySelectorAll(".pipeline-stage-header").forEach(header => { header.addEventListener("click", () => { const body = header.nextElementSibling; body.classList.toggle("collapsed"); const arrow = header.querySelector(".stage-arrow"); if (arrow) arrow.textContent = body.classList.contains("collapsed") ? "▶" : "▼"; }); }); showToast("Pipeline complete", "info"); } catch (e) { pipelineEl.innerHTML = `Error: ${e.message}
`; showToast("Pipeline failed", "error"); } }, renderPipelineSections(results) { const sections = []; // Triage stage if (results.triage_state) { const plan = results.triage_state; sections.push({ stage: "triage", title: "Stage 1: Source Triage", status: "complete", sections: [ { label: "Objective", content: plan.OBJECTIVE || "" }, { label: "Sub-questions", content: plan["SUB-QUESTIONS"] || "" }, { label: "Classification", content: plan.CLASSIFICATION || "" }, { label: "Reading Order", content: plan.READING_ORDER || "" }, { label: "Extraction Criteria", content: plan["EXTRACTION CRITERIA"] || "" }, { label: "Risks & Gaps", content: plan["RISKS and GAPS"] || plan.RISKS_AND_GAPS || "" }, ], }); } // Evidence stage if (results.evidence_rows && results.evidence_rows.length > 0) { sections.push({ stage: "evidence", title: `Stage 2: Extracted Evidence (${results.evidence_rows.length} rows)`, status: "complete", sections: [ { label: "", content: "table", rows: results.evidence_rows }, ], }); } // Synthesis stage if (results.synthesis) { sections.push({ stage: "synthesis", title: `Stage 3: Synthesis (mode: ${results.output_mode || "auto"})`, status: "complete", sections: [ { label: "Result", content: results.synthesis, mode: results.output_mode }, ], }); } return sections; }, renderSectionHtml(section) { let cardsHtml = ""; section.sections.forEach(sec => { if (sec.content === "table") { const rows = sec.rows || []; const hasComparison = rows[0] && rows[0].source_a !== undefined; if (hasComparison) { cardsHtml += `| Topic | Source A | Source B | Agreement | Conflict | Notes |
|---|---|---|---|---|---|
| ${r.topic || ""} | ${(r.source_a || "").substring(0, 200)} | ${(r.source_b || "").substring(0, 200)} | ${r.agreement || ""} | ${r.conflict || ""} | ${r.notes || ""} |
| ${c} | `).join("")}||||||||
|---|---|---|---|---|---|---|---|---|
| ${i + 1} | ${r.topic || ""} | ${r.evidence_type || ""} | ${(r.description || "").substring(0, 150)} | ${(r.trace_ref || "").substring(0, 150)} | ${(r.evidence || "").substring(0, 200)} | ${(r.analyst_note || "").substring(0, 150)} | ${r.confidence || "Medium"} | ${r.review_needed || "No"} |
Error loading pipeline: ${e.message}
`; } }, };