510 lines
23 KiB
HTML
510 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>WEVIA Vault — Sovereign Memory Manager</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
:root{--bg:#080b12;--bg2:#0d1117;--bg3:#151b25;--bg4:#1c2333;--fg:#c9d1d9;--fg2:#8b949e;--fg3:#484f58;--accent:#58a6ff;--accent2:#1f6feb;--green:#3fb950;--orange:#d29922;--red:#f85149;--purple:#bc8cff;--cyan:#39d353;--border:#21262d;--r:8px;--glow:0 0 20px rgba(88,166,255,0.15)}
|
|
body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--fg);height:100vh;display:grid;grid-template-columns:260px 1fr 320px;grid-template-rows:56px 1fr;overflow:hidden}
|
|
::selection{background:var(--accent2);color:#fff}
|
|
::-webkit-scrollbar{width:6px}
|
|
::-webkit-scrollbar-track{background:var(--bg2)}
|
|
::-webkit-scrollbar-thumb{background:var(--fg3);border-radius:3px}
|
|
|
|
/* HEADER */
|
|
.hdr{grid-column:1/-1;background:var(--bg2);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 20px;z-index:10}
|
|
.hdr-left{display:flex;align-items:center;gap:12px}
|
|
.hdr-logo{font-size:15px;font-weight:700;letter-spacing:1px;display:flex;align-items:center;gap:10px}
|
|
.hdr-logo .dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green);animation:pulse 2s infinite}
|
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
|
.hdr-logo span{color:var(--accent)}
|
|
.hdr-stats{display:flex;gap:16px;font-size:12px;color:var(--fg2)}
|
|
.hdr-stats b{color:var(--fg);font-weight:600}
|
|
.hdr-right{display:flex;gap:10px}
|
|
.btn{padding:6px 14px;border-radius:var(--r);border:1px solid var(--border);background:var(--bg3);color:var(--fg);font-size:12px;cursor:pointer;font-family:inherit;transition:all .2s}
|
|
.btn:hover{border-color:var(--accent);color:var(--accent);box-shadow:var(--glow)}
|
|
.btn-primary{background:var(--accent2);border-color:var(--accent2);color:#fff}
|
|
.btn-primary:hover{background:var(--accent);box-shadow:0 0 20px rgba(88,166,255,.3)}
|
|
|
|
/* SIDEBAR */
|
|
.sidebar{background:var(--bg2);border-right:1px solid var(--border);overflow-y:auto;padding:16px}
|
|
.sidebar h3{font-size:11px;text-transform:uppercase;letter-spacing:2px;color:var(--fg3);margin:16px 0 8px;font-weight:600}
|
|
.sidebar h3:first-child{margin-top:0}
|
|
.dir-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:6px;cursor:pointer;font-size:13px;transition:all .15s;color:var(--fg2)}
|
|
.dir-item:hover{background:var(--bg4);color:var(--fg)}
|
|
.dir-item.active{background:var(--bg4);color:var(--accent);border-left:2px solid var(--accent)}
|
|
.dir-item .icon{font-size:16px;width:20px;text-align:center}
|
|
.dir-item .count{margin-left:auto;font-size:11px;background:var(--bg);padding:2px 6px;border-radius:10px;color:var(--fg3)}
|
|
.file-item{display:flex;align-items:center;gap:8px;padding:6px 10px 6px 28px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--fg2);transition:all .15s}
|
|
.file-item:hover{background:var(--bg4);color:var(--fg)}
|
|
.file-item.active{color:var(--accent)}
|
|
.file-item .ext{font-size:10px;color:var(--fg3);margin-left:auto;font-family:'JetBrains Mono',monospace}
|
|
|
|
/* MAIN */
|
|
.main{overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:20px}
|
|
|
|
/* Search bar */
|
|
.search-wrap{position:relative}
|
|
.search-wrap input{width:100%;padding:12px 16px 12px 40px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);color:var(--fg);font-size:14px;font-family:inherit;outline:none;transition:border .2s}
|
|
.search-wrap input:focus{border-color:var(--accent);box-shadow:var(--glow)}
|
|
.search-wrap::before{content:'🔍';position:absolute;left:14px;top:50%;transform:translateY(-50%);font-size:14px}
|
|
.search-type{display:flex;gap:6px;margin-top:8px}
|
|
.search-type label{font-size:11px;color:var(--fg2);display:flex;align-items:center;gap:4px;cursor:pointer}
|
|
.search-type input[type=radio]{accent-color:var(--accent)}
|
|
|
|
/* Stats cards */
|
|
.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
|
|
.stat-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:16px;text-align:center}
|
|
.stat-card .val{font-size:28px;font-weight:700;color:var(--accent);font-family:'JetBrains Mono',monospace}
|
|
.stat-card .label{font-size:11px;color:var(--fg2);margin-top:4px;text-transform:uppercase;letter-spacing:1px}
|
|
|
|
/* Results */
|
|
.results{display:flex;flex-direction:column;gap:8px}
|
|
.result-item{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:14px 16px;cursor:pointer;transition:all .15s}
|
|
.result-item:hover{border-color:var(--accent);transform:translateX(4px)}
|
|
.result-item .file{font-size:13px;font-weight:600;color:var(--accent);font-family:'JetBrains Mono',monospace}
|
|
.result-item .score{float:right;font-size:11px;padding:2px 8px;border-radius:10px;background:var(--accent2);color:#fff}
|
|
.result-item .snippet{font-size:12px;color:var(--fg2);margin-top:6px;line-height:1.5}
|
|
.result-item .tags{margin-top:6px;display:flex;gap:4px;flex-wrap:wrap}
|
|
.result-item .tag{font-size:10px;padding:2px 6px;border-radius:4px;background:var(--bg4);color:var(--purple)}
|
|
|
|
/* Note viewer */
|
|
.note-view{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:20px;flex:1;min-height:300px}
|
|
.note-view .note-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--border)}
|
|
.note-view .note-title{font-size:16px;font-weight:700;color:var(--accent)}
|
|
.note-view .note-meta{font-size:11px;color:var(--fg3)}
|
|
.note-view .note-content{font-size:13px;line-height:1.8;color:var(--fg);white-space:pre-wrap;font-family:'JetBrains Mono',monospace}
|
|
.note-view textarea{width:100%;min-height:250px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:12px;color:var(--fg);font-size:13px;font-family:'JetBrains Mono',monospace;line-height:1.6;resize:vertical;outline:none}
|
|
.note-view textarea:focus{border-color:var(--accent)}
|
|
|
|
/* RIGHT PANEL */
|
|
.panel{background:var(--bg2);border-left:1px solid var(--border);overflow-y:auto;padding:16px}
|
|
.panel h3{font-size:11px;text-transform:uppercase;letter-spacing:2px;color:var(--fg3);margin:16px 0 8px;font-weight:600}
|
|
.panel h3:first-child{margin-top:0}
|
|
|
|
/* Graph mini */
|
|
.graph-mini{width:100%;height:200px;background:var(--bg);border-radius:var(--r);border:1px solid var(--border);position:relative;overflow:hidden}
|
|
.graph-mini canvas{width:100%;height:100%}
|
|
|
|
/* Activity feed */
|
|
.activity{display:flex;flex-direction:column;gap:6px}
|
|
.activity-item{font-size:11px;color:var(--fg2);padding:6px 8px;border-radius:4px;background:var(--bg3);display:flex;gap:8px;align-items:center}
|
|
.activity-item .time{color:var(--fg3);font-family:'JetBrains Mono',monospace;min-width:40px}
|
|
.activity-item .act{color:var(--green)}
|
|
|
|
/* Crons */
|
|
.cron-list{display:flex;flex-direction:column;gap:4px}
|
|
.cron-item{font-size:11px;padding:6px 8px;background:var(--bg3);border-radius:4px;display:flex;justify-content:space-between;color:var(--fg2)}
|
|
.cron-item .freq{color:var(--cyan);font-family:'JetBrains Mono',monospace}
|
|
|
|
/* New note modal */
|
|
.modal-bg{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:100;align-items:center;justify-content:center}
|
|
.modal-bg.show{display:flex}
|
|
.modal{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:24px;width:500px;max-width:90vw}
|
|
.modal h2{font-size:16px;margin-bottom:16px;color:var(--accent)}
|
|
.modal input,.modal select,.modal textarea{width:100%;padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--fg);font-size:13px;font-family:inherit;margin-bottom:12px;outline:none}
|
|
.modal input:focus,.modal select:focus,.modal textarea:focus{border-color:var(--accent)}
|
|
.modal textarea{min-height:150px;font-family:'JetBrains Mono',monospace;resize:vertical}
|
|
.modal-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}
|
|
|
|
/* Responsive */
|
|
@media(max-width:900px){body{grid-template-columns:1fr;grid-template-rows:56px 1fr}.sidebar,.panel{display:none}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- HEADER -->
|
|
<div class="hdr">
|
|
<div class="hdr-left">
|
|
<div class="hdr-logo"><div class="dot"></div>WEVIA <span>VAULT</span></div>
|
|
<div class="hdr-stats">
|
|
<span>Notes: <b id="hFiles">—</b></span>
|
|
<span>Dirs: <b id="hDirs">—</b></span>
|
|
<span>Size: <b id="hSize">—</b></span>
|
|
<span>Qdrant: <b id="hQdrant">16 vectors</b></span>
|
|
</div>
|
|
</div>
|
|
<div class="hdr-right">
|
|
<button class="btn" onclick="reEmbed()">🧠 Re-Embed</button>
|
|
<button class="btn btn-primary" onclick="showNewNote()">+ New Note</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SIDEBAR -->
|
|
<div class="sidebar" id="sidebar">
|
|
<h3>Vault Explorer</h3>
|
|
<div id="dirList"></div>
|
|
</div>
|
|
|
|
<!-- MAIN -->
|
|
<div class="main" id="main">
|
|
<!-- Search -->
|
|
<div class="search-wrap">
|
|
<input type="text" id="searchInput" placeholder="Search vault... (semantic or full-text)" onkeydown="if(event.key==='Enter')doSearch()">
|
|
<div class="search-type">
|
|
<label><input type="radio" name="stype" value="semantic" checked> Semantic (AI)</label>
|
|
<label><input type="radio" name="stype" value="fulltext"> Full-text</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="stats-grid" id="statsGrid">
|
|
<div class="stat-card"><div class="val" id="sNotes">—</div><div class="label">Notes</div></div>
|
|
<div class="stat-card"><div class="val" id="sDirs">—</div><div class="label">Directories</div></div>
|
|
<div class="stat-card"><div class="val" id="sSize">—</div><div class="label">Total Size</div></div>
|
|
<div class="stat-card"><div class="val" id="sTools">238</div><div class="label">Tools Wired</div></div>
|
|
</div>
|
|
|
|
<!-- Results / Note Viewer -->
|
|
<div id="results" class="results"></div>
|
|
<div id="noteView" class="note-view" style="display:none">
|
|
<div class="note-header">
|
|
<div>
|
|
<div class="note-title" id="noteTitle"></div>
|
|
<div class="note-meta" id="noteMeta"></div>
|
|
</div>
|
|
<div>
|
|
<button class="btn" id="editBtn" onclick="toggleEdit()">✏️ Edit</button>
|
|
<button class="btn" id="saveBtn" onclick="saveNote()" style="display:none">💾 Save</button>
|
|
</div>
|
|
</div>
|
|
<div class="note-content" id="noteContent"></div>
|
|
<textarea id="noteEditor" style="display:none"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RIGHT PANEL -->
|
|
<div class="panel">
|
|
<h3>Vault Graph</h3>
|
|
<div class="graph-mini"><canvas id="graphCanvas"></canvas></div>
|
|
|
|
<h3>Directories</h3>
|
|
<div id="dirStats"></div>
|
|
|
|
<h3>Crons</h3>
|
|
<div class="cron-list">
|
|
<div class="cron-item"><span>Daily metrics log</span><span class="freq">*/4h</span></div>
|
|
<div class="cron-item"><span>Qdrant re-embed</span><span class="freq">5AM</span></div>
|
|
<div class="cron-item"><span>Auto-heal FPM</span><span class="freq">*/5min</span></div>
|
|
<div class="cron-item"><span>CPU hog killer</span><span class="freq">*/10min</span></div>
|
|
</div>
|
|
|
|
<h3>Semantic Engines</h3>
|
|
<div class="activity">
|
|
<div class="activity-item"><span class="act">●</span> Qdrant (obsidian_vault collection)</div>
|
|
<div class="activity-item"><span class="act">●</span> Ollama all-minilm (384-dim)</div>
|
|
<div class="activity-item"><span class="act">●</span> Full-text PHP search</div>
|
|
</div>
|
|
|
|
<h3>Quick Actions</h3>
|
|
<div style="display:flex;flex-direction:column;gap:6px">
|
|
<button class="btn" onclick="masterCmd('vault obsidian stats')" style="width:100%">📊 Master: Vault Stats</button>
|
|
<button class="btn" onclick="masterCmd('nonreg')" style="width:100%">🧪 Master: NonReg</button>
|
|
<button class="btn" onclick="masterCmd('diagnostic complet')" style="width:100%">🔍 Master: Diagnostic</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NEW NOTE MODAL -->
|
|
<div class="modal-bg" id="modalBg" onclick="if(event.target===this)this.classList.remove('show')">
|
|
<div class="modal">
|
|
<h2>📝 New Note</h2>
|
|
<select id="newDir">
|
|
<option value="doctrines">doctrines/</option>
|
|
<option value="decisions">decisions/</option>
|
|
<option value="sessions">sessions/</option>
|
|
<option value="ethica">ethica/</option>
|
|
<option value="infra">infra/</option>
|
|
<option value="kb">kb/</option>
|
|
<option value="tools">tools/</option>
|
|
<option value="daily">daily/</option>
|
|
<option value="arena">arena/</option>
|
|
</select>
|
|
<input type="text" id="newFilename" placeholder="filename (without .md)">
|
|
<input type="text" id="newTags" placeholder="tags: doctrine, critical, backup">
|
|
<textarea id="newContent" placeholder="# Title\n\nContent here..."></textarea>
|
|
<div class="modal-actions">
|
|
<button class="btn" onclick="document.getElementById('modalBg').classList.remove('show')">Cancel</button>
|
|
<button class="btn btn-primary" onclick="createNote()">Create Note</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API_VAULT = '/api/wevia-vault.php';
|
|
const API_SEMANTIC = '/api/wevia-vault-search.php';
|
|
const API_MASTER = '/api/wevia-master-api.php';
|
|
let currentFile = null;
|
|
let currentDir = null;
|
|
|
|
// === INIT ===
|
|
async function init() {
|
|
await loadStats();
|
|
await loadDirs();
|
|
drawGraph();
|
|
}
|
|
|
|
// === STATS ===
|
|
async function loadStats() {
|
|
try {
|
|
const r = await fetch(API_VAULT + '?action=stats');
|
|
const d = await r.json();
|
|
document.getElementById('sNotes').textContent = d.files || 0;
|
|
document.getElementById('hFiles').textContent = d.files || 0;
|
|
document.getElementById('sDirs').textContent = (d.dirs||[]).length;
|
|
document.getElementById('hDirs').textContent = (d.dirs||[]).length;
|
|
const kb = Math.round((d.bytes||0)/1024);
|
|
document.getElementById('sSize').textContent = kb + 'KB';
|
|
document.getElementById('hSize').textContent = kb + 'KB';
|
|
|
|
// Dir stats panel
|
|
const ds = document.getElementById('dirStats');
|
|
ds.innerHTML = (d.dirs||[]).map(dr =>
|
|
`<div class="activity-item"><span style="min-width:80px">${dr.name}/</span><b>${dr.files}</b> notes</div>`
|
|
).join('');
|
|
} catch(e) { console.error(e); }
|
|
}
|
|
|
|
// === DIRS ===
|
|
async function loadDirs() {
|
|
try {
|
|
const r = await fetch(API_VAULT + '?action=list');
|
|
const d = await r.json();
|
|
const sb = document.getElementById('dirList');
|
|
const icons = {doctrines:'📜',tools:'🔧',sessions:'📅',decisions:'⚖️',ethica:'💊',infra:'🖥️',kb:'📚',arena:'⚡',daily:'📊'};
|
|
sb.innerHTML = `<div class="dir-item ${!currentDir?'active':''}" onclick="loadDir('')"><span class="icon">🏠</span> All<span class="count">${d.count}</span></div>`;
|
|
(d.files||[]).filter(f=>f.type==='dir').forEach(f => {
|
|
sb.innerHTML += `<div class="dir-item ${currentDir===f.name?'active':''}" onclick="loadDir('${f.name}')"><span class="icon">${icons[f.name]||'📁'}</span> ${f.name}<span class="count" id="dc_${f.name}">…</span></div>`;
|
|
// Load file count
|
|
fetch(API_VAULT+'?action=list&dir='+f.name).then(r=>r.json()).then(dd=>{
|
|
const el = document.getElementById('dc_'+f.name);
|
|
if(el) el.textContent = dd.count;
|
|
});
|
|
});
|
|
} catch(e) { console.error(e); }
|
|
}
|
|
|
|
async function loadDir(dir) {
|
|
currentDir = dir;
|
|
loadDirs();
|
|
const r = await fetch(API_VAULT + '?action=list&dir=' + dir);
|
|
const d = await r.json();
|
|
const res = document.getElementById('results');
|
|
document.getElementById('noteView').style.display = 'none';
|
|
res.innerHTML = '';
|
|
(d.files||[]).filter(f=>f.type==='file'&&f.name.endsWith('.md')).forEach(f => {
|
|
const path = dir ? dir+'/'+f.name : f.name;
|
|
res.innerHTML += `<div class="result-item" onclick="loadNote('${path}')">
|
|
<div class="file">${f.name}</div>
|
|
<div class="snippet">${(f.size/1024).toFixed(1)}KB · ${dir||'root'}</div>
|
|
</div>`;
|
|
});
|
|
}
|
|
|
|
// === SEARCH ===
|
|
async function doSearch() {
|
|
const q = document.getElementById('searchInput').value.trim();
|
|
if (!q) return;
|
|
const type = document.querySelector('input[name=stype]:checked').value;
|
|
const res = document.getElementById('results');
|
|
document.getElementById('noteView').style.display = 'none';
|
|
res.innerHTML = '<div style="text-align:center;color:var(--fg3);padding:20px">Searching...</div>';
|
|
|
|
try {
|
|
let url = type === 'semantic'
|
|
? API_SEMANTIC + '?q=' + encodeURIComponent(q)
|
|
: API_VAULT + '?action=search&q=' + encodeURIComponent(q);
|
|
const r = await fetch(url);
|
|
const d = await r.json();
|
|
if (!d.results || d.results.length === 0) {
|
|
res.innerHTML = '<div style="text-align:center;color:var(--fg3);padding:20px">No results</div>';
|
|
return;
|
|
}
|
|
res.innerHTML = `<div style="font-size:12px;color:var(--fg3);margin-bottom:4px">${d.count} results for "${d.query}" (${type})</div>`;
|
|
d.results.forEach(r => {
|
|
const score = r.score ? `<span class="score">${r.score}</span>` : '';
|
|
const tags = (r.tags||'').split(',').filter(t=>t.trim()).map(t=>`<span class="tag">${t.trim()}</span>`).join('');
|
|
res.innerHTML += `<div class="result-item" onclick="loadNote('${r.file}')">
|
|
<div class="file">${r.file} ${score}</div>
|
|
<div class="snippet">${(r.snippet||'').substring(0,200)}</div>
|
|
${tags?`<div class="tags">${tags}</div>`:''}
|
|
</div>`;
|
|
});
|
|
} catch(e) {
|
|
res.innerHTML = `<div style="color:var(--red);padding:20px">Error: ${e.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// === NOTE VIEWER ===
|
|
async function loadNote(file) {
|
|
try {
|
|
const r = await fetch(API_VAULT + '?action=read&file=' + encodeURIComponent(file));
|
|
const d = await r.json();
|
|
if (d.error) { alert(d.error); return; }
|
|
currentFile = file;
|
|
document.getElementById('results').innerHTML = '';
|
|
const nv = document.getElementById('noteView');
|
|
nv.style.display = 'block';
|
|
document.getElementById('noteTitle').textContent = file.split('/').pop();
|
|
document.getElementById('noteMeta').textContent = file;
|
|
document.getElementById('noteContent').textContent = d.content;
|
|
document.getElementById('noteContent').style.display = 'block';
|
|
document.getElementById('noteEditor').style.display = 'none';
|
|
document.getElementById('editBtn').style.display = '';
|
|
document.getElementById('saveBtn').style.display = 'none';
|
|
} catch(e) { alert(e.message); }
|
|
}
|
|
|
|
function toggleEdit() {
|
|
const content = document.getElementById('noteContent');
|
|
const editor = document.getElementById('noteEditor');
|
|
content.style.display = 'none';
|
|
editor.style.display = 'block';
|
|
editor.value = content.textContent;
|
|
document.getElementById('editBtn').style.display = 'none';
|
|
document.getElementById('saveBtn').style.display = '';
|
|
}
|
|
|
|
async function saveNote() {
|
|
const content = document.getElementById('noteEditor').value;
|
|
try {
|
|
const r = await fetch(API_VAULT, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
|
body: `action=write&file=${encodeURIComponent(currentFile)}&content=${encodeURIComponent(content)}`
|
|
});
|
|
const d = await r.json();
|
|
if (d.ok) {
|
|
document.getElementById('noteContent').textContent = content;
|
|
document.getElementById('noteContent').style.display = 'block';
|
|
document.getElementById('noteEditor').style.display = 'none';
|
|
document.getElementById('editBtn').style.display = '';
|
|
document.getElementById('saveBtn').style.display = 'none';
|
|
}
|
|
} catch(e) { alert(e.message); }
|
|
}
|
|
|
|
// === NEW NOTE ===
|
|
function showNewNote() { document.getElementById('modalBg').classList.add('show'); }
|
|
|
|
async function createNote() {
|
|
const dir = document.getElementById('newDir').value;
|
|
const name = document.getElementById('newFilename').value.trim().replace(/\s+/g,'-');
|
|
const tags = document.getElementById('newTags').value.trim();
|
|
const body = document.getElementById('newContent').value;
|
|
if (!name) { alert('Filename required'); return; }
|
|
|
|
const content = `---\ntags: [${tags}]\ncreated: ${new Date().toISOString().split('T')[0]}\n---\n${body}`;
|
|
try {
|
|
const r = await fetch(API_VAULT, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
|
body: `action=write&file=${encodeURIComponent(dir+'/'+name+'.md')}&content=${encodeURIComponent(content)}`
|
|
});
|
|
const d = await r.json();
|
|
if (d.ok) {
|
|
document.getElementById('modalBg').classList.remove('show');
|
|
document.getElementById('newFilename').value = '';
|
|
document.getElementById('newTags').value = '';
|
|
document.getElementById('newContent').value = '';
|
|
loadStats();
|
|
loadDir(dir);
|
|
}
|
|
} catch(e) { alert(e.message); }
|
|
}
|
|
|
|
// === RE-EMBED ===
|
|
async function reEmbed() {
|
|
const btn = event.target;
|
|
btn.textContent = '🧠 Embedding...';
|
|
btn.disabled = true;
|
|
try {
|
|
const r = await fetch('/api/wevia-action-engine.php', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
|
body: 'action=exec_s204&cmd=timeout+30+python3+/opt/weval-l99/tools/vault-embed.py+2>&1'
|
|
});
|
|
const d = await r.json();
|
|
btn.textContent = '🧠 Re-Embed ✅';
|
|
setTimeout(()=>{ btn.textContent='🧠 Re-Embed'; btn.disabled=false; }, 3000);
|
|
} catch(e) { btn.textContent = '🧠 Error'; btn.disabled = false; }
|
|
}
|
|
|
|
// === MASTER CMD ===
|
|
async function masterCmd(msg) {
|
|
const res = document.getElementById('results');
|
|
document.getElementById('noteView').style.display = 'none';
|
|
res.innerHTML = '<div style="text-align:center;color:var(--fg3);padding:20px">⏳ Asking Master...</div>';
|
|
try {
|
|
const r = await fetch(API_MASTER, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({message: msg})
|
|
});
|
|
const d = await r.json();
|
|
res.innerHTML = `<div class="result-item"><div class="file">Master Response (${d.source||'?'})</div><pre class="snippet" style="white-space:pre-wrap;max-height:400px;overflow:auto">${typeof d.content==='string'?d.content:JSON.stringify(d.content,null,2)}</pre></div>`;
|
|
} catch(e) {
|
|
res.innerHTML = `<div style="color:var(--red);padding:20px">Master timeout</div>`;
|
|
}
|
|
}
|
|
|
|
// === MINI GRAPH ===
|
|
function drawGraph() {
|
|
const canvas = document.getElementById('graphCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
canvas.width = canvas.offsetWidth * 2;
|
|
canvas.height = canvas.offsetHeight * 2;
|
|
ctx.scale(2, 2);
|
|
const W = canvas.offsetWidth, H = canvas.offsetHeight;
|
|
|
|
const dirs = ['doctrines','tools','sessions','infra','kb','ethica','arena','daily','decisions'];
|
|
const colors = ['#58a6ff','#3fb950','#d29922','#f85149','#bc8cff','#39d353','#ff7b72','#79c0ff','#d2a8ff'];
|
|
const cx = W/2, cy = H/2;
|
|
|
|
// Center node
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, 12, 0, Math.PI*2);
|
|
ctx.fillStyle = '#58a6ff';
|
|
ctx.fill();
|
|
ctx.font = '8px DM Sans';
|
|
ctx.fillStyle = '#fff';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText('VAULT', cx, cy+3);
|
|
|
|
// Directory nodes
|
|
dirs.forEach((d, i) => {
|
|
const angle = (i / dirs.length) * Math.PI * 2 - Math.PI/2;
|
|
const r = Math.min(W, H) * 0.35;
|
|
const x = cx + r * Math.cos(angle);
|
|
const y = cy + r * Math.sin(angle);
|
|
|
|
// Edge
|
|
ctx.beginPath();
|
|
ctx.moveTo(cx, cy);
|
|
ctx.lineTo(x, y);
|
|
ctx.strokeStyle = colors[i] + '40';
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
|
|
// Node
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, 8, 0, Math.PI*2);
|
|
ctx.fillStyle = colors[i];
|
|
ctx.fill();
|
|
|
|
// Label
|
|
ctx.font = '7px DM Sans';
|
|
ctx.fillStyle = '#8b949e';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText(d, x, y + 16);
|
|
});
|
|
}
|
|
|
|
window.addEventListener('resize', drawGraph);
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html>
|