// Store - Application state management const Store = { currentDoc: null, currentSession: null, documents: [], sessions: [], memories: [], findings: [], pageChunks: [], currentPage: 0, polygonsVisible: true, textVisible: true, chunkLabelsVisible: true, }; // ── API Helper ── async function api(endpoint, options = {}) { const defaultOpts = { headers: { 'Content-Type': 'application/json' }, }; const merged = { ...defaultOpts, ...options }; if (options.body && typeof options.body === 'object' && !(options.body instanceof FormData)) { merged.body = JSON.stringify(options.body); } const url = endpoint.startsWith('http') ? endpoint : `/api${endpoint}`; const resp = await fetch(url, merged); if (!resp.ok) { const text = await resp.text(); throw new Error(`${resp.status}: ${text}`); } const ct = resp.headers.get('content-type') || ''; if (ct.includes('json')) return resp.json(); return resp; } // ── Document Store ── const DocStore = { async load() { const res = await api('/documents'); Store.documents = res.documents || []; return Store.documents; }, async getChunks(docId, page) { const params = new URLSearchParams(); if (page !== undefined) params.set('page', page); const res = await api(`/documents/${docId}/chunks?${params}`); Store.pageChunks = res.chunks || []; return Store.pageChunks; }, async upload(file) { const fd = new FormData(); fd.append('file', file); const res = await api('/documents/upload', { method: 'POST', body: fd }); return res; }, async uploadUrl(url, filename) { const res = await api('/documents/upload', { method: 'POST', body: { pdf_url: url, filename }, }); return res; }, }; // ── Research Store ── const ResearchStore = { async run(query, skillNames) { const res = await api('/research/run', { method: 'POST', body: { query, skills: skillNames, doc_id: Store.currentDoc }, }); return res; }, async createSession(query) { const res = await api('/research/session', { method: 'POST', body: query, }); return res; }, async semanticSearch(query, limit = 20) { const res = await api('/research/semantic', { method: 'POST', body: { query, doc_id: Store.currentDoc, limit }, }); return res; }, async textSearch(query, limit = 20) { const res = await api('/research/text-search', { method: 'POST', body: { query, doc_id: Store.currentDoc, limit }, }); return res; }, async getFindings(sessionId) { const res = await api(`/research/findings?session_id=${sessionId || ''}`); Store.findings = res.findings || []; return Store.findings; }, async getMemories(sessionId) { const res = await api(`/research/memories?session_id=${sessionId || ''}`); Store.memories = res.memories || []; return Store.memories; }, async saveFinding(sessionId, query, response) { await api('/research/run', { method: 'POST', body: { query, doc_id: sessionId }, }); }, }; // ── Memory Store ── const MemoryStore = { async save(content, type = 'fact', importance = 3, sourceDocId) { const res = await api('/memories/save', { method: 'POST', body: { session_id: Store.currentSession, content, memory_type: type, importance, source_doc_id: sourceDocId, }, }); return res; }, async search(query, limit = 10) { const res = await api('/memories/search', { method: 'POST', body: { query, limit }, }); return res; }, }; // ── Agent Store ── const AgentStore = { async getSkills() { const res = await api('/agents/skills'); return res.skills; }, async chat(message, model) { const res = await api('/ollama/chat', { method: 'POST', body: { model: model || 'gpt-oss:20b', messages: [{ role: 'user', content: message }], }, }); return res; }, }; // ── Viewer Store ── const ViewerStore = { async getPolygonView(docId, page) { const res = await api(`/documents/${docId}/polygon-view?page=${page}`); return res; }, async getPageText(docId, page) { const res = await api(`/documents/${docId}/page-text?page=${page}`); return res; }, }; // ── Health Store ── const HealthStore = { async check() { const res = await api('/health'); return res; }, };