@tasksai/install 0.1.38 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +34 -0
  2. package/bootstrap/Install RealtorTasksAI.command +37 -0
  3. package/bootstrap/Install RealtorTasksAI.ps1 +42 -0
  4. package/package.json +14 -3
  5. package/runtime/document_renderer.py +882 -0
  6. package/runtime/server.py +55 -581
  7. package/runtime/software_use.py +39 -0
  8. package/runtime/workflow-requirements.txt +5 -0
  9. package/runtime/workflows/__init__.py +0 -0
  10. package/runtime/workflows/account_snapshot.py +33 -0
  11. package/runtime/workflows/attachments.py +45 -0
  12. package/runtime/workflows/authority_guides.py +81 -0
  13. package/runtime/workflows/catalog.py +51 -0
  14. package/runtime/workflows/catalog_app.py +174 -0
  15. package/runtime/workflows/catalog_generation.py +186 -0
  16. package/runtime/workflows/catalog_output.py +312 -0
  17. package/runtime/workflows/catalog_suggestions.py +51 -0
  18. package/runtime/workflows/catalog_workspace.py +152 -0
  19. package/runtime/workflows/cli.py +66 -0
  20. package/runtime/workflows/document_answers.py +96 -0
  21. package/runtime/workflows/document_selection.py +50 -0
  22. package/runtime/workflows/documents.py +243 -0
  23. package/runtime/workflows/embedded/catalog.html +83 -0
  24. package/runtime/workflows/embedded/offer-review.html +516 -0
  25. package/runtime/workflows/embedded/preview.html +36 -0
  26. package/runtime/workflows/embedded_actions.py +96 -0
  27. package/runtime/workflows/embedded_demo.py +424 -0
  28. package/runtime/workflows/folders.py +120 -0
  29. package/runtime/workflows/gateway.py +157 -0
  30. package/runtime/workflows/generation_lock.py +26 -0
  31. package/runtime/workflows/launcher.py +61 -0
  32. package/runtime/workflows/licensed_delivery.py +46 -0
  33. package/runtime/workflows/mcp_dev.py +52 -0
  34. package/runtime/workflows/meeting_plan.py +92 -0
  35. package/runtime/workflows/model_client.py +26 -0
  36. package/runtime/workflows/numeric_consistency.py +72 -0
  37. package/runtime/workflows/public_source_fetch.py +66 -0
  38. package/runtime/workflows/realtor/__init__.py +0 -0
  39. package/runtime/workflows/realtor/adapter.py +179 -0
  40. package/runtime/workflows/realtor/seller_offer/ORIGIN.json +16 -0
  41. package/runtime/workflows/realtor/seller_offer/__init__.py +0 -0
  42. package/runtime/workflows/realtor/seller_offer/examples/demo-input.json +414 -0
  43. package/runtime/workflows/realtor/seller_offer/examples/make_demo.py +44 -0
  44. package/runtime/workflows/realtor/seller_offer/references/input-contract.md +65 -0
  45. package/runtime/workflows/realtor/seller_offer/references/source-boundaries.md +16 -0
  46. package/runtime/workflows/realtor/seller_offer/scripts/__init__.py +0 -0
  47. package/runtime/workflows/realtor/seller_offer/scripts/build_workbook.mjs +233 -0
  48. package/runtime/workflows/realtor/seller_offer/scripts/build_workbook.py +163 -0
  49. package/runtime/workflows/realtor/seller_offer/scripts/offer_engine.py +370 -0
  50. package/runtime/workflows/realtor/seller_offer/scripts/presentation.py +52 -0
  51. package/runtime/workflows/realtor/seller_offer/scripts/render_outputs.py +228 -0
  52. package/runtime/workflows/realtor/seller_offer/scripts/run_package.py +95 -0
  53. package/runtime/workflows/realtor/seller_offer/tests/test_engine.py +252 -0
  54. package/runtime/workflows/realtor/seller_offer/tests/test_workbook.mjs +28 -0
  55. package/runtime/workflows/realtor-release-registry.json +705 -0
  56. package/runtime/workflows/released_catalog.py +91 -0
  57. package/runtime/workflows/source_capture.py +82 -0
  58. package/runtime/workflows/store.py +492 -0
  59. package/runtime/workflows/table_calculations.py +132 -0
  60. package/runtime/workflows/template.py +65 -0
  61. package/runtime/workflows/workspace_launch.py +76 -0
  62. package/src/index.js +217 -118
  63. package/src/managed-python.js +63 -0
  64. package/src/operation-lock.js +22 -0
  65. package/src/prepared-update.js +86 -0
  66. package/src/private-workflow.js +116 -0
  67. package/src/python-runtime.js +50 -0
  68. package/src/recover-installation.js +30 -0
  69. package/src/recovery-lock.js +30 -0
  70. package/src/software-hash.js +24 -0
  71. package/src/software-use.js +32 -0
  72. package/src/update-journal.js +24 -0
  73. package/src/update-recovery.js +72 -0
  74. package/src/workspace-runtime.js +45 -0
@@ -0,0 +1,83 @@
1
+ <!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>RealtorTasksAI · Your projects</title>
2
+ <style>
3
+ *{box-sizing:border-box}body{font:16px/1.5 system-ui;color:#233b30;background:#eff3ef;margin:0}main{max-width:980px;margin:auto;padding:28px}header{display:flex;align-items:center;justify-content:space-between;gap:16px}h1{font-size:27px}h2{font-size:20px}section{background:white;padding:22px;border:1px solid #dbe4dd;border-radius:14px;margin:18px 0}button{background:#347462;color:white;border:0;border-radius:8px;padding:11px 16px;font:inherit;cursor:pointer;margin:4px}button.secondary{background:#e8efea;color:#264c37}button:disabled{opacity:.5}input,textarea,select{font:inherit;padding:10px;border:1px solid #b9c9be;border-radius:7px;width:100%;margin:7px 0 15px}textarea{min-height:90px}label{display:block}small,.muted{color:#607165}#projects button{display:block;width:100%;text-align:left;background:#edf3ee;color:#233b30}#status{white-space:pre-wrap}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}a{color:#276648}.row{display:flex;gap:8px;flex-wrap:wrap}[hidden]{display:none!important}
4
+ </style><main><header><div><strong>RealtorTasksAI</strong><div class="muted">Your local workspace</div><div id="account" class="muted">Credit balance not yet reported</div></div><div class="row"><button id="home" class="secondary">Saved projects</button><button id="close-workspace" class="secondary">Save and close workspace</button></div></header><p id="status" role="status"></p>
5
+ <div id="dashboard"><h1>Your projects</h1><section><h2>Start from a selected skill</h2><p class="muted">Skills appear here after you select them through the TasksAI loader. Opening saved work does not execute the skill again.</p><label>Skill<select id="skills"></select></label><label>Project name<input id="matter" placeholder="For example, Oak Street listing"></label><button id="create">Create project</button></section><section><h2>Saved projects</h2><div id="projects"></div><button id="more" class="secondary" hidden>More projects</button></section></div>
6
+ <div id="project" hidden><h1 id="title"></h1><p id="version" class="muted"></p><section><h2>Project information</h2><p class="muted">Save what you know. The skill’s full instructions determine which information is needed for your particular task.</p><form id="answers"><div id="fields"></div><button>Save answers</button></form></section>
7
+ <section><h2>Supporting documents</h2><p class="muted">PDF, Word or text files up to __UPLOAD_LIMIT_MB__ MB each. Files are saved on this computer.</p><input id="upload" type="file" accept=".pdf,.docx,.txt,.md"><button id="add-document">Add document</button><div class="row"><button id="input" class="secondary">Choose source folder</button><button id="scan" class="secondary">Scan source folder</button></div><p id="source-folder" class="muted"></p><ul id="documents"></ul></section>
8
+ <section id="authority-section" hidden><h2>Authority source guides</h2><p class="muted">These guides identify sources to check. The source pages have not been read by this workspace. Add any needed source documents above.</p><div id="authority-guides"></div></section>
9
+ <section><h2>Prepare your documents</h2><label>Additional instructions (optional)<textarea id="request" placeholder="Leave blank for this skill’s standard output, or describe a specific audience or preference."></textarea></label><button id="recommend-documents" class="secondary">Find matching documents locally</button><p id="document-matches" class="muted"></p><div id="generation-documents"></div><label><input id="consent" type="checkbox" style="width:auto"> Allow my signed-in AI assistant to process my saved answers, their source references, selected document text, and selected source guide in the cloud.</label><p class="muted">TasksAI saves projects and Word/Excel files on this computer. Selected information is sent to OpenAI through your signed-in assistant. Generating uses your assistant allowance; it does not execute the TasksAI skill again.</p><button id="suggest" class="secondary">Find answers in selected documents</button><div id="suggestions"></div><button id="generate">Generate documents</button><ul id="missing"></ul></section>
10
+ <section><h2>Your results</h2><div id="files"></div><p id="no-files" class="muted">No documents generated yet.</p><div class="row"><button id="output" class="secondary">Save results to…</button><button id="export">Save current files to folder</button></div><p id="output-folder" class="muted"></p><div id="review-controls" hidden><h3>Record your review</h3><label>Reviewer name<input id="reviewer"></label><label><input id="review-confirmed" type="checkbox" style="width:auto"> I reviewed the current documents and supporting evidence.</label><button id="review">Mark this version reviewed</button></div><details><summary>What this skill produces</summary><pre id="output-description"></pre></details></section></div></main>
11
+ <script>
12
+ const $=id=>document.getElementById(id);let current,offset=0,busy=false,closed=false;
13
+ async function action(action,args={}){const response=await fetch(location.pathname+'/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action,...args})});const result=await response.json();if(!response.ok)throw Error(result.error||'Unable to complete this action');return result;}
14
+ async function run(fn){if(busy)return;busy=true;document.querySelectorAll('button').forEach(b=>b.disabled=true);$('status').textContent='Working…';try{await fn();}catch(error){$('status').textContent=error.message;}finally{busy=false;document.querySelectorAll('button').forEach(b=>b.disabled=closed||b.dataset.saved==='true');}}
15
+ function clean(text){return text.replace(/\*\*/g,'');}
16
+ function refreshSuggestionComparisons(){for(const section of $('suggestions').querySelectorAll('section[data-field]')){const saved=current.intake.answers[section.dataset.field]||'',note=section.querySelector('.saved-answer-comparison');note.hidden=!saved.trim()||saved.trim()===section.dataset.value.trim();note.textContent='Your saved answer: '+saved+' — using this suggestion will replace it.';section.querySelector('.dismiss-suggestion').textContent=saved.trim()?'Keep my answer':'Skip this suggestion';}}
17
+ function refreshReviewControls(){$('review-controls').hidden=!current.files.length||current.needs_regeneration;$('review-confirmed').checked=false;}
18
+
19
+ async function projects(reset=true){if(reset){offset=0;$('projects').replaceChildren();}const result=await action('projects',{offset});for(const row of result.projects){const button=document.createElement('button');button.textContent=row.matter;button.onclick=()=>run(()=>open(row.id));$('projects').append(button);}offset=result.next_offset;$('more').hidden=offset===null;}
20
+ async function account(){try{const data=await action('account');$('account').textContent=Number.isInteger(data.credits)?'Last reported balance: '+data.credits+' credits · '+new Date(data.updated_at).toLocaleString():'Credit balance not yet reported';$('account').title='Ask your AI assistant to check your TasksAI balance for an updated figure.';}catch{$('account').textContent='Credit balance unavailable';}}
21
+ async function home(){await account();$('project').hidden=true;$('dashboard').hidden=false;current=null;await projects();const result=await action('skills');$('skills').replaceChildren();for(const skill of result.skills){const option=document.createElement('option');option.value=JSON.stringify(skill);option.textContent=skill.skill_id.replace(/^realtor_/,'').replaceAll('_',' ');$('skills').append(option);}$('status').textContent=result.skills.length?'Your saved work is available.':'Choose a skill through the TasksAI loader to begin.';}
22
+ async function authorityGuides(){
23
+ const result=await action('authority-guides',{job_id:current.job_id});$('authority-guides').replaceChildren();$('authority-section').hidden=!result.guides.length&&!result.unavailable;
24
+ if(current.authority_guide){$('authority-section').hidden=false;const clear=document.createElement('button');clear.textContent='Clear selected source guide';clear.className='secondary';clear.onclick=()=>run(async()=>{await action('select-authority',{job_id:current.job_id,guide_id:null,sequence:current.draft_sequence});await open(current.job_id);});$('authority-guides').append(clear);if(current.authority_guide.refresh_needed){const notice=document.createElement('p');notice.textContent='Your selected guide is unavailable or needs refreshing. Choose a current guide before generating.';$('authority-guides').append(notice);}}
25
+ for(const guide of result.guides){
26
+ const packet=guide.packet,details=document.createElement('details'),summary=document.createElement('summary');
27
+ const location=[packet.state_name||packet.state_code,packet.county,packet.locality,packet.district,packet.mls].filter(Boolean).join(' · ');
28
+ summary.textContent=(current.authority_guide?.id===guide.id?'Selected · ':'')+(location||'Jurisdiction not supplied')+' — '+(guide.refresh_needed?'Refresh needed':packet.status==='provisional'?'Provisional source guide':'Source guide');details.append(summary);
29
+ const dates=document.createElement('p');dates.textContent='Registry review: '+(packet.registry_reviewed_on||'Not supplied')+' · Refresh due: '+(packet.registry_refresh_due_on||'Not supplied');details.append(dates);
30
+ for(const source of packet.sources){const item=document.createElement('p'),title=document.createElement('strong');title.textContent=source.title||'Source';item.append(title);const context=document.createElement('span');context.textContent=' — '+(source.authority||'')+' · '+(source.verification_status==='machine_researched'?'Machine researched':source.verification_status||'Verification not supplied')+' · Refresh due: '+(source.refresh_due_on||'Not supplied');item.append(context);try{const url=new URL(source.url);if(url.protocol==='https:'){const link=document.createElement('a');link.href=url.href;link.target='_blank';link.rel='noopener noreferrer';link.textContent=' Open source website';item.append(link);}}catch{}if(!guide.refresh_needed&&current.authority_guide?.id===guide.id){const capture=document.createElement('button');capture.className='secondary';capture.textContent='Add source to supporting documents';capture.onclick=()=>run(async()=>{await action('capture-source',{job_id:current.job_id,guide_id:guide.id,source_index:packet.sources.indexOf(source)});await supporting();$('consent').checked=false;$('status').textContent='Source captured locally. Select it under Prepare your documents when you want AI to use it.';});item.append(capture);}details.append(item);}
31
+ for(const message of [...packet.required_user_authorities.map(value=>'Still needed: '+value),...packet.instructions]){const line=document.createElement('p');line.textContent=message;details.append(line);}
32
+ if(!guide.refresh_needed&&current.authority_guide?.id!==guide.id){const select=document.createElement('button');select.textContent='Use this guide for the project';select.onclick=()=>run(async()=>{await action('select-authority',{job_id:current.job_id,guide_id:guide.id,sequence:current.draft_sequence});await open(current.job_id);});details.append(select);}
33
+ $('authority-guides').append(details);
34
+ }
35
+ if(result.unavailable){const note=document.createElement('p');note.textContent='Some saved guides could not be loaded. Ask your assistant to retrieve the current authority sources again.';$('authority-guides').append(note);}
36
+ }
37
+ async function supporting(){$('document-matches').textContent='';const result=await action('documents',{job_id:current.job_id});$('documents').replaceChildren();$('generation-documents').replaceChildren();for(const doc of result.documents){const label=document.createElement('label'),check=document.createElement('input');check.type='checkbox';check.value=doc.id;check.disabled=doc.available===false;check.style.width='auto';label.append(check,document.createTextNode(' '+doc.filename));$('generation-documents').append(label);const li=document.createElement('li');if(doc.available!==false){const link=document.createElement('a');link.textContent=doc.filename;link.href=location.pathname+'/source/'+current.job_id+'/'+doc.id;link.title='Open saved original';li.append(link);}else{li.textContent=doc.filename;}for(const message of doc.attention||[]){const note=document.createElement('small');note.style.display='block';note.textContent=message;li.append(note);}$('documents').append(li);}const folders=await action('folders',{job_id:current.job_id});$('source-folder').textContent=folders.input_folder||'No source folder selected';$('output-folder').textContent=folders.output_folder||'No results folder selected';await authorityGuides();}
38
+ async function open(id){if(current?.job_id!==id){$('request').value='';$('reviewer').value='';$('suggestions').replaceChildren();}current=await action('open',{job_id:id});$('request').value=current.user_request||'';$('dashboard').hidden=true;$('project').hidden=false;$('title').textContent=current.matter;$('version').textContent=current.current_revision?'Saved documents · '+current.review_status.replaceAll('_',' '):'Saved project · ready for your information';$('fields').replaceChildren();let group='';for(const field of current.fields){if(field.group&&field.group!==group){const heading=document.createElement('h3');heading.textContent=clean(field.group);$('fields').append(heading);group=field.group;}const label=document.createElement('label');label.textContent=clean(field.instruction);const requirement={required:'Required for a complete result',conditional:'Needed when applicable',optional:'Optional'}[field.requirement];if(requirement){const hint=document.createElement('span');hint.className='input-requirement';hint.style.display='block';hint.style.fontSize='0.85em';hint.textContent=requirement;label.append(hint);}const input=document.createElement('textarea');input.name=field.key;input.maxLength=10000;input.value=current.intake.answers[field.key]||'';input.oninput=()=>label.querySelector('small')?.remove();label.append(input);if(current.answer_evidence[field.key]){const citation=document.createElement('small');citation.textContent='Source: '+current.answer_evidence[field.key].filename+' · '+current.answer_evidence[field.key].locator;label.append(citation);}$('fields').append(label);}$('missing').replaceChildren();for(const text of current.generation_notice?.missing||current.missing||[]){const li=document.createElement('li');li.textContent=text;$('missing').append(li);}$('review-controls').hidden=!current.files.length||current.needs_regeneration;$('review-confirmed').checked=false;$('consent').checked=false;$('output-description').textContent=current.output_instructions;$('files').replaceChildren();for(const kind of current.files){const link=document.createElement('a');link.textContent='Open '+(kind==='word'?'Word document':'Excel workbook');link.href=location.pathname+'/file/'+current.job_id+'/'+kind+'?revision='+encodeURIComponent(current.current_revision);link.style.marginRight='20px';$('files').append(link);}$('no-files').hidden=!!current.files.length;await supporting();$('status').textContent=current.generation_notice?.message||(current.needs_regeneration?'Saved instructions are newer than the current files. Generate documents to include your changes.':'Project reopened.');}
39
+ $('close-workspace').onclick=()=>run(async()=>{
40
+ if($('upload').files.length)throw Error('Add the selected document or clear the file selection before closing.');
41
+ if(!current&&$('matter').value.trim())throw Error('Create the new project or clear its name before closing.');
42
+ const draft=current?{job_id:current.job_id,sequence:current.draft_sequence,user_request:$('request').value,answers:Object.fromEntries(new FormData($('answers')))}:null;
43
+ const response=await fetch(location.pathname+'/shutdown',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({confirmed:true,draft})});
44
+ const result=await response.json();if(!response.ok)throw Error(result.error||'Unable to close workspace');
45
+ closed=true;$('project').hidden=true;$('dashboard').hidden=true;
46
+ $('status').textContent='Workspace closed. Your saved projects and files remain on this computer. You can close this tab and reopen TasksAI from your AI assistant.';
47
+ });
48
+ $('home').onclick=()=>run(home);$('more').onclick=()=>run(()=>projects(false));$('create').onclick=()=>run(async()=>{if(!$('skills').value)throw Error('Choose a skill through the loader first.');const skill=JSON.parse($('skills').value);const view=await action('create',{skill_id:skill.skill_id,version:skill.version,content_hash:skill.content_sha256,matter:$('matter').value,request_id:crypto.randomUUID()});await open(view.job_id);});
49
+ $('answers').onsubmit=event=>{event.preventDefault();run(async()=>{current=await action('answers',{job_id:current.job_id,sequence:current.draft_sequence,user_request:$('request').value,answers:Object.fromEntries(new FormData($('answers')))});$('status').textContent='Answers saved. Existing documents have not changed.';});};
50
+ for(const kind of ['input','output','scan','export'])$(kind).onclick=()=>run(async()=>{const result=await action(kind,{job_id:current.job_id});await supporting();$('status').textContent=result.saved?result.saved.length+' documents saved. '+result.skipped.length+' skipped. '+result.skipped.map(s=>s.name+': '+s.reason).join('; '):result.files?'Saved to '+result.folder:'Folder selection saved.';});
51
+ let generationRequest;
52
+ $('recommend-documents').onclick=()=>run(async()=>{
53
+ current=await action('answers',{job_id:current.job_id,sequence:current.draft_sequence,user_request:$('request').value,answers:Object.fromEntries(new FormData($('answers')))});
54
+ const result=await action('recommend-documents',{job_id:current.job_id});const selected=new Set(result.documents.filter(d=>d.selected).map(d=>d.id));
55
+ for(const checkbox of $('generation-documents').querySelectorAll('input')){checkbox.checked=selected.has(checkbox.value);checkbox.parentElement.querySelector('.match-reason')?.remove();const match=result.documents.find(d=>d.id===checkbox.value);if(match){const note=document.createElement('small');note.className='match-reason';note.style.display='block';note.textContent=match.reason;checkbox.parentElement.append(note);}}
56
+ $('consent').checked=false;$('document-matches').textContent=result.method+' '+result.scanned+' files checked; '+selected.size+' suggested. '+(result.not_scanned?result.not_scanned+' older files were not scanned. ':'')+result.unavailable.map(d=>d.filename+': '+d.reason).join('; ');
57
+ $('status').textContent='Review the selected files, then confirm before asking AI to find answers.';
58
+ });
59
+ $('suggest').onclick=()=>run(async()=>{
60
+ if(!$('consent').checked)throw Error('Confirm sending the selected text to your AI assistant.');
61
+ current=await action('answers',{job_id:current.job_id,sequence:current.draft_sequence,user_request:$('request').value,answers:Object.fromEntries(new FormData($('answers')))});
62
+ refreshReviewControls();$('status').textContent='Finding answers and checking their sources…';
63
+ const result=await action('suggest',{job_id:current.job_id,consent:true,document_ids:[...$('generation-documents').querySelectorAll('input:checked')].map(i=>i.value)});
64
+ const jobId=current.job_id;$('suggestions').replaceChildren();
65
+ for(const candidate of result.candidates){const section=document.createElement('section'),label=document.createElement('strong'),answer=document.createElement('p'),quote=document.createElement('blockquote'),button=document.createElement('button'),savedNote=document.createElement('p'),dismiss=document.createElement('button');section.dataset.field=candidate.field;section.dataset.value=candidate.value;savedNote.className='saved-answer-comparison';dismiss.className='secondary dismiss-suggestion';dismiss.type='button';dismiss.onclick=()=>{section.remove();$('status').textContent='Suggestion dismissed. Your saved answer is unchanged.';};
66
+ label.textContent=clean(result.fields[candidate.field])+(candidate.conflict?' — conflicting answers; choose one':'');answer.textContent=candidate.value;quote.textContent=candidate.filename+' · '+candidate.locator+' — '+candidate.quote;button.type='button';button.textContent='Use and save this answer';
67
+ button.onclick=()=>run(async()=>{if(current.job_id!==jobId)throw Error('Reopen the original project to use this suggestion.');current=await action('apply-suggestion',{job_id:jobId,suggestion_id:result.id,candidate_id:candidate.id,sequence:current.draft_sequence});const field=$('fields').querySelector('[name="'+candidate.field+'"]');field.value=candidate.value;field.parentElement.querySelector('small')?.remove();const citation=document.createElement('small');citation.textContent='Source: '+candidate.filename+' · '+candidate.locator;field.parentElement.append(citation);button.textContent='Saved with source reference';button.dataset.saved='true';refreshSuggestionComparisons();refreshReviewControls();$('status').textContent='Answer and source saved. Generate documents when ready.';});section.append(label,answer,savedNote,quote,button,dismiss);$('suggestions').append(section);}refreshSuggestionComparisons();
68
+ $('status').textContent=result.candidates.length+' suggested answers found. '+result.missing.length+' questions have no supported answer in the selected documents.';
69
+ });
70
+ $('generate').onclick=()=>run(async()=>{
71
+ if(!$('consent').checked)throw Error('Confirm sending the selected information to your AI assistant.');
72
+ // Save the currently visible form before generating; never use stale edits.
73
+ current=await action('answers',{job_id:current.job_id,sequence:current.draft_sequence,user_request:$('request').value,answers:Object.fromEntries(new FormData($('answers')))});
74
+ generationRequest={job_id:current.job_id,sequence:current.draft_sequence,revision:current.current_revision,request:$('request').value,document_ids:[...$('generation-documents').querySelectorAll('input:checked')].map(i=>i.value),consent:true,request_id:crypto.randomUUID()};
75
+ const started=Date.now();
76
+ const progress=()=>{const seconds=Math.floor((Date.now()-started)/1000);$('status').textContent='Preparing your documents · '+Math.floor(seconds/60)+':'+String(seconds%60).padStart(2,'0')+' elapsed. Larger tasks may take several minutes, especially if a draft needs correction. Your previous files remain saved.';};
77
+ progress();const timer=setInterval(progress,1000);let result;
78
+ try{result=await action('generate',generationRequest);}finally{clearInterval(timer);}
79
+ await open(current.job_id);$('status').textContent=result.message;
80
+ });
81
+ $('review').onclick=()=>run(async()=>{await action('review',{job_id:current.job_id,revision:current.current_revision,reviewer:$('reviewer').value,confirmed:$('review-confirmed').checked,request_id:crypto.randomUUID()});await open(current.job_id);$('status').textContent='Review recorded for this version.';});
82
+ $('add-document').onclick=()=>run(async()=>{const file=$('upload').files[0];if(!file||file.size>__UPLOAD_LIMIT_BYTES__)throw Error('Choose a document up to __UPLOAD_LIMIT_MB__ MB.');const encoded=await new Promise((resolve,reject)=>{const reader=new FileReader();reader.onload=()=>resolve(reader.result.split(',')[1]);reader.onerror=reject;reader.readAsDataURL(file);});await action('upload',{job_id:current.job_id,filename:file.name,encoded,request_id:crypto.randomUUID()});await supporting();$('upload').value='';$('status').textContent='Document saved locally.';});run(home);
83
+ </script></html>