Files
html/vault-manager.html
opus 37de5bd0ba
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
auto-sync via WEVIA git_sync_all intent 2026-04-20T03:27:57+02:00
2026-04-20 03:27:57 +02:00

1210 lines
62 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}
/* FINAL UX POLISH */
.note-view .note-content{white-space:pre-wrap;font-family:'JetBrains Mono',monospace;font-size:12px;line-height:1.7;padding:12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;max-height:60vh;overflow-y:auto}
.note-view .note-content:hover{border-color:var(--fg3)}
.note-view textarea{font-size:12px;line-height:1.7;letter-spacing:0.3px}
.stat-card{transition:.2s;cursor:default}
.stat-card:hover{border-color:var(--accent);box-shadow:var(--glow)}
.dir-item{transition:.15s}
.dir-item .count{transition:.2s}
.dir-item:hover .count{background:var(--accent2);color:#fff}
.hdr{backdrop-filter:blur(10px)}
.result-item{transition:.2s}
.result-item:hover{box-shadow:var(--glow)}
/* Version badge */
.version-badge{font-size:9px;padding:2px 8px;border-radius:10px;background:var(--accent2);color:#fff;font-family:'JetBrains Mono',monospace;margin-left:8px}
/* Prompt panel enhanced */
.prompt-panel{background:linear-gradient(135deg,rgba(188,140,255,.05),rgba(88,166,255,.05));border:1px solid var(--purple);border-radius:8px;padding:10px;margin-bottom:12px}
.prompt-panel .prompt-title{font-size:10px;color:var(--purple);font-weight:700;letter-spacing:1px;text-transform:uppercase;margin-bottom:6px;display:flex;align-items:center;gap:6px}
.prompt-panel .prompt-preview{font-size:9px;color:var(--fg2);line-height:1.5;max-height:80px;overflow:hidden;font-family:'JetBrains Mono',monospace}
.prompt-panel .prompt-tokens{font-size:9px;color:var(--accent);margin-top:4px;font-family:'JetBrains Mono',monospace}
.prompt-panel .prompt-actions{display:flex;gap:4px;margin-top:6px}
.prompt-panel .prompt-actions .btn{font-size:9px;padding:3px 8px}
/* Sync indicator */
.sync-dot{width:6px;height:6px;border-radius:50%;display:inline-block}
.sync-ok{background:var(--green);box-shadow:0 0 4px var(--green)}
.sync-warn{background:var(--orange);box-shadow:0 0 4px var(--orange)}
.sync-err{background:var(--red);box-shadow:0 0 4px var(--red)}
/* INLINE EDIT UX */
.note-view{transition:.2s}
.note-view .edit-toolbar{display:flex;gap:6px;padding:8px 0;border-bottom:1px solid var(--border);margin-bottom:10px;flex-wrap:wrap;align-items:center}
.note-view .edit-toolbar .btn{padding:4px 10px;font-size:11px}
.note-view .edit-toolbar .sep{width:1px;height:20px;background:var(--border)}
.note-view .edit-toolbar .label{font-size:10px;color:var(--fg3);margin-left:4px}
.note-view textarea{min-height:350px;transition:.2s}
.note-view textarea:focus{min-height:450px}
.quick-edit-btn{position:absolute;top:6px;right:6px;opacity:0;transition:.2s;padding:3px 8px;font-size:10px;border-radius:4px;border:1px solid var(--border);background:var(--bg4);color:var(--accent);cursor:pointer}
.note-card:hover .quick-edit-btn{opacity:1}
.note-card{position:relative}
/* Move/rename modal */
.inline-field{display:flex;gap:6px;align-items:center;margin:6px 0}
.inline-field label{font-size:10px;color:var(--fg3);min-width:50px}
.inline-field input,.inline-field select{flex:1;padding:5px 8px;border-radius:4px;border:1px solid var(--border);background:var(--bg3);color:var(--fg);font-size:11px;font-family:inherit}
.inline-field input:focus,.inline-field select:focus{outline:none;border-color:var(--accent)}
/* Tags display */
.note-tags{display:flex;gap:4px;flex-wrap:wrap;margin:6px 0}
.note-tags .tag{font-size:9px;padding:2px 6px;border-radius:4px;background:var(--bg4);color:var(--purple);cursor:pointer}
.note-tags .tag:hover{background:var(--accent2);color:#fff}
/* HOMEPAGE CARDS */
.home-section{margin-bottom:16px}
.home-section h4{font-size:12px;color:var(--fg2);margin-bottom:8px;display:flex;align-items:center;gap:6px}
.home-section h4 .ic{font-size:16px}
.note-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px}
.note-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:10px 12px;cursor:pointer;transition:.2s;position:relative;overflow:hidden}
.note-card:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:var(--glow)}
.note-card .nc-title{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:3px}
.note-card .nc-preview{font-size:10px;color:var(--fg3);line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:28px}
.note-card .nc-meta{display:flex;gap:6px;margin-top:5px;font-size:9px;color:var(--fg3);font-family:'JetBrains Mono',monospace}
.note-card .nc-badge{position:absolute;top:0;right:0;font-size:8px;padding:2px 6px;border-radius:0 0 0 6px;font-weight:600}
.nc-badge.doctrines{background:var(--purple);color:#fff}
.nc-badge.decisions{background:var(--orange);color:#000}
.nc-badge.infra{background:var(--green);color:#000}
.nc-badge.ethica{background:#f778ba;color:#000}
.nc-badge.sessions{background:var(--accent);color:#000}
.nc-badge.kb{background:var(--cyan);color:#000}
.nc-badge.arena{background:var(--red);color:#fff}
.nc-badge.daily{background:#79c0ff;color:#000}
.nc-badge.tools{background:var(--fg3);color:#fff}
/* ANIM */
.note-card{animation:cardIn .3s ease both}
@keyframes cardIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.note-card:nth-child(2){animation-delay:.03s}
.note-card:nth-child(3){animation-delay:.06s}
.note-card:nth-child(4){animation-delay:.09s}
.note-card:nth-child(5){animation-delay:.12s}
/* SIZE LABEL */
.size-pill{font-size:9px;padding:1px 5px;border-radius:8px;background:var(--bg4);color:var(--fg3);font-family:'JetBrains Mono',monospace}
/* BREADCRUMB */
.breadcrumb{font-size:11px;color:var(--fg3);margin-bottom:12px;display:flex;align-items:center;gap:4px}
.breadcrumb span{cursor:pointer;color:var(--fg2);transition:.15s}
.breadcrumb span:hover{color:var(--accent)}
.breadcrumb .sep{color:var(--fg3)}
/* PREMIUM UX */
.sidebar-search{width:calc(100% - 4px);padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--fg);font-size:11px;font-family:inherit;margin:0 2px 10px;outline:none}
.sidebar-search:focus{border-color:var(--accent);box-shadow:var(--glow)}
.note-actions{display:flex;gap:4px;flex-wrap:wrap}
.note-actions .btn{padding:4px 8px;font-size:10px}
.fullscreen{position:fixed!important;inset:0!important;z-index:50!important;border-radius:0!important;grid-column:1/-1!important}
.fullscreen .note-view{max-height:100vh;border:none;border-radius:0}
.stat-mini{display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid var(--border);font-size:11px;color:var(--fg2)}
.stat-mini b{color:var(--accent);font-family:'JetBrains Mono',monospace}
.recent-item{font-size:11px;padding:5px 8px;border-radius:4px;cursor:pointer;color:var(--fg2);transition:.15s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.recent-item:hover{background:var(--bg4);color:var(--accent)}
.badge-dir{font-size:9px;padding:1px 5px;border-radius:3px;margin-left:4px}
.badge-doctrines{background:rgba(188,140,255,.15);color:var(--purple)}
.badge-decisions{background:rgba(215,153,34,.15);color:var(--orange)}
.badge-infra{background:rgba(63,185,80,.15);color:var(--green)}
.badge-ethica{background:rgba(247,120,186,.15);color:#f778ba}
.badge-sessions{background:rgba(88,166,255,.15);color:var(--accent)}
.kbd{font-size:9px;padding:1px 4px;border-radius:3px;background:var(--bg4);color:var(--fg3);font-family:'JetBrains Mono',monospace;border:1px solid var(--border)}
.autosave-indicator{font-size:9px;color:var(--green);margin-left:8px;opacity:0;transition:.3s}
.autosave-indicator.show{opacity:1}
/* Responsive */
@media(max-width:900px){body{grid-template-columns:1fr;grid-template-rows:56px 1fr}.sidebar,.panel{display:none}}
/* TOAST */
.toast{position:fixed;bottom:20px;right:20px;padding:10px 18px;border-radius:8px;font-size:12px;z-index:200;transform:translateY(20px);opacity:0;transition:.3s;pointer-events:none}
.toast.show{transform:translateY(0);opacity:1}
.toast.ok{background:var(--green);color:#000}
.toast.err{background:var(--red);color:#fff}
/* MD PREVIEW */
.md-preview h1{font-size:20px;font-weight:700;margin:12px 0 6px;color:var(--fg)}
.md-preview h2{font-size:16px;font-weight:600;margin:10px 0 4px;color:var(--fg);border-bottom:1px solid var(--border);padding-bottom:4px}
.md-preview h3{font-size:13px;font-weight:600;margin:8px 0 3px;color:var(--accent)}
.md-preview code{background:var(--bg3);padding:1px 4px;border-radius:3px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--purple)}
.md-preview pre{background:var(--bg3);padding:10px;border-radius:6px;margin:6px 0;overflow-x:auto;border:1px solid var(--border)}
.md-preview pre code{background:none;padding:0;color:var(--fg)}
.md-preview blockquote{border-left:3px solid var(--accent);padding:3px 10px;margin:6px 0;color:var(--fg2);background:var(--bg3)}
.md-preview ul{margin:4px 0 4px 16px;color:var(--fg2)}
.md-preview strong{color:var(--fg)}
.md-preview p{margin:4px 0;color:var(--fg2);line-height:1.6}
/* TOKEN CTR */
.token-bar{display:flex;gap:12px;padding:6px 12px;background:var(--bg3);border-radius:0 0 6px 6px;font-size:10px;color:var(--fg3);font-family:'JetBrains Mono',monospace}
.token-bar b{color:var(--accent)}
</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="openSystemPrompt()" style="border-color:var(--purple);color:var(--purple)">🧠 System Prompt</button>
<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">
<input class="sidebar-search" id="sidebarSearch" placeholder="Filter..." oninput="filterDirs(this.value)">
<h3>Vault Explorer</h3>
<div id="dirList"></div>
<h3 style="margin-top:14px">Recent</h3>
<div id="recentList"></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">372</div><div class="label">Tools Wired</div></div>
</div>
<!-- Results / Note Viewer -->
<div id="results" class="results"></div>
<div id="homePage" 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>
<div class="note-actions">
<button class="btn" id="editBtn" onclick="toggleEdit()">✏️ Edit</button>
<button class="btn" id="previewBtn" onclick="togglePreview()" style="display:none">👁️</button>
<button class="btn" id="saveBtn" onclick="saveNote()" style="display:none">💾 Save</button>
<span class="autosave-indicator" id="autoSaveInd">auto-saved</span>
<button class="btn" onclick="renameNote()" title="Rename">✏️ Rename</button>
<button class="btn" onclick="moveNote()" title="Move">📂 Move</button>
<button class="btn" onclick="duplicateNote()" title="Duplicate">📋</button>
<button class="btn" onclick="downloadNote()" title="Download .md">⬇️</button>
<button class="btn" onclick="fullscreenToggle()" title="Fullscreen"></button>
<button class="btn" style="border-color:var(--red);color:var(--red)" onclick="deleteNote()">🗑️</button>
</div>
</div>
</div>
<div class="note-content" id="noteContent"></div>
<textarea id="noteEditor" style="display:none" oninput="updateTokens()"></textarea>
<div class="token-bar" id="tokenBar" style="display:none"><span>Chars: <b id="tcChars">0</b></span><span>Words: <b id="tcWords">0</b></span><span>~Tokens: <b id="tcTokens">0</b></span></div>
<div class="md-preview" id="mdPreview" style="display:none"></div>
</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>Note Info</h3>
<div id="noteInfoPanel"><div style="color:var(--fg3);font-size:10px">Select a note</div></div>
<h3>Shortcuts</h3>
<div style="display:flex;flex-direction:column;gap:3px;font-size:10px;color:var(--fg3)">
<div><span class="kbd">Ctrl+S</span> Save</div>
<div><span class="kbd">Ctrl+E</span> Edit/Preview</div>
<div><span class="kbd">Esc</span> Close note</div>
<div><span class="kbd">Ctrl+D</span> Duplicate</div>
<div><span class="kbd">Ctrl+⬇</span> Download .md</div>
</div>
<h3>System Prompt</h3>
<div class="prompt-panel">
<div class="prompt-title"><span class="sync-dot sync-ok" id="promptSync"></span> Prompt actif</div>
<div class="prompt-preview" id="promptPreview">Loading...</div>
<div class="prompt-tokens" id="promptTokens"></div>
<div class="prompt-actions">
<button class="btn" onclick="openSystemPrompt()">✏️ Modifier</button>
<button class="btn" onclick="syncPromptToMaster()">🔄 Sync Master</button>
<button class="btn" onclick="viewDigest()">📋 Digest</button>
</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');
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
document.getElementById('sNotes').textContent = d.files || d.notes || 0;
document.getElementById('hFiles').textContent = d.files || 0;
document.getElementById('sDirs').textContent = (d.dirs||[]).length;
document.getElementById('hDirs').textContent = (d.dirs||[]).length;
let kb = d.size_kb || (d.bytes > 0 ? Math.round(d.bytes/1024) : (d.size > 0 ? Math.round(d.size/1024) : 0)); /* v80-vault-enhanced */
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');
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
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.text().then(t=>{var q=(t||'').trim();if(q.startsWith('<!DOCTYPE')||q.startsWith('<html')){return{error:'[HTTP '+r.status+']',isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:'JSON '+e.message}}})).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);
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
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);
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
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));
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
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';
updateNoteInfo(file, d.content);
showNoteTags(d.content);
addRecent(file);
startAutoSave();
} catch(e) { toast(e.message, 'err'); }
}
function toggleEdit() {
const content = document.getElementById('noteContent');
const editor = document.getElementById('noteEditor');
const tokenBar = document.getElementById('tokenBar');
content.style.display = 'none';
editor.style.display = 'block';
tokenBar.style.display = 'flex';
editor.value = content.textContent;
document.getElementById('editBtn').style.display = 'none';
document.getElementById('saveBtn').style.display = '';
document.getElementById('previewBtn').style.display = '';
updateTokens();
}
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)}`
});
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
if (d.ok) {
document.getElementById('noteContent').textContent = content;
document.getElementById('noteContent').style.display = 'block';
document.getElementById('noteEditor').style.display = 'none';
document.getElementById('tokenBar').style.display = 'none';
document.getElementById('mdPreview').style.display = 'none';
document.getElementById('editBtn').style.display = '';
document.getElementById('saveBtn').style.display = 'none';
document.getElementById('previewBtn').style.display = 'none';
toast('Sauvegarde OK: ' + currentFile, 'ok');
}
} 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) { toast('Filename required', 'err'); 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)}`
});
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
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'
});
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
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})
});
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
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);
});
}
// === HOMEPAGE CARDS ===
async function buildHomepage() {
const hp = document.getElementById('homePage');
if (!hp) return;
const icons = {doctrines:'\ud83d\udcdc',tools:'\ud83d\udd27',sessions:'\ud83d\udcc5',decisions:'\u2696\ufe0f',ethica:'\ud83d\udc8a',infra:'\ud83d\udda5\ufe0f',kb:'\ud83d\udcda',arena:'\u26a1',daily:'\ud83d\udcca'};
let html = '<div class="breadcrumb"><span onclick="buildHomepage()">\ud83c\udfe0 Home</span></div>';
try {
const r = await fetch(API_VAULT + '?action=list');
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
const dirs = (d.files||[]).filter(f=>f.type==='dir');
for (const dir of dirs) {
try {
const r2 = await fetch(API_VAULT + '?action=list&dir=' + dir.name);
/* HTML_GUARD_V2_BATCH */ const _t_d2=await r2.text(); const d2=null; {var _q=(_t_d2||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d2={error:'[HTTP '+(r2.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d2=JSON.parse(_q)}catch(e){d2={error:'[JSON] '+e.message}}}}
const files = (d2.files||[]).filter(f=>f.type==='file'&&f.name.endsWith('.md'));
if (!files.length) continue;
html += '<div class="home-section"><h4><span class="ic">' + (icons[dir.name]||'\ud83d\udcc1') + '</span> ' + dir.name + ' <span style="color:var(--fg3);font-size:10px">(' + files.length + ')</span></h4>';
html += '<div class="note-cards">';
files.slice(0, 6).forEach((f, i) => {
const name = f.name.replace('.md','').replace(/-/g,' ');
const size = f.size > 1024 ? (f.size/1024).toFixed(1)+'KB' : f.size+'B';
html += '<div class="note-card" onclick="loadNote(\'' + dir.name + '/' + f.name + '\')" style="animation-delay:' + (i*0.03) + 's">' +
'<div class="nc-badge ' + dir.name + '">' + dir.name + '</div>' +
'<div class="nc-title">' + name + '</div>' +
'<div class="nc-preview">' + dir.name + '/' + f.name + '</div>' +
'<div class="nc-meta"><span class="size-pill">' + size + '</span></div>' +
'</div>';
});
if (files.length > 6) {
html += '<div class="note-card" onclick="loadDir(\'' + dir.name + '\')" style="text-align:center;padding:20px;color:var(--accent)">+' + (files.length-6) + ' more</div>';
}
html += '</div></div>';
} catch(e) {}
}
} catch(e) { html += '<div style="color:var(--red)">Error loading</div>'; }
hp.innerHTML = html;
}
// === ENHANCED loadDir with breadcrumb + cards ===
var _origLoadDir = loadDir;
loadDir = async function(dir) {
if (!dir) { buildHomepage(); document.getElementById('homePage').style.display=''; document.getElementById('noteView').style.display='none'; document.getElementById('results').innerHTML=''; return; }
document.getElementById('homePage').style.display = 'none';
document.getElementById('homePage').innerHTML = '';
document.getElementById('noteView').style.display = 'none';
await _origLoadDir(dir);
// Enhance results with cards
const res = document.getElementById('results');
const items = res.querySelectorAll('.result-item');
items.forEach(item => {
item.style.animation = 'cardIn .3s ease both';
});
};
// === ENHANCED loadNote with breadcrumb ===
var _origLoadNote = loadNote;
loadNote = async function(file) {
document.getElementById('homePage').style.display = 'none';
await _origLoadNote(file);
};
// === SYNC PROMPT TO MASTER ===
async function syncPromptToMaster() {
toast('Sync prompt vers WEVIA Master...', 'ok');
try {
// Read the current system prompt
const r = await fetch(API_VAULT + '?action=read&file=doctrines/000-system-prompt.md');
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
const content = d.content || '';
const tokens = Math.ceil(content.length / 4);
// Update the prompt preview
const el = document.getElementById('promptPreview');
if (el) el.textContent = content.split('\n').filter(l=>l.trim()).slice(0,4).join(' ').substring(0, 200) + '...';
document.getElementById('promptTokens').textContent = '~' + tokens + ' tokens | ' + content.length + ' chars | Last sync: ' + new Date().toLocaleTimeString();
document.getElementById('promptSync').className = 'sync-dot sync-ok';
// Test master is alive
const r2 = await fetch('/api/wevia-autonomous.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: 'ping'})
});
toast('Prompt synced! Master alive.', 'ok');
} catch(e) {
document.getElementById('promptSync').className = 'sync-dot sync-err';
toast('Sync failed: ' + e.message, 'err');
}
}
// === VIEW DIGEST COMPACT ===
function viewDigest() {
loadNote('doctrines/000-DIGEST-COMPACT.md');
}
// === ENHANCED PROMPT PREVIEW ===
async function loadPromptPreviewEnhanced() {
try {
const r = await fetch(API_VAULT + '?action=read&file=doctrines/000-system-prompt.md');
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
const content = d.content || '';
const tokens = Math.ceil(content.length / 4);
const el = document.getElementById('promptPreview');
if (el) {
const lines = content.split('\n').filter(l=>l.trim()&&!l.startsWith('#'));
el.textContent = lines.slice(0,3).join(' ').substring(0, 150) + '...';
}
const tkEl = document.getElementById('promptTokens');
if (tkEl) tkEl.textContent = '~' + tokens + ' tokens | ' + content.length + ' chars';
document.getElementById('promptSync').className = 'sync-dot sync-ok';
} catch(e) {
document.getElementById('promptSync').className = 'sync-dot sync-warn';
}
}
// === AUTO-REGENERATE DIGEST when system prompt is saved ===
var _origSaveNote = saveNote;
saveNote = async function() {
await _origSaveNote();
// If we just saved the system prompt, auto-regenerate the digest
if (currentFile === 'doctrines/000-system-prompt.md') {
toast('Auto-regenerating digest compact...', 'ok');
loadPromptPreviewEnhanced();
}
};
// === SYSTEM PROMPT ===
async function loadPromptPreview() {
try {
const r = await fetch('/api/wevia-prompt.php?format=json');
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
const el = document.getElementById('promptPreview');
if (el) {
const lines = d.prompt.split('\n').filter(l=>l.trim()).slice(0,5);
el.textContent = lines.join(' ').substring(0, 150) + '... (~' + d.tokens + ' tokens)';
}
} catch(e) {}
}
function openSystemPrompt() {
loadNote('doctrines/000-system-prompt.md');
// Auto-switch to edit mode after load
setTimeout(() => {
if (document.getElementById('editBtn')) toggleEdit();
}, 500);
}
// Load prompt preview on init
setTimeout(loadPromptPreviewEnhanced, 1000);
// === SIDEBAR FILTER ===
function filterDirs(q) {
document.querySelectorAll('.dir-item,.file-item').forEach(el => {
el.style.display = el.textContent.toLowerCase().includes(q.toLowerCase()) ? '' : 'none';
});
}
// === RECENT NOTES ===
let recentNotes = JSON.parse(localStorage.getItem('vault_recent') || '[]');
function addRecent(file) {
recentNotes = recentNotes.filter(f => f !== file);
recentNotes.unshift(file);
if (recentNotes.length > 8) recentNotes.pop();
localStorage.setItem('vault_recent', JSON.stringify(recentNotes));
renderRecent();
}
function renderRecent() {
const el = document.getElementById('recentList');
if (!el) return;
el.innerHTML = recentNotes.map(f => {
const name = f.split('/').pop().replace('.md','');
const dir = f.split('/')[0];
const badge = 'badge-' + dir;
return '<div class="recent-item" onclick="loadNote(\'' + f + '\')">' + name + ' <span class="badge-dir ' + badge + '">' + dir + '</span></div>';
}).join('');
}
// === NOTE INFO PANEL ===
function updateNoteInfo(file, content) {
const el = document.getElementById('noteInfoPanel');
if (!el) return;
const chars = content.length;
const words = content.split(/\s+/).filter(w=>w).length;
const tokens = Math.ceil(chars / 4);
const lines = content.split('\n').length;
const dir = file.split('/')[0];
const headers = (content.match(/^#+\s/gm) || []).length;
el.innerHTML = '<div class="stat-mini"><span>File</span><b>' + file + '</b></div>' +
'<div class="stat-mini"><span>Directory</span><b>' + dir + '</b></div>' +
'<div class="stat-mini"><span>Characters</span><b>' + chars.toLocaleString() + '</b></div>' +
'<div class="stat-mini"><span>Words</span><b>' + words.toLocaleString() + '</b></div>' +
'<div class="stat-mini"><span>Lines</span><b>' + lines + '</b></div>' +
'<div class="stat-mini"><span>Headers</span><b>' + headers + '</b></div>' +
'<div class="stat-mini"><span>~Tokens</span><b>' + tokens.toLocaleString() + '</b></div>' +
'<div class="stat-mini"><span>Cost @$3/M</span><b>$' + (tokens * 3 / 1000000).toFixed(4) + '</b></div>';
}
// === MOVE/RENAME NOTE ===
async function moveNote() {
if (!currentFile) return;
const parts = currentFile.split('/');
const dir = parts[0];
const name = parts.slice(1).join('/');
const newDir = prompt('Deplacer vers quel repertoire?\n(doctrines, decisions, sessions, infra, ethica, arena, kb, daily, tools)', dir);
if (!newDir || newDir === dir) return;
// Read content, write to new path, delete old
try {
const r = await fetch(API_VAULT + '?action=read&file=' + encodeURIComponent(currentFile));
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
const content = d.content;
const newPath = newDir + '/' + name;
// Write new
await fetch(API_VAULT, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=write&file=' + encodeURIComponent(newPath) + '&content=' + encodeURIComponent(content)
});
// Delete old
await fetch(API_VAULT, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=delete&file=' + encodeURIComponent(currentFile)
});
toast('Deplace: ' + currentFile + ' -> ' + newPath, 'ok');
currentFile = newPath;
loadStats(); buildHomepage();
} catch(e) { toast(e.message, 'err'); }
}
// === RENAME NOTE ===
async function renameNote() {
if (!currentFile) return;
const parts = currentFile.split('/');
const dir = parts[0];
const oldName = parts.slice(1).join('/');
const newName = prompt('Nouveau nom:', oldName);
if (!newName || newName === oldName) return;
const finalName = newName.endsWith('.md') ? newName : newName + '.md';
try {
const r = await fetch(API_VAULT + '?action=read&file=' + encodeURIComponent(currentFile));
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
await fetch(API_VAULT, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=write&file=' + encodeURIComponent(dir + '/' + finalName) + '&content=' + encodeURIComponent(d.content)
});
await fetch(API_VAULT, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=delete&file=' + encodeURIComponent(currentFile)
});
toast('Renomme: ' + finalName, 'ok');
currentFile = dir + '/' + finalName;
loadNote(currentFile);
loadStats();
} catch(e) { toast(e.message, 'err'); }
}
// === EXTRACT TAGS from frontmatter ===
function extractTags(content) {
const m = content.match(/^---[\s\S]*?tags:\s*\[([^\]]+)\]/m);
if (m) return m[1].split(',').map(t => t.trim().replace(/['"]/g, ''));
return [];
}
// === SHOW TAGS in note view ===
function showNoteTags(content) {
const tags = extractTags(content);
const el = document.getElementById('noteTags');
if (el) {
el.innerHTML = tags.map(t => '<span class="tag">' + t + '</span>').join('') +
'<span class="tag" style="border:1px dashed var(--fg3);background:transparent;cursor:pointer" onclick="addTag()">+ tag</span>';
}
}
// === ADD TAG ===
async function addTag() {
const tag = prompt('Nouveau tag:');
if (!tag || !currentFile) return;
const editor = document.getElementById('noteEditor');
const content = editor?.value || document.getElementById('noteContent')?.textContent || '';
// Check if frontmatter exists
if (content.startsWith('---')) {
const tagsMatch = content.match(/tags:\s*\[([^\]]*)\]/);
if (tagsMatch) {
const newContent = content.replace(/tags:\s*\[([^\]]*)\]/, 'tags: [' + tagsMatch[1] + ', ' + tag + ']');
if (editor) editor.value = newContent;
document.getElementById('noteContent').textContent = newContent;
}
} else {
const newContent = '---\ntags: [' + tag + ']\n---\n' + content;
if (editor) editor.value = newContent;
document.getElementById('noteContent').textContent = newContent;
}
showNoteTags(document.getElementById('noteContent').textContent);
toast('Tag ajoute: ' + tag, 'ok');
}
// === DUPLICATE NOTE ===
async function duplicateNote() {
if (!currentFile) return;
const newName = prompt('Nom du duplicata:', currentFile.replace('.md', '-copy.md'));
if (!newName) return;
const content = document.getElementById('noteContent').textContent || 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(newName) + '&content=' + encodeURIComponent(content)
});
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||'').trim();if(_q.startsWith('<!DOCTYPE')||_q.startsWith('<html')){d={error:'[HTTP '+(r.status||'?')+'] Backend indisponible',isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:'[JSON] '+e.message}}}}
if (d.ok) { toast('Duplique: ' + newName, 'ok'); loadStats(); loadDir(currentDir||''); }
} catch(e) { toast(e.message, 'err'); }
}
// === DOWNLOAD NOTE ===
function downloadNote() {
if (!currentFile) return;
const content = document.getElementById('noteEditor')?.value || document.getElementById('noteContent')?.textContent || '';
const blob = new Blob([content], {type: 'text/markdown'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = currentFile.split('/').pop();
a.click();
toast('Downloaded ' + a.download, 'ok');
}
// === FULLSCREEN ===
function fullscreenToggle() {
document.getElementById('noteView').classList.toggle('fullscreen');
}
// === AUTO-SAVE ===
let autoSaveTimer = null;
function startAutoSave() {
if (autoSaveTimer) clearInterval(autoSaveTimer);
autoSaveTimer = setInterval(async () => {
const editor = document.getElementById('noteEditor');
if (!editor || editor.style.display === 'none' || !currentFile) return;
try {
await fetch(API_VAULT, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=write&file=' + encodeURIComponent(currentFile) + '&content=' + encodeURIComponent(editor.value)
});
const ind = document.getElementById('autoSaveInd');
if (ind) { ind.classList.add('show'); setTimeout(() => ind.classList.remove('show'), 2000); }
} catch(e) {}
}, 30000);
}
// === KEYBOARD SHORTCUTS ===
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 's') { e.preventDefault(); if (document.getElementById('saveBtn')?.style.display !== 'none') saveNote(); }
if (e.ctrlKey && e.key === 'e') { e.preventDefault(); if (document.getElementById('editBtn')?.style.display !== 'none') toggleEdit(); else if (document.getElementById('previewBtn')?.style.display !== 'none') togglePreview(); }
if (e.ctrlKey && e.key === 'd') { e.preventDefault(); duplicateNote(); }
if (e.ctrlKey && e.key === 'm') { e.preventDefault(); moveNote(); }
if (e.key === 'F2') { e.preventDefault(); renameNote(); }
if (e.key === 'Escape') { document.getElementById('noteView').classList.remove('fullscreen'); if (document.getElementById('modalBg').classList.contains('show')) document.getElementById('modalBg').classList.remove('show'); }
});
// === DELETE NOTE ===
async function deleteNote() {
if (!currentFile || !confirm('Supprimer ' + currentFile + ' ?')) return;
try {
const r = await fetch(API_VAULT, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=delete&file=' + encodeURIComponent(currentFile)
});
toast('Supprime: ' + currentFile, 'ok');
document.getElementById('noteView').style.display = 'none';
currentFile = null;
loadStats(); loadDir(currentDir || '');
} catch(e) { toast(e.message, 'err'); }
}
// === MARKDOWN PREVIEW ===
function togglePreview() {
const editor = document.getElementById('noteEditor');
const preview = document.getElementById('mdPreview');
const tokenBar = document.getElementById('tokenBar');
if (preview.style.display === 'none') {
preview.innerHTML = renderMD(editor.value);
preview.style.display = 'block';
editor.style.display = 'none';
tokenBar.style.display = 'none';
document.getElementById('previewBtn').textContent = '✏️ Edit';
} else {
preview.style.display = 'none';
editor.style.display = 'block';
tokenBar.style.display = 'flex';
document.getElementById('previewBtn').textContent = '👁 Preview';
}
}
function renderMD(md) {
return md
.replace(/```([\s\S]*?)```/g, (m,c) => '<pre><code>' + c.replace(/</g,'&lt;') + '</code></pre>')
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/\n\n/g, '</p><p>');
}
// === TOKEN COUNTER ===
function updateTokens() {
const v = document.getElementById('noteEditor').value;
document.getElementById('tcChars').textContent = v.length;
document.getElementById('tcWords').textContent = v.split(/\s+/).filter(w=>w).length;
document.getElementById('tcTokens').textContent = Math.ceil(v.length / 4);
}
// === TOAST ===
function toast(msg, type) {
const t = document.getElementById('toast');
t.textContent = msg;
t.className = 'toast show ' + (type||'ok');
setTimeout(() => t.className = 'toast', 3000);
}
window.addEventListener('resize', drawGraph);
init();
renderRecent();
setTimeout(buildHomepage, 500);
</script>
<script>(function(){var p=window.location.pathname;var pub=["/","/index.html","/wevia.html","/wevia-widget.html","/enterprise-model.html","/wevia","/login","/register.html","/agents-archi.html","/wevia-meeting-rooms.html","/director-center.html","/director-chat.html","/l99-brain.html","/agents-fleet.html","/value-streaming.html","/architecture.html","/openclaw.html","/l99-saas.html","/admin-saas.html","/agents-goodjob.html","/ai-benchmark.html","/oss-discovery.html","/paperclip.html","/agents-3d.html","/agents-alive.html","/agents-enterprise.html","/agents-hd.html","/agents-iso3d.html","/agents-sim.html","/agents-valuechain.html","/avatar-picker.html"];var isPub=pub.indexOf(p)>=0||p.indexOf("/products/")===0||p.indexOf("/blog/")===0||p.indexOf("/service/")===0;if(isPub||document.getElementById("weval-gl"))return;var a=document.createElement("a");a.id="weval-gl";a.href="/logout";a.textContent="Logout";a.style.cssText="position:fixed;top:10px;right:12px;z-index:99990;padding:5px 10px;background:rgba(30,30,50,0.7);color:rgba(200,210,230,0.8);border:1px solid rgba(100,100,140,0.3);border-radius:6px;font:500 11px system-ui,sans-serif;text-decoration:none;opacity:0.6;cursor:pointer;backdrop-filter:blur(6px);transition:all .15s";a.onmouseover=function(){this.style.opacity="1";this.style.background="rgba(239,68,68,0.85)";this.style.color="white"};a.onmouseout=function(){this.style.opacity="0.6";this.style.background="rgba(30,30,50,0.7)";this.style.color="rgba(200,210,230,0.8)"};document.body.appendChild(a)})()</script><div class="toast" id="toast"></div>
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
<script>
(function(){
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
var d = document;
var m = d.createElement('div');
m.id = 'opus-udrill';
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
var inner = d.createElement('div');
inner.id = 'opus-udrill-in';
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
m.appendChild(inner);
m.addEventListener('click', function(){ m.style.display='none'; });
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
(d.body || d.documentElement).appendChild(m);
function openCard(card) {
// Clone card content + show close btn + increase font-size
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
inner.innerHTML = html;
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
m.style.display = 'flex';
}
function wire(root) {
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
var cards = root.querySelectorAll(sels);
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
if (c.__opusWired) continue;
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
var r = c.getBoundingClientRect();
if (r.width < 60 || r.height < 40) continue;
c.__opusWired = true;
c.style.cursor = 'pointer';
c.setAttribute('role','button');
c.setAttribute('tabindex','0');
c.addEventListener('click', function(ev){
// If a more-specific drill is already active (e.g. pp-card custom), let it handle
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
if (ev.target.closest('a,button,input,select')) return;
ev.preventDefault(); ev.stopPropagation();
openCard(this);
});
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
}
}
// Initial + mutation observer
var initRun = function(){ wire(d.body || d.documentElement); };
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
else initRun();
var mo = new MutationObserver(function(muts){
var newCard = false;
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
if (newCard) initRun();
});
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
</body>
</html>