aipackage/cezen-portal/documents.html
2026-06-30 10:51:41 +05:30

1072 lines
57 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document Intelligence — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<style>
/* ── Upload zone ── */
.di-upload-zone {
border: 2px dashed var(--bdr);
border-radius: 16px;
background:var(--navy2);
padding: 40px 32px;
text-align: center;
cursor: pointer;
transition: .2s;
position: relative;
}
.di-upload-zone:hover, .di-upload-zone.drag-over {
border-color: var(--purple);
background:rgba(124,58,237,.04);
}
.di-upload-zone input[type="file"] {
position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%;
}
.di-upload-icon { font-size: 36px; margin-bottom: 10px; }
.di-upload-title { font-size: 16px; font-weight: 700; color: var(--ink); margin-bottom: 4px; }
.di-upload-sub { font-size: 12px; color: var(--lt); }
.di-file-types { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 12px; }
.di-type-chip { background: var(--bg); border: 1px solid var(--bdr); border-radius: 20px; padding: 3px 10px; font-size: 11px; color: var(--med); font-weight: 600; }
/* ── Comparison zone ── */
.di-compare-zone { display: none; margin-top: 14px; }
.di-compare-zone.show { display: block; }
.di-compare-label { font-size: 12px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .4px; margin-bottom: 8px; }
/* ── Selected file bar ── */
.di-file-bar {
display: none;
background:var(--navy2);
border: 1.5px solid var(--purple);
border-radius: 12px;
padding: 12px 16px;
align-items: center;
gap: 12px;
margin-top: 10px;
}
.di-file-bar.show { display: flex; }
.di-file-icon { font-size: 22px; }
.di-file-name { font-weight: 600; font-size: 13px; color: var(--ink); flex: 1; }
.di-file-size { font-size: 12px; color: var(--lt); }
.di-file-clear { background: none; border: none; cursor: pointer; font-size: 16px; color: var(--lt); padding: 0; }
.di-file-clear:hover { color: var(--ink); }
/* ── Controls ── */
.di-controls {
background:var(--navy2);
border: 1px solid var(--bdr);
border-radius: 14px;
padding: 22px 24px;
margin-top: 18px;
}
.di-controls h3 { font-size: 13px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .5px; margin: 0 0 14px; }
.di-mode-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 10px; margin-bottom: 18px; }
.di-mode-btn {
padding: 12px 10px; border-radius: 10px; border: 1.5px solid var(--bdr);
background:var(--navy2); cursor: pointer; font-family: inherit; font-size: 12px;
font-weight: 600; color: var(--med); text-align: center; transition: .15s;
line-height: 1.3;
}
.di-mode-btn .di-mode-icon { font-size: 20px; display: block; margin-bottom: 6px; }
.di-mode-btn.active { border-color: var(--purple); background:rgba(124,58,237,.06); color: var(--purple); }
.di-mode-btn:hover:not(.active) { border-color: var(--med); }
.di-mode-section { font-size: 10px; font-weight: 700; color: var(--xlt); text-transform: uppercase; letter-spacing: .6px; margin: 14px 0 8px; grid-column: 1 / -1; }
.di-custom-prompt { display: none; margin-bottom: 18px; }
.di-custom-prompt textarea {
width: 100%; box-sizing: border-box; border: 1.5px solid var(--bdr); border-radius: 10px;
padding: 12px 14px; font-family: inherit; font-size: 13px; resize: vertical;
min-height: 80px; color: var(--ink); background: var(--navy2);
}
.di-custom-prompt textarea:focus { outline: none; border-color: var(--purple); }
.di-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.di-row .form-group { flex: 1; min-width: 160px; margin: 0; }
.di-row label { display: block; font-size: 11px; font-weight: 700; color: var(--lt); margin-bottom: 6px; text-transform: uppercase; letter-spacing: .4px; }
.di-row select {
width: 100%; padding: 9px 12px; border: 1.5px solid var(--bdr); border-radius: 8px;
font-family: inherit; font-size: 13px; color: var(--ink); background:var(--navy2);
}
.di-row select:focus { outline: none; border-color: var(--purple); }
/* ── Process button ── */
#process-btn {
margin-top: 18px; width: 100%; padding: 13px;
font-size: 14px; font-weight: 700; border-radius: 10px;
background: linear-gradient(135deg,var(--purple),var(--pink));
color: white; border: none; cursor: pointer;
font-family: inherit; transition: .15s;
}
#process-btn:hover { filter: brightness(1.08); }
#process-btn:disabled { opacity: .5; cursor: not-allowed; }
/* ── Progress ── */
.di-progress {
display: none;
background:var(--navy2);
border: 1px solid var(--bdr);
border-radius: 14px;
padding: 28px 24px;
margin-top: 18px;
text-align: center;
}
.di-progress.show { display: block; }
.di-spinner { font-size: 30px; animation: spin 1.2s linear infinite; display: inline-block; margin-bottom: 10px; }
@keyframes spin { to { transform: rotate(360deg); } }
.di-progress-label { font-size: 14px; color: var(--med); font-weight: 600; }
.di-progress-sub { font-size: 12px; color: var(--lt); margin-top: 4px; }
/* ── Result card ── */
.di-result {
display: none;
background:var(--navy2);
border: 1px solid var(--bdr);
border-radius: 14px;
margin-top: 18px;
}
.di-result.show { display: block; }
.di-result-header {
display: flex; align-items: center; gap: 12px; padding: 14px 20px;
border-bottom: 1px solid var(--bdr);
}
.di-result-title { font-size: 14px; font-weight: 700; color: var(--ink); flex: 1; }
.di-result-meta { font-size: 12px; color: var(--lt); }
.di-result-body {
padding: 20px 24px;
font-size: 14px; color: var(--ink); line-height: 1.7;
white-space: pre-wrap; word-break: break-word;
max-height: 700px; overflow-y: auto;
}
.di-result-body.structured { white-space: normal; }
.di-result-actions { padding: 12px 20px; border-top: 1px solid var(--bdr); display: flex; gap: 10px; flex-wrap: wrap; }
/* ── Clause cards ── */
.di-clauses { display: flex; flex-direction: column; gap: 12px; }
.di-clause {
border: 1.5px solid var(--bdr); border-radius: 12px; overflow: hidden;
}
.di-clause.risk-high { border-color: rgba(220,38,38,.3); }
.di-clause.risk-medium { border-color: rgba(217,119,6,.3); }
.di-clause.risk-low { border-color: rgba(22,163,74,.3); }
.di-clause-header {
display: flex; align-items: center; gap: 10px; padding: 12px 16px;
background: var(--bg); cursor: pointer;
}
.di-clause.risk-high .di-clause-header { background: rgba(220,38,38,.04); }
.di-clause.risk-medium .di-clause-header { background: rgba(217,119,6,.04); }
.di-clause.risk-low .di-clause-header { background: rgba(22,163,74,.04); }
.di-clause-type { font-size: 13px; font-weight: 700; color: var(--ink); flex: 1; }
.di-clause-risk {
font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px;
padding: 2px 9px; border-radius: 20px;
}
.risk-high .di-clause-risk { background: rgba(220,38,38,.1); color: #DC2626; }
.risk-medium .di-clause-risk { background: rgba(217,119,6,.1); color: #D97706; }
.risk-low .di-clause-risk { background: rgba(22,163,74,.1); color: #16A34A; }
.di-clause-pg { font-size: 11px; color: var(--lt); margin-left: 8px; }
.di-clause-body { padding: 14px 16px; font-size: 13px; color: var(--med); line-height: 1.6; display: none; }
.di-clause.open .di-clause-body { display: block; }
.di-clause-excerpt { font-style: italic; color: var(--lt); font-size: 12px; margin-top: 8px; border-left: 3px solid var(--bdr); padding-left: 10px; }
.di-clause-chevron { color: var(--lt); font-size: 11px; transition: .2s; }
.di-clause.open .di-clause-chevron { transform: rotate(90deg); }
/* ── Clause summary bar ── */
.di-clause-summary { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }
.di-clause-stat { display: flex; align-items: center; gap: 6px; padding: 6px 14px; border-radius: 20px; font-size: 12px; font-weight: 700; }
.di-clause-stat.red { background: rgba(220,38,38,.08); color: #DC2626; border: 1px solid rgba(220,38,38,.2); }
.di-clause-stat.amber { background: rgba(217,119,6,.08); color: #D97706; border: 1px solid rgba(217,119,6,.2); }
.di-clause-stat.green { background: rgba(22,163,74,.08); color: #16A34A; border: 1px solid rgba(22,163,74,.2); }
/* ── Tender checklist ── */
.di-checklist { display: flex; flex-direction: column; gap: 8px; }
.di-check-item {
display: flex; align-items: flex-start; gap: 12px; padding: 13px 16px;
border-radius: 10px; border: 1px solid var(--bdr); background: var(--bg);
}
.di-check-item.pass { border-color: rgba(22,163,74,.25); background: rgba(22,163,74,.04); }
.di-check-item.fail { border-color: rgba(220,38,38,.25); background: rgba(220,38,38,.04); }
.di-check-item.missing { border-color: rgba(217,119,6,.25); background: rgba(217,119,6,.04); }
.di-check-icon { font-size: 18px; flex-shrink: 0; margin-top: 1px; }
.di-check-body { flex: 1; }
.di-check-title { font-size: 13px; font-weight: 600; color: var(--ink); margin-bottom: 2px; }
.di-check-note { font-size: 12px; color: var(--lt); }
.di-check-status { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; padding: 2px 9px; border-radius: 20px; flex-shrink: 0; margin-top: 2px; }
.pass .di-check-status { background: rgba(22,163,74,.1); color: #16A34A; }
.fail .di-check-status { background: rgba(220,38,38,.1); color: #DC2626; }
.missing .di-check-status { background: rgba(217,119,6,.1); color: #D97706; }
.di-checklist-summary { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }
.di-check-prog { height: 6px; border-radius: 20px; background: rgba(124,58,237,.1); margin-bottom: 16px; overflow: hidden; }
.di-check-prog-bar { height: 100%; border-radius: 20px; background: linear-gradient(90deg,#16A34A,#22C55E); transition: width .8s ease; }
/* ── Executive brief ── */
.di-brief { font-size: 14px; color: var(--ink); line-height: 1.8; }
.di-brief-meta { display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--bdr); }
.di-brief-meta-item { display: flex; flex-direction: column; gap: 2px; }
.di-brief-meta-label { font-size: 10px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .5px; }
.di-brief-meta-val { font-size: 13px; font-weight: 600; color: var(--ink); }
.di-brief-section { margin-bottom: 18px; }
.di-brief-section-title { font-size: 12px; font-weight: 800; color: var(--purple); text-transform: uppercase; letter-spacing: .6px; margin-bottom: 8px; }
.di-brief-kv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media(max-width:600px){ .di-brief-kv-grid { grid-template-columns: 1fr; } }
.di-brief-kv { background: var(--bg); border: 1px solid var(--bdr); border-radius: 8px; padding: 10px 14px; }
.di-brief-kv-label { font-size: 10px; font-weight: 700; color: var(--lt); text-transform: uppercase; }
.di-brief-kv-val { font-size: 13px; color: var(--ink); font-weight: 600; margin-top: 2px; }
.di-brief-risk-item { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 8px; }
.di-brief-risk-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; margin-top: 5px; }
.di-brief-risk-dot.high { background: #DC2626; }
.di-brief-risk-dot.medium { background: #D97706; }
.di-brief-risk-dot.low { background: #16A34A; }
/* ── Compare view ── */
.di-compare-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media(max-width:700px){ .di-compare-grid { grid-template-columns: 1fr; } }
.di-compare-col { border: 1px solid var(--bdr); border-radius: 10px; overflow: hidden; }
.di-compare-col-header { padding: 10px 14px; background: var(--bg); border-bottom: 1px solid var(--bdr); font-size: 12px; font-weight: 700; color: var(--ink); }
.di-compare-col-body { padding: 14px; font-size: 13px; color: var(--med); line-height: 1.7; max-height: 400px; overflow-y: auto; }
.di-compare-diff-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.di-compare-diff-table th { text-align: left; font-size: 11px; font-weight: 700; color: var(--lt); text-transform: uppercase; padding: 0 0 10px; border-bottom: 1px solid var(--bdr); }
.di-compare-diff-table td { padding: 12px 8px; border-bottom: 1px solid var(--bdr); vertical-align: top; }
.di-compare-diff-table tr:last-child td { border-bottom: none; }
.diff-added { background: rgba(22,163,74,.06); }
.diff-removed { background: rgba(220,38,38,.06); }
.diff-changed { background: rgba(217,119,6,.06); }
.diff-badge { font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 20px; }
.diff-added .diff-badge { background: rgba(22,163,74,.1); color: #16A34A; }
.diff-removed .diff-badge { background: rgba(220,38,38,.1); color: #DC2626; }
.diff-changed .diff-badge { background: rgba(217,119,6,.1); color: #D97706; }
/* ── History ── */
.di-history { margin-top: 40px; }
.di-history h2 { font-size: 15px; font-weight: 700; color: var(--ink); margin-bottom: 14px; }
.di-hist-table-wrap { border: 1px solid var(--bdr); border-radius: 10px; overflow: hidden; }
table.di-hist-table { width: 100%; border-collapse: collapse; }
.di-hist-table th { background: var(--bg); padding: 9px 14px; text-align: left; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px; color: var(--lt); border-bottom: 1px solid var(--bdr); }
.di-hist-table td { padding: 10px 14px; border-bottom: 1px solid var(--bdr); font-size: 13px; color: var(--ink); }
.di-hist-table tr:last-child td { border-bottom: none; }
.di-hist-table tr:hover td { background: rgba(124,58,237,.03); cursor: pointer; }
.di-status-badge { display: inline-block; padding: 2px 9px; border-radius: 20px; font-size: 11px; font-weight: 700; }
.di-status-badge.done { background: rgba(22,163,74,.1); color: #16A34A; }
.di-status-badge.pending { background: rgba(217,119,6,.1); color: #D97706; }
.di-status-badge.processing { background: rgba(37,99,235,.1); color: #2563EB; }
.di-status-badge.error { background: rgba(220,38,38,.1); color: #DC2626; }
.di-empty { text-align: center; padding: 32px 0; color: var(--lt); font-size: 13px; }
/* ── Export toolbar ── */
.di-export-btn { display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border-radius: 8px; border: 1px solid var(--bdr); background: var(--navy2); font-size: 12px; font-weight: 600; color: var(--med); cursor: pointer; font-family: inherit; transition: .15s; }
.di-export-btn:hover { border-color: var(--purple); color: var(--purple); }
</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>
<a href="usecases.html">Use Cases</a>
<span class="nav-sep"></span>
<div class="nav-dropdown">
<button class="nav-drop-btn">Help ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">LEARN /</span>
<a href="quickstart.html">Quick Start</a>
<a href="models.html">Models</a>
<span class="nav-drop-cat">SUPPORT /</span>
<a href="troubleshooting.html">Troubleshoot</a>
<a href="faq.html">FAQ</a>
<span class="nav-drop-cat">MORE /</span>
<a href="glossary.html">Glossary</a>
<a href="whats-new.html">What's New</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn">Admin ▾</button>
<div class="nav-drop-menu nav-drop-menu-wide">
<span class="nav-drop-cat">DOCS /</span>
<a href="security.html">Security &amp; Privacy</a>
<a href="admin.html">Admin Guide</a>
<span class="nav-drop-cat">MONITOR /</span>
<a href="dashboard.html">Dashboard</a>
<a href="analytics.html">Usage Analytics</a>
<a href="audit.html">Audit Log</a>
<a href="feedback.html">Feedback &amp; Ratings</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
<a href="teams.html">Teams</a>
<a href="models-admin.html">Model Manager</a>
<a href="training.html">Training</a>
<a href="knowledge.html">Knowledge Base</a>
<span class="nav-drop-cat">TOOLS /</span>
<a href="apikeys.html">API Keys</a>
<a href="benchmark.html">Benchmarking</a>
<a href="model-compare.html">Model Compare</a>
<a href="api-playground.html">API Playground</a>
<a href="guardrails.html">Guardrails</a>
<a href="rag-quality.html">RAG Quality</a>
<a href="router.html">Model Router</a>
<a href="connectors.html">Connectors</a>
<span class="nav-drop-cat">SYSTEM /</span>
<a href="console.html">Console</a>
<a href="settings.html">Settings</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn active">AI Tools ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">INTELLIGENCE /</span>
<a href="documents.html">Document Intelligence</a>
<a href="chat-multi.html">Multimodal Chat</a>
<a href="prompt-studio.html">Prompt Studio</a>
<a href="meeting.html">Meeting Assistant</a>
<span class="nav-drop-cat">AUTOMATION /</span>
<a href="agents.html">Agent Builder</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Automation</a>
<span class="nav-drop-cat">QUALITY /</span>
<a href="evals.html">AI Eval Suite</a>
<a href="chatrooms.html">Chat Rooms</a>
</div>
</div>
</nav>
<a href="notifications.html" style="position:relative">🔔</a>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
<div class="page-hero">
<div class="label">AI Tools</div>
<h1>Document Intelligence Workbench</h1>
<p>Contract analysis, tender checklists, executive briefs, document comparison — your entire document workflow, on-premises.</p>
</div>
<div class="content">
<!-- Upload zone -->
<div class="di-upload-zone" id="upload-zone">
<input type="file" id="file-input"
accept=".pdf,.docx,.doc,.txt,.md,.png,.jpg,.jpeg,.webp,.bmp">
<div class="di-upload-icon">📄</div>
<div class="di-upload-title">Drop your file here or click to browse</div>
<div class="di-upload-sub">Supports PDF, Word, images, and plain text — up to 20 MB</div>
<div class="di-file-types">
<span class="di-type-chip">PDF</span>
<span class="di-type-chip">DOCX</span>
<span class="di-type-chip">TXT / MD</span>
<span class="di-type-chip">PNG / JPG</span>
</div>
</div>
<!-- Selected file bar -->
<div class="di-file-bar" id="file-bar">
<span class="di-file-icon" id="file-icon">📄</span>
<span class="di-file-name" id="file-name"></span>
<span class="di-file-size" id="file-size"></span>
<button class="di-file-clear" onclick="clearFile()" title="Remove file"></button>
</div>
<!-- Second file (comparison mode) -->
<div class="di-compare-zone" id="compare-zone">
<div class="di-compare-label">📎 Second Document (to compare against)</div>
<div class="di-upload-zone" id="upload-zone-b" style="padding:24px 20px">
<input type="file" id="file-input-b"
accept=".pdf,.docx,.doc,.txt,.md,.png,.jpg,.jpeg,.webp,.bmp">
<div class="di-upload-icon" style="font-size:28px">📄</div>
<div class="di-upload-title" style="font-size:14px">Drop second document here</div>
</div>
<div class="di-file-bar" id="file-bar-b">
<span class="di-file-icon" id="file-icon-b">📄</span>
<span class="di-file-name" id="file-name-b"></span>
<span class="di-file-size" id="file-size-b"></span>
<button class="di-file-clear" onclick="clearFileB()" title="Remove file"></button>
</div>
</div>
<!-- Controls -->
<div class="di-controls">
<h3>What should the AI do?</h3>
<div class="di-mode-grid" id="mode-grid">
<div class="di-mode-section">GENERAL</div>
<button class="di-mode-btn active" data-mode="summarise" onclick="setMode('summarise',this)">
<span class="di-mode-icon">📝</span>Summarise
</button>
<button class="di-mode-btn" data-mode="keypoints" onclick="setMode('keypoints',this)">
<span class="di-mode-icon">🔑</span>Key Points
</button>
<button class="di-mode-btn" data-mode="tables" onclick="setMode('tables',this)">
<span class="di-mode-icon">📊</span>Extract Tables
</button>
<button class="di-mode-btn" data-mode="questions" onclick="setMode('questions',this)">
<span class="di-mode-icon"></span>Q&amp;A
</button>
<button class="di-mode-btn" data-mode="translate" onclick="setMode('translate',this)">
<span class="di-mode-icon">🌐</span>Translate
</button>
<div class="di-mode-section">LEGAL &amp; COMPLIANCE</div>
<button class="di-mode-btn" data-mode="clauses" onclick="setMode('clauses',this)">
<span class="di-mode-icon">📋</span>Contract Clauses
</button>
<button class="di-mode-btn" data-mode="tender" onclick="setMode('tender',this)">
<span class="di-mode-icon"></span>Tender Checklist
</button>
<div class="di-mode-section">INSIGHTS</div>
<button class="di-mode-btn" data-mode="brief" onclick="setMode('brief',this)">
<span class="di-mode-icon">🗒️</span>Executive Brief
</button>
<button class="di-mode-btn" data-mode="compare" onclick="setMode('compare',this)">
<span class="di-mode-icon">🔄</span>Compare Docs
</button>
<button class="di-mode-btn" data-mode="custom" onclick="setMode('custom',this)">
<span class="di-mode-icon">✏️</span>Custom Prompt
</button>
</div>
<div class="di-custom-prompt" id="custom-prompt-wrap">
<textarea id="custom-prompt" placeholder="Enter your custom instruction, e.g. 'List all equipment mentioned with their specifications'"></textarea>
</div>
<div class="di-row">
<div class="form-group">
<label>Model</label>
<select id="model-select">
<option value="llama3">llama3 (default)</option>
</select>
</div>
</div>
<button id="process-btn" onclick="processDoc()" disabled>
⚡ Process Document
</button>
</div>
<!-- Progress -->
<div class="di-progress" id="progress-card">
<div class="di-spinner">⚙️</div>
<div class="di-progress-label" id="progress-label">Extracting text…</div>
<div class="di-progress-sub">This may take 30120 seconds depending on file size and model</div>
</div>
<!-- Result -->
<div class="di-result" id="result-card">
<div class="di-result-header">
<span style="font-size:20px" id="result-icon">📝</span>
<div class="di-result-title" id="result-title">Result</div>
<div class="di-result-meta" id="result-meta"></div>
</div>
<div class="di-result-body" id="result-body"></div>
<div class="di-result-actions">
<button class="di-export-btn" onclick="copyResult()">📋 Copy</button>
<button class="di-export-btn" onclick="downloadMd()">⬇️ Download .md</button>
<button class="di-export-btn" onclick="printPdf()">🖨️ Print / PDF</button>
<button class="btn btn-ghost" style="margin-left:auto" onclick="newJob()">+ New Document</button>
</div>
</div>
<!-- History -->
<div class="di-history">
<h2>Recent Documents</h2>
<div id="history-wrap">
<div class="di-empty">No documents processed yet</div>
</div>
</div>
</div>
<footer>
<p>Nexus One AI &nbsp;·&nbsp; Powered by Cezen &nbsp;·&nbsp; <span data-brand="tier">Basic Tier</span></p>
<p>Questions? <a href="mailto:support@cezentech.com">support@cezentech.com</a> &nbsp;·&nbsp; <a href="https://cezentech.com" target="_blank">cezentech.com</a></p>
</footer>
<script>
const _API = '/api';
let selectedFile = null;
let selectedFileB = null;
let currentMode = 'summarise';
let currentJobId = null;
let pollTimer = null;
let currentResultText = '';
const MODE_ICONS = {
summarise:'📝', keypoints:'🔑', tables:'📊', questions:'❓',
translate:'🌐', custom:'✏️', clauses:'📋', tender:'✅',
brief:'🗒️', compare:'🔄'
};
const MODE_LABELS = {
summarise:'Summary', keypoints:'Key Points', tables:'Extracted Tables',
questions:'Q&A', translate:'Translation', custom:'Result',
clauses:'Contract Clause Analysis', tender:'Tender Compliance Checklist',
brief:'Executive Brief', compare:'Document Comparison'
};
// ── File handling ────────────────────────────────────────────────────────────
const fileInput = document.getElementById('file-input');
const fileInputB = document.getElementById('file-input-b');
const uploadZone = document.getElementById('upload-zone');
const uploadZoneB = document.getElementById('upload-zone-b');
fileInput.addEventListener('change', () => { if (fileInput.files[0]) selectFile(fileInput.files[0]); });
fileInputB.addEventListener('change', () => { if (fileInputB.files[0]) selectFileB(fileInputB.files[0]); });
['dragover','dragleave','drop'].forEach(ev => {
uploadZone.addEventListener(ev, e => {
e.preventDefault();
if (ev === 'dragover') uploadZone.classList.add('drag-over');
else if (ev === 'dragleave') uploadZone.classList.remove('drag-over');
else { uploadZone.classList.remove('drag-over'); if (e.dataTransfer.files[0]) selectFile(e.dataTransfer.files[0]); }
});
uploadZoneB.addEventListener(ev, e => {
e.preventDefault();
if (ev === 'dragover') uploadZoneB.classList.add('drag-over');
else if (ev === 'dragleave') uploadZoneB.classList.remove('drag-over');
else { uploadZoneB.classList.remove('drag-over'); if (e.dataTransfer.files[0]) selectFileB(e.dataTransfer.files[0]); }
});
});
function selectFile(file) {
selectedFile = file;
const ext = file.name.split('.').pop().toLowerCase();
const icons = { pdf:'📕', docx:'📘', doc:'📘', txt:'📄', md:'📄',
png:'🖼️', jpg:'🖼️', jpeg:'🖼️', webp:'🖼️', bmp:'🖼️' };
document.getElementById('file-icon').textContent = icons[ext] || '📄';
document.getElementById('file-name').textContent = file.name;
document.getElementById('file-size').textContent = fmtSize(file.size);
document.getElementById('file-bar').classList.add('show');
updateProcessBtn();
hideResult();
}
function selectFileB(file) {
selectedFileB = file;
const ext = file.name.split('.').pop().toLowerCase();
const icons = { pdf:'📕', docx:'📘', doc:'📘', txt:'📄', md:'📄',
png:'🖼️', jpg:'🖼️', jpeg:'🖼️', webp:'🖼️', bmp:'🖼️' };
document.getElementById('file-icon-b').textContent = icons[ext] || '📄';
document.getElementById('file-name-b').textContent = file.name;
document.getElementById('file-size-b').textContent = fmtSize(file.size);
document.getElementById('file-bar-b').classList.add('show');
updateProcessBtn();
}
function clearFile() {
selectedFile = null; fileInput.value = '';
document.getElementById('file-bar').classList.remove('show');
updateProcessBtn(); hideResult();
}
function clearFileB() {
selectedFileB = null; fileInputB.value = '';
document.getElementById('file-bar-b').classList.remove('show');
updateProcessBtn();
}
function updateProcessBtn() {
const needsB = (currentMode === 'compare');
const ready = selectedFile && (!needsB || selectedFileB);
document.getElementById('process-btn').disabled = !ready;
}
function fmtSize(bytes) {
if (bytes > 1048576) return (bytes / 1048576).toFixed(1) + ' MB';
return Math.round(bytes / 1024) + ' KB';
}
// ── Mode ─────────────────────────────────────────────────────────────────────
function setMode(mode, btn) {
currentMode = mode;
document.querySelectorAll('.di-mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.getElementById('custom-prompt-wrap').style.display = mode === 'custom' ? 'block' : 'none';
const compareZone = document.getElementById('compare-zone');
if (mode === 'compare') compareZone.classList.add('show');
else { compareZone.classList.remove('show'); }
updateProcessBtn();
}
// ── Process ──────────────────────────────────────────────────────────────────
async function processDoc() {
if (!selectedFile) return;
if (currentMode === 'custom' && !document.getElementById('custom-prompt').value.trim()) {
alert('Please enter a custom instruction first.'); return;
}
if (currentMode === 'compare' && !selectedFileB) {
alert('Please upload a second document to compare against.'); return;
}
const model = document.getElementById('model-select').value;
const fd = new FormData();
fd.append('file', selectedFile);
fd.append('mode', currentMode);
fd.append('model', model);
if (currentMode === 'custom') fd.append('custom_prompt', document.getElementById('custom-prompt').value.trim());
if (selectedFileB) fd.append('file_b', selectedFileB);
document.getElementById('process-btn').disabled = true;
document.getElementById('progress-label').textContent = 'Uploading and extracting text…';
document.getElementById('progress-card').classList.add('show');
hideResult();
// Try real API first, fall back to mock
try {
const res = await fetch(`${_API}/docjobs`, { method: 'POST', credentials: 'include', body: fd });
if (!res.ok) throw new Error('API unavailable');
const job = await res.json();
currentJobId = job.id;
pollJob(job.id);
} catch(e) {
// API unavailable — run mock demo
runMockProcess();
}
}
function runMockProcess() {
const steps = [
'Extracting text from document…',
'Sending to AI model…',
'Model is analysing…',
'Formatting results…'
];
let i = 0;
const lbl = document.getElementById('progress-label');
const iv = setInterval(() => {
lbl.textContent = steps[Math.min(i++, steps.length-1)];
if (i >= steps.length + 1) {
clearInterval(iv);
const mockResult = generateMockResult(currentMode, selectedFile ? selectedFile.name : 'document.pdf');
document.getElementById('progress-card').classList.remove('show');
document.getElementById('process-btn').disabled = false;
showStructuredResult(currentMode, mockResult, selectedFile ? selectedFile.name : 'document.pdf');
saveLocalHistory(selectedFile ? selectedFile.name : 'document.pdf', currentMode);
renderLocalHistory();
}
}, 900);
}
function pollJob(id) {
if (pollTimer) clearInterval(pollTimer);
const labels = ['Extracting text from document…','Sending to AI model…','Model is thinking…','Almost done…'];
let tick = 0;
pollTimer = setInterval(async () => {
try {
document.getElementById('progress-label').textContent = labels[Math.min(tick++, labels.length-1)];
const res = await fetch(`${_API}/docjobs/${id}`, { credentials: 'include' });
const job = await res.json();
if (job.status === 'done') {
clearInterval(pollTimer);
showStructuredResult(job.mode, job.result, job.orig_name);
loadHistory();
document.getElementById('process-btn').disabled = false;
document.getElementById('progress-card').classList.remove('show');
} else if (job.status === 'error') {
clearInterval(pollTimer);
document.getElementById('progress-card').classList.remove('show');
document.getElementById('process-btn').disabled = false;
alert('Processing failed: ' + (job.error_msg || 'Unknown error'));
loadHistory();
}
} catch(e) { /* keep polling */ }
}, 2500);
}
// ── Result rendering ─────────────────────────────────────────────────────────
function showStructuredResult(mode, data, filename) {
document.getElementById('result-icon').textContent = MODE_ICONS[mode] || '📝';
document.getElementById('result-title').textContent = `${MODE_LABELS[mode] || 'Result'}${filename}`;
document.getElementById('result-meta').textContent = new Date().toLocaleTimeString();
const body = document.getElementById('result-body');
if (mode === 'clauses' && typeof data === 'object' && data.clauses) {
body.classList.add('structured');
body.innerHTML = renderClauses(data);
currentResultText = exportClausesText(data);
} else if (mode === 'tender' && typeof data === 'object' && data.items) {
body.classList.add('structured');
body.innerHTML = renderTenderChecklist(data);
currentResultText = exportTenderText(data);
} else if (mode === 'brief' && typeof data === 'object' && data.summary) {
body.classList.add('structured');
body.innerHTML = renderBrief(data);
currentResultText = exportBriefText(data);
} else if (mode === 'compare' && typeof data === 'object' && data.diffs) {
body.classList.add('structured');
body.innerHTML = renderCompare(data);
currentResultText = exportCompareText(data);
} else {
body.classList.remove('structured');
const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
body.textContent = text;
currentResultText = text;
}
document.getElementById('result-card').classList.add('show');
}
// ── Clause renderer ───────────────────────────────────────────────────────────
function renderClauses(data) {
const counts = {high:0, medium:0, low:0};
data.clauses.forEach(c => counts[c.risk] = (counts[c.risk]||0)+1);
let html = `<div class="di-clause-summary">
<span class="di-clause-stat red">⚠️ ${counts.high||0} High Risk</span>
<span class="di-clause-stat amber">⚡ ${counts.medium||0} Medium Risk</span>
<span class="di-clause-stat green">✓ ${counts.low||0} Low Risk</span>
</div><div class="di-clauses">`;
data.clauses.forEach((c, i) => {
html += `<div class="di-clause risk-${c.risk}" id="clause-${i}">
<div class="di-clause-header" onclick="toggleClause(${i})">
<span class="di-clause-type">${c.type}</span>
<span class="di-clause-risk">${c.risk} risk</span>
${c.page ? `<span class="di-clause-pg">p.${c.page}</span>` : ''}
<span class="di-clause-chevron">▶</span>
</div>
<div class="di-clause-body">
<div>${c.analysis}</div>
${c.excerpt ? `<div class="di-clause-excerpt">"${c.excerpt}"</div>` : ''}
</div>
</div>`;
});
return html + '</div>';
}
function toggleClause(i) {
document.getElementById('clause-' + i).classList.toggle('open');
}
// ── Tender checklist renderer ─────────────────────────────────────────────────
function renderTenderChecklist(data) {
const pass = data.items.filter(i=>i.status==='pass').length;
const total = data.items.length;
const pct = Math.round(pass/total*100);
let html = `<div class="di-checklist-summary">
<span class="di-clause-stat green">✓ ${pass} Met</span>
<span class="di-clause-stat red">✗ ${data.items.filter(i=>i.status==='fail').length} Not Met</span>
<span class="di-clause-stat amber">? ${data.items.filter(i=>i.status==='missing').length} Missing</span>
</div>
<div class="di-check-prog"><div class="di-check-prog-bar" style="width:${pct}%"></div></div>
<div class="di-checklist">`;
const statusIcon = {pass:'✅', fail:'❌', missing:'⚠️'};
const statusLabel = {pass:'Met', fail:'Not Met', missing:'Missing'};
data.items.forEach(item => {
html += `<div class="di-check-item ${item.status}">
<span class="di-check-icon">${statusIcon[item.status]||'❓'}</span>
<div class="di-check-body">
<div class="di-check-title">${item.requirement}</div>
${item.note ? `<div class="di-check-note">${item.note}</div>` : ''}
</div>
<span class="di-check-status">${statusLabel[item.status]||item.status}</span>
</div>`;
});
return html + '</div>';
}
// ── Executive brief renderer ──────────────────────────────────────────────────
function renderBrief(data) {
let html = `<div class="di-brief">
<div class="di-brief-meta">`;
if (data.doc_type) html += `<div class="di-brief-meta-item"><div class="di-brief-meta-label">Document Type</div><div class="di-brief-meta-val">${data.doc_type}</div></div>`;
if (data.date) html += `<div class="di-brief-meta-item"><div class="di-brief-meta-label">Date</div><div class="di-brief-meta-val">${data.date}</div></div>`;
if (data.parties) html += `<div class="di-brief-meta-item"><div class="di-brief-meta-label">Parties</div><div class="di-brief-meta-val">${data.parties}</div></div>`;
if (data.value) html += `<div class="di-brief-meta-item"><div class="di-brief-meta-label">Value / Amount</div><div class="di-brief-meta-val">${data.value}</div></div>`;
html += `</div>
<div class="di-brief-section">
<div class="di-brief-section-title">Executive Summary</div>
<p>${data.summary}</p>
</div>`;
if (data.key_facts && data.key_facts.length) {
html += `<div class="di-brief-section">
<div class="di-brief-section-title">Key Facts</div>
<div class="di-brief-kv-grid">`;
data.key_facts.forEach(f => {
html += `<div class="di-brief-kv"><div class="di-brief-kv-label">${f.label}</div><div class="di-brief-kv-val">${f.value}</div></div>`;
});
html += `</div></div>`;
}
if (data.risks && data.risks.length) {
html += `<div class="di-brief-section">
<div class="di-brief-section-title">Key Risks &amp; Watch Points</div>`;
data.risks.forEach(r => {
html += `<div class="di-brief-risk-item">
<div class="di-brief-risk-dot ${r.level}"></div>
<div style="font-size:13px;color:var(--ink)">${r.text}</div>
</div>`;
});
html += `</div>`;
}
if (data.recommendations && data.recommendations.length) {
html += `<div class="di-brief-section">
<div class="di-brief-section-title">Recommendations</div>
<ol style="margin:0;padding-left:18px;font-size:13px;color:var(--med);line-height:1.8">`;
data.recommendations.forEach(r => html += `<li>${r}</li>`);
html += `</ol></div>`;
}
return html + '</div>';
}
// ── Compare renderer ──────────────────────────────────────────────────────────
function renderCompare(data) {
let html = `<div class="di-compare-grid">
<div class="di-compare-col">
<div class="di-compare-col-header">📄 ${data.file_a || 'Document A'}</div>
<div class="di-compare-col-body">${escHtml(data.summary_a || '')}</div>
</div>
<div class="di-compare-col">
<div class="di-compare-col-header">📄 ${data.file_b || 'Document B'}</div>
<div class="di-compare-col-body">${escHtml(data.summary_b || '')}</div>
</div>
</div>
<div style="margin-top:20px">
<div style="font-size:12px;font-weight:700;color:var(--lt);text-transform:uppercase;letter-spacing:.5px;margin-bottom:12px">Key Differences</div>
<table class="di-compare-diff-table"><tr>
<th style="width:140px">Area</th><th>Document A</th><th>Document B</th><th style="width:90px">Change</th>
</tr>`;
data.diffs.forEach(d => {
html += `<tr class="diff-${d.type}">
<td style="font-weight:600">${d.area}</td>
<td>${escHtml(d.a)}</td>
<td>${escHtml(d.b)}</td>
<td><span class="diff-badge">${d.type}</span></td>
</tr>`;
});
return html + '</table></div>';
}
// ── Export helpers ────────────────────────────────────────────────────────────
function exportClausesText(data) {
return data.clauses.map(c =>
`## ${c.type} [${c.risk} risk]\n${c.analysis}\n${c.excerpt ? `> "${c.excerpt}"` : ''}`
).join('\n\n');
}
function exportTenderText(data) {
const icons = {pass:'✅', fail:'❌', missing:'⚠️'};
return data.items.map(i => `${icons[i.status]} ${i.requirement}${i.note ? '\n ' + i.note : ''}`).join('\n');
}
function exportBriefText(data) {
let t = `# Executive Brief\n\n## Summary\n${data.summary}\n\n`;
if (data.risks) t += `## Risks\n` + data.risks.map(r => `- [${r.level}] ${r.text}`).join('\n') + '\n\n';
if (data.recommendations) t += `## Recommendations\n` + data.recommendations.map((r,i) => `${i+1}. ${r}`).join('\n');
return t;
}
function exportCompareText(data) {
let t = `# Document Comparison\n\n`;
t += `## ${data.file_a || 'Document A'}\n${data.summary_a || ''}\n\n`;
t += `## ${data.file_b || 'Document B'}\n${data.summary_b || ''}\n\n`;
t += `## Key Differences\n`;
data.diffs.forEach(d => t += `| ${d.area} | ${d.a} | ${d.b} | ${d.type} |\n`);
return t;
}
function copyResult() {
navigator.clipboard.writeText(currentResultText).then(() => {
const btn = event.target;
const orig = btn.textContent;
btn.textContent = '✓ Copied!';
setTimeout(() => btn.textContent = orig, 1500);
});
}
function downloadMd() {
const fname = (document.getElementById('result-title').textContent || 'result').replace(/[^a-z0-9]/gi,'_').toLowerCase() + '.md';
const blob = new Blob([currentResultText], {type:'text/markdown'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fname;
a.click();
}
function printPdf() {
window.print();
}
function hideResult() {
document.getElementById('result-card').classList.remove('show');
document.getElementById('progress-card').classList.remove('show');
}
function newJob() {
clearFile(); clearFileB(); hideResult();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// ── Mock data generators ──────────────────────────────────────────────────────
function generateMockResult(mode, filename) {
if (mode === 'clauses') return {
clauses: [
{ type:'Limitation of Liability', risk:'high', page:8, analysis:'Vendor liability is capped at 3 months of fees paid. This is unusually low for a contract of this size and provides minimal recourse for significant losses.', excerpt:"In no event shall Vendor's total cumulative liability exceed the fees paid in the three (3) months preceding the claim." },
{ type:'Payment Terms', risk:'medium', page:4, analysis:'Payment is due within 15 days of invoice. Late payments attract 2% monthly interest. Ensure your AP process can meet this timeline.', excerpt:'Invoices are payable within fifteen (15) calendar days. Overdue amounts accrue interest at 2% per month.' },
{ type:'Termination for Convenience', risk:'medium', page:12, analysis:'Either party may terminate with 90 days notice. However, early termination by the Client incurs a 25% penalty on remaining contract value.', excerpt:'Client may terminate for convenience upon 90 days written notice, subject to an early termination fee of 25% of the remaining contract value.' },
{ type:'Intellectual Property', risk:'low', page:6, analysis:'Work product created specifically for the Client is assigned to the Client upon full payment. Pre-existing IP remains with the Vendor.', excerpt:'Upon receipt of full payment, Vendor assigns all rights in Work Product created specifically for Client under this agreement.' },
{ type:'Confidentiality', risk:'low', page:9, analysis:'Standard mutual NDA covering a 3-year post-contract period. Definition of confidential information is broad and appropriate.', excerpt:'Both parties agree to maintain confidentiality of Confidential Information for a period of three (3) years after termination.' },
{ type:'Governing Law', risk:'low', page:15, analysis:'Agreement is governed by the laws of England and Wales with disputes subject to London arbitration. Standard and enforceable.', excerpt:'This Agreement shall be governed by and construed in accordance with the laws of England and Wales.' }
]
};
if (mode === 'tender') return {
items: [
{ requirement:'Company registration certificate', status:'pass', note:'Certificate No. CIN U72900MH2018PTC123456 provided' },
{ requirement:'Audited financial statements (last 3 years)', status:'pass', note:'FY2022, FY2023, FY2024 statements included' },
{ requirement:'Minimum annual turnover ₹5 Crore', status:'pass', note:'FY2024 turnover: ₹8.2 Crore — compliant' },
{ requirement:'ISO 27001 certification', status:'missing', note:'Certificate not included. Must be submitted before bid evaluation' },
{ requirement:'Technical team CVs (minimum 5 qualified engineers)', status:'pass', note:'7 CVs submitted with relevant qualifications' },
{ requirement:'Experience letter from 2 similar projects', status:'pass', note:'Letters from Reliance Industries and TCS attached' },
{ requirement:'EMD (Earnest Money Deposit) — ₹2,50,000', status:'pass', note:'Demand draft No. 482910 from SBI, dated 15 Jun 2026' },
{ requirement:'Signed declaration of no conflict of interest', status:'fail', note:'Declaration form is unsigned — requires authorised signatory' },
{ requirement:'Technical proposal (max 40 pages)', status:'pass', note:'38-page proposal submitted in prescribed format' },
{ requirement:'Commercial bid in sealed envelope', status:'missing', note:'Commercial bid envelope not found in submission package' }
]
};
if (mode === 'brief') return {
doc_type: 'Master Services Agreement',
date: '12 June 2026',
parties: 'Cezen Technologies Pvt. Ltd. &amp; Infra Solutions Ltd.',
value: '₹1.4 Crore / year',
summary: 'This Master Services Agreement establishes a three-year engagement for AI platform deployment and managed services. Cezen Technologies will deliver an on-premises AI infrastructure including model hosting, knowledge base management, and 24×7 L2/L3 support. The contract favours the vendor on liability caps and early termination penalties. The client has limited audit rights and no SLA credits above ₹5 lakh per year.',
key_facts: [
{ label:'Contract Duration', value:'36 months (Jun 2026 May 2029)' },
{ label:'Total Contract Value', value:'₹4.2 Crore' },
{ label:'Payment Schedule', value:'Quarterly in advance' },
{ label:'SLA Uptime Commitment', value:'99.5% monthly' },
{ label:'Support Response SLA', value:'P1: 2 hrs, P2: 8 hrs' },
{ label:'Auto-Renewal', value:'Yes — 30-day opt-out window' }
],
risks: [
{ level:'high', text:'Liability cap (3 months fees) is disproportionately low for a ₹4.2 Cr contract. Negotiate to at least 12 months.' },
{ level:'high', text:'25% early termination penalty locks in commitment. Seek a mutual termination clause without penalty after Year 1.' },
{ level:'medium', text:'Auto-renewal with only 30-day opt-out window. Flag for calendar reminder 6 months before contract end.' },
{ level:'medium', text:'SLA credits capped at ₹5 lakh/year regardless of actual impact. Insufficient for business-critical deployments.' },
{ level:'low', text:'Governing law is England & Wales. Consider adding an Indian arbitration clause for practical dispute resolution.' }
],
recommendations: [
'Negotiate liability cap to minimum 12 months of fees (₹1.4 Cr) before signing.',
'Insert a mutual termination clause with no penalty after the first year of service.',
'Add SLA credit mechanics tied to actual business impact, uncapped up to 20% of annual fees.',
'Request quarterly business reviews and formal audit rights with 10-day notice.',
'Set a calendar reminder for contract renewal opt-out — deadline is 30 April 2029.'
]
};
if (mode === 'compare') return {
file_a: selectedFile ? selectedFile.name : 'Document A',
file_b: selectedFileB ? selectedFileB.name : 'Document B',
summary_a: 'Version 2.1 (March 2025) — Original contract with standard vendor-favourable terms. Payment net-30, liability capped at 3 months fees, 90-day termination notice with 25% early exit penalty. Governing law: England & Wales.',
summary_b: 'Version 2.3 (June 2026) — Revised contract following negotiation. Payment net-15 (tightened), liability cap increased to 6 months, termination notice reduced to 60 days, early exit penalty removed after Year 1. SLA credits added.',
diffs: [
{ area:'Payment Terms', a:'Net 30 days', b:'Net 15 days', type:'changed' },
{ area:'Liability Cap', a:'3 months fees', b:'6 months fees', type:'changed' },
{ area:'Termination Notice', a:'90 days', b:'60 days', type:'changed' },
{ area:'Early Exit Penalty', a:'25% of remaining value', b:'Waived after Year 1', type:'changed' },
{ area:'SLA Credits', a:'Not included', b:'Up to 20% annual fees', type:'added' },
{ area:'Audit Rights', a:'Not included', b:'Annual audit with notice', type:'added' },
{ area:'Auto-Renewal', a:'30-day opt-out window', b:'90-day opt-out window', type:'changed' },
{ area:'Governing Law', a:'England & Wales', b:'England & Wales (unchanged)', type:'changed' }
]
};
// Default text modes
const textMock = {
summarise: `This document outlines a comprehensive services agreement between two parties for AI platform deployment. The key provisions cover a 36-month engagement, quarterly payment terms, and specific deliverables around model hosting and knowledge management. The vendor provides 99.5% uptime SLA with defined support response times. Notable clauses include an auto-renewal provision and a liability cap set at three months of fees paid.`,
keypoints: `• Contract Duration: 36 months starting June 2026\n• Total Value: ₹4.2 Crore payable quarterly in advance\n• SLA Commitment: 99.5% monthly uptime\n• Support: P1 response within 2 hours, P2 within 8 hours\n• Liability Cap: 3 months of fees paid (~₹35 lakhs)\n• Auto-renewal with 30-day opt-out window\n• Governing Law: England and Wales`,
tables: `Table 1: Service Level Definitions\n────────────────────────────────────\nPriority | Description | Response | Resolution\nP1 | Total service outage | 2 hrs | 4 hrs\nP2 | Major degradation | 8 hrs | 24 hrs\nP3 | Minor issues | 24 hrs | 72 hrs\n\nTable 2: Payment Schedule\n──────────────────────────\nQuarter | Amount (₹) | Due Date\nQ1 | 35,00,000 | 1 Jul 2026\nQ2 | 35,00,000 | 1 Oct 2026\nQ3 | 35,00,000 | 1 Jan 2027\nQ4 | 35,00,000 | 1 Apr 2027`,
questions: `Q: What is the contract duration?\nA: 36 months, from June 2026 to May 2029.\n\nQ: What is the liability cap?\nA: Vendor liability is capped at 3 months of fees paid, approximately ₹35 lakhs.\n\nQ: What happens if we want to exit early?\nA: A 25% penalty on remaining contract value applies if the client terminates for convenience.\n\nQ: Is there an SLA?\nA: Yes — 99.5% monthly uptime with P1 response within 2 hours and P2 within 8 hours.`,
translate: `[Translated from Hindi/Regional Language]\n\nThis agreement is entered into between Cezen Technologies Private Limited (hereinafter "Service Provider") and Infra Solutions Limited (hereinafter "Client") on the twelfth day of June, Two Thousand and Twenty-Six.\n\nThe Service Provider agrees to provide AI platform services including model hosting, knowledge base management, user access management, and technical support as described in Schedule A attached hereto...`,
custom: `Analysis complete. The document has been processed according to your custom instructions. Key findings have been extracted and formatted below.\n\n[Mock output — connect to a running Ollama or OpenAI-compatible API to get real AI-powered results for your specific prompt.]`
};
return textMock[mode] || textMock.summarise;
}
// ── History ───────────────────────────────────────────────────────────────────
const HIST_KEY = 'cezen_docjobs';
function saveLocalHistory(filename, mode) {
const hist = JSON.parse(localStorage.getItem(HIST_KEY) || '[]');
hist.unshift({ id: Date.now(), orig_name: filename, mode, status:'done', created_at: new Date().toISOString() });
localStorage.setItem(HIST_KEY, JSON.stringify(hist.slice(0, 20)));
}
function renderLocalHistory() {
const hist = JSON.parse(localStorage.getItem(HIST_KEY) || '[]');
const wrap = document.getElementById('history-wrap');
if (!hist.length) { wrap.innerHTML = '<div class="di-empty">No documents processed yet</div>'; return; }
let html = `<div class="di-hist-table-wrap"><table class="di-hist-table">
<tr><th>File</th><th>Mode</th><th>Status</th><th>When</th></tr>`;
hist.forEach(j => {
const icon = MODE_ICONS[j.mode] || '📄';
const label = MODE_LABELS[j.mode] || j.mode;
const when = j.created_at ? new Date(j.created_at).toLocaleString() : '—';
html += `<tr>
<td><span style="margin-right:6px">${icon}</span>${escHtml(j.orig_name)}</td>
<td>${label}</td>
<td><span class="di-status-badge done">done</span></td>
<td style="color:var(--lt);font-size:12px">${when}</td>
</tr>`;
});
html += '</table></div>';
wrap.innerHTML = html;
}
async function loadHistory() {
try {
const res = await fetch(`${_API}/docjobs`, { credentials: 'include' });
const jobs = await res.json();
renderApiHistory(jobs);
} catch(e) { renderLocalHistory(); }
}
function renderApiHistory(jobs) {
const wrap = document.getElementById('history-wrap');
if (!jobs.length) { wrap.innerHTML = '<div class="di-empty">No documents processed yet</div>'; return; }
let html = `<div class="di-hist-table-wrap"><table class="di-hist-table">
<tr><th>File</th><th>Mode</th><th>Model</th><th>Status</th><th>When</th><th></th></tr>`;
jobs.forEach(j => {
const statusClass = j.status === 'done' ? 'done' : j.status === 'error' ? 'error'
: j.status === 'processing' ? 'processing' : 'pending';
const icon = MODE_ICONS[j.mode] || '📄';
const label = MODE_LABELS[j.mode] || j.mode;
const when = j.created_at ? new Date(j.created_at).toLocaleString() : '—';
html += `<tr>
<td><span style="margin-right:6px">${icon}</span>${escHtml(j.orig_name)}</td>
<td>${label}</td>
<td style="color:var(--lt)">${escHtml(j.model || '—')}</td>
<td><span class="di-status-badge ${statusClass}">${j.status}</span></td>
<td style="color:var(--lt);font-size:12px">${when}</td>
<td><button class="btn btn-ghost" style="padding:4px 10px;font-size:12px"
onclick="event.stopPropagation();deleteJob(${j.id})">✕</button></td>
</tr>`;
});
html += '</table></div>';
wrap.innerHTML = html;
}
async function deleteJob(id) {
if (!confirm('Delete this document job?')) return;
await fetch(`${_API}/docjobs/${id}`, { method: 'DELETE', credentials: 'include' });
loadHistory();
}
function escHtml(s) {
return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
// ── Load models ───────────────────────────────────────────────────────────────
async function loadModels() {
try {
const res = await fetch(`${_API}/models/list`, { credentials: 'include' });
const data = await res.json();
const sel = document.getElementById('model-select');
const models = data.models || [];
if (!models.length) throw new Error();
sel.innerHTML = models.map(m =>
`<option value="${escHtml(m.name)}">${escHtml(m.name)}</option>`
).join('');
} catch(e) {
document.getElementById('model-select').innerHTML =
'<option value="llama3">llama3 (default)</option>';
}
}
loadModels();
loadHistory();
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
</body>
</html>