// 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 = `

Research Results

Session: ${sessionId.substring(0, 8)}

Query: ${query}


`; output.appendChild(info); // Results from each skill if (results.results) { for (const [skill, response] of Object.entries(results.results)) { const section = document.createElement("div"); section.className = "research-section"; section.id = `section-${skill}`; section.innerHTML = `

${skill.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())}

`; const content = document.createElement("div"); content.innerHTML = renderContent(response); section.appendChild(content); output.appendChild(section); } } STORE.currentSessionId = sessionId; showToast("Research complete", "info"); } catch (e) { output.innerHTML = `

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 += `
Cross-Comparison
`; cardsHtml += ``; rows.forEach(r => { cardsHtml += ``; }); cardsHtml += `
TopicSource ASource BAgreementConflictNotes
${r.topic || ""} ${(r.source_a || "").substring(0, 200)} ${(r.source_b || "").substring(0, 200)} ${r.agreement || ""} ${r.conflict || ""} ${r.notes || ""}
`; } else { const cols = ["#", "Topic", "Evidence Type", "Description", "Doc Ref", "Evidence", "Analyst Note", "Confidence", "Review"]; cardsHtml += `
Extracted Evidence
`; cardsHtml += `${cols.map(c => ``).join("")}`; rows.forEach((r, i) => { const confClass = (r.confidence || "medium").toLowerCase().replace(" ", "-"); cardsHtml += ``; }); cardsHtml += `
${c}
${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"}
`; } } else { cardsHtml += `
${sec.label || ""}
`; cardsHtml += `
${sec.content}
`; } }); return `
${section.title} Complete
${cardsHtml}
`; }, async loadPipelineSession(sessionId) { if (!sessionId) return; const pipelineEl = $("#pipeline-output"); try { const res = await API.get(`/api/research/pipeline/${sessionId}/render`); const sections = res.sections || []; pipelineEl.innerHTML = sections.length > 0 ? sections.map(s => this.renderSectionHtml(s)).join('') : "
ℹ️
No pipeline data for this session
"; // 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") ? "▶" : "▼"; }); }); } catch (e) { pipelineEl.innerHTML = `

Error loading pipeline: ${e.message}

`; } }, };