chore: version up to 2.80.36 and package with UI/UX refinement

This commit is contained in:
g1nation
2026-05-12 23:41:18 +09:00
parent f6b27a125b
commit e0af15767a
15 changed files with 390 additions and 149 deletions
+68 -27
View File
@@ -173,62 +173,66 @@
const rbDot = document.getElementById('rbDot');
const rbContent = document.getElementById('rbContent');
const ctxBadge = document.getElementById('ctxBadge');
const ctxBrainName = document.getElementById('ctxBrainName');
const ctxAgentName = document.getElementById('ctxAgentName');
const ctxProjectName = document.getElementById('ctxProjectName');
const recordsLatest = document.getElementById('recordsLatest');
// ── Ready-status bar ─────────────────────────────────────────────────
let readyState = {};
function escAttr(t) { return String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
function fmtK(n) {
if (typeof n !== 'number' || !isFinite(n)) return '?';
if (n >= 1000) return (n / 1000).toFixed(n >= 10000 ? 0 : 1).replace(/\.0$/, '') + 'k';
return String(n);
}
function shortModel(m) { m = String(m || ''); const i = m.lastIndexOf('/'); return i >= 0 ? m.slice(i + 1) : m; }
function selText(sel) { try { return sel && sel.selectedIndex >= 0 ? (sel.options[sel.selectedIndex].text || '').trim() : ''; } catch { return ''; } }
function truncMid(s, n) { s = String(s || ''); if (s.length <= n) return s; const h = Math.max(4, Math.floor((n - 1) / 2)); return s.slice(0, h) + '…' + s.slice(-h); }
// ── Context Bar (Brain / Agent / Project summary) + Records line ──────
function syncContextBar() {
if (ctxBrainName) ctxBrainName.textContent = selText(brainSel) || '—';
if (ctxAgentName) { const t = selText(agentSel); ctxAgentName.textContent = (!t || /no agent/i.test(t)) ? '기본' : t; }
if (ctxProjectName) ctxProjectName.textContent = selText(designerSel) || '—';
}
function syncRecordsLine() {
if (!recordsLatest) return;
const opt = chronicleRecordSel && chronicleRecordSel.value ? selText(chronicleRecordSel) : '';
recordsLatest.textContent = opt ? '· ' + truncMid(opt, 38) : '';
}
// ── Ready-status bar (Engine / Model / Brain count / Context / Memory) ──
let readyState = {};
function renderReadyBar() {
if (!readyBar || !rbContent) return;
const s = readyState;
const segs = [];
// Engine
if (s.engine) {
const on = s.engine.online;
const tag = on === true ? '온라인' : on === false ? '오프라인' : '확인 중';
segs.push(`<span class="rb-seg ${on === false ? 'bad' : on === true ? 'ok' : ''}">${s.engine.label || 'Engine'}: ${tag}</span>`);
const tag = on === true ? 'Online' : on === false ? 'Offline' : '확인 중';
segs.push(`<span class="rb-seg ${on === false ? 'bad' : on === true ? 'ok' : ''}">${escAttr(tag)}</span>`);
}
// Model
if (s.model && s.model.name) {
const loaded = s.model.loaded;
const dot = loaded === true ? ' ' : loaded === false ? '○ ' : '';
segs.push(`<span class="rb-seg" title="${loaded === true ? '메모리에 로드됨' : loaded === false ? '아직 로드되지 않음' : ''}">${dot}${escAttr(s.model.name)}</span>`);
segs.push(`<span class="rb-seg" title="${escAttr(s.model.name)}${loaded === true ? ' — 메모리에 로드됨' : loaded === false ? ' — 아직 로드 안 됨' : ''}">${escAttr(shortModel(s.model.name))}</span>`);
}
// Brain
if (s.brain) {
segs.push(`<span class="rb-seg">Brain ${typeof s.brain.files === 'number' ? s.brain.files : '?'}<span class="rb-dim"> ${escAttr(s.brain.name || '')}</span></span>`);
if (s.brain && typeof s.brain.files === 'number') {
segs.push(`<span class="rb-seg" title="${escAttr('Brain: ' + (s.brain.name || ''))}">Brain ${s.brain.files}</span>`);
}
// Agent + scope
if (s.agent && s.agent.name) {
const scope = s.agent.scopeFolders > 0
? ` <span class="rb-link" data-act="map">(범위 ${s.agent.scopeFolders})</span>`
: ` <span class="rb-dim">(범위 미설정)</span>`;
segs.push(`<span class="rb-seg">Agent: ${escAttr(s.agent.name)}${scope}</span>`);
} else {
segs.push(`<span class="rb-seg rb-dim">Agent 없음</span>`);
}
// Memory
segs.push(`<span class="rb-seg ${s.memory ? '' : 'rb-dim'}">메모리 ${s.memory ? '켜짐' : '꺼짐'}</span>`);
// Multi-agent (only when on)
if (s.multiAgent) segs.push(`<span class="rb-seg">멀티에이전트</span>`);
// Context window (capped for small models gets a ↓ marker)
if (typeof s.contextLength === 'number') {
if (s.cappedForSmallModel) {
segs.push(`<span class="rb-seg" title="작은 모델(≤4B) 감지 — 예산을 ${fmtK(s.contextLength)} tokens 로 축소 (설정 g1nation.contextLength = ${fmtK(s.nominalContextLength)}). g1nation.smallModelContextCap 로 조절.">ctx ${fmtK(s.contextLength)}<span class="rb-dim"> ↓작은모델</span></span>`);
segs.push(`<span class="rb-seg rb-warn" title="${escAttr('작은 모델(≤4B) 감지 — 예산을 ' + fmtK(s.contextLength) + ' tokens 로 축소 (설정 g1nation.contextLength = ' + fmtK(s.nominalContextLength) + '). g1nation.smallModelContextCap 로 조절.')}">ctx ${fmtK(s.contextLength)} <span class="rb-dim">· 소형모델 제한</span></span>`);
} else {
segs.push(`<span class="rb-seg" title="모델 context window (g1nation.contextLength). 실제 로드된 값과 맞춰주세요.">ctx ${fmtK(s.contextLength)}</span>`);
}
}
segs.push(`<span class="rb-seg ${s.memory ? '' : 'rb-dim'}">메모리 ${s.memory ? 'On' : 'Off'}</span>`);
if (s.multiAgent) segs.push(`<span class="rb-seg">멀티에이전트</span>`);
rbContent.innerHTML = segs.join('<span class="rb-sep">·</span>');
if (rbDot) {
const on = s.engine && s.engine.online;
rbDot.className = 'rb-dot ' + (on === true ? 'ok' : on === false ? 'bad' : 'warn');
}
}
function escAttr(t) { return String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
// ── Context-budget badge (직전 요청 기준) ────────────────────────────
function renderCtxBadge(b) {
@@ -587,6 +591,7 @@
const addOpt = document.createElement('option');
addOpt.value = 'new'; addOpt.innerText = '+ Add New Brain...';
brainSel.appendChild(addOpt);
syncContextBar();
break;
case 'sessionList':
historyList.innerHTML = '';
@@ -653,6 +658,7 @@
vscode.postMessage({ type: 'getAgentContent', path: msg.selected });
}
vscode.postMessage({ type: 'getKnowledgeScope', agentPath: msg.selected });
syncContextBar();
break;
case 'agentMapData':
if (msg.value) {
@@ -728,6 +734,7 @@
newDesignerOpt.value = 'new';
newDesignerOpt.innerText = '+ Add Designer Project...';
designerSel.appendChild(newDesignerOpt);
syncContextBar();
vscode.postMessage({ type: 'getChronicleRecords' });
break;
case 'chronicleRecords':
@@ -737,6 +744,7 @@
emptyRecordOpt.value = '';
emptyRecordOpt.innerText = 'No records yet';
chronicleRecordSel.appendChild(emptyRecordOpt);
syncRecordsLine();
break;
}
msg.value.forEach(record => {
@@ -746,6 +754,7 @@
o.title = record.path;
chronicleRecordSel.appendChild(o);
});
syncRecordsLine();
break;
case 'agentContent':
agentPrompt.value = msg.value;
@@ -1072,6 +1081,38 @@
vscode.postMessage({ type: 'openChronicleRecord', path: chronicleRecordSel.value });
};
// ── Header dropdowns (Tools ▾ / Edit ▾ / Records ▾) ──────────────────
function closeAllDropdowns(except) {
document.querySelectorAll('.hdr-menu.open').forEach(m => { if (m !== except) m.classList.remove('open'); });
}
document.querySelectorAll('[data-dd]').forEach(dd => {
const trigger = dd.querySelector('[data-dd-trigger]');
const menu = dd.querySelector('[data-dd-menu]');
if (!trigger || !menu) return;
trigger.addEventListener('click', e => {
e.stopPropagation();
const willOpen = !menu.classList.contains('open');
closeAllDropdowns(menu);
menu.classList.toggle('open', willOpen);
});
// Clicks inside the menu shouldn't bubble to the document (which would close it). A click
// on a <button> means "I picked something" → close after its own handler runs; a <select>
// (or label/spacer) keeps the menu open so the user can change several things.
menu.addEventListener('click', e => {
e.stopPropagation();
if (e.target && e.target.closest && e.target.closest('button')) {
setTimeout(() => menu.classList.remove('open'), 0);
}
});
});
document.addEventListener('click', () => closeAllDropdowns());
// Keep the Context Bar / Records line in sync with the (now-collapsed) selectors.
[brainSel, agentSel, designerSel].forEach(s => s && s.addEventListener('change', syncContextBar));
if (chronicleRecordSel) chronicleRecordSel.addEventListener('change', syncRecordsLine);
syncContextBar();
syncRecordsLine();
vscode.postMessage({ type: 'getModels' });
vscode.postMessage({ type: 'getAgents' });
vscode.postMessage({ type: 'getChronicleProjects' });