Spaces:
Running
Running
Commit Β·
5fafc19
1
Parent(s): c0a4e5a
Update 2026-03-22 16:53:56
Browse files- .claude/settings.local.json +2 -1
- agents/planner.py +1 -1
- templates/index.html +454 -180
- write_html.py +528 -0
.claude/settings.local.json
CHANGED
|
@@ -2,7 +2,8 @@
|
|
| 2 |
"permissions": {
|
| 3 |
"allow": [
|
| 4 |
"Bash(wc -l /e/HuggingFace/docmind/*.py /e/HuggingFace/docmind/**/*.py)",
|
| 5 |
-
"Bash(python3:*)"
|
|
|
|
| 6 |
]
|
| 7 |
}
|
| 8 |
}
|
|
|
|
| 2 |
"permissions": {
|
| 3 |
"allow": [
|
| 4 |
"Bash(wc -l /e/HuggingFace/docmind/*.py /e/HuggingFace/docmind/**/*.py)",
|
| 5 |
+
"Bash(python3:*)",
|
| 6 |
+
"Bash(python write_html.py)"
|
| 7 |
]
|
| 8 |
}
|
| 9 |
}
|
agents/planner.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from agents.llm_factory import call_llm
|
| 2 |
|
| 3 |
_TEMPLATE = """You are a research planning agent. Given the user's question, produce a brief research plan.
|
| 4 |
-
|
| 5 |
Output your plan in 2-3 concise sentences. Start with "PLAN:".
|
| 6 |
|
| 7 |
Question: {question}
|
|
|
|
| 1 |
from agents.llm_factory import call_llm
|
| 2 |
|
| 3 |
_TEMPLATE = """You are a research planning agent. Given the user's question, produce a brief research plan.
|
| 4 |
+
Describe which aspects of the uploaded document are most relevant to answer the question.
|
| 5 |
Output your plan in 2-3 concise sentences. Start with "PLAN:".
|
| 6 |
|
| 7 |
Question: {question}
|
templates/index.html
CHANGED
|
@@ -1,246 +1,520 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
</head>
|
| 67 |
<body>
|
|
|
|
|
|
|
| 68 |
<header>
|
| 69 |
-
<
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
</header>
|
|
|
|
| 73 |
<main>
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
<div class="
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
</div>
|
| 81 |
-
<div
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
</div>
|
| 88 |
-
<input type="file" id="fi" accept=".pdf" style="display:none" onchange="fc(event)"/>
|
| 89 |
-
<div id="pdf-msg"></div>
|
| 90 |
</div>
|
| 91 |
-
<div
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
</div>
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
</div>
|
|
|
|
| 101 |
</div>
|
| 102 |
-
|
| 103 |
-
<
|
| 104 |
-
|
| 105 |
-
<div class="
|
| 106 |
-
<
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
<div
|
| 110 |
-
|
| 111 |
-
<span id="
|
|
|
|
| 112 |
</div>
|
| 113 |
</div>
|
| 114 |
-
<div
|
| 115 |
-
<div id="answer-wrap">
|
| 116 |
-
<div class="ans-label">Answer</div>
|
| 117 |
<div id="answer-text"></div>
|
| 118 |
-
<div id="verdict"></div>
|
| 119 |
</div>
|
| 120 |
</div>
|
|
|
|
| 121 |
</main>
|
|
|
|
| 122 |
<script>
|
| 123 |
let pollTimer=null,seen=0;
|
| 124 |
-
const esc=s=>String(s).replace(/&/g,
|
| 125 |
|
|
|
|
|
|
|
|
|
|
| 126 |
function switchTab(btn,name){
|
| 127 |
-
document.querySelectorAll(
|
| 128 |
-
btn.classList.add(
|
| 129 |
-
document.getElementById(
|
| 130 |
-
document.getElementById(
|
| 131 |
}
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
| 134 |
function fc(e){if(e.target.files[0])up(e.target.files[0]);}
|
| 135 |
|
|
|
|
| 136 |
async function up(file){
|
| 137 |
-
if(!file.name.toLowerCase().endsWith(
|
| 138 |
-
|
| 139 |
-
const fd=new FormData();fd.append(
|
| 140 |
try{
|
| 141 |
-
const r=await fetch(
|
| 142 |
const d=await r.json();
|
| 143 |
-
if(d.error){
|
| 144 |
-
|
| 145 |
-
|
|
|
|
| 146 |
}
|
| 147 |
|
|
|
|
| 148 |
async function fetchURL(){
|
| 149 |
-
const url=document.getElementById(
|
| 150 |
-
if(!url){
|
| 151 |
-
document.getElementById(
|
| 152 |
-
|
|
|
|
| 153 |
try{
|
| 154 |
-
const r=await fetch(
|
| 155 |
const d=await r.json();
|
| 156 |
-
if(d.error){
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
|
|
|
| 160 |
}
|
| 161 |
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
document.getElementById(
|
| 165 |
-
document.getElementById(
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
p.
|
|
|
|
|
|
|
| 169 |
}
|
| 170 |
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
async function ask(){
|
| 174 |
-
const q=document.getElementById(
|
| 175 |
-
document.getElementById(
|
| 176 |
-
if(!q){document.getElementById(
|
| 177 |
-
|
| 178 |
-
document.getElementById(
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
seen=0;clearInterval(pollTimer);
|
|
|
|
| 182 |
try{
|
| 183 |
-
const r=await fetch(
|
| 184 |
const d=await r.json();
|
| 185 |
-
if(d.error){
|
| 186 |
-
traceErr(d.error);
|
| 187 |
-
document.getElementById('ask-btn').disabled=false;
|
| 188 |
-
return;
|
| 189 |
-
}
|
| 190 |
pollTimer=setInterval(()=>poll(d.query_id),1500);
|
| 191 |
-
}catch(e){
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
| 195 |
}
|
| 196 |
|
|
|
|
| 197 |
async function poll(qid){
|
| 198 |
try{
|
| 199 |
-
const r=await fetch(
|
| 200 |
-
if(!r.ok){traceErr(
|
| 201 |
const d=await r.json();
|
| 202 |
-
|
| 203 |
-
if([
|
| 204 |
clearInterval(pollTimer);
|
| 205 |
-
|
| 206 |
-
if(d.status===
|
| 207 |
-
else if(d.status===
|
| 208 |
}
|
| 209 |
-
}catch(e){traceErr(
|
| 210 |
-
}
|
| 211 |
-
|
| 212 |
-
function traceErr(msg){
|
| 213 |
-
const log=document.getElementById('trace-log');
|
| 214 |
-
log.innerHTML+='<div class="t-step"><span class="t-badge" style="background:rgba(239,68,68,.15);color:var(--red)">error</span><span class="t-msg" style="color:var(--red)">'+esc(msg)+'</span></div>';
|
| 215 |
-
log.scrollTop=log.scrollHeight;
|
| 216 |
}
|
| 217 |
|
| 218 |
-
|
|
|
|
| 219 |
if(!steps.length)return;
|
| 220 |
-
const log=document.getElementById(
|
| 221 |
-
if(seen===0)log.innerHTML=
|
| 222 |
for(let i=seen;i<steps.length;i++){
|
| 223 |
const s=steps[i];
|
| 224 |
-
const
|
| 225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
}
|
| 227 |
-
seen=steps.length;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
}
|
| 229 |
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
document.getElementById(
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
|
|
|
|
|
|
| 237 |
}
|
| 238 |
|
| 239 |
-
|
|
|
|
| 240 |
const el=document.getElementById(id);
|
| 241 |
-
if(type===
|
| 242 |
-
else if(type===
|
| 243 |
-
else el.innerHTML=
|
| 244 |
}
|
| 245 |
</script>
|
| 246 |
</body>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
+
<meta charset="UTF-8"/>
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
| 6 |
+
<title>DocMind β AI Document Research</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root{
|
| 9 |
+
--bg:#0d0f1a;--surface:#13161f;--card:#181c27;--card2:#1e2230;
|
| 10 |
+
--border:#252836;--border2:#2e3244;
|
| 11 |
+
--text:#e8eaf2;--sub:#b0b8cc;--muted:#7880a0;
|
| 12 |
+
--accent:#5b8ff9;--accent2:#3a6ee8;
|
| 13 |
+
--green:#22d47a;--red:#f05c5c;--teal:#29c6d4;--gold:#f5a623;--purple:#a78bfa;
|
| 14 |
+
}
|
| 15 |
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
| 16 |
+
html,body{height:100%}
|
| 17 |
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
| 18 |
+
background:var(--bg);color:var(--text);font-size:14px;line-height:1.5}
|
| 19 |
+
|
| 20 |
+
/* ββ ANIMATIONS ββββββββββββββββββββββββββββββββββ */
|
| 21 |
+
@keyframes spin{to{transform:rotate(360deg)}}
|
| 22 |
+
@keyframes fadeUp{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
| 23 |
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.45}}
|
| 24 |
+
@keyframes shimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}
|
| 25 |
+
|
| 26 |
+
/* ββ HEADER ββββββββββββββββββββββββββββββββββββββ */
|
| 27 |
+
header{
|
| 28 |
+
background:var(--surface);
|
| 29 |
+
border-bottom:1px solid var(--border);
|
| 30 |
+
padding:0 28px;height:56px;
|
| 31 |
+
display:flex;align-items:center;gap:14px;
|
| 32 |
+
position:sticky;top:0;z-index:100;
|
| 33 |
+
}
|
| 34 |
+
.logo{display:flex;align-items:center;gap:9px;text-decoration:none}
|
| 35 |
+
.logo-icon{width:30px;height:30px;background:linear-gradient(135deg,var(--accent),var(--purple));
|
| 36 |
+
border-radius:8px;display:flex;align-items:center;justify-content:center;
|
| 37 |
+
font-size:15px;flex-shrink:0}
|
| 38 |
+
.logo-text{font-size:1rem;font-weight:800;color:var(--text);letter-spacing:-.3px}
|
| 39 |
+
.logo-text span{color:var(--accent)}
|
| 40 |
+
.logo-sub{font-size:.68rem;color:var(--muted);margin-left:2px;font-weight:400;
|
| 41 |
+
display:none} /* shown on wider screens */
|
| 42 |
+
.hdr-badges{display:flex;gap:8px;align-items:center;margin-left:4px}
|
| 43 |
+
.badge{font-size:.65rem;font-weight:700;padding:3px 9px;border-radius:20px;letter-spacing:.02em}
|
| 44 |
+
.badge-model{background:rgba(91,143,249,.12);border:1px solid rgba(91,143,249,.25);color:var(--accent)}
|
| 45 |
+
.badge-src{background:rgba(113,128,160,.1);border:1px solid var(--border);color:var(--muted);
|
| 46 |
+
max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
| 47 |
+
.badge-src.loaded{background:rgba(34,212,122,.1);border-color:rgba(34,212,122,.3);color:var(--green)}
|
| 48 |
+
#hdr-src{margin-left:auto}
|
| 49 |
+
|
| 50 |
+
/* ββ LAYOUT βββββββββββββββββββββββββββββββββββββββ */
|
| 51 |
+
main{display:grid;grid-template-columns:320px 1fr;height:calc(100vh - 56px);overflow:hidden}
|
| 52 |
+
.panel{padding:24px 22px;overflow-y:auto;height:100%}
|
| 53 |
+
.panel-left{border-right:1px solid var(--border);background:var(--surface)}
|
| 54 |
+
.panel-right{background:var(--bg)}
|
| 55 |
+
|
| 56 |
+
/* ββ SECTION HEADERS ββββββββββββββββββββββββββββββ */
|
| 57 |
+
.sec-head{display:flex;align-items:center;gap:8px;margin-bottom:16px}
|
| 58 |
+
.sec-icon{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;
|
| 59 |
+
justify-content:center;font-size:13px;flex-shrink:0}
|
| 60 |
+
.sec-icon-blue{background:rgba(91,143,249,.15)}
|
| 61 |
+
.sec-icon-purple{background:rgba(167,139,250,.15)}
|
| 62 |
+
.sec-title{font-size:.75rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--sub)}
|
| 63 |
+
|
| 64 |
+
/* ββ TABS ββββββββββββββββββββββββββββββββββββββββββ */
|
| 65 |
+
.tabs{display:flex;gap:2px;background:rgba(255,255,255,.03);border-radius:9px;
|
| 66 |
+
padding:3px;margin-bottom:18px;border:1px solid var(--border)}
|
| 67 |
+
.tab-btn{flex:1;background:none;border:none;color:var(--muted);font-size:.78rem;
|
| 68 |
+
font-weight:600;padding:7px 10px;border-radius:7px;cursor:pointer;
|
| 69 |
+
transition:all .18s;font-family:inherit;display:flex;align-items:center;
|
| 70 |
+
justify-content:center;gap:5px}
|
| 71 |
+
.tab-btn.active{background:var(--card2);color:var(--text);
|
| 72 |
+
box-shadow:0 1px 6px rgba(0,0,0,.35)}
|
| 73 |
+
|
| 74 |
+
/* ββ DROPZONE ββββββββββββββββββββββββββββββββββββββ */
|
| 75 |
+
.dropzone{
|
| 76 |
+
border:2px dashed var(--border2);border-radius:12px;padding:28px 16px;
|
| 77 |
+
text-align:center;cursor:pointer;transition:all .22s;position:relative;overflow:hidden;
|
| 78 |
+
}
|
| 79 |
+
.dropzone::before{
|
| 80 |
+
content:"";position:absolute;inset:0;
|
| 81 |
+
background:linear-gradient(90deg,transparent 0%,rgba(91,143,249,.04) 50%,transparent 100%);
|
| 82 |
+
background-size:200% 100%;opacity:0;transition:opacity .3s;
|
| 83 |
+
}
|
| 84 |
+
.dropzone:hover{border-color:var(--accent);background:rgba(91,143,249,.04)}
|
| 85 |
+
.dropzone:hover::before{opacity:1;animation:shimmer 2s infinite}
|
| 86 |
+
.dropzone.drag-over{border-color:var(--accent);background:rgba(91,143,249,.08);transform:scale(1.01)}
|
| 87 |
+
.dz-icon{font-size:2.2rem;margin-bottom:10px;display:block}
|
| 88 |
+
.dz-label{font-size:.84rem;color:var(--sub);line-height:1.5}
|
| 89 |
+
.dz-label strong{color:var(--accent)}
|
| 90 |
+
.dz-hint{font-size:.72rem;color:var(--muted);margin-top:5px}
|
| 91 |
+
|
| 92 |
+
/* ββ URL INPUT ββββββββββββββββββββββββββββββββββββββ */
|
| 93 |
+
.url-row{display:flex;gap:8px;margin-bottom:10px}
|
| 94 |
+
.url-row input{flex:1;background:var(--card);border:1px solid var(--border2);
|
| 95 |
+
border-radius:8px;padding:9px 12px;color:var(--text);font-size:.84rem;
|
| 96 |
+
font-family:inherit;outline:none;transition:border-color .18s}
|
| 97 |
+
.url-row input:focus{border-color:var(--accent)}
|
| 98 |
+
|
| 99 |
+
/* ββ SOURCE LOADED CARD ββββββββββββββββββββββββββββ */
|
| 100 |
+
#source-card{
|
| 101 |
+
display:none;margin-top:16px;background:var(--card);
|
| 102 |
+
border:1px solid rgba(34,212,122,.2);border-radius:10px;padding:13px 14px;
|
| 103 |
+
animation:fadeUp .25s ease;
|
| 104 |
+
}
|
| 105 |
+
.sc-row{display:flex;align-items:flex-start;gap:10px}
|
| 106 |
+
.sc-icon{width:34px;height:34px;background:rgba(34,212,122,.12);border-radius:8px;
|
| 107 |
+
flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:16px}
|
| 108 |
+
.sc-info{flex:1;min-width:0}
|
| 109 |
+
.sc-name{font-size:.82rem;font-weight:600;color:var(--text);
|
| 110 |
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:3px}
|
| 111 |
+
.sc-meta{font-size:.72rem;color:var(--teal)}
|
| 112 |
+
.sc-ready{display:inline-flex;align-items:center;gap:5px;margin-top:8px;
|
| 113 |
+
font-size:.7rem;font-weight:700;color:var(--green);
|
| 114 |
+
background:rgba(34,212,122,.08);border:1px solid rgba(34,212,122,.18);
|
| 115 |
+
border-radius:5px;padding:3px 8px}
|
| 116 |
+
|
| 117 |
+
/* ββ MESSAGES ββββββββββββββββββββββββββββββββββββββ */
|
| 118 |
+
.msg{border-radius:8px;padding:10px 13px;font-size:.79rem;
|
| 119 |
+
margin-top:10px;line-height:1.55;animation:fadeUp .2s ease}
|
| 120 |
+
.msg-ok{background:rgba(34,212,122,.07);border:1px solid rgba(34,212,122,.2);color:var(--green)}
|
| 121 |
+
.msg-err{background:rgba(240,92,92,.07);border:1px solid rgba(240,92,92,.2);color:var(--red)}
|
| 122 |
+
.msg-info{color:var(--muted);font-size:.76rem;margin-top:8px}
|
| 123 |
+
|
| 124 |
+
/* ββ BUTTONS βββββββββββββββββββββββββββββββββββββββ */
|
| 125 |
+
.btn{display:inline-flex;align-items:center;gap:7px;padding:9px 20px;
|
| 126 |
+
border-radius:8px;border:none;font-size:.84rem;font-weight:600;
|
| 127 |
+
cursor:pointer;transition:all .18s;font-family:inherit}
|
| 128 |
+
.btn-primary{background:linear-gradient(135deg,var(--accent),var(--accent2));
|
| 129 |
+
color:#fff;box-shadow:0 2px 8px rgba(91,143,249,.25)}
|
| 130 |
+
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 4px 14px rgba(91,143,249,.35)}
|
| 131 |
+
.btn-primary:active{transform:none}
|
| 132 |
+
.btn-primary:disabled{opacity:.5;cursor:default;transform:none;box-shadow:none}
|
| 133 |
+
.btn-sm{padding:7px 14px;font-size:.78rem}
|
| 134 |
+
.btn-ghost{background:rgba(255,255,255,.06);border:1px solid var(--border2);
|
| 135 |
+
color:var(--sub);font-size:.74rem;padding:5px 12px;border-radius:6px}
|
| 136 |
+
.btn-ghost:hover{background:rgba(255,255,255,.1);color:var(--text)}
|
| 137 |
+
.spinner{width:13px;height:13px;border:2px solid rgba(255,255,255,.3);
|
| 138 |
+
border-top-color:#fff;border-radius:50%;animation:spin .65s linear infinite;flex-shrink:0}
|
| 139 |
+
|
| 140 |
+
/* ββ QUESTION AREA βββββββββββββββββββββββββββββββββ */
|
| 141 |
+
.q-card{background:var(--card);border:1px solid var(--border2);
|
| 142 |
+
border-radius:11px;padding:16px;margin-bottom:16px}
|
| 143 |
+
.q-label{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;
|
| 144 |
+
letter-spacing:.06em;margin-bottom:8px}
|
| 145 |
+
textarea{width:100%;background:transparent;border:none;color:var(--text);
|
| 146 |
+
font-size:.9rem;font-family:inherit;outline:none;resize:none;
|
| 147 |
+
line-height:1.6;min-height:72px}
|
| 148 |
+
textarea::placeholder{color:var(--muted)}
|
| 149 |
+
.q-footer{display:flex;align-items:center;gap:10px;padding-top:12px;
|
| 150 |
+
border-top:1px solid var(--border)}
|
| 151 |
+
#q-err{font-size:.77rem;color:var(--red)}
|
| 152 |
+
|
| 153 |
+
/* ββ PIPELINE ββββββββββββββββββββββββββββββββββββββ */
|
| 154 |
+
#pipeline{display:none;margin-bottom:14px;animation:fadeUp .2s ease}
|
| 155 |
+
.pipe-row{display:flex;align-items:center;gap:0;background:var(--card);
|
| 156 |
+
border:1px solid var(--border2);border-radius:10px;padding:10px 14px;
|
| 157 |
+
overflow-x:auto}
|
| 158 |
+
.pipe-step{display:flex;align-items:center;gap:5px;font-size:.72rem;font-weight:600;
|
| 159 |
+
color:var(--muted);white-space:nowrap;padding:4px 8px;border-radius:6px;
|
| 160 |
+
transition:all .2s}
|
| 161 |
+
.pipe-step.active{color:var(--accent);background:rgba(91,143,249,.12)}
|
| 162 |
+
.pipe-step.done{color:var(--green)}
|
| 163 |
+
.pipe-step .step-dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0}
|
| 164 |
+
.pipe-step.active .step-dot{animation:pulse .9s ease infinite}
|
| 165 |
+
.pipe-arrow{color:var(--border2);font-size:.8rem;padding:0 2px;flex-shrink:0}
|
| 166 |
+
|
| 167 |
+
/* ββ TRACE LOG βββββββββββββββββββββββββββββββββββββ */
|
| 168 |
+
#trace-wrap{display:none;margin-bottom:16px;animation:fadeUp .2s ease}
|
| 169 |
+
.trace-hdr{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
| 170 |
+
.trace-title{font-size:.7rem;font-weight:700;text-transform:uppercase;
|
| 171 |
+
letter-spacing:.07em;color:var(--muted)}
|
| 172 |
+
.trace-box{background:var(--card);border:1px solid var(--border2);border-radius:10px;
|
| 173 |
+
padding:4px 0;max-height:220px;overflow-y:auto}
|
| 174 |
+
.t-step{display:flex;align-items:flex-start;gap:10px;padding:8px 14px;
|
| 175 |
+
border-bottom:1px solid var(--border);animation:fadeUp .18s ease;transition:background .15s}
|
| 176 |
+
.t-step:last-child{border-bottom:none}
|
| 177 |
+
.t-step:hover{background:rgba(255,255,255,.02)}
|
| 178 |
+
.t-icon{font-size:13px;flex-shrink:0;margin-top:1px;width:18px;text-align:center}
|
| 179 |
+
.t-agent{font-size:.64rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;
|
| 180 |
+
padding:2px 7px;border-radius:4px;flex-shrink:0;white-space:nowrap}
|
| 181 |
+
.a-planner {background:rgba(91,143,249,.14);color:var(--accent)}
|
| 182 |
+
.a-retriever{background:rgba(41,198,212,.14);color:var(--teal)}
|
| 183 |
+
.a-grader {background:rgba(245,166,35,.14);color:var(--gold)}
|
| 184 |
+
.a-generator{background:rgba(34,212,122,.14);color:var(--green)}
|
| 185 |
+
.a-critic {background:rgba(167,139,250,.14);color:var(--purple)}
|
| 186 |
+
.a-error {background:rgba(240,92,92,.14);color:var(--red)}
|
| 187 |
+
.t-msg{flex:1;font-size:.78rem;color:var(--sub);line-height:1.5}
|
| 188 |
+
.t-lat{font-size:.65rem;color:rgba(128,136,160,.5);flex-shrink:0;margin-top:2px}
|
| 189 |
+
|
| 190 |
+
/* ββ ANSWER CARD βββββββββββββββββββββββββββββββββββ */
|
| 191 |
+
#answer-wrap{display:none;background:var(--card);border:1px solid var(--border2);
|
| 192 |
+
border-radius:12px;overflow:hidden;animation:fadeUp .3s ease}
|
| 193 |
+
.ans-header{display:flex;align-items:center;justify-content:space-between;
|
| 194 |
+
padding:13px 18px 12px;border-bottom:1px solid var(--border);
|
| 195 |
+
background:rgba(255,255,255,.02)}
|
| 196 |
+
.ans-title{display:flex;align-items:center;gap:8px;font-size:.72rem;font-weight:800;
|
| 197 |
+
text-transform:uppercase;letter-spacing:.08em;color:var(--sub)}
|
| 198 |
+
.ans-title-icon{width:22px;height:22px;background:rgba(34,212,122,.15);border-radius:6px;
|
| 199 |
+
display:flex;align-items:center;justify-content:center;font-size:11px}
|
| 200 |
+
.ans-actions{display:flex;align-items:center;gap:8px}
|
| 201 |
+
#verdict-badge{font-size:.68rem;font-weight:700;padding:3px 10px;border-radius:20px}
|
| 202 |
+
.v-ok {background:rgba(34,212,122,.1);border:1px solid rgba(34,212,122,.25);color:var(--green)}
|
| 203 |
+
.v-warn{background:rgba(245,166,35,.1);border:1px solid rgba(245,166,35,.25);color:var(--gold)}
|
| 204 |
+
.ans-body{padding:18px}
|
| 205 |
+
#answer-text{font-size:.9rem;line-height:1.78;color:var(--text);
|
| 206 |
+
white-space:pre-wrap;word-break:break-word}
|
| 207 |
+
|
| 208 |
+
/* ββ RESPONSIVE ββββββββββββββββββββββββββββββββββββ */
|
| 209 |
+
@media(min-width:1100px){.logo-sub{display:block}}
|
| 210 |
+
@media(max-width:768px){
|
| 211 |
+
main{grid-template-columns:1fr;height:auto;overflow:visible}
|
| 212 |
+
.panel{height:auto}
|
| 213 |
+
.panel-left{border-right:none;border-bottom:1px solid var(--border)}
|
| 214 |
+
}
|
| 215 |
+
</style>
|
| 216 |
</head>
|
| 217 |
<body>
|
| 218 |
+
|
| 219 |
+
<!-- ββ HEADER ββ -->
|
| 220 |
<header>
|
| 221 |
+
<a class="logo" href="#">
|
| 222 |
+
<div class="logo-icon">🧠</div>
|
| 223 |
+
<span class="logo-text">Doc<span>Mind</span></span>
|
| 224 |
+
</a>
|
| 225 |
+
<span class="logo-sub">AI Document Research</span>
|
| 226 |
+
<div class="hdr-badges">
|
| 227 |
+
<span class="badge badge-model">Qwen 2.5 7B</span>
|
| 228 |
+
</div>
|
| 229 |
+
<span class="badge badge-src" id="hdr-src">No source loaded</span>
|
| 230 |
</header>
|
| 231 |
+
|
| 232 |
<main>
|
| 233 |
+
<!-- ββ LEFT PANEL: KNOWLEDGE BASE ββ -->
|
| 234 |
+
<div class="panel panel-left">
|
| 235 |
+
<div class="sec-head">
|
| 236 |
+
<div class="sec-icon sec-icon-blue">📚</div>
|
| 237 |
+
<span class="sec-title">Knowledge Base</span>
|
| 238 |
+
</div>
|
| 239 |
+
|
| 240 |
+
<div class="tabs">
|
| 241 |
+
<button class="tab-btn active" onclick="switchTab(this,'pdf')">📄 Upload PDF</button>
|
| 242 |
+
<button class="tab-btn" onclick="switchTab(this,'url')">🌐 Paste URL</button>
|
| 243 |
+
</div>
|
| 244 |
+
|
| 245 |
+
<!-- PDF TAB -->
|
| 246 |
+
<div id="tab-pdf">
|
| 247 |
+
<div class="dropzone" id="dz"
|
| 248 |
+
onclick="document.getElementById('fi').click()"
|
| 249 |
+
ondragover="dg(event,true)" ondragleave="dg(event,false)" ondrop="dp(event)">
|
| 250 |
+
<span class="dz-icon">📄</span>
|
| 251 |
+
<div class="dz-label"><strong>Click to browse</strong> or drag & drop</div>
|
| 252 |
+
<div class="dz-hint">PDF only · max 10 MB</div>
|
| 253 |
+
</div>
|
| 254 |
+
<input type="file" id="fi" accept=".pdf" style="display:none" onchange="fc(event)"/>
|
| 255 |
+
<div id="pdf-msg"></div>
|
| 256 |
+
</div>
|
| 257 |
+
|
| 258 |
+
<!-- URL TAB -->
|
| 259 |
+
<div id="tab-url" style="display:none">
|
| 260 |
+
<div class="url-row">
|
| 261 |
+
<input type="url" id="url-inp" placeholder="https://en.wikipedia.org/wiki/..."
|
| 262 |
+
onkeydown="if(event.key==='Enter')fetchURL()"/>
|
| 263 |
+
<button class="btn btn-primary btn-sm" id="url-btn" onclick="fetchURL()">Fetch</button>
|
| 264 |
</div>
|
| 265 |
+
<div class="msg-info" style="margin-bottom:4px">Wikipedia, government sites, and docs work best. Some sites block automated access.</div>
|
| 266 |
+
<div id="url-msg"></div>
|
| 267 |
+
</div>
|
| 268 |
+
|
| 269 |
+
<!-- SOURCE LOADED -->
|
| 270 |
+
<div id="source-card">
|
| 271 |
+
<div class="sc-row">
|
| 272 |
+
<div class="sc-icon" id="sc-icon">📄</div>
|
| 273 |
+
<div class="sc-info">
|
| 274 |
+
<div class="sc-name" id="source-name"></div>
|
| 275 |
+
<div class="sc-meta" id="source-chunks"></div>
|
| 276 |
</div>
|
|
|
|
|
|
|
| 277 |
</div>
|
| 278 |
+
<div class="sc-ready">✓ Ready for questions</div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
|
| 282 |
+
<!-- ββ RIGHT PANEL: RESEARCH ββ -->
|
| 283 |
+
<div class="panel panel-right">
|
| 284 |
+
<div class="sec-head">
|
| 285 |
+
<div class="sec-icon sec-icon-purple">🔍</div>
|
| 286 |
+
<span class="sec-title">Research Query</span>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<div class="q-card">
|
| 290 |
+
<div class="q-label">Your Question</div>
|
| 291 |
+
<textarea id="q-inp" rows="3"
|
| 292 |
+
placeholder="Ask anything about the loaded document or URL Press Enter to submit, Shift+Enter for new line"
|
| 293 |
+
onkeydown="qk(event)"></textarea>
|
| 294 |
+
<div class="q-footer">
|
| 295 |
+
<button class="btn btn-primary" id="ask-btn" onclick="ask()">⚡ Ask</button>
|
| 296 |
+
<span id="q-err"></span>
|
| 297 |
</div>
|
| 298 |
+
</div>
|
| 299 |
+
|
| 300 |
+
<!-- AGENT PIPELINE -->
|
| 301 |
+
<div id="pipeline">
|
| 302 |
+
<div class="pipe-row">
|
| 303 |
+
<div class="pipe-step" id="ps-planner"><span class="step-dot"></span>🧮 Planner</div>
|
| 304 |
+
<div class="pipe-arrow">→</div>
|
| 305 |
+
<div class="pipe-step" id="ps-retriever"><span class="step-dot"></span>🔍 Retriever</div>
|
| 306 |
+
<div class="pipe-arrow">→</div>
|
| 307 |
+
<div class="pipe-step" id="ps-grader"><span class="step-dot"></span>⚖ Grader</div>
|
| 308 |
+
<div class="pipe-arrow">→</div>
|
| 309 |
+
<div class="pipe-step" id="ps-generator"><span class="step-dot"></span>✍ Generator</div>
|
| 310 |
+
<div class="pipe-arrow">→</div>
|
| 311 |
+
<div class="pipe-step" id="ps-critic"><span class="step-dot"></span>🔬 Critic</div>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
|
| 315 |
+
<!-- TRACE LOG -->
|
| 316 |
+
<div id="trace-wrap">
|
| 317 |
+
<div class="trace-hdr">
|
| 318 |
+
<span class="trace-title">📰 Agent Trace</span>
|
| 319 |
</div>
|
| 320 |
+
<div class="trace-box" id="trace-log"></div>
|
| 321 |
</div>
|
| 322 |
+
|
| 323 |
+
<!-- ANSWER -->
|
| 324 |
+
<div id="answer-wrap">
|
| 325 |
+
<div class="ans-header">
|
| 326 |
+
<div class="ans-title">
|
| 327 |
+
<div class="ans-title-icon">💡</div>
|
| 328 |
+
Answer
|
| 329 |
+
</div>
|
| 330 |
+
<div class="ans-actions">
|
| 331 |
+
<span id="verdict-badge"></span>
|
| 332 |
+
<button class="btn btn-ghost" id="copy-btn" onclick="copyAns()">📋 Copy</button>
|
| 333 |
</div>
|
| 334 |
</div>
|
| 335 |
+
<div class="ans-body">
|
|
|
|
|
|
|
| 336 |
<div id="answer-text"></div>
|
|
|
|
| 337 |
</div>
|
| 338 |
</div>
|
| 339 |
+
</div>
|
| 340 |
</main>
|
| 341 |
+
|
| 342 |
<script>
|
| 343 |
let pollTimer=null,seen=0;
|
| 344 |
+
const esc=s=>String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
| 345 |
|
| 346 |
+
const AGENT_ICONS={planner:"🧮",retriever:"🔍",grader:"⚖",generator:"✍",critic:"🔬"};
|
| 347 |
+
|
| 348 |
+
/* ββ TABS ββ */
|
| 349 |
function switchTab(btn,name){
|
| 350 |
+
document.querySelectorAll(".tab-btn").forEach(b=>b.classList.remove("active"));
|
| 351 |
+
btn.classList.add("active");
|
| 352 |
+
document.getElementById("tab-pdf").style.display=name==="pdf"?"":"none";
|
| 353 |
+
document.getElementById("tab-url").style.display=name==="url"?"":"none";
|
| 354 |
}
|
| 355 |
+
|
| 356 |
+
/* ββ DRAG & DROP ββ */
|
| 357 |
+
function dg(e,over){e.preventDefault();document.getElementById("dz").classList[over?"add":"remove"]("drag-over");}
|
| 358 |
+
function dp(e){e.preventDefault();document.getElementById("dz").classList.remove("drag-over");const f=e.dataTransfer.files[0];if(f)up(f);}
|
| 359 |
function fc(e){if(e.target.files[0])up(e.target.files[0]);}
|
| 360 |
|
| 361 |
+
/* ββ UPLOAD PDF ββ */
|
| 362 |
async function up(file){
|
| 363 |
+
if(!file.name.toLowerCase().endsWith(".pdf")){showMsg("pdf-msg","error","Only PDF files are supported.");return;}
|
| 364 |
+
showMsg("pdf-msg","info","Uploading "+file.name+"...");
|
| 365 |
+
const fd=new FormData();fd.append("file",file);
|
| 366 |
try{
|
| 367 |
+
const r=await fetch("/api/upload",{method:"POST",body:fd});
|
| 368 |
const d=await r.json();
|
| 369 |
+
if(d.error){showMsg("pdf-msg","error",d.error);return;}
|
| 370 |
+
setSource(d.filename,d.chunks,"pdf");
|
| 371 |
+
showMsg("pdf-msg","ok","✓ "+d.chunks+" chunks indexed from "+d.filename);
|
| 372 |
+
}catch(e){showMsg("pdf-msg","error","Upload failed: "+e.message);}
|
| 373 |
}
|
| 374 |
|
| 375 |
+
/* ββ FETCH URL ββ */
|
| 376 |
async function fetchURL(){
|
| 377 |
+
const url=document.getElementById("url-inp").value.trim();
|
| 378 |
+
if(!url){showMsg("url-msg","error","Please enter a URL.");return;}
|
| 379 |
+
const btn=document.getElementById("url-btn");
|
| 380 |
+
btn.disabled=true;btn.textContent="Fetching...";
|
| 381 |
+
showMsg("url-msg","info","Fetching and indexing page...");
|
| 382 |
try{
|
| 383 |
+
const r=await fetch("/api/ingest_url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url})});
|
| 384 |
const d=await r.json();
|
| 385 |
+
if(d.error){showMsg("url-msg","error",d.error);return;}
|
| 386 |
+
setSource(d.url,d.chunks,"url");
|
| 387 |
+
showMsg("url-msg","ok","✓ "+d.chunks+" chunks indexed");
|
| 388 |
+
}catch(e){showMsg("url-msg","error","Failed: "+e.message);}
|
| 389 |
+
finally{btn.disabled=false;btn.textContent="Fetch";}
|
| 390 |
}
|
| 391 |
|
| 392 |
+
/* ββ SOURCE LOADED ββ */
|
| 393 |
+
function setSource(name,chunks,type){
|
| 394 |
+
document.getElementById("source-name").textContent=name;
|
| 395 |
+
document.getElementById("source-chunks").textContent=chunks+" chunks indexed";
|
| 396 |
+
document.getElementById("sc-icon").textContent=type==="pdf"?"📄":"🌐";
|
| 397 |
+
document.getElementById("source-card").style.display="block";
|
| 398 |
+
const p=document.getElementById("hdr-src");
|
| 399 |
+
p.textContent=name.length>30?name.slice(0,30)+"...":name;
|
| 400 |
+
p.classList.add("loaded");
|
| 401 |
}
|
| 402 |
|
| 403 |
+
/* ββ PIPELINE ββ */
|
| 404 |
+
const PIPE_AGENTS=["planner","retriever","grader","generator","critic"];
|
| 405 |
+
function resetPipeline(){PIPE_AGENTS.forEach(a=>{const el=document.getElementById("ps-"+a);if(el){el.classList.remove("active","done");}});}
|
| 406 |
+
function setAgent(name,done){
|
| 407 |
+
const el=document.getElementById("ps-"+name);
|
| 408 |
+
if(!el)return;
|
| 409 |
+
if(done){el.classList.remove("active");el.classList.add("done");}
|
| 410 |
+
else{el.classList.remove("done");el.classList.add("active");}
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
/* ββ ASK ββ */
|
| 414 |
+
function qk(e){if(e.key==="Enter"&&!e.shiftKey){e.preventDefault();ask();}}
|
| 415 |
|
| 416 |
async function ask(){
|
| 417 |
+
const q=document.getElementById("q-inp").value.trim();
|
| 418 |
+
document.getElementById("q-err").textContent="";
|
| 419 |
+
if(!q){document.getElementById("q-err").textContent="Please enter a question.";return;}
|
| 420 |
+
|
| 421 |
+
const btn=document.getElementById("ask-btn");
|
| 422 |
+
btn.disabled=true;
|
| 423 |
+
btn.innerHTML="<span class='spinner'></span> Thinking...";
|
| 424 |
+
|
| 425 |
+
document.getElementById("pipeline").style.display="block";
|
| 426 |
+
document.getElementById("trace-wrap").style.display="block";
|
| 427 |
+
document.getElementById("trace-log").innerHTML="<div class='t-step'><span class='t-msg' style='color:var(--muted);font-style:italic'>Initialising agents...</span></div>";
|
| 428 |
+
document.getElementById("answer-wrap").style.display="none";
|
| 429 |
+
resetPipeline();
|
| 430 |
seen=0;clearInterval(pollTimer);
|
| 431 |
+
|
| 432 |
try{
|
| 433 |
+
const r=await fetch("/api/research",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({question:q})});
|
| 434 |
const d=await r.json();
|
| 435 |
+
if(d.error){traceErr(d.error);resetAskBtn();return;}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
pollTimer=setInterval(()=>poll(d.query_id),1500);
|
| 437 |
+
}catch(e){traceErr("Network error: "+e.message);resetAskBtn();}
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
function resetAskBtn(){
|
| 441 |
+
const btn=document.getElementById("ask-btn");
|
| 442 |
+
btn.disabled=false;
|
| 443 |
+
btn.innerHTML="⚡ Ask";
|
| 444 |
}
|
| 445 |
|
| 446 |
+
/* ββ POLL ββ */
|
| 447 |
async function poll(qid){
|
| 448 |
try{
|
| 449 |
+
const r=await fetch("/api/trace/"+qid);
|
| 450 |
+
if(!r.ok){traceErr("Server error "+r.status);clearInterval(pollTimer);resetAskBtn();return;}
|
| 451 |
const d=await r.json();
|
| 452 |
+
renderTrace(d.trace||[]);
|
| 453 |
+
if(["complete","error"].includes(d.status)){
|
| 454 |
clearInterval(pollTimer);
|
| 455 |
+
resetAskBtn();
|
| 456 |
+
if(d.status==="complete"&&d.result)renderAnswer(d.result);
|
| 457 |
+
else if(d.status==="error"&&d.result)traceErr(d.result.error||"An error occurred.");
|
| 458 |
}
|
| 459 |
+
}catch(e){traceErr("Poll error: "+e.message);clearInterval(pollTimer);resetAskBtn();}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
}
|
| 461 |
|
| 462 |
+
/* ββ TRACE ββ */
|
| 463 |
+
function renderTrace(steps){
|
| 464 |
if(!steps.length)return;
|
| 465 |
+
const log=document.getElementById("trace-log");
|
| 466 |
+
if(seen===0)log.innerHTML="";
|
| 467 |
for(let i=seen;i<steps.length;i++){
|
| 468 |
const s=steps[i];
|
| 469 |
+
const icon=AGENT_ICONS[s.agent]||"●";
|
| 470 |
+
const lat=s.latency_ms>0?"<span class='t-lat'>"+s.latency_ms+"ms</span>":"";
|
| 471 |
+
const cls="a-"+(["planner","retriever","grader","generator","critic"].includes(s.agent)?s.agent:"error");
|
| 472 |
+
log.innerHTML+="<div class='t-step'>"
|
| 473 |
+
+"<span class='t-icon'>"+icon+"</span>"
|
| 474 |
+
+"<span class='t-agent "+cls+"'>"+s.agent+"</span>"
|
| 475 |
+
+"<span class='t-msg'>"+esc(s.message)+"</span>"
|
| 476 |
+
+lat+"</div>";
|
| 477 |
+
if(s.status==="running")setAgent(s.agent,false);
|
| 478 |
+
else if(s.status==="complete")setAgent(s.agent,true);
|
| 479 |
}
|
| 480 |
+
seen=steps.length;
|
| 481 |
+
log.scrollTop=log.scrollHeight;
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
function traceErr(msg){
|
| 485 |
+
const log=document.getElementById("trace-log");
|
| 486 |
+
log.innerHTML+="<div class='t-step'><span class='t-icon'>❌</span><span class='t-agent a-error'>error</span><span class='t-msg' style='color:var(--red)'>"+esc(msg)+"</span></div>";
|
| 487 |
+
log.scrollTop=log.scrollHeight;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
/* ββ ANSWER ββ */
|
| 491 |
+
function renderAnswer(result){
|
| 492 |
+
document.getElementById("answer-wrap").style.display="block";
|
| 493 |
+
document.getElementById("answer-text").textContent=result.generation||"No answer generated.";
|
| 494 |
+
const vb=document.getElementById("verdict-badge");
|
| 495 |
+
if(result.verdict==="APPROVED"){vb.className="v-ok";vb.textContent="✓ High confidence";}
|
| 496 |
+
else if(result.verdict){vb.className="v-warn";vb.textContent="⚠ Verify with source";}
|
| 497 |
+
else{vb.textContent="";}
|
| 498 |
+
document.getElementById("answer-wrap").scrollIntoView({behavior:"smooth",block:"start"});
|
| 499 |
}
|
| 500 |
|
| 501 |
+
/* ββ COPY ββ */
|
| 502 |
+
async function copyAns(){
|
| 503 |
+
const text=document.getElementById("answer-text").textContent;
|
| 504 |
+
try{
|
| 505 |
+
await navigator.clipboard.writeText(text);
|
| 506 |
+
const btn=document.getElementById("copy-btn");
|
| 507 |
+
btn.innerHTML="✓ Copied!";
|
| 508 |
+
setTimeout(()=>{btn.innerHTML="📋 Copy";},2000);
|
| 509 |
+
}catch(e){}
|
| 510 |
}
|
| 511 |
|
| 512 |
+
/* ββ MSG HELPER ββ */
|
| 513 |
+
function showMsg(id,type,msg){
|
| 514 |
const el=document.getElementById(id);
|
| 515 |
+
if(type==="ok")el.innerHTML="<div class='msg msg-ok'>"+msg+"</div>";
|
| 516 |
+
else if(type==="error")el.innerHTML="<div class='msg msg-err'>"+esc(msg)+"</div>";
|
| 517 |
+
else el.innerHTML="<div class='msg-info'>"+esc(msg)+"</div>";
|
| 518 |
}
|
| 519 |
</script>
|
| 520 |
</body>
|
write_html.py
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pathlib
|
| 2 |
+
|
| 3 |
+
HTML = '''\
|
| 4 |
+
<!DOCTYPE html>
|
| 5 |
+
<html lang="en">
|
| 6 |
+
<head>
|
| 7 |
+
<meta charset="UTF-8"/>
|
| 8 |
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
| 9 |
+
<title>DocMind β AI Document Research</title>
|
| 10 |
+
<style>
|
| 11 |
+
:root{
|
| 12 |
+
--bg:#0d0f1a;--surface:#13161f;--card:#181c27;--card2:#1e2230;
|
| 13 |
+
--border:#252836;--border2:#2e3244;
|
| 14 |
+
--text:#e8eaf2;--sub:#b0b8cc;--muted:#7880a0;
|
| 15 |
+
--accent:#5b8ff9;--accent2:#3a6ee8;
|
| 16 |
+
--green:#22d47a;--red:#f05c5c;--teal:#29c6d4;--gold:#f5a623;--purple:#a78bfa;
|
| 17 |
+
}
|
| 18 |
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
| 19 |
+
html,body{height:100%}
|
| 20 |
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
| 21 |
+
background:var(--bg);color:var(--text);font-size:14px;line-height:1.5}
|
| 22 |
+
|
| 23 |
+
/* ββ ANIMATIONS ββββββββββββββββββββββββββββββββββ */
|
| 24 |
+
@keyframes spin{to{transform:rotate(360deg)}}
|
| 25 |
+
@keyframes fadeUp{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
| 26 |
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.45}}
|
| 27 |
+
@keyframes shimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}
|
| 28 |
+
|
| 29 |
+
/* ββ HEADER ββββββββββββββββββββββββββββββββββββββ */
|
| 30 |
+
header{
|
| 31 |
+
background:var(--surface);
|
| 32 |
+
border-bottom:1px solid var(--border);
|
| 33 |
+
padding:0 28px;height:56px;
|
| 34 |
+
display:flex;align-items:center;gap:14px;
|
| 35 |
+
position:sticky;top:0;z-index:100;
|
| 36 |
+
}
|
| 37 |
+
.logo{display:flex;align-items:center;gap:9px;text-decoration:none}
|
| 38 |
+
.logo-icon{width:30px;height:30px;background:linear-gradient(135deg,var(--accent),var(--purple));
|
| 39 |
+
border-radius:8px;display:flex;align-items:center;justify-content:center;
|
| 40 |
+
font-size:15px;flex-shrink:0}
|
| 41 |
+
.logo-text{font-size:1rem;font-weight:800;color:var(--text);letter-spacing:-.3px}
|
| 42 |
+
.logo-text span{color:var(--accent)}
|
| 43 |
+
.logo-sub{font-size:.68rem;color:var(--muted);margin-left:2px;font-weight:400;
|
| 44 |
+
display:none} /* shown on wider screens */
|
| 45 |
+
.hdr-badges{display:flex;gap:8px;align-items:center;margin-left:4px}
|
| 46 |
+
.badge{font-size:.65rem;font-weight:700;padding:3px 9px;border-radius:20px;letter-spacing:.02em}
|
| 47 |
+
.badge-model{background:rgba(91,143,249,.12);border:1px solid rgba(91,143,249,.25);color:var(--accent)}
|
| 48 |
+
.badge-src{background:rgba(113,128,160,.1);border:1px solid var(--border);color:var(--muted);
|
| 49 |
+
max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
| 50 |
+
.badge-src.loaded{background:rgba(34,212,122,.1);border-color:rgba(34,212,122,.3);color:var(--green)}
|
| 51 |
+
#hdr-src{margin-left:auto}
|
| 52 |
+
|
| 53 |
+
/* ββ LAYOUT βββββββββββββββββββββββββββββββββββββββ */
|
| 54 |
+
main{display:grid;grid-template-columns:320px 1fr;height:calc(100vh - 56px);overflow:hidden}
|
| 55 |
+
.panel{padding:24px 22px;overflow-y:auto;height:100%}
|
| 56 |
+
.panel-left{border-right:1px solid var(--border);background:var(--surface)}
|
| 57 |
+
.panel-right{background:var(--bg)}
|
| 58 |
+
|
| 59 |
+
/* ββ SECTION HEADERS ββββββββββββββββββββββββββββββ */
|
| 60 |
+
.sec-head{display:flex;align-items:center;gap:8px;margin-bottom:16px}
|
| 61 |
+
.sec-icon{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;
|
| 62 |
+
justify-content:center;font-size:13px;flex-shrink:0}
|
| 63 |
+
.sec-icon-blue{background:rgba(91,143,249,.15)}
|
| 64 |
+
.sec-icon-purple{background:rgba(167,139,250,.15)}
|
| 65 |
+
.sec-title{font-size:.75rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--sub)}
|
| 66 |
+
|
| 67 |
+
/* ββ TABS ββββββββββββββββββββββββββββββββββββββββββ */
|
| 68 |
+
.tabs{display:flex;gap:2px;background:rgba(255,255,255,.03);border-radius:9px;
|
| 69 |
+
padding:3px;margin-bottom:18px;border:1px solid var(--border)}
|
| 70 |
+
.tab-btn{flex:1;background:none;border:none;color:var(--muted);font-size:.78rem;
|
| 71 |
+
font-weight:600;padding:7px 10px;border-radius:7px;cursor:pointer;
|
| 72 |
+
transition:all .18s;font-family:inherit;display:flex;align-items:center;
|
| 73 |
+
justify-content:center;gap:5px}
|
| 74 |
+
.tab-btn.active{background:var(--card2);color:var(--text);
|
| 75 |
+
box-shadow:0 1px 6px rgba(0,0,0,.35)}
|
| 76 |
+
|
| 77 |
+
/* ββ DROPZONE ββββββββββββββββββββββββββββββββββββββ */
|
| 78 |
+
.dropzone{
|
| 79 |
+
border:2px dashed var(--border2);border-radius:12px;padding:28px 16px;
|
| 80 |
+
text-align:center;cursor:pointer;transition:all .22s;position:relative;overflow:hidden;
|
| 81 |
+
}
|
| 82 |
+
.dropzone::before{
|
| 83 |
+
content:"";position:absolute;inset:0;
|
| 84 |
+
background:linear-gradient(90deg,transparent 0%,rgba(91,143,249,.04) 50%,transparent 100%);
|
| 85 |
+
background-size:200% 100%;opacity:0;transition:opacity .3s;
|
| 86 |
+
}
|
| 87 |
+
.dropzone:hover{border-color:var(--accent);background:rgba(91,143,249,.04)}
|
| 88 |
+
.dropzone:hover::before{opacity:1;animation:shimmer 2s infinite}
|
| 89 |
+
.dropzone.drag-over{border-color:var(--accent);background:rgba(91,143,249,.08);transform:scale(1.01)}
|
| 90 |
+
.dz-icon{font-size:2.2rem;margin-bottom:10px;display:block}
|
| 91 |
+
.dz-label{font-size:.84rem;color:var(--sub);line-height:1.5}
|
| 92 |
+
.dz-label strong{color:var(--accent)}
|
| 93 |
+
.dz-hint{font-size:.72rem;color:var(--muted);margin-top:5px}
|
| 94 |
+
|
| 95 |
+
/* ββ URL INPUT ββββββββββββββββββββββββββββββββββββββ */
|
| 96 |
+
.url-row{display:flex;gap:8px;margin-bottom:10px}
|
| 97 |
+
.url-row input{flex:1;background:var(--card);border:1px solid var(--border2);
|
| 98 |
+
border-radius:8px;padding:9px 12px;color:var(--text);font-size:.84rem;
|
| 99 |
+
font-family:inherit;outline:none;transition:border-color .18s}
|
| 100 |
+
.url-row input:focus{border-color:var(--accent)}
|
| 101 |
+
|
| 102 |
+
/* ββ SOURCE LOADED CARD ββββββββββββββββββββββββββββ */
|
| 103 |
+
#source-card{
|
| 104 |
+
display:none;margin-top:16px;background:var(--card);
|
| 105 |
+
border:1px solid rgba(34,212,122,.2);border-radius:10px;padding:13px 14px;
|
| 106 |
+
animation:fadeUp .25s ease;
|
| 107 |
+
}
|
| 108 |
+
.sc-row{display:flex;align-items:flex-start;gap:10px}
|
| 109 |
+
.sc-icon{width:34px;height:34px;background:rgba(34,212,122,.12);border-radius:8px;
|
| 110 |
+
flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:16px}
|
| 111 |
+
.sc-info{flex:1;min-width:0}
|
| 112 |
+
.sc-name{font-size:.82rem;font-weight:600;color:var(--text);
|
| 113 |
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:3px}
|
| 114 |
+
.sc-meta{font-size:.72rem;color:var(--teal)}
|
| 115 |
+
.sc-ready{display:inline-flex;align-items:center;gap:5px;margin-top:8px;
|
| 116 |
+
font-size:.7rem;font-weight:700;color:var(--green);
|
| 117 |
+
background:rgba(34,212,122,.08);border:1px solid rgba(34,212,122,.18);
|
| 118 |
+
border-radius:5px;padding:3px 8px}
|
| 119 |
+
|
| 120 |
+
/* ββ MESSAGES ββββββββββββββββββββββββββββββββββββββ */
|
| 121 |
+
.msg{border-radius:8px;padding:10px 13px;font-size:.79rem;
|
| 122 |
+
margin-top:10px;line-height:1.55;animation:fadeUp .2s ease}
|
| 123 |
+
.msg-ok{background:rgba(34,212,122,.07);border:1px solid rgba(34,212,122,.2);color:var(--green)}
|
| 124 |
+
.msg-err{background:rgba(240,92,92,.07);border:1px solid rgba(240,92,92,.2);color:var(--red)}
|
| 125 |
+
.msg-info{color:var(--muted);font-size:.76rem;margin-top:8px}
|
| 126 |
+
|
| 127 |
+
/* ββ BUTTONS βββββββββββββββββββββββββββββββββββββββ */
|
| 128 |
+
.btn{display:inline-flex;align-items:center;gap:7px;padding:9px 20px;
|
| 129 |
+
border-radius:8px;border:none;font-size:.84rem;font-weight:600;
|
| 130 |
+
cursor:pointer;transition:all .18s;font-family:inherit}
|
| 131 |
+
.btn-primary{background:linear-gradient(135deg,var(--accent),var(--accent2));
|
| 132 |
+
color:#fff;box-shadow:0 2px 8px rgba(91,143,249,.25)}
|
| 133 |
+
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 4px 14px rgba(91,143,249,.35)}
|
| 134 |
+
.btn-primary:active{transform:none}
|
| 135 |
+
.btn-primary:disabled{opacity:.5;cursor:default;transform:none;box-shadow:none}
|
| 136 |
+
.btn-sm{padding:7px 14px;font-size:.78rem}
|
| 137 |
+
.btn-ghost{background:rgba(255,255,255,.06);border:1px solid var(--border2);
|
| 138 |
+
color:var(--sub);font-size:.74rem;padding:5px 12px;border-radius:6px}
|
| 139 |
+
.btn-ghost:hover{background:rgba(255,255,255,.1);color:var(--text)}
|
| 140 |
+
.spinner{width:13px;height:13px;border:2px solid rgba(255,255,255,.3);
|
| 141 |
+
border-top-color:#fff;border-radius:50%;animation:spin .65s linear infinite;flex-shrink:0}
|
| 142 |
+
|
| 143 |
+
/* ββ QUESTION AREA βββββββββββββββββββββββββββββββββ */
|
| 144 |
+
.q-card{background:var(--card);border:1px solid var(--border2);
|
| 145 |
+
border-radius:11px;padding:16px;margin-bottom:16px}
|
| 146 |
+
.q-label{font-size:.7rem;font-weight:700;color:var(--muted);text-transform:uppercase;
|
| 147 |
+
letter-spacing:.06em;margin-bottom:8px}
|
| 148 |
+
textarea{width:100%;background:transparent;border:none;color:var(--text);
|
| 149 |
+
font-size:.9rem;font-family:inherit;outline:none;resize:none;
|
| 150 |
+
line-height:1.6;min-height:72px}
|
| 151 |
+
textarea::placeholder{color:var(--muted)}
|
| 152 |
+
.q-footer{display:flex;align-items:center;gap:10px;padding-top:12px;
|
| 153 |
+
border-top:1px solid var(--border)}
|
| 154 |
+
#q-err{font-size:.77rem;color:var(--red)}
|
| 155 |
+
|
| 156 |
+
/* ββ PIPELINE ββββββββββββββββββββββββββββββββββββββ */
|
| 157 |
+
#pipeline{display:none;margin-bottom:14px;animation:fadeUp .2s ease}
|
| 158 |
+
.pipe-row{display:flex;align-items:center;gap:0;background:var(--card);
|
| 159 |
+
border:1px solid var(--border2);border-radius:10px;padding:10px 14px;
|
| 160 |
+
overflow-x:auto}
|
| 161 |
+
.pipe-step{display:flex;align-items:center;gap:5px;font-size:.72rem;font-weight:600;
|
| 162 |
+
color:var(--muted);white-space:nowrap;padding:4px 8px;border-radius:6px;
|
| 163 |
+
transition:all .2s}
|
| 164 |
+
.pipe-step.active{color:var(--accent);background:rgba(91,143,249,.12)}
|
| 165 |
+
.pipe-step.done{color:var(--green)}
|
| 166 |
+
.pipe-step .step-dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0}
|
| 167 |
+
.pipe-step.active .step-dot{animation:pulse .9s ease infinite}
|
| 168 |
+
.pipe-arrow{color:var(--border2);font-size:.8rem;padding:0 2px;flex-shrink:0}
|
| 169 |
+
|
| 170 |
+
/* ββ TRACE LOG βββββββββββββββββββββββββββββββββββββ */
|
| 171 |
+
#trace-wrap{display:none;margin-bottom:16px;animation:fadeUp .2s ease}
|
| 172 |
+
.trace-hdr{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
| 173 |
+
.trace-title{font-size:.7rem;font-weight:700;text-transform:uppercase;
|
| 174 |
+
letter-spacing:.07em;color:var(--muted)}
|
| 175 |
+
.trace-box{background:var(--card);border:1px solid var(--border2);border-radius:10px;
|
| 176 |
+
padding:4px 0;max-height:220px;overflow-y:auto}
|
| 177 |
+
.t-step{display:flex;align-items:flex-start;gap:10px;padding:8px 14px;
|
| 178 |
+
border-bottom:1px solid var(--border);animation:fadeUp .18s ease;transition:background .15s}
|
| 179 |
+
.t-step:last-child{border-bottom:none}
|
| 180 |
+
.t-step:hover{background:rgba(255,255,255,.02)}
|
| 181 |
+
.t-icon{font-size:13px;flex-shrink:0;margin-top:1px;width:18px;text-align:center}
|
| 182 |
+
.t-agent{font-size:.64rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;
|
| 183 |
+
padding:2px 7px;border-radius:4px;flex-shrink:0;white-space:nowrap}
|
| 184 |
+
.a-planner {background:rgba(91,143,249,.14);color:var(--accent)}
|
| 185 |
+
.a-retriever{background:rgba(41,198,212,.14);color:var(--teal)}
|
| 186 |
+
.a-grader {background:rgba(245,166,35,.14);color:var(--gold)}
|
| 187 |
+
.a-generator{background:rgba(34,212,122,.14);color:var(--green)}
|
| 188 |
+
.a-critic {background:rgba(167,139,250,.14);color:var(--purple)}
|
| 189 |
+
.a-error {background:rgba(240,92,92,.14);color:var(--red)}
|
| 190 |
+
.t-msg{flex:1;font-size:.78rem;color:var(--sub);line-height:1.5}
|
| 191 |
+
.t-lat{font-size:.65rem;color:rgba(128,136,160,.5);flex-shrink:0;margin-top:2px}
|
| 192 |
+
|
| 193 |
+
/* ββ ANSWER CARD βββββββββββββββββββββββββββββββββββ */
|
| 194 |
+
#answer-wrap{display:none;background:var(--card);border:1px solid var(--border2);
|
| 195 |
+
border-radius:12px;overflow:hidden;animation:fadeUp .3s ease}
|
| 196 |
+
.ans-header{display:flex;align-items:center;justify-content:space-between;
|
| 197 |
+
padding:13px 18px 12px;border-bottom:1px solid var(--border);
|
| 198 |
+
background:rgba(255,255,255,.02)}
|
| 199 |
+
.ans-title{display:flex;align-items:center;gap:8px;font-size:.72rem;font-weight:800;
|
| 200 |
+
text-transform:uppercase;letter-spacing:.08em;color:var(--sub)}
|
| 201 |
+
.ans-title-icon{width:22px;height:22px;background:rgba(34,212,122,.15);border-radius:6px;
|
| 202 |
+
display:flex;align-items:center;justify-content:center;font-size:11px}
|
| 203 |
+
.ans-actions{display:flex;align-items:center;gap:8px}
|
| 204 |
+
#verdict-badge{font-size:.68rem;font-weight:700;padding:3px 10px;border-radius:20px}
|
| 205 |
+
.v-ok {background:rgba(34,212,122,.1);border:1px solid rgba(34,212,122,.25);color:var(--green)}
|
| 206 |
+
.v-warn{background:rgba(245,166,35,.1);border:1px solid rgba(245,166,35,.25);color:var(--gold)}
|
| 207 |
+
.ans-body{padding:18px}
|
| 208 |
+
#answer-text{font-size:.9rem;line-height:1.78;color:var(--text);
|
| 209 |
+
white-space:pre-wrap;word-break:break-word}
|
| 210 |
+
|
| 211 |
+
/* ββ RESPONSIVE ββββββββββββββββββββββββββββββββββββ */
|
| 212 |
+
@media(min-width:1100px){.logo-sub{display:block}}
|
| 213 |
+
@media(max-width:768px){
|
| 214 |
+
main{grid-template-columns:1fr;height:auto;overflow:visible}
|
| 215 |
+
.panel{height:auto}
|
| 216 |
+
.panel-left{border-right:none;border-bottom:1px solid var(--border)}
|
| 217 |
+
}
|
| 218 |
+
</style>
|
| 219 |
+
</head>
|
| 220 |
+
<body>
|
| 221 |
+
|
| 222 |
+
<!-- ββ HEADER ββ -->
|
| 223 |
+
<header>
|
| 224 |
+
<a class="logo" href="#">
|
| 225 |
+
<div class="logo-icon">🧠</div>
|
| 226 |
+
<span class="logo-text">Doc<span>Mind</span></span>
|
| 227 |
+
</a>
|
| 228 |
+
<span class="logo-sub">AI Document Research</span>
|
| 229 |
+
<div class="hdr-badges">
|
| 230 |
+
<span class="badge badge-model">Qwen 2.5 7B</span>
|
| 231 |
+
</div>
|
| 232 |
+
<span class="badge badge-src" id="hdr-src">No source loaded</span>
|
| 233 |
+
</header>
|
| 234 |
+
|
| 235 |
+
<main>
|
| 236 |
+
<!-- ββ LEFT PANEL: KNOWLEDGE BASE ββ -->
|
| 237 |
+
<div class="panel panel-left">
|
| 238 |
+
<div class="sec-head">
|
| 239 |
+
<div class="sec-icon sec-icon-blue">📚</div>
|
| 240 |
+
<span class="sec-title">Knowledge Base</span>
|
| 241 |
+
</div>
|
| 242 |
+
|
| 243 |
+
<div class="tabs">
|
| 244 |
+
<button class="tab-btn active" onclick="switchTab(this,'pdf')">📄 Upload PDF</button>
|
| 245 |
+
<button class="tab-btn" onclick="switchTab(this,'url')">🌐 Paste URL</button>
|
| 246 |
+
</div>
|
| 247 |
+
|
| 248 |
+
<!-- PDF TAB -->
|
| 249 |
+
<div id="tab-pdf">
|
| 250 |
+
<div class="dropzone" id="dz"
|
| 251 |
+
onclick="document.getElementById('fi').click()"
|
| 252 |
+
ondragover="dg(event,true)" ondragleave="dg(event,false)" ondrop="dp(event)">
|
| 253 |
+
<span class="dz-icon">📄</span>
|
| 254 |
+
<div class="dz-label"><strong>Click to browse</strong> or drag & drop</div>
|
| 255 |
+
<div class="dz-hint">PDF only · max 10 MB</div>
|
| 256 |
+
</div>
|
| 257 |
+
<input type="file" id="fi" accept=".pdf" style="display:none" onchange="fc(event)"/>
|
| 258 |
+
<div id="pdf-msg"></div>
|
| 259 |
+
</div>
|
| 260 |
+
|
| 261 |
+
<!-- URL TAB -->
|
| 262 |
+
<div id="tab-url" style="display:none">
|
| 263 |
+
<div class="url-row">
|
| 264 |
+
<input type="url" id="url-inp" placeholder="https://en.wikipedia.org/wiki/..."
|
| 265 |
+
onkeydown="if(event.key==='Enter')fetchURL()"/>
|
| 266 |
+
<button class="btn btn-primary btn-sm" id="url-btn" onclick="fetchURL()">Fetch</button>
|
| 267 |
+
</div>
|
| 268 |
+
<div class="msg-info" style="margin-bottom:4px">Wikipedia, government sites, and docs work best. Some sites block automated access.</div>
|
| 269 |
+
<div id="url-msg"></div>
|
| 270 |
+
</div>
|
| 271 |
+
|
| 272 |
+
<!-- SOURCE LOADED -->
|
| 273 |
+
<div id="source-card">
|
| 274 |
+
<div class="sc-row">
|
| 275 |
+
<div class="sc-icon" id="sc-icon">📄</div>
|
| 276 |
+
<div class="sc-info">
|
| 277 |
+
<div class="sc-name" id="source-name"></div>
|
| 278 |
+
<div class="sc-meta" id="source-chunks"></div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
<div class="sc-ready">✓ Ready for questions</div>
|
| 282 |
+
</div>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
<!-- ββ RIGHT PANEL: RESEARCH ββ -->
|
| 286 |
+
<div class="panel panel-right">
|
| 287 |
+
<div class="sec-head">
|
| 288 |
+
<div class="sec-icon sec-icon-purple">🔍</div>
|
| 289 |
+
<span class="sec-title">Research Query</span>
|
| 290 |
+
</div>
|
| 291 |
+
|
| 292 |
+
<div class="q-card">
|
| 293 |
+
<div class="q-label">Your Question</div>
|
| 294 |
+
<textarea id="q-inp" rows="3"
|
| 295 |
+
placeholder="Ask anything about the loaded document or URL Press Enter to submit, Shift+Enter for new line"
|
| 296 |
+
onkeydown="qk(event)"></textarea>
|
| 297 |
+
<div class="q-footer">
|
| 298 |
+
<button class="btn btn-primary" id="ask-btn" onclick="ask()">⚡ Ask</button>
|
| 299 |
+
<span id="q-err"></span>
|
| 300 |
+
</div>
|
| 301 |
+
</div>
|
| 302 |
+
|
| 303 |
+
<!-- AGENT PIPELINE -->
|
| 304 |
+
<div id="pipeline">
|
| 305 |
+
<div class="pipe-row">
|
| 306 |
+
<div class="pipe-step" id="ps-planner"><span class="step-dot"></span>🧮 Planner</div>
|
| 307 |
+
<div class="pipe-arrow">→</div>
|
| 308 |
+
<div class="pipe-step" id="ps-retriever"><span class="step-dot"></span>🔍 Retriever</div>
|
| 309 |
+
<div class="pipe-arrow">→</div>
|
| 310 |
+
<div class="pipe-step" id="ps-grader"><span class="step-dot"></span>⚖ Grader</div>
|
| 311 |
+
<div class="pipe-arrow">→</div>
|
| 312 |
+
<div class="pipe-step" id="ps-generator"><span class="step-dot"></span>✍ Generator</div>
|
| 313 |
+
<div class="pipe-arrow">→</div>
|
| 314 |
+
<div class="pipe-step" id="ps-critic"><span class="step-dot"></span>🔬 Critic</div>
|
| 315 |
+
</div>
|
| 316 |
+
</div>
|
| 317 |
+
|
| 318 |
+
<!-- TRACE LOG -->
|
| 319 |
+
<div id="trace-wrap">
|
| 320 |
+
<div class="trace-hdr">
|
| 321 |
+
<span class="trace-title">📰 Agent Trace</span>
|
| 322 |
+
</div>
|
| 323 |
+
<div class="trace-box" id="trace-log"></div>
|
| 324 |
+
</div>
|
| 325 |
+
|
| 326 |
+
<!-- ANSWER -->
|
| 327 |
+
<div id="answer-wrap">
|
| 328 |
+
<div class="ans-header">
|
| 329 |
+
<div class="ans-title">
|
| 330 |
+
<div class="ans-title-icon">💡</div>
|
| 331 |
+
Answer
|
| 332 |
+
</div>
|
| 333 |
+
<div class="ans-actions">
|
| 334 |
+
<span id="verdict-badge"></span>
|
| 335 |
+
<button class="btn btn-ghost" id="copy-btn" onclick="copyAns()">📋 Copy</button>
|
| 336 |
+
</div>
|
| 337 |
+
</div>
|
| 338 |
+
<div class="ans-body">
|
| 339 |
+
<div id="answer-text"></div>
|
| 340 |
+
</div>
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
</main>
|
| 344 |
+
|
| 345 |
+
<script>
|
| 346 |
+
let pollTimer=null,seen=0;
|
| 347 |
+
const esc=s=>String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
| 348 |
+
|
| 349 |
+
const AGENT_ICONS={planner:"🧮",retriever:"🔍",grader:"⚖",generator:"✍",critic:"🔬"};
|
| 350 |
+
|
| 351 |
+
/* ββ TABS ββ */
|
| 352 |
+
function switchTab(btn,name){
|
| 353 |
+
document.querySelectorAll(".tab-btn").forEach(b=>b.classList.remove("active"));
|
| 354 |
+
btn.classList.add("active");
|
| 355 |
+
document.getElementById("tab-pdf").style.display=name==="pdf"?"":"none";
|
| 356 |
+
document.getElementById("tab-url").style.display=name==="url"?"":"none";
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
/* ββ DRAG & DROP ββ */
|
| 360 |
+
function dg(e,over){e.preventDefault();document.getElementById("dz").classList[over?"add":"remove"]("drag-over");}
|
| 361 |
+
function dp(e){e.preventDefault();document.getElementById("dz").classList.remove("drag-over");const f=e.dataTransfer.files[0];if(f)up(f);}
|
| 362 |
+
function fc(e){if(e.target.files[0])up(e.target.files[0]);}
|
| 363 |
+
|
| 364 |
+
/* ββ UPLOAD PDF ββ */
|
| 365 |
+
async function up(file){
|
| 366 |
+
if(!file.name.toLowerCase().endsWith(".pdf")){showMsg("pdf-msg","error","Only PDF files are supported.");return;}
|
| 367 |
+
showMsg("pdf-msg","info","Uploading "+file.name+"...");
|
| 368 |
+
const fd=new FormData();fd.append("file",file);
|
| 369 |
+
try{
|
| 370 |
+
const r=await fetch("/api/upload",{method:"POST",body:fd});
|
| 371 |
+
const d=await r.json();
|
| 372 |
+
if(d.error){showMsg("pdf-msg","error",d.error);return;}
|
| 373 |
+
setSource(d.filename,d.chunks,"pdf");
|
| 374 |
+
showMsg("pdf-msg","ok","✓ "+d.chunks+" chunks indexed from "+d.filename);
|
| 375 |
+
}catch(e){showMsg("pdf-msg","error","Upload failed: "+e.message);}
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
/* ββ FETCH URL ββ */
|
| 379 |
+
async function fetchURL(){
|
| 380 |
+
const url=document.getElementById("url-inp").value.trim();
|
| 381 |
+
if(!url){showMsg("url-msg","error","Please enter a URL.");return;}
|
| 382 |
+
const btn=document.getElementById("url-btn");
|
| 383 |
+
btn.disabled=true;btn.textContent="Fetching...";
|
| 384 |
+
showMsg("url-msg","info","Fetching and indexing page...");
|
| 385 |
+
try{
|
| 386 |
+
const r=await fetch("/api/ingest_url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url})});
|
| 387 |
+
const d=await r.json();
|
| 388 |
+
if(d.error){showMsg("url-msg","error",d.error);return;}
|
| 389 |
+
setSource(d.url,d.chunks,"url");
|
| 390 |
+
showMsg("url-msg","ok","✓ "+d.chunks+" chunks indexed");
|
| 391 |
+
}catch(e){showMsg("url-msg","error","Failed: "+e.message);}
|
| 392 |
+
finally{btn.disabled=false;btn.textContent="Fetch";}
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
/* ββ SOURCE LOADED ββ */
|
| 396 |
+
function setSource(name,chunks,type){
|
| 397 |
+
document.getElementById("source-name").textContent=name;
|
| 398 |
+
document.getElementById("source-chunks").textContent=chunks+" chunks indexed";
|
| 399 |
+
document.getElementById("sc-icon").textContent=type==="pdf"?"📄":"🌐";
|
| 400 |
+
document.getElementById("source-card").style.display="block";
|
| 401 |
+
const p=document.getElementById("hdr-src");
|
| 402 |
+
p.textContent=name.length>30?name.slice(0,30)+"...":name;
|
| 403 |
+
p.classList.add("loaded");
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
/* ββ PIPELINE ββ */
|
| 407 |
+
const PIPE_AGENTS=["planner","retriever","grader","generator","critic"];
|
| 408 |
+
function resetPipeline(){PIPE_AGENTS.forEach(a=>{const el=document.getElementById("ps-"+a);if(el){el.classList.remove("active","done");}});}
|
| 409 |
+
function setAgent(name,done){
|
| 410 |
+
const el=document.getElementById("ps-"+name);
|
| 411 |
+
if(!el)return;
|
| 412 |
+
if(done){el.classList.remove("active");el.classList.add("done");}
|
| 413 |
+
else{el.classList.remove("done");el.classList.add("active");}
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
/* ββ ASK ββ */
|
| 417 |
+
function qk(e){if(e.key==="Enter"&&!e.shiftKey){e.preventDefault();ask();}}
|
| 418 |
+
|
| 419 |
+
async function ask(){
|
| 420 |
+
const q=document.getElementById("q-inp").value.trim();
|
| 421 |
+
document.getElementById("q-err").textContent="";
|
| 422 |
+
if(!q){document.getElementById("q-err").textContent="Please enter a question.";return;}
|
| 423 |
+
|
| 424 |
+
const btn=document.getElementById("ask-btn");
|
| 425 |
+
btn.disabled=true;
|
| 426 |
+
btn.innerHTML="<span class='spinner'></span> Thinking...";
|
| 427 |
+
|
| 428 |
+
document.getElementById("pipeline").style.display="block";
|
| 429 |
+
document.getElementById("trace-wrap").style.display="block";
|
| 430 |
+
document.getElementById("trace-log").innerHTML="<div class='t-step'><span class='t-msg' style='color:var(--muted);font-style:italic'>Initialising agents...</span></div>";
|
| 431 |
+
document.getElementById("answer-wrap").style.display="none";
|
| 432 |
+
resetPipeline();
|
| 433 |
+
seen=0;clearInterval(pollTimer);
|
| 434 |
+
|
| 435 |
+
try{
|
| 436 |
+
const r=await fetch("/api/research",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({question:q})});
|
| 437 |
+
const d=await r.json();
|
| 438 |
+
if(d.error){traceErr(d.error);resetAskBtn();return;}
|
| 439 |
+
pollTimer=setInterval(()=>poll(d.query_id),1500);
|
| 440 |
+
}catch(e){traceErr("Network error: "+e.message);resetAskBtn();}
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
function resetAskBtn(){
|
| 444 |
+
const btn=document.getElementById("ask-btn");
|
| 445 |
+
btn.disabled=false;
|
| 446 |
+
btn.innerHTML="⚡ Ask";
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
/* ββ POLL ββ */
|
| 450 |
+
async function poll(qid){
|
| 451 |
+
try{
|
| 452 |
+
const r=await fetch("/api/trace/"+qid);
|
| 453 |
+
if(!r.ok){traceErr("Server error "+r.status);clearInterval(pollTimer);resetAskBtn();return;}
|
| 454 |
+
const d=await r.json();
|
| 455 |
+
renderTrace(d.trace||[]);
|
| 456 |
+
if(["complete","error"].includes(d.status)){
|
| 457 |
+
clearInterval(pollTimer);
|
| 458 |
+
resetAskBtn();
|
| 459 |
+
if(d.status==="complete"&&d.result)renderAnswer(d.result);
|
| 460 |
+
else if(d.status==="error"&&d.result)traceErr(d.result.error||"An error occurred.");
|
| 461 |
+
}
|
| 462 |
+
}catch(e){traceErr("Poll error: "+e.message);clearInterval(pollTimer);resetAskBtn();}
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
/* ββ TRACE ββ */
|
| 466 |
+
function renderTrace(steps){
|
| 467 |
+
if(!steps.length)return;
|
| 468 |
+
const log=document.getElementById("trace-log");
|
| 469 |
+
if(seen===0)log.innerHTML="";
|
| 470 |
+
for(let i=seen;i<steps.length;i++){
|
| 471 |
+
const s=steps[i];
|
| 472 |
+
const icon=AGENT_ICONS[s.agent]||"●";
|
| 473 |
+
const lat=s.latency_ms>0?"<span class='t-lat'>"+s.latency_ms+"ms</span>":"";
|
| 474 |
+
const cls="a-"+(["planner","retriever","grader","generator","critic"].includes(s.agent)?s.agent:"error");
|
| 475 |
+
log.innerHTML+="<div class='t-step'>"
|
| 476 |
+
+"<span class='t-icon'>"+icon+"</span>"
|
| 477 |
+
+"<span class='t-agent "+cls+"'>"+s.agent+"</span>"
|
| 478 |
+
+"<span class='t-msg'>"+esc(s.message)+"</span>"
|
| 479 |
+
+lat+"</div>";
|
| 480 |
+
if(s.status==="running")setAgent(s.agent,false);
|
| 481 |
+
else if(s.status==="complete")setAgent(s.agent,true);
|
| 482 |
+
}
|
| 483 |
+
seen=steps.length;
|
| 484 |
+
log.scrollTop=log.scrollHeight;
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
function traceErr(msg){
|
| 488 |
+
const log=document.getElementById("trace-log");
|
| 489 |
+
log.innerHTML+="<div class='t-step'><span class='t-icon'>❌</span><span class='t-agent a-error'>error</span><span class='t-msg' style='color:var(--red)'>"+esc(msg)+"</span></div>";
|
| 490 |
+
log.scrollTop=log.scrollHeight;
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
/* ββ ANSWER ββ */
|
| 494 |
+
function renderAnswer(result){
|
| 495 |
+
document.getElementById("answer-wrap").style.display="block";
|
| 496 |
+
document.getElementById("answer-text").textContent=result.generation||"No answer generated.";
|
| 497 |
+
const vb=document.getElementById("verdict-badge");
|
| 498 |
+
if(result.verdict==="APPROVED"){vb.className="v-ok";vb.textContent="✓ High confidence";}
|
| 499 |
+
else if(result.verdict){vb.className="v-warn";vb.textContent="⚠ Verify with source";}
|
| 500 |
+
else{vb.textContent="";}
|
| 501 |
+
document.getElementById("answer-wrap").scrollIntoView({behavior:"smooth",block:"start"});
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
/* ββ COPY ββ */
|
| 505 |
+
async function copyAns(){
|
| 506 |
+
const text=document.getElementById("answer-text").textContent;
|
| 507 |
+
try{
|
| 508 |
+
await navigator.clipboard.writeText(text);
|
| 509 |
+
const btn=document.getElementById("copy-btn");
|
| 510 |
+
btn.innerHTML="✓ Copied!";
|
| 511 |
+
setTimeout(()=>{btn.innerHTML="📋 Copy";},2000);
|
| 512 |
+
}catch(e){}
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
/* ββ MSG HELPER ββ */
|
| 516 |
+
function showMsg(id,type,msg){
|
| 517 |
+
const el=document.getElementById(id);
|
| 518 |
+
if(type==="ok")el.innerHTML="<div class='msg msg-ok'>"+msg+"</div>";
|
| 519 |
+
else if(type==="error")el.innerHTML="<div class='msg msg-err'>"+esc(msg)+"</div>";
|
| 520 |
+
else el.innerHTML="<div class='msg-info'>"+esc(msg)+"</div>";
|
| 521 |
+
}
|
| 522 |
+
</script>
|
| 523 |
+
</body>
|
| 524 |
+
</html>
|
| 525 |
+
'''
|
| 526 |
+
|
| 527 |
+
pathlib.Path('E:/HuggingFace/docmind/templates/index.html').write_text(HTML, encoding='utf-8')
|
| 528 |
+
print('Done, size:', len(HTML))
|