feat(opax): add MD docs module to sidebar + panel
This commit is contained in:
parent
b2c5813e75
commit
66c93ee41e
172
static/opax.html
172
static/opax.html
|
|
@ -218,6 +218,40 @@
|
|||
.if-btn{font-size:11px;padding:4px 10px;border-radius:6px;background:var(--bg-3);border:1px solid var(--border);color:var(--text-dim);transition:background .14s;white-space:nowrap}
|
||||
.if-btn:hover{background:var(--bg);color:var(--text)}
|
||||
.if-frame{flex:1;border:none;width:100%;height:100%}
|
||||
|
||||
/* MD MODULE */
|
||||
.md-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
|
||||
.md-card{background:var(--bg-2);border:1px solid var(--border);border-radius:var(--radius);padding:20px;display:flex;flex-direction:column;gap:10px;cursor:pointer;transition:border-color .14s,background .14s}
|
||||
.md-card:hover{border-color:var(--accent);background:rgba(124,92,255,.04)}
|
||||
.md-hd{display:flex;align-items:center;gap:10px}
|
||||
.md-icon{font-size:22px;flex-shrink:0}
|
||||
.md-name{font-size:14px;font-weight:700}
|
||||
.md-path{font-size:11px;font-family:var(--mono);color:var(--text-faint);margin-top:2px}
|
||||
.md-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
|
||||
.md-ft{display:flex;align-items:center;justify-content:space-between;margin-top:4px}
|
||||
.md-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:99px;background:rgba(124,92,255,.15);color:var(--accent)}
|
||||
.md-badge.auth{background:rgba(62,207,142,.15);color:var(--green)}
|
||||
.md-badge.append{background:rgba(0,212,168,.15);color:var(--accent-2)}
|
||||
.md-open-btn{font-size:11px;font-weight:600;padding:5px 12px;border-radius:7px;background:var(--bg-3);border:1px solid var(--border);color:var(--text-dim);transition:background .14s,color .14s}
|
||||
.md-open-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||||
|
||||
/* inline MD viewer */
|
||||
.md-viewer{flex:1;overflow-y:auto;padding:32px 40px;font-size:14px;line-height:1.75;color:var(--text)}
|
||||
.md-viewer h1{font-size:22px;font-weight:800;margin-bottom:16px;letter-spacing:-0.02em}
|
||||
.md-viewer h2{font-size:16px;font-weight:700;margin:24px 0 10px;color:var(--text)}
|
||||
.md-viewer h3{font-size:14px;font-weight:600;margin:18px 0 8px;color:var(--text-dim)}
|
||||
.md-viewer p{margin-bottom:12px;color:var(--text-dim)}
|
||||
.md-viewer code{font-family:var(--mono);font-size:12px;background:rgba(255,255,255,.06);padding:2px 6px;border-radius:4px}
|
||||
.md-viewer pre{background:var(--bg-2);border:1px solid var(--border);padding:14px 16px;border-radius:10px;overflow-x:auto;font-family:var(--mono);font-size:12px;margin:12px 0;line-height:1.6}
|
||||
.md-viewer table{width:100%;border-collapse:collapse;margin:14px 0;font-size:13px}
|
||||
.md-viewer th{text-align:left;padding:8px 12px;border-bottom:1px solid var(--border);font-size:11px;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-faint)}
|
||||
.md-viewer td{padding:9px 12px;border-bottom:1px solid var(--border);color:var(--text-dim)}
|
||||
.md-viewer tr:last-child td{border-bottom:none}
|
||||
.md-viewer ul,.md-viewer ol{margin:0 0 12px 20px;color:var(--text-dim)}
|
||||
.md-viewer li{margin-bottom:4px}
|
||||
.md-viewer blockquote{border-left:3px solid var(--accent);padding:8px 16px;margin:12px 0;background:rgba(124,92,255,.06);border-radius:0 8px 8px 0;color:var(--text-dim)}
|
||||
.md-viewer hr{border:none;border-top:1px solid var(--border);margin:20px 0}
|
||||
.md-viewer-loading{display:flex;align-items:center;justify-content:center;height:200px;color:var(--text-faint);font-family:var(--mono);font-size:13px;gap:10px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -259,6 +293,11 @@
|
|||
<span class="nav-icon">⌨️</span> Terminal
|
||||
</div>
|
||||
|
||||
<div class="side-label">Dokumentasjon</div>
|
||||
<div class="nav-item" onclick="go('md')" id="nav-md">
|
||||
<span class="nav-icon">📄</span> MD
|
||||
</div>
|
||||
|
||||
<div class="side-foot">
|
||||
<div class="me-card">
|
||||
<div class="me-name">Chris Christiansen</div>
|
||||
|
|
@ -416,6 +455,27 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MD DOCS -->
|
||||
<div class="panel" id="panel-md">
|
||||
<div class="panel-head">
|
||||
<div class="p-avatar" style="background:linear-gradient(135deg,#5c8fff,#00d4a8)">📄</div>
|
||||
<div>
|
||||
<div class="p-title">MD — Dokumenter</div>
|
||||
<div class="p-sub">Kjerndoks · vauco-saas/OSVauco</div>
|
||||
</div>
|
||||
<div class="p-actions">
|
||||
<button class="hbtn" id="md-back-btn" style="display:none" onclick="mdShowGrid()">← Tilbake</button>
|
||||
<a class="hbtn" id="md-gh-btn" style="display:none" href="#" target="_blank">GitHub ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="md-grid-view" class="panel-body">
|
||||
<div class="md-grid" id="md-grid"></div>
|
||||
</div>
|
||||
<div id="md-viewer-view" style="display:none;flex:1;overflow:hidden;flex-direction:column">
|
||||
<div class="md-viewer" id="md-viewer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /panels -->
|
||||
</div><!-- /main -->
|
||||
</div><!-- /shell -->
|
||||
|
|
@ -426,6 +486,19 @@ const AGENT_URL = ''
|
|||
const USER_ID = 'chris'
|
||||
const STORAGE_KEY = 'jason_history_v2'
|
||||
const SESSION_KEY = 'jason_session_v2'
|
||||
const GH_RAW = 'https://raw.githubusercontent.com/vauco-saas/OSVauco/main/'
|
||||
const GH_BLOB = 'https://github.com/vauco-saas/OSVauco/blob/main/'
|
||||
|
||||
const MD_DOCS = [
|
||||
{ icon:'🏛️', name:'MASTERPLAN', path:'docs/MASTERPLAN.md', desc:'Autoritativ sannhet — vinner alltid ved konflikt', badge:'autoritativ', badgeCls:'auth' },
|
||||
{ icon:'🗺️', name:'ROADMAP', path:'docs/ROADMAP.md', desc:'Hva som skjer akkurat nå — prioriteringer per sprint', badge:'aktiv', badgeCls:'' },
|
||||
{ icon:'🤝', name:'HANDOFF', path:'docs/HANDOFF.md', desc:'Siste sesjonsstatus — overskrives hver sesjon', badge:'sesjon', badgeCls:'' },
|
||||
{ icon:'📏', name:'AGENT RULEBOOK',path:'docs/AGENT_RULEBOOK.md', desc:'Låste regler for alle agenter — R-01 til R-08', badge:'låst', badgeCls:'auth' },
|
||||
{ icon:'📚', name:'LEARNINGS', path:'docs/LEARNINGS.md', desc:'Append-only lærdom fra alle sesjoner', badge:'append-only', badgeCls:'append' },
|
||||
{ icon:'🌍', name:'EMMA world.md', path:'docs/gemma/world.md', desc:'Emmas kontekstpakke — leses ved oppstart', badge:'emma', badgeCls:'' },
|
||||
{ icon:'🏗️', name:'ARCHITECTURE', path:'docs/ARCHITECTURE.md', desc:'Teknisk arkitektur — tjenester, flows, infra', badge:'tech', badgeCls:'' },
|
||||
{ icon:'🤖', name:'AGENTS', path:'AGENTS.md', desc:'Agent-oversikt — roller, ML-faser, state store', badge:'ml-visjon', badgeCls:'' },
|
||||
]
|
||||
|
||||
const PANEL_META = {
|
||||
jason: ['Jason', 'Gemini 2.5 Pro · ADK'],
|
||||
|
|
@ -434,6 +507,7 @@ const PANEL_META = {
|
|||
vms: ['VM-er', 'Compute Engine · us-central1-b'],
|
||||
sites: ['Sites & Moduler', 'Alle Vauco-flater'],
|
||||
terminal: ['Terminal', 'OPAX agent · Cloud Run'],
|
||||
md: ['MD — Dokumenter', 'Kjerndoks · vauco-saas/OSVauco'],
|
||||
}
|
||||
|
||||
const SITES_DATA = [
|
||||
|
|
@ -457,7 +531,6 @@ document.getElementById('sb-session').textContent = 'session: '+SESSION_ID.slice
|
|||
// PANEL SWITCH
|
||||
const PANELS = Object.keys(PANEL_META)
|
||||
function go(name){
|
||||
// remove any iframe overlay first
|
||||
document.getElementById('panel-iframe-overlay')?.remove()
|
||||
PANELS.forEach(p=>{
|
||||
document.getElementById('panel-'+p).classList.remove('active')
|
||||
|
|
@ -472,6 +545,7 @@ function go(name){
|
|||
if(name==='build') loadBuild()
|
||||
if(name==='sites') renderSites()
|
||||
if(name==='terminal') initTerminal()
|
||||
if(name==='md') renderMdGrid()
|
||||
}
|
||||
|
||||
// BUILD STATUS POLL
|
||||
|
|
@ -581,6 +655,94 @@ function openInline(url, name){
|
|||
document.getElementById('tb-sub').textContent=url
|
||||
}
|
||||
|
||||
// MD MODULE
|
||||
function renderMdGrid(){
|
||||
const g=document.getElementById('md-grid')
|
||||
if(g.children.length) return
|
||||
g.innerHTML=MD_DOCS.map(d=>`
|
||||
<div class="md-card" onclick="openMd('${d.path}','${d.name}')">
|
||||
<div class="md-hd">
|
||||
<span class="md-icon">${d.icon}</span>
|
||||
<div>
|
||||
<div class="md-name">${d.name}</div>
|
||||
<div class="md-path">${d.path}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-desc">${d.desc}</div>
|
||||
<div class="md-ft">
|
||||
<span class="md-badge ${d.badgeCls}">${d.badge}</span>
|
||||
<button class="md-open-btn" onclick="event.stopPropagation();openMd('${d.path}','${d.name}')">Åpne</button>
|
||||
</div>
|
||||
</div>`).join('')
|
||||
}
|
||||
|
||||
async function openMd(path, name){
|
||||
document.getElementById('md-grid-view').style.display='none'
|
||||
const vv=document.getElementById('md-viewer-view')
|
||||
vv.style.display='flex'
|
||||
document.getElementById('md-back-btn').style.display=''
|
||||
const ghBtn=document.getElementById('md-gh-btn')
|
||||
ghBtn.href=GH_BLOB+path
|
||||
ghBtn.style.display=''
|
||||
document.getElementById('tb-title').textContent='MD · '+name
|
||||
document.getElementById('tb-sub').textContent=path
|
||||
const viewer=document.getElementById('md-viewer')
|
||||
viewer.innerHTML='<div class="md-viewer-loading"><span style="animation:pulse 1s infinite">⏳</span> Laster '+esc(path)+'...</div>'
|
||||
try{
|
||||
const raw=await fetch(GH_RAW+path+'?t='+Date.now()).then(r=>{ if(!r.ok) throw new Error('HTTP '+r.status); return r.text() })
|
||||
viewer.innerHTML=renderMd(raw)
|
||||
}catch(e){
|
||||
viewer.innerHTML=`<div class="md-viewer-loading">❌ Kunne ikke laste: ${esc(e.message)}</div>`
|
||||
}
|
||||
}
|
||||
|
||||
function mdShowGrid(){
|
||||
document.getElementById('md-grid-view').style.display=''
|
||||
document.getElementById('md-viewer-view').style.display='none'
|
||||
document.getElementById('md-back-btn').style.display='none'
|
||||
document.getElementById('md-gh-btn').style.display='none'
|
||||
document.getElementById('tb-title').textContent='MD — Dokumenter'
|
||||
document.getElementById('tb-sub').textContent='Kjerndoks · vauco-saas/OSVauco'
|
||||
}
|
||||
|
||||
function renderMd(text){
|
||||
let s=esc(text)
|
||||
// code blocks first
|
||||
s=s.replace(/```[^\n]*\n([\s\S]*?)```/g,'<pre><code>$1</code></pre>')
|
||||
s=s.replace(/`([^`]+)`/g,'<code>$1</code>')
|
||||
// headings
|
||||
s=s.replace(/^### (.+)$/gm,'<h3>$1</h3>')
|
||||
s=s.replace(/^## (.+)$/gm,'<h2>$1</h2>')
|
||||
s=s.replace(/^# (.+)$/gm,'<h1>$1</h1>')
|
||||
// bold
|
||||
s=s.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')
|
||||
// hr
|
||||
s=s.replace(/^---$/gm,'<hr>')
|
||||
// blockquote
|
||||
s=s.replace(/^> (.+)$/gm,'<blockquote>$1</blockquote>')
|
||||
// tables (simple)
|
||||
s=s.replace(/((?:^\|.+\|\n)+)/gm, tbl=>{
|
||||
const rows=tbl.trim().split('\n').filter(r=>!/^\|[-| :]+\|$/.test(r))
|
||||
if(!rows.length) return tbl
|
||||
let out='<table>'
|
||||
rows.forEach((r,i)=>{
|
||||
const cells=r.split('|').slice(1,-1)
|
||||
out+=i===0?'<thead><tr>':'<tr>'
|
||||
cells.forEach(c=>{ out+=i===0?`<th>${c.trim()}</th>`:`<td>${c.trim()}</td>` })
|
||||
out+=i===0?'</tr></thead><tbody>':'</tr>'
|
||||
})
|
||||
out+='</tbody></table>'
|
||||
return out
|
||||
})
|
||||
// lists
|
||||
s=s.replace(/^[-*] (.+)$/gm,'<li>$1</li>')
|
||||
s=s.replace(/(<li>.*<\/li>)/s, m=>'<ul>'+m+'</ul>')
|
||||
s=s.replace(/^\d+\. (.+)$/gm,'<li>$1</li>')
|
||||
// paragraphs — wrap loose lines
|
||||
s=s.replace(/^(?!<[htublp|]).+$/gm, line=>line.trim()?`<p>${line}</p>`:'')
|
||||
return s
|
||||
}
|
||||
|
||||
// TERMINAL
|
||||
let termInited=false
|
||||
function initTerminal(){
|
||||
|
|
@ -614,10 +776,12 @@ async function termKey(e){
|
|||
try{ const d=await fetch('/health').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') }catch(e){ tprint('Feil: '+e.message,'err') }
|
||||
break
|
||||
case 'billing':
|
||||
try{ const d=await fetch('/billing/summary').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') }catch(e){ tprint('Feil: '+e.message,'err') }
|
||||
try{ const d=await fetch('/billing/summary').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') }
|
||||
catch(e){ tprint('Feil: '+e.message,'err') }
|
||||
break
|
||||
case 'build':
|
||||
try{ const d=await fetch('/opax/build-status').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') }catch(e){ tprint('Feil: '+e.message,'err') }
|
||||
try{ const d=await fetch('/opax/build-status').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') }
|
||||
catch(e){ tprint('Feil: '+e.message,'err') }
|
||||
break
|
||||
default: tprint('Ukjent kommando: '+cmd+' (skriv «help»)','err')
|
||||
}
|
||||
|
|
@ -625,7 +789,7 @@ async function termKey(e){
|
|||
function confirmVmStop(){
|
||||
if(!confirm('⚠️ Stop osvauco-dev-vm? Du mister SSH-tilgang til den startes igjen.')) return
|
||||
tprint('→ Stopper VM via: bash scripts/stop-osvauco-dev-vm.sh','info')
|
||||
switchPanel('terminal');go('terminal')
|
||||
go('terminal')
|
||||
}
|
||||
|
||||
// JASON CHAT
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user