Spaces:
Running
Running
File size: 22,872 Bytes
d57af24 e40787d d57af24 505c183 e40787d 4315215 e40787d 4315215 505c183 4315215 e40787d d57af24 c0def13 505c183 4315215 d57af24 c0def13 e40787d c0def13 fe60233 e40787d 505c183 c0def13 d57af24 e40787d d57af24 e40787d d57af24 e40787d c0def13 d57af24 b37258e d57af24 c0def13 505c183 d57af24 e40787d d57af24 e40787d d57af24 e40787d d57af24 e40787d d57af24 e40787d d57af24 e40787d d57af24 c0def13 e40787d 505c183 e40787d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | 'use strict';
const $ = id => document.getElementById(id);
let currentRun = null, investigationController = null, modelController = null, selectedContext = null;
function privateSession(){return Boolean($('private-session')?.checked);}
const array = value => Array.isArray(value) ? value : [];
const printable = value => typeof value === 'string' ? value : JSON.stringify(value, null, 2);
function element(tag, content, className) { const node = document.createElement(tag); if(content !== undefined) node.textContent = printable(content); if(className) node.className = className; return node; }
const storageKey = 'pocket-phd-static-runs';
function saveRun(run, runs) {
if(privateSession()){run.storage_warning='Private session: result stays in memory. Export before leaving.';return run;}
try {
if(runs===null) throw new Error('Existing notebook could not be read.');
if(JSON.stringify(savedRuns())!==JSON.stringify(runs)) throw new Error('Notebook changed in another tab.');
localStorage.setItem(storageKey,JSON.stringify([run,...runs.filter(item=>item.id!==run.id)].slice(0,50)));
}
catch {run.storage_warning='This result was not saved: storage is unavailable, full, unreadable, or changed in another tab. Existing notebook data was preserved. Export this result before leaving, then reload to inspect the notebook.';}
return run;
}
function savedRuns() {
try {
const runs=JSON.parse(localStorage.getItem(storageKey) || '[]');
if(!Array.isArray(runs) || runs.some(run=>!run || typeof run.id!=='string' || typeof run.question!=='string')) throw new Error();
return runs;
} catch { throw new Error('Browser notebook storage is unavailable or unreadable. Existing data has been preserved.'); }
}
function parseEvidence(raw) {
if(typeof raw!=='string' || raw.length>250000) throw new Error('Evidence JSON must contain at most 250,000 characters.');
if(!raw.trim()) return [];
const sources=JSON.parse(raw);
if(!Array.isArray(sources) || sources.length>20) throw new Error('Use a JSON array with at most 20 evidence records.');
if(sources.some(source=>!source || typeof source!=='object' || Array.isArray(source) || typeof source.title!=='string' || !source.title.trim())) throw new Error('Each source needs a title.');
return sources;
}
function formatEvidence(sources) {
const formatted=JSON.stringify(sources,null,2);return formatted.length<=250000?formatted:JSON.stringify(sources);
}
function previewEvidence() {
const target=$('context-preview');target.replaceChildren();
try {
const sources=parseEvidence($('sources').value),preview=window.PocketResearch.contextPreview(sources,selectedContext===null?undefined:selectedContext);
if(!preview.length){target.append(element('p','No pasted evidence yet. Crossref records, if requested, are added when the investigation starts.','hint'));return;}
for(const source of preview){const item=element('article',undefined,'context-record');const label=element('label');const checkbox=element('input');checkbox.type='checkbox';checkbox.checked=source.included;checkbox.disabled=Boolean(investigationController);checkbox.onchange=()=>{const ids=selectedContext===null?preview.filter(item=>item.included).map(item=>item.id):selectedContext.slice();if(checkbox.checked && ids.length>=6){checkbox.checked=false;status('Select at most six sources.','error');return;}selectedContext=checkbox.checked?[...ids,source.id]:ids.filter(id=>id!==source.id);previewEvidence();};label.append(checkbox,element('span',`Use ${source.id} in Local AI`));item.append(label);item.append(element('strong',`${source.id}: ${source.title}`),element('p',source.included?`Included in Local AI context${source.truncated?' · title or excerpt shortened for context':''}`:'Not selected for Local AI context.','hint'));if(source.included)item.append(element('pre',source.excerpt || 'Title only: no excerpt supplied.','context-excerpt'));target.append(item);}
} catch(error){target.append(element('p',`Preview unavailable: ${error.message}`,'hint'));}
}
async function request(path, options={}) {
if(path==='/api/config') return {live_available:Boolean(navigator.gpu),model:'Local WebGPU model — load before running'};
let runs;
try {runs=savedRuns();} catch(error) {if(path!=='/api/research') throw error;runs=null;}
if(path==='/api/research') {
const input=JSON.parse(options.body);
if(input.mode==='live' && !window.PocketResearch.ready()) throw new Error('Load the free local AI first.');
investigationController=new AbortController();$('stop-run').hidden=false;
const signal=investigationController.signal;const id=crypto.randomUUID(),created_at=new Date().toISOString();
const evidence=await window.PocketResearch.prepareEvidence(input,{signal});
signal.throwIfAborted();
input.sources=evidence.sources;
if(input.mode==='live') {
if(!evidence.sources.length) throw new Error('Local AI needs evidence. Enable Crossref retrieval or paste source records with excerpts.');
const onCheckpoint=checkpoint=>{checkpoint.id=id;checkpoint.created_at=created_at;saveRun(checkpoint,runs);if(!checkpoint.storage_warning)runs=savedRuns();showRun(checkpoint,false);};
const run=await window.PocketResearch.investigate(input,evidence, message=>status(message,'pending'),signal,{onCheckpoint});
run.id=id;run.created_at=created_at;
return saveRun(run,runs);
}
if(input.sources.length>20) throw new Error('Use at most 20 evidence records.');
const response=await fetch('./demo-template.json',{signal});
if(!response.ok) throw new Error('Demo template could not be loaded.');
const template=await response.json();signal.throwIfAborted();
const substitute=value=> typeof value==='string' ? value.replaceAll('POCKET_RESEARCH_TOPIC',input.question.replace(/[ ?.]+$/,'')) : Array.isArray(value) ? value.map(substitute) : value && typeof value==='object' ? Object.fromEntries(Object.entries(value).map(([key,item])=>[key,substitute(item)])) : value;
const run=substitute(template);
run.question=input.question;run.id=id;run.created_at=created_at;run.status='complete';run.context_ids=input.context_ids===undefined?input.sources.slice(0,6).map((_,index)=>`S${index+1}`):input.context_ids;
run.sources=input.sources;run.retrieval=evidence.retrieval;
run.evidence_status=run.sources.length ? 'supplied records only' : 'no evidence';
if(run.sources.length) {
run.limitations=run.limitations.filter(item=>!item.startsWith('No evidence was supplied.'));
run.gaps.forEach(gap=>gap.evidence_status='demo template; no source entailment claimed');
run.agents[2].output.gaps.forEach(gap=>gap.evidence_status='demo template; no source entailment claimed');
}
run.agents[0].output.research_question=input.question;
const scout=run.agents[1].output;
scout.synthesis=`${run.sources.length} supplied records are available. This demo counts records and does not interpret or verify findings.`;
scout.citations=run.sources.map(source=>source.id);
run.limitations.push(...evidence.limitations,'Method demo uses templates. Select Local AI for model-generated proposals. Notes are stored in this browser.');
return saveRun(run,runs);
}
if(path==='/api/runs') return runs;
const id=decodeURIComponent(path.split('/').pop());
const run=runs.find(item=>item.id===id);
if(!run) throw new Error('Investigation not found in this browser.');
if(options.method==='DELETE') {if(privateSession())throw new Error('Private session cannot delete saved notebook entries.');localStorage.setItem(storageKey,JSON.stringify(runs.filter(item=>item.id!==id)));return {deleted:true};}
return run;
}
function status(message, type='') { const node = $('status'); node.hidden = false; node.className = type; node.textContent = message; }
function fields(parent, data, excluded=[]) { const list = element('dl'); Object.entries(data || {}).forEach(([key, value]) => { if(excluded.includes(key) || value === null || value === undefined) return; list.append(element('dt', key.replaceAll('_', ' ')), element('dd', Array.isArray(value) ? value.map(printable).join('\n') : printable(value))); }); parent.append(list); }
function card(data, index, label) { const node=element('article', undefined, 'card'); node.append(element('span', `${label} ${String(index+1).padStart(2,'0')}`, 'tag'), element('h4', data.title || data.name || `${label} ${index+1}`)); return node; }
function renderGaps(run) { const target=$('gaps'); target.replaceChildren(); array(run.gaps).forEach((gap,index)=> { const node=card(gap,index,'Gap'); if(gap.description) node.append(element('p',gap.description)); fields(node,gap,['id','title','description']); target.append(node); }); if(!target.childNodes.length) target.append(element('p','No knowledge gaps returned for this run.','empty')); }
function renderHypotheses(run) { const target=$('hypotheses'); target.replaceChildren(); array(run.hypotheses).forEach((hypothesis,index)=> { const node=card(hypothesis,index,'Hypothesis'); fields(node,hypothesis,['id','title','gap_id']); const plans=array(run.experiments).filter(plan=>plan.hypothesis_id===hypothesis.id); if(plans.length){ const details=element('details'); details.append(element('summary','Explore the test plan')); plans.forEach(plan=>{const section=element('section'); section.append(element('h4',plan.title || 'Experiment'));fields(section,plan,['id','hypothesis_id','title']);details.append(section);});node.append(details);} target.append(node); }); if(!target.childNodes.length) target.append(element('p','No hypotheses returned for this run.','empty')); }
function safeURL(value) { try {const url=new URL(value); return ['https:','http:'].includes(url.protocol) ? url.href : null;}catch{return null;} }
function renderSources(run) { const target=$('evidence');target.replaceChildren();if(run.retrieval) {const info=element('article',undefined,'notice');info.append(element('h3','Literature search'));fields(info,run.retrieval);target.append(info);}array(run.sources).forEach((source,index)=>{const node=card(source,index,source.id || 'Source');node.classList.add('source-card');node.append(element('p',[source.year,Array.isArray(source.authors)?source.authors.join(', '):source.authors].filter(Boolean).join(' · '),'meta'));if(source.abstract)node.append(element('p',source.abstract));if(run.mode==='live'){const preview=window.PocketResearch.contextPreview(run.sources,run.context_ids)[index];node.append(element('p',preview.included?`Used by model${preview.truncated?' · title or excerpt shortened for context':''}`:'Not selected for model context','meta'));}if(source.extraction){const details=element('details');details.append(element('summary','Extraction provenance'));fields(details,source.extraction);node.append(details);}if(source.provenance)node.append(element('p',`Provenance: ${printable(source.provenance)}`,'meta'));const url=safeURL(source.url);if(url){const link=element('a',source.url,'source-link');link.href=url;link.target='_blank';link.rel='noopener noreferrer';node.append(link);}else if(source.url)node.append(element('p',source.url));target.append(node);});if(!array(run.sources).length)target.append(element('p','No sources were returned. Findings without source support should be treated as exploratory proposals.','empty')); }
function renderAgents(run) {const target=$('agents');target.replaceChildren();array(run.agents).forEach((agent,index)=>{const node=card(agent,index,'Agent');node.append(element('span',agent.status || 'Recorded','tag'),element('pre',agent.error || printable(agent.output) || 'No output recorded.','agent-output'));target.append(node);});if(!target.childNodes.length)target.append(element('p','No agent outputs were returned.','empty'));}
function showRun(run,final=true) {currentRun=run;$('results').hidden=false;$('results-title').textContent=run.question || 'Research investigation';$('run-mode').textContent=run.mode==='demo'?'METHOD DEMO · ILLUSTRATIVE OUTPUT':'LIVE INVESTIGATION · NOVELTY UNVERIFIED';$('result-notice').textContent=run.mode==='demo'?'This is an illustrative method demonstration. Its deterministic hypotheses are not validated research findings or a comprehensive review.':'Model-generated proposals require source verification, domain review, and replication. Novelty has not been established.';$('result-notice').classList.toggle('run-pending',run.status==='running');if(run.status==='running'){$('result-notice').textContent='Investigation running. Completed stages appear as they finish; later stages are still pending.';$('result-notice').classList.remove('run-failure');}else if(run.status && run.status!=='complete'){$('result-notice').textContent=`Investigation ${run.status}. ${run.error || 'Some agent stages did not complete. Review the agent notebook for details.'}`;$('result-notice').classList.add('run-failure');}else{$('result-notice').classList.remove('run-failure');}renderGaps(run);renderHypotheses(run);renderSources(run);renderAgents(run);$('limitations').replaceChildren();array(run.limitations).forEach(limit=>$('limitations').append(element('p',limit)));if(!array(run.limitations).length)$('limitations').append(element('p','No run-specific limitations were returned. This tool cannot establish novelty or validate causal claims.'));if(final){selectTab(document.querySelector('[data-tab="findings"]'));$('results-title').focus();$('results').scrollIntoView({behavior:matchMedia('(prefers-reduced-motion: reduce)').matches?'instant':'smooth',block:'start'});}}
async function history() {try{const runs=await request('/api/runs');const target=$('history-list');target.replaceChildren();runs.forEach(run=>{const button=element('button',run.question || run.id,'history-item');button.type='button';button.append(element('small',[run.mode,run.created_at ? new Date(run.created_at).toLocaleString():null].filter(Boolean).join(' · ')));button.onclick=async()=>{try{showRun(await request(`/api/runs/${encodeURIComponent(run.id)}`));}catch(error){status(error.message,'error');}};target.append(button);});if(!runs.length)target.append(element('p','Your investigations will appear here.','hint'));}catch{$('history-list').replaceChildren(element('p','Research history is temporarily unavailable.','hint'));}}
$('research-form').addEventListener('submit',async event=>{event.preventDefault();if(modelController){status('Finish or cancel model loading before starting.','error');return;}let sources=[];try{sources=parseEvidence($('sources').value);$('sources').removeAttribute('aria-invalid');}catch(error){status(`Check your evidence: ${error.message}`,'error');$('sources').setAttribute('aria-invalid','true');$('sources').focus();return;}const question=$('question').value.trim();if(!question){status('Enter a research question.','error');return;}const mode=document.querySelector('input[name="mode"]:checked').value;setBusy(true);$('research-form').setAttribute('aria-busy','true');status(mode==='live'?'The six-agent investigation is running. Live research may take several minutes; completed stages appear as they finish.':'Running the six-role browser demonstration…','pending');try{const run=await request('/api/research',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({question,mode,sources,context_ids:selectedContext===null?undefined:selectedContext.slice(),retrieve:$('retrieve').checked,search_query:$('search-query').value.trim()})});showRun(run);status(run.storage_warning || (run.status==='failed'?'The investigation failed. Review the saved agent notebook for details.':run.status==='partial'?'A partial investigation was saved. Review incomplete stages in the agent notebook.':run.status==='stopped'?'Investigation stopped. Completed stages remain available for export.':'Investigation saved to your research notebook.'),run.storage_warning || run.status==='failed'?'error':'');await history();}catch(error){status(error.name==='AbortError'?'Investigation stopped.':error.message,error.name==='AbortError'?'':'error');}finally{investigationController=null;$('stop-run').hidden=true;setBusy(false);$('research-form').removeAttribute('aria-busy');}});
$('example').onclick=()=>{$('question').value='How might retrieval practice and sleep timing interact to improve long-term learning in university students?';$('search-query').value='retrieval practice sleep memory';$('question').focus();};
$('refresh-history').onclick=history;
function selectTab(button){document.querySelectorAll('[data-tab]').forEach(item=>{item.setAttribute('aria-selected',String(item===button));item.tabIndex=item===button?0:-1;});document.querySelectorAll('.tab-panel').forEach(panel=>panel.hidden=panel.id!==button.dataset.tab);}
document.querySelectorAll('[data-tab]').forEach((button,index)=>{button.onclick=()=>selectTab(button);button.onkeydown=event=>{const tabs=[...document.querySelectorAll('[data-tab]')];let next;if(event.key==='ArrowRight')next=(index+1)%tabs.length;if(event.key==='ArrowLeft')next=(index+tabs.length-1)%tabs.length;if(event.key==='Home')next=0;if(event.key==='End')next=tabs.length-1;if(next!==undefined){event.preventDefault();selectTab(tabs[next]);tabs[next].focus();}};});
function setBusy(busy){document.querySelectorAll('#research-form input,#research-form textarea,#research-form button').forEach(control=>{if(control.id==='stop-run')return;if(busy){control.dataset.previouslyDisabled=String(control.disabled);control.disabled=true;}else{control.disabled=control.dataset.previouslyDisabled==='true';delete control.dataset.previouslyDisabled;}});$('example').disabled=busy;$('delete-run').disabled=busy;$('clear-notebook').disabled=busy;}
function download(extension){
if(!currentRun)return;
const metadata={context_ids:currentRun.context_ids || null,status:currentRun.status || (currentRun.mode==='demo'?'complete':'unknown'),error:currentRun.error || null,model:currentRun.model || null,created_at:currentRun.created_at || null,evidence_status:currentRun.evidence_status || null,retrieval:currentRun.retrieval || null,storage_warning:currentRun.storage_warning || null};
const content=extension==='json'?JSON.stringify(currentRun,null,2):`# ${currentRun.question}\n\n${currentRun.mode==='demo'?'Illustrative method demo':'Local AI proposals'}. Novelty unverified.\n\n## Run status and provenance\n\n`+JSON.stringify(metadata,null,2)+'\n\n'+['gaps','hypotheses','experiments','sources','agents','limitations'].map(key=>`## ${key}\n\n`+JSON.stringify(currentRun[key],null,2)).join('\n\n');
const url=URL.createObjectURL(new Blob([content],{type:extension==='json'?'application/json':'text/markdown'}));
const link=element('a');link.href=url;link.download=`pocket-phd-${currentRun.id}.${extension}`;document.body.append(link);link.click();link.remove();URL.revokeObjectURL(url);
}
$('delete-run').onclick=async()=>{if(!currentRun || !confirm('Delete this saved investigation? This cannot be undone.'))return;try{await request(`/api/runs/${encodeURIComponent(currentRun.id)}`,{method:'DELETE',headers:{'X-Pocket-PhD':'local'}});currentRun=null;$('results').hidden=true;status('Investigation deleted.');await history();}catch(error){status(error.message,'error');}};
$('export-json').onclick=()=>download('json');
$('export-md').onclick=()=>download('md');
(async()=>{try{const config=await request('/api/config');document.querySelector('input[value="live"]').disabled=!config.live_available;$('live-label').classList.toggle('unavailable',!config.live_available);$('live-note').textContent=config.live_available?`Configured: ${config.model || 'research model'}`:'WebGPU unavailable — try Chrome or Edge';}catch{$('live-note').textContent='Model configuration unavailable';}await history();})();
$('load-model').onclick=async()=>{ modelController=new AbortController();$('cancel-model').hidden=false; $('load-model').disabled=true; try {await window.PocketResearch.load(message=>{$('model-status').textContent=message;},{signal:modelController.signal}); $('live-note').textContent='Model loaded — runs on this device';document.querySelector('input[value="live"]').disabled=false;$('model-status').textContent='Model ready. Select Local AI to use it; Method demo remains selected until you switch.';} catch(error){$('model-status').textContent=error.message;} finally{modelController=null;$('cancel-model').hidden=true;$('load-model').disabled=false;} };
$('stop-run').onclick=()=>{investigationController?.abort();$('live-note').textContent='Stopped — reload the local model before another AI run';};
$('sources').addEventListener('input',()=>{selectedContext=null;previewEvidence();});
$('evidence-file').addEventListener('change',async()=>{
const file=$('evidence-file').files[0];if(!file)return;
try{
if(file.size>500000)throw new Error('Evidence file must be no larger than 500,000 bytes.');
const text=await file.text(),sources=parseEvidence(text);
await window.PocketResearch.prepareEvidence({sources,question:'',retrieve:false});
$('sources').value=formatEvidence(sources);selectedContext=null;$('sources').removeAttribute('aria-invalid');previewEvidence();
status(`Imported ${sources.length} evidence records locally. Review the model context before running.`);
}catch(error){status(`Evidence import failed: ${error.message}`,'error');}
finally{$('evidence-file').value='';}
});
previewEvidence();
$('cancel-model').onclick=()=>{modelController?.abort();window.PocketResearch.cancelLoad?.();};
$('clear-notebook').onclick=()=>{if(privateSession()){status('Private session cannot change saved notebook data.','error');return;}if(!confirm('Clear every Pocket PhD notebook entry? Export anything you need first. Other browser data and model caches will remain.'))return;try{savedRuns();localStorage.removeItem(storageKey);history();status('Pocket PhD notebook cleared.');}catch(error){status(error.message,'error');}};
$('add-evidence').onclick=()=>{try{const sources=parseEvidence($('sources').value),title=$('evidence-title').value.trim(),url=$('evidence-url').value.trim(),abstract=$('evidence-excerpt').value.trim();if(!title)throw new Error('Enter a source title.');if(url && !safeURL(url))throw new Error('Use an HTTP or HTTPS source URL.');if(sources.length>=20)throw new Error('Use at most 20 evidence records.');sources.push({title,url,abstract});const formatted=formatEvidence(sources);parseEvidence(formatted);$('sources').value=formatted;selectedContext=null;previewEvidence();for(const id of ['evidence-title','evidence-url','evidence-excerpt'])$(id).value='';status('Evidence added. Review its context selection.');}catch(error){status(error.message,'error');}};
document.querySelectorAll('input[name="mode"]').forEach(control=>control.addEventListener('change',()=>{$('run-button').textContent=control.value==='live'?'Start Local AI investigation':'Start method demo';}));
|