Files
aipackage/cezen-portal/nexus-compare.html
T

44 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Nexus Compare — Nexus One AI</title><link rel="stylesheet" href="style.css?v=12">
<style>
body{background:var(--bg)}.cmp-shell{max-width:1280px;margin:auto;padding:30px 28px 70px}.cmp-hero{display:flex;justify-content:space-between;gap:30px;align-items:flex-start;margin-bottom:22px}.cmp-eyebrow{font-size:10px;font-weight:800;letter-spacing:1px;text-transform:uppercase;color:var(--purple)}.cmp-hero h1{margin:5px 0 7px;color:var(--ink);font-size:30px}.cmp-hero p{margin:0;max-width:680px;color:var(--med);font-size:14px;line-height:1.65}.cmp-engine{padding:8px 12px;border:1px solid rgba(22,163,74,.22);background:rgba(22,163,74,.07);color:#15803d;border-radius:20px;font-size:11px;font-weight:700}.cmp-upload-grid{display:grid;grid-template-columns:1fr 54px 1fr;align-items:stretch}.cmp-drop{min-height:170px;background:var(--navy2);border:1px dashed rgba(124,58,237,.38);border-radius:14px;padding:22px;display:grid;place-items:center;text-align:center;cursor:pointer;transition:.15s}.cmp-drop:hover,.cmp-drop.over{border-color:var(--purple);background:rgba(124,58,237,.04)}.cmp-drop input{display:none}.cmp-letter{width:38px;height:38px;border-radius:11px;background:rgba(124,58,237,.1);color:var(--purple);display:grid;place-items:center;font-weight:900;margin:auto auto 10px}.cmp-file{font-size:13px;font-weight:800;color:var(--ink);overflow-wrap:anywhere}.cmp-hint{font-size:11px;color:var(--lt);margin-top:5px}.cmp-swap{display:grid;place-items:center;color:var(--lt);font-size:18px}.cmp-actions{display:flex;justify-content:center;align-items:center;gap:12px;margin:18px 0 28px}.cmp-btn{border:1px solid var(--bdr);border-radius:9px;background:var(--navy2);color:var(--med);padding:10px 15px;font:700 12px inherit;cursor:pointer}.cmp-btn.primary{background:var(--purple);border-color:var(--purple);color:white;min-width:150px}.cmp-btn:disabled{opacity:.45;cursor:not-allowed}.cmp-results{display:none}.cmp-results.open{display:block}.cmp-summary{display:grid;grid-template-columns:1.4fr repeat(4,1fr);gap:10px;margin-bottom:16px}.cmp-stat{background:var(--navy2);border:1px solid var(--bdr);border-radius:12px;padding:15px}.cmp-stat strong{display:block;font-size:22px;color:var(--ink)}.cmp-stat span{font-size:10px;text-transform:uppercase;letter-spacing:.7px;color:var(--lt);font-weight:800}.cmp-stat.similarity{background:linear-gradient(135deg,rgba(124,58,237,.11),var(--navy2))}.cmp-ai{border:1px solid rgba(124,58,237,.22);background:rgba(124,58,237,.05);border-radius:12px;padding:15px;margin-bottom:16px}.cmp-ai-head{display:flex;justify-content:space-between;align-items:center;font-size:12px;font-weight:800;color:var(--ink)}.cmp-ai-text{margin-top:10px;color:var(--med);font-size:13px;line-height:1.65;white-space:pre-wrap}.cmp-toolbar{display:flex;justify-content:space-between;align-items:center;margin:20px 0 10px}.cmp-toolbar h2{font-size:16px;color:var(--ink);margin:0}.cmp-filter{display:flex;gap:5px}.cmp-filter button{border:1px solid var(--bdr);background:var(--navy2);border-radius:7px;padding:6px 9px;color:var(--lt);font:700 10px inherit;cursor:pointer}.cmp-filter button.active{color:var(--purple);border-color:rgba(124,58,237,.35)}.cmp-change{display:grid;grid-template-columns:1fr 1fr;background:var(--navy2);border:1px solid var(--bdr);border-radius:11px;overflow:hidden;margin-bottom:9px}.cmp-change-head{grid-column:1/-1;padding:8px 12px;border-bottom:1px solid var(--bdr);font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.7px}.cmp-change.added .cmp-change-head{color:#15803d;background:#f0fdf4}.cmp-change.removed .cmp-change-head{color:#b91c1c;background:#fef2f2}.cmp-change.modified .cmp-change-head{color:#a16207;background:#fffbeb}.cmp-pane{padding:13px;min-height:42px;color:var(--med);font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.cmp-pane+ .cmp-pane{border-left:1px solid var(--bdr)}.cmp-location{display:block;margin:-3px 0 8px;color:var(--purple);font:800 10px/1.2 Inter,system-ui,sans-serif;text-transform:uppercase;letter-spacing:.65px}.cmp-empty{color:var(--lt);font-style:italic}.cmp-previews{display:flex;gap:8px}.cmp-status{text-align:center;color:var(--med);font-size:12px;display:none}.cmp-status.show{display:block}@media(max-width:800px){.cmp-upload-grid{grid-template-columns:1fr}.cmp-swap{padding:8px}.cmp-summary{grid-template-columns:1fr 1fr}.cmp-stat.similarity{grid-column:1/-1}.cmp-change{grid-template-columns:1fr}.cmp-pane+.cmp-pane{border-left:0;border-top:1px solid var(--bdr)}}
</style>
</head>
<body>
<header class="topnav"><a href="index.html" class="brand">Nexus One <span>AI</span></a><nav><a href="index.html">Home</a><a href="quickstart.html">Quick Start</a><a href="prompts.html">Prompt Library</a><div class="nav-dropdown"><button class="nav-drop-btn active">AI Tools ▾</button><div class="nav-drop-menu">
<span class="nav-drop-cat">MODEL BUILD /</span>
<a href="dataset-creation.html">Dataset Creation</a>
<a href="training.html">Model Training</a><span class="nav-drop-cat">ENABLED /</span><a href="tool-openwebui.html">Open WebUI</a><a href="nexus-notebook.html">Nexus Notebook</a><a href="nexus-compare.html" class="active">Nexus Compare</a><a href="tool-jupyter.html">Jupyter Notebook</a></div></div></nav><div class="nav-right"><div id="nav-org-logo" class="nav-org-logo"></div><span class="nav-tier-badge" data-brand="tier" data-tier-slug="basic">Basic Tier</span></div></header>
<main class="cmp-shell">
<div class="cmp-hero"><div><div class="cmp-eyebrow">Document intelligence</div><h1>Nexus Compare</h1><p>Compare documents across formats. Nexus identifies additions, removals and material changes while preserving the structure of paragraphs, headings and tables.</p></div><div class="cmp-engine">Runs locally on Nexus</div></div>
<div class="cmp-upload-grid">
<label class="cmp-drop" id="left-drop"><input type="file" id="left-file" onchange="picked('left',this.files[0])"><div><div class="cmp-letter">A</div><div class="cmp-file" id="left-name">Choose original file</div><div class="cmp-hint">PDF, Word, Excel, PowerPoint, images, text and more</div></div></label>
<div class="cmp-swap">⇄</div>
<label class="cmp-drop" id="right-drop"><input type="file" id="right-file" onchange="picked('right',this.files[0])"><div><div class="cmp-letter">B</div><div class="cmp-file" id="right-name">Choose revised file</div><div class="cmp-hint">The files do not need to have the same format</div></div></label>
</div>
<div class="cmp-actions"><button class="cmp-btn" onclick="resetCompare()">Clear</button><button class="cmp-btn primary" id="compare-btn" onclick="compareNow()" disabled>Compare files</button></div><div class="cmp-status" id="status">Normalising document structure with Docling…</div>
<section class="cmp-results" id="results">
<div class="cmp-summary"><div class="cmp-stat similarity"><strong id="similarity">—</strong><span>Content similarity</span></div><div class="cmp-stat"><strong id="modified">0</strong><span>Modified</span></div><div class="cmp-stat"><strong id="added">0</strong><span>Added</span></div><div class="cmp-stat"><strong id="removed">0</strong><span>Removed</span></div><div class="cmp-stat"><strong id="unchanged">0</strong><span>Unchanged</span></div></div>
<div class="cmp-ai"><div class="cmp-ai-head"><span>Nexus AI change brief</span><button class="cmp-btn" id="brief-btn" onclick="generateBrief()">Generate brief</button></div><div class="cmp-ai-text" id="ai-text">Generate a grounded summary of the most important changes, risks and contradictions.</div></div>
<div class="cmp-toolbar"><h2>Detailed comparison</h2><div class="cmp-previews"><button class="cmp-btn" onclick="openFile('left')">View original</button><button class="cmp-btn" onclick="openFile('right')">View revised</button><button class="cmp-btn" id="visual-btn" onclick="openFile('visual')" hidden>Visual PDF diff</button></div></div>
<div class="cmp-toolbar"><span id="engine-label" class="cmp-hint"></span><div class="cmp-filter"><button class="active" onclick="filterChanges('all',this)">All</button><button onclick="filterChanges('modified',this)">Modified</button><button onclick="filterChanges('added',this)">Added</button><button onclick="filterChanges('removed',this)">Removed</button></div></div>
<div id="changes"></div>
</section>
</main>
<script>
const NOTEBOOK_API='/notebook-api',API='/api';let files={left:null,right:null},result=null;
function esc(v){return String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
function picked(side,file){if(!file)return;files[side]=file;document.getElementById(side+'-name').textContent=file.name;document.getElementById(side+'-drop').classList.add('over');document.getElementById('compare-btn').disabled=!(files.left&&files.right)}
['left','right'].forEach(side=>{const el=document.getElementById(side+'-drop');el.addEventListener('dragover',e=>{e.preventDefault();el.classList.add('over')});el.addEventListener('dragleave',()=>el.classList.remove('over'));el.addEventListener('drop',e=>{e.preventDefault();picked(side,e.dataTransfer.files[0])})});
async function compareNow(){const btn=document.getElementById('compare-btn'),status=document.getElementById('status');btn.disabled=true;status.classList.add('show');document.getElementById('results').classList.remove('open');const fd=new FormData();fd.append('left',files.left);fd.append('right',files.right);try{const res=await fetch(NOTEBOOK_API+'/compare',{method:'POST',credentials:'include',body:fd});const data=await res.json();if(!res.ok)throw new Error(data.detail||'Comparison failed');result=data;renderResult()}catch(e){alert(e.message)}finally{btn.disabled=false;status.classList.remove('show')}}
function renderResult(){document.getElementById('results').classList.add('open');document.getElementById('similarity').textContent=result.similarity+'%';['modified','added','removed','unchanged'].forEach(k=>document.getElementById(k).textContent=result.counts[k]||0);document.getElementById('engine-label').textContent=`Engine: ${result.engine==='docling'?'Docling structured comparison':'diffoscope technical comparison'}${result.truncated?' · first 300 changes shown':''}`;document.getElementById('visual-btn').hidden=!result.visual_diff_available;document.getElementById('ai-text').textContent='Generate a grounded summary of the most important changes, risks and contradictions.';renderChanges('all');document.getElementById('results').scrollIntoView({behavior:'smooth',block:'start'})}
function renderChanges(filter){const changes=(result.changes||[]).filter(c=>filter==='all'||c.type===filter);document.getElementById('changes').innerHTML=changes.length?changes.map((c,i)=>`<div class="cmp-change ${c.type}"><div class="cmp-change-head">${c.type} · Change ${i+1} · ${esc(c.left_location||'No original location')} → ${esc(c.right_location||'No revised location')}</div><div class="cmp-pane ${c.left?'':'cmp-empty'}"><span class="cmp-location">Original · ${esc(c.left_location||'Not present')}</span>${c.left?esc(c.left):'No corresponding content in original'}</div><div class="cmp-pane ${c.right?'':'cmp-empty'}"><span class="cmp-location">Revised · ${esc(c.right_location||'Not present')}</span>${c.right?esc(c.right):'No corresponding content in revised file'}</div></div>`).join(''):`<div class="cmp-stat"><span>No ${filter==='all'?'':filter+' '}changes found</span></div>`}
function filterChanges(type,button){document.querySelectorAll('.cmp-filter button').forEach(b=>b.classList.remove('active'));button.classList.add('active');renderChanges(type)}
function openFile(side){if(!result)return;const url=side==='visual'?result.visual_diff:(side==='left'?result.left_preview:result.right_preview);if(url)window.open(url,'_blank','noopener')}
async function generateBrief(){if(!result)return;const btn=document.getElementById('brief-btn'),out=document.getElementById('ai-text');btn.disabled=true;out.textContent='Analysing material changes with the local model…';const evidence=result.changes.slice(0,25).map((c,i)=>`CHANGE ${i+1} (${c.type})\nORIGINAL ${c.left_location||''}: ${c.left||'[none]'}\nREVISED ${c.right_location||''}: ${c.right||'[none]'}`).join('\n\n').slice(0,24000);const prompt=`Compare ${result.left_name} with ${result.right_name}. Similarity is ${result.similarity}%. Write a concise enterprise change brief with: Executive summary, Material changes including slide or page numbers, Risks or contradictions, and Recommended review actions. Use only the supplied differences.\n\n${evidence}`;try{const res=await fetch(API+'/chat',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({model:'llama3',message:prompt,temperature:.1,max_tokens:900})});const data=await res.json();if(!res.ok)throw new Error(data.detail||'AI brief failed');out.textContent=data.response||data.message||'No brief returned'}catch(e){out.textContent='Brief unavailable: '+e.message}finally{btn.disabled=false}}
function resetCompare(){files={left:null,right:null};result=null;['left','right'].forEach(s=>{document.getElementById(s+'-file').value='';document.getElementById(s+'-name').textContent=s==='left'?'Choose original file':'Choose revised file';document.getElementById(s+'-drop').classList.remove('over')});document.getElementById('compare-btn').disabled=true;document.getElementById('results').classList.remove('open')}
</script><script src="auth.js?v=20260721-consolidated"></script><script src="branding.js?v=20260721-consolidated"></script></body></html>