1210 lines
62 KiB
HTML
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,'<') + '</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>
|