PabloRod11 commited on
Commit
fe60233
·
verified ·
1 Parent(s): d092a5b

Require evidence for local AI and reject unsupported citation claims

Browse files
Files changed (4) hide show
  1. README.md +1 -1
  2. app.js +1 -0
  3. index.html +1 -1
  4. research.js +2 -1
README.md CHANGED
@@ -15,7 +15,7 @@ Choose **Load free local AI** to download Qwen2.5 1.5B via WebLLM (roughly 1 GB;
15
 
16
  Optional scholarly retrieval sends your question or keywords to the free Crossref API and returns up to eight metadata records, with abstracts where available. No full-text systematic review is performed.
17
 
18
- Select **Local AI**, then start an investigation. Small-model structured-output failures preserve completed stages and mark the result partial or failed. The deterministic method demo remains available.
19
 
20
  Notes stay in this browser and can be exported. Browser storage can be cleared. Context is bounded to six records and 800 characters per excerpt; the notebook retains complete evidence. Citation checks validate IDs, not claim support. All novelty is unverified. Expert review remains necessary.
21
 
 
15
 
16
  Optional scholarly retrieval sends your question or keywords to the free Crossref API and returns up to eight metadata records, with abstracts where available. No full-text systematic review is performed.
17
 
18
+ Select **Local AI**, enable Crossref retrieval or paste evidence, then start an investigation. Local AI requires at least one evidence record. Small-model structured-output failures preserve completed stages and mark the result partial or failed. The deterministic method demo remains available.
19
 
20
  Notes stay in this browser and can be exported. Browser storage can be cleared. Context is bounded to six records and 800 characters per excerpt; the notebook retains complete evidence. Citation checks validate IDs, not claim support. All novelty is unverified. Expert review remains necessary.
21
 
app.js CHANGED
@@ -15,6 +15,7 @@ async function request(path, options={}) {
15
  const evidence=await window.PocketResearch.prepareEvidence(input);
16
  input.sources=evidence.sources;
17
  if(input.mode==='live') {
 
18
  const run=await window.PocketResearch.investigate(input,evidence, message=>status(message,'pending'));
19
  run.id=crypto.randomUUID();run.created_at=new Date().toISOString();
20
  localStorage.setItem(storageKey,JSON.stringify([run,...runs].slice(0,50)));return run;
 
15
  const evidence=await window.PocketResearch.prepareEvidence(input);
16
  input.sources=evidence.sources;
17
  if(input.mode==='live') {
18
+ if(!evidence.sources.length) throw new Error('Local AI needs evidence. Enable Crossref retrieval or paste source records with excerpts.');
19
  const run=await window.PocketResearch.investigate(input,evidence, message=>status(message,'pending'));
20
  run.id=crypto.randomUUID();run.created_at=new Date().toISOString();
21
  localStorage.setItem(storageKey,JSON.stringify([run,...runs].slice(0,50)));return run;
index.html CHANGED
@@ -2,6 +2,6 @@
2
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Pocket PhD · Research workbench</title><link rel="stylesheet" href="./style.css"><script src="./research.js" defer></script><script src="./app.js" defer></script></head>
3
  <body><a class="skip" href="#question">Skip to research question</a><header><a class="brand" href="./" aria-label="Pocket PhD home"><span class="brand-mark" aria-hidden="true">PhD</span><span>Pocket PhD<small>Research notebook</small></span></a><span class="local-tag">Local AI workspace</span></header>
4
  <main><section class="intro"><div class="eyebrow">New investigation</div><h1>What would you like to find out?</h1><p>Work from published evidence to candidate hypotheses and a testable plan.</p></section>
5
- <div class="workspace"><section class="composer panel" aria-labelledby="compose-title"><div class="section-top"><div><h2 id="compose-title">Research question</h2></div><button type="button" id="example" class="text-button">Use example</button></div><form id="research-form"><label class="sr-only" for="question">Research question</label><textarea id="question" required minlength="10" maxlength="2000" rows="4" placeholder="Ask a specific question about a phenomenon, population, or mechanism."></textarea><details class="evidence-input"><summary>Add your own evidence <span>optional</span></summary><label for="sources">Paste sources as a JSON array</label><p class="hint">Each source needs a title. Optional: URL, abstract or excerpt, year, and authors. Your text becomes evidence; the tool does not verify pasted claims.</p><textarea id="sources" rows="6" placeholder='[{"title":"Paper title","url":"https://…","abstract":"Relevant findings…","year":2025}]'></textarea></details><fieldset><legend>Research mode</legend><div class="modes"><label><input type="radio" name="mode" value="demo" checked><span><strong>Method demo</strong><small>Deterministic, illustrative reasoning</small></span></label><label id="live-label" class="unavailable"><input type="radio" name="mode" value="live" disabled><span><strong>Local AI</strong><small id="live-note">Checking browser support…</small></span></label></div></fieldset><div class="local-ai"><button id="load-model" type="button">Load free local AI</button><p id="model-status" class="hint" role="status">Downloads a model to your browser (about 1 GB). Requires WebGPU and sufficient device memory. No API key or paid service.</p></div><label class="retrieve"><input type="checkbox" id="retrieve"> Retrieve scholarly records from Crossref</label><label class="search-label" for="search-query">Literature search terms <span class="hint">optional</span></label><input class="search-query" id="search-query" maxlength="500" placeholder="e.g. retrieval practice sleep memory"><p class="hint">Search terms go to Crossref. Records may lack abstracts; this is not a full-text review. Notes and AI reasoning stay on your device.</p><div class="form-bottom"><p class="hint">Hypotheses are proposals, not discoveries.<br>Novelty requires expert review and a broader literature search.</p><button type="submit" id="run-button" class="primary">Start investigation</button></div></form><div id="status" role="status" aria-live="polite" hidden></div></section><aside class="sidebar"><section class="panel team"><h2>Research method</h2><ol><li><span>01</span><div>Question Framer<small>Define scope & assumptions</small></div></li><li><span>02</span><div>Literature Scout<small>Assemble the evidence</small></div></li><li><span>03</span><div>Gap Analyst<small>Find unanswered questions</small></div></li><li><span>04</span><div>Hypothesis Architect<small>Propose causal explanations</small></div></li><li><span>05</span><div>Skeptic<small>Challenge novelty & inference</small></div></li><li><span>06</span><div>Experiment Designer<small>Make predictions testable</small></div></li></ol></section><section class="history"><div class="section-top"><h3>Research notebook</h3><button id="refresh-history" class="text-button" aria-label="Refresh research history">↻</button></div><div id="history-list"><p class="hint">Loading previous investigations…</p></div></section></aside></div>
6
  <section id="results" hidden aria-labelledby="results-title"><div class="results-header"><div><div class="eyebrow" id="run-mode">INVESTIGATION</div><h2 id="results-title"></h2></div><div class="exports"><button id="export-md">Export notes ↓</button><button id="export-json">JSON ↓</button><button id="delete-run">Delete</button></div></div><div id="result-notice" class="notice"></div><nav class="tabs" aria-label="Research results"><button data-tab="findings" aria-current="page">Findings</button><button data-tab="evidence">Evidence library</button><button data-tab="agents">Agent notebook</button></nav><div id="findings" class="tab-panel"><div class="findings-grid"><section><h3>Knowledge gaps</h3><div id="gaps"></div></section><section><h3>Candidate hypotheses</h3><div id="hypotheses"></div></section></div><section class="limits"><h3>Limits of this investigation</h3><div id="limitations"></div></section></div><div id="evidence" class="tab-panel" hidden></div><div id="agents" class="tab-panel" hidden></div></section>
7
  <footer><span>Pocket PhD</span><p>Research notebook</p></footer></main></body></html>
 
2
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Pocket PhD · Research workbench</title><link rel="stylesheet" href="./style.css"><script src="./research.js" defer></script><script src="./app.js" defer></script></head>
3
  <body><a class="skip" href="#question">Skip to research question</a><header><a class="brand" href="./" aria-label="Pocket PhD home"><span class="brand-mark" aria-hidden="true">PhD</span><span>Pocket PhD<small>Research notebook</small></span></a><span class="local-tag">Local AI workspace</span></header>
4
  <main><section class="intro"><div class="eyebrow">New investigation</div><h1>What would you like to find out?</h1><p>Work from published evidence to candidate hypotheses and a testable plan.</p></section>
5
+ <div class="workspace"><section class="composer panel" aria-labelledby="compose-title"><div class="section-top"><div><h2 id="compose-title">Research question</h2></div><button type="button" id="example" class="text-button">Use example</button></div><form id="research-form"><label class="sr-only" for="question">Research question</label><textarea id="question" required minlength="10" maxlength="2000" rows="4" placeholder="Ask a specific question about a phenomenon, population, or mechanism."></textarea><details class="evidence-input"><summary>Add your own evidence <span>optional</span></summary><label for="sources">Paste sources as a JSON array</label><p class="hint">Each source needs a title. Optional: URL, abstract or excerpt, year, and authors. Your text becomes evidence; the tool does not verify pasted claims.</p><textarea id="sources" rows="6" placeholder='[{"title":"Paper title","url":"https://…","abstract":"Relevant findings…","year":2025}]'></textarea></details><fieldset><legend>Research mode</legend><div class="modes"><label><input type="radio" name="mode" value="demo" checked><span><strong>Method demo</strong><small>Deterministic, illustrative reasoning</small></span></label><label id="live-label" class="unavailable"><input type="radio" name="mode" value="live" disabled><span><strong>Local AI · experimental</strong><small id="live-note">Checking browser support…</small></span></label></div></fieldset><div class="local-ai"><button id="load-model" type="button">Load free local AI</button><p id="model-status" class="hint" role="status">Downloads a model to your browser (about 1 GB). Requires WebGPU and sufficient device memory. No API key or paid service.</p></div><label class="retrieve"><input type="checkbox" id="retrieve"> Retrieve scholarly records from Crossref</label><label class="search-label" for="search-query">Literature search terms <span class="hint">optional</span></label><input class="search-query" id="search-query" maxlength="500" placeholder="e.g. retrieval practice sleep memory"><p class="hint">Search terms go to Crossref. Records may lack abstracts; this is not a full-text review. Notes and AI reasoning stay on your device.</p><div class="form-bottom"><p class="hint">Hypotheses are proposals, not discoveries.<br>Novelty requires expert review and a broader literature search.</p><button type="submit" id="run-button" class="primary">Start investigation</button></div></form><div id="status" role="status" aria-live="polite" hidden></div></section><aside class="sidebar"><section class="panel team"><h2>Research method</h2><ol><li><span>01</span><div>Question Framer<small>Define scope & assumptions</small></div></li><li><span>02</span><div>Literature Scout<small>Assemble the evidence</small></div></li><li><span>03</span><div>Gap Analyst<small>Find unanswered questions</small></div></li><li><span>04</span><div>Hypothesis Architect<small>Propose causal explanations</small></div></li><li><span>05</span><div>Skeptic<small>Challenge novelty & inference</small></div></li><li><span>06</span><div>Experiment Designer<small>Make predictions testable</small></div></li></ol></section><section class="history"><div class="section-top"><h3>Research notebook</h3><button id="refresh-history" class="text-button" aria-label="Refresh research history">↻</button></div><div id="history-list"><p class="hint">Loading previous investigations…</p></div></section></aside></div>
6
  <section id="results" hidden aria-labelledby="results-title"><div class="results-header"><div><div class="eyebrow" id="run-mode">INVESTIGATION</div><h2 id="results-title"></h2></div><div class="exports"><button id="export-md">Export notes ↓</button><button id="export-json">JSON ↓</button><button id="delete-run">Delete</button></div></div><div id="result-notice" class="notice"></div><nav class="tabs" aria-label="Research results"><button data-tab="findings" aria-current="page">Findings</button><button data-tab="evidence">Evidence library</button><button data-tab="agents">Agent notebook</button></nav><div id="findings" class="tab-panel"><div class="findings-grid"><section><h3>Knowledge gaps</h3><div id="gaps"></div></section><section><h3>Candidate hypotheses</h3><div id="hypotheses"></div></section></div><section class="limits"><h3>Limits of this investigation</h3><div id="limitations"></div></section></div><div id="evidence" class="tab-panel" hidden></div><div id="agents" class="tab-panel" hidden></div></section>
7
  <footer><span>Pocket PhD</span><p>Research notebook</p></footer></main></body></html>
research.js CHANGED
@@ -47,6 +47,7 @@ window.PocketResearch = (() => {
47
  }
48
  function outputSchema(shape, sourceIDs) {
49
  if(typeof shape==='string') return {type:'string',maxLength:240,...(shape==='unverified'?{enum:['unverified']}:shape==='low|medium|high'?{enum:['low','medium','high']}:shape==='source ID'?{enum:[...sourceIDs]}:{})};
 
50
  if(Array.isArray(shape)) return {type:'array',items:shape[0]==='source ID' && !sourceIDs.size?{type:'string'}:outputSchema(shape[0],sourceIDs),maxItems:shape[0]==='source ID'&&!sourceIDs.size?0:typeof shape[0]==='object'?2:6};
51
  return {type:'object',properties:Object.fromEntries(Object.entries(shape).map(([key,value])=>[key,outputSchema(value,sourceIDs)])),required:Object.keys(shape),additionalProperties:false};
52
  }
@@ -81,7 +82,7 @@ window.PocketResearch = (() => {
81
  const {roles,schemas}=await response.json();
82
  const prior={},agents=[];let failure=null;
83
  // Bound context for the small local model; the notebook retains complete records.
84
- const contextSources=evidence.sources.slice(0,6).map(source=>({...source,abstract:source.abstract.slice(0,800)}));
85
  const sourceIDs=new Set(contextSources.map(source=>source.id));
86
  for(const [stage,name] of roles) {
87
  if(failure) {agents.push({id:stage,name,status:'skipped',output:{}});continue;}
 
47
  }
48
  function outputSchema(shape, sourceIDs) {
49
  if(typeof shape==='string') return {type:'string',maxLength:240,...(shape==='unverified'?{enum:['unverified']}:shape==='low|medium|high'?{enum:['low','medium','high']}:shape==='source ID'?{enum:[...sourceIDs]}:{})};
50
+ if(Array.isArray(shape) && shape[0]==='source ID' && !sourceIDs.size) return {type:'array',const:[]};
51
  if(Array.isArray(shape)) return {type:'array',items:shape[0]==='source ID' && !sourceIDs.size?{type:'string'}:outputSchema(shape[0],sourceIDs),maxItems:shape[0]==='source ID'&&!sourceIDs.size?0:typeof shape[0]==='object'?2:6};
52
  return {type:'object',properties:Object.fromEntries(Object.entries(shape).map(([key,value])=>[key,outputSchema(value,sourceIDs)])),required:Object.keys(shape),additionalProperties:false};
53
  }
 
82
  const {roles,schemas}=await response.json();
83
  const prior={},agents=[];let failure=null;
84
  // Bound context for the small local model; the notebook retains complete records.
85
+ const contextSources=evidence.sources.slice(0,6).map(source=>({id:source.id,title:source.title.slice(0,200),abstract:source.abstract.slice(0,800),year:Number.isInteger(source.year)?source.year:null}));
86
  const sourceIDs=new Set(contextSources.map(source=>source.id));
87
  for(const [stage,name] of roles) {
88
  if(failure) {agents.push({id:stage,name,status:'skipped',output:{}});continue;}