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
+111
View File
@@ -755,6 +755,7 @@
.ready-bar .rb-seg.ok { color: var(--success); }
.ready-bar .rb-seg.bad { color: var(--error); }
.ready-bar .rb-seg.rb-dim, .ready-bar .rb-dim { color: var(--border-bright); }
.ready-bar .rb-seg.rb-warn { color: var(--warning); font-weight: 600; }
.ready-bar .rb-sep { color: var(--border); margin: 0 1px; }
.ready-bar .rb-link { color: var(--accent); cursor: pointer; }
.ready-bar .rb-link:hover { text-decoration: underline; }
@@ -826,3 +827,113 @@
}
.lesson-candidate-box .lc-rec { border-color: var(--warning); color: var(--warning); }
.lesson-candidate-box button:hover { border-color: var(--border-bright); }
/* ════════════════════════════════════════════════════════════════
Compact header: top-bar dropdowns + Context Bar + Records line
(collapse the old "select bomb" into role-grouped popovers)
════════════════════════════════════════════════════════════════ */
/* compact toggle chips kept visible in the top bar (Trace / Web) */
.toggle-chip { font-size: 10.5px; padding: 0 8px; }
/* a trigger + popover menu (Tools ▾ / Edit ▾ / Records ▾) */
.hdr-dropdown { position: relative; display: inline-flex; }
.hdr-menu {
position: absolute;
top: calc(100% + 5px);
right: 0;
display: none;
flex-direction: column;
gap: 2px;
min-width: 190px;
max-width: calc(100vw - 16px);
padding: 5px;
background: var(--surface);
border: 1px solid var(--border-bright);
border-radius: 9px;
box-shadow: 0 10px 30px rgba(0,0,0,0.35);
z-index: 300;
}
.hdr-menu.open { display: flex; }
.hdr-menu-wide { width: min(330px, calc(100vw - 16px)); }
.hdr-menu-label {
font-size: 9.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
color: var(--text-dim);
padding: 5px 8px 2px;
}
.hdr-menu-label:first-child { padding-top: 2px; }
.hdr-menu-item {
display: block;
width: 100%;
text-align: left;
padding: 6px 9px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-primary);
font-size: 12px;
cursor: pointer;
white-space: nowrap;
}
.hdr-menu-item:hover { background: var(--control-bg-hover); color: var(--text-bright); }
.hdr-menu .toggle-item::after { content: ' · 꺼짐'; color: var(--text-dim); font-size: 10px; }
.hdr-menu .toggle-item.active { color: var(--accent); }
.hdr-menu .toggle-item.active::after { content: ' · 켜짐'; color: var(--accent); }
.hdr-menu .control-row { margin: 0 4px 2px; }
.hdr-menu .select-wrap { margin: 0 4px 2px; }
/* Context Bar — "what knowledge context this answer uses" */
.context-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 12px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
font-size: 11px;
min-width: 0;
}
.context-summary {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 5px;
overflow-x: auto;
scrollbar-width: none;
white-space: nowrap;
color: var(--text-dim);
}
.context-summary::-webkit-scrollbar { display: none; }
.context-summary .cb-key { color: var(--text-dim); }
.context-summary .cb-val { color: var(--text-bright); font-weight: 600; }
.context-summary .cb-sep { color: var(--border); }
.context-edit-dd { flex-shrink: 0; }
/* Records line — auto-records status + collapsed record picker */
.records-line {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 12px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
font-size: 10.5px;
color: var(--text-dim);
min-width: 0;
}
.records-line .rl-summary {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.records-line .rl-latest { color: var(--border-bright); overflow: hidden; text-overflow: ellipsis; }
.records-line .hdr-dropdown { flex-shrink: 0; }
+81 -52
View File
@@ -13,70 +13,99 @@
<div class="brand"><div class="logo"></div> Astra</div>
<div class="header-actions">
<button class="icon-btn" id="newChatBtn" data-tooltip="New Chat">New</button>
<button class="icon-btn" id="saveWikiRawBtn" data-tooltip="Save Wiki Raw">Wiki</button>
<button class="icon-btn active" id="brainTraceBtn" data-tooltip="Second Brain Trace Mode">Trace</button>
<button class="icon-btn" id="brainTraceDebugBtn" data-tooltip="Second Brain Debug JSON">Dbg</button>
<button class="icon-btn" id="internetBtn" data-tooltip="Internet Access">Web</button>
<button class="icon-btn" id="historyBtn" data-tooltip="View History">Log</button>
<button class="icon-btn toggle-chip active" id="brainTraceBtn" data-tooltip="Second Brain Trace Mode">Trace</button>
<button class="icon-btn toggle-chip" id="internetBtn" data-tooltip="Internet Access">Web</button>
<div class="hdr-dropdown" data-dd>
<button class="icon-btn" id="toolsMenuBtn" data-dd-trigger data-tooltip="Tools">Tools ▾</button>
<div class="hdr-menu" id="toolsMenu" data-dd-menu>
<div class="hdr-menu-label">Tools</div>
<button class="hdr-menu-item toggle-item" id="brainTraceDebugBtn" data-tooltip="Second Brain Debug JSON">Debug Trace JSON</button>
<button class="hdr-menu-item" id="saveWikiRawBtn" data-tooltip="Save Wiki Raw">Save Wiki Raw</button>
<button class="hdr-menu-item" id="brainBtn" data-tooltip="Sync Knowledge (commit + push)">Sync Knowledge</button>
</div>
</div>
<button class="icon-btn" id="historyBtn" data-tooltip="View History">History</button>
<button class="icon-btn" id="settingsBtn" data-tooltip="Settings">Set</button>
</div>
</div>
<div class="header-controls">
<div class="select-stack">
<div class="select-line">
<div class="status-pill"><span id="statusDot" class="status-dot"></span><span id="engineStatusText">Engine</span></div>
<div class="select-wrap"><select id="modelSel" title="Select Model"></select></div>
</div>
<div class="paired-row">
<div class="control-row">
<div class="select-wrap"><select id="brainSel" title="Select Brain"></select></div>
<div class="tool-group" aria-label="Brain actions">
<button class="icon-btn" id="addBrainBtn" data-tooltip="Add Brain">Add</button>
<button class="icon-btn" id="editBrainBtn" data-tooltip="Edit Brain">Edit</button>
<button class="icon-btn" id="deleteBrainBtn" data-tooltip="Delete Brain">Del</button>
<button class="icon-btn" id="brainBtn" data-tooltip="Sync Knowledge">Sync</button>
</div>
</div>
<div class="control-row">
<div class="select-wrap"><select id="agentSel" title="Select Agentic Skill"></select></div>
<div class="tool-group" aria-label="Agent actions">
<button class="icon-btn" id="addAgentBtn" data-tooltip="Create Agent">Add</button>
<button class="icon-btn" id="editAgentBtn" data-tooltip="Edit Agent Skill">Edit</button>
<button class="icon-btn" id="deleteAgentBtn" data-tooltip="Delete Agent Skill">Del</button>
</div>
</div>
<div class="control-row">
<div class="select-wrap"><select id="knowledgeScopeSel" title="Knowledge folders mapped to this agent"></select></div>
<div class="tool-group" aria-label="Knowledge map actions">
<button class="icon-btn" id="editKnowledgeMapBtn" data-tooltip="Edit Agent ↔ Knowledge Map">Map</button>
<button class="icon-btn" id="reloadKnowledgeMapBtn" data-tooltip="Reload Knowledge Map">Rld</button>
</div>
</div>
<div id="readyBar" class="ready-bar" title="현재 준비 상태 — 엔진 / 모델 / 컨텍스트 창 / 메모리">
<span class="rb-dot" id="rbDot"></span>
<span class="rb-content" id="rbContent">준비 상태 확인 중…</span>
<span id="statusDot" class="status-dot" hidden></span><span id="engineStatusText" hidden></span>
</div>
<div class="context-bar">
<div class="context-summary" id="contextSummary">
<span class="cb-seg"><span class="cb-key">Brain</span> <span id="ctxBrainName" class="cb-val"></span></span>
<span class="cb-sep">·</span>
<span class="cb-seg"><span class="cb-key">Agent</span> <span id="ctxAgentName" class="cb-val"></span></span>
<span class="cb-sep">·</span>
<span class="cb-seg"><span class="cb-key">Project</span> <span id="ctxProjectName" class="cb-val"></span></span>
</div>
<div class="hdr-dropdown context-edit-dd" data-dd>
<button class="icon-btn" id="contextEditBtn" data-dd-trigger data-tooltip="Model / Brain / Agent / Knowledge Map / Project">Edit ▾</button>
<div class="hdr-menu hdr-menu-wide" id="contextEditMenu" data-dd-menu>
<div class="hdr-menu-label">Model</div>
<div class="select-wrap"><select id="modelSel" title="Select Model"></select></div>
<div class="hdr-menu-label">Brain</div>
<div class="control-row">
<div class="select-wrap"><select id="brainSel" title="Select Brain"></select></div>
<div class="tool-group" aria-label="Brain actions">
<button class="icon-btn" id="addBrainBtn" data-tooltip="Add Brain">Add</button>
<button class="icon-btn" id="editBrainBtn" data-tooltip="Edit Brain">Edit</button>
<button class="icon-btn" id="deleteBrainBtn" data-tooltip="Delete Brain">Del</button>
</div>
</div>
<div class="hdr-menu-label">Agent</div>
<div class="control-row">
<div class="select-wrap"><select id="agentSel" title="Select Agentic Skill"></select></div>
<div class="tool-group" aria-label="Agent actions">
<button class="icon-btn" id="addAgentBtn" data-tooltip="Create Agent">Add</button>
<button class="icon-btn" id="editAgentBtn" data-tooltip="Edit Agent Skill">Edit</button>
<button class="icon-btn" id="deleteAgentBtn" data-tooltip="Delete Agent Skill">Del</button>
</div>
</div>
<div class="hdr-menu-label">Agent ↔ Knowledge</div>
<div class="control-row">
<div class="select-wrap"><select id="knowledgeScopeSel" title="Knowledge folders mapped to this agent"></select></div>
<div class="tool-group" aria-label="Knowledge map actions">
<button class="icon-btn" id="editKnowledgeMapBtn" data-tooltip="Edit Agent ↔ Knowledge Map">Map</button>
<button class="icon-btn" id="reloadKnowledgeMapBtn" data-tooltip="Reload Knowledge Map">Rld</button>
</div>
</div>
<div class="hdr-menu-label">Project (Chronicle)</div>
<div class="control-row">
<div class="select-wrap"><select id="designerSel" title="Select Designer Project"></select></div>
<div class="tool-group" aria-label="Designer actions">
<button class="icon-btn" id="addDesignerBtn" data-tooltip="Create Designer Project">Add</button>
<button class="icon-btn" id="openDesignerBtn" data-tooltip="Open Record Folder">Open</button>
</div>
</div>
<div class="control-row record-row">
<div class="status-pill" id="chronicleAutoStatus" title="Project records are saved automatically after meaningful project turns.">
<span class="status-dot ready"></span><span>Auto Records</span>
</div>
<div class="select-wrap"><select id="chronicleRecordSel" title="Select Chronicle Record"></select></div>
<div class="tool-group" aria-label="Chronicle record actions">
<button class="icon-btn" id="refreshChronicleRecordsBtn" data-tooltip="Refresh Records">Ref</button>
<button class="icon-btn" id="openChronicleRecordBtn" data-tooltip="Open Selected Record">Open</button>
</div>
</div>
</div>
</div>
</div>
<div id="readyBar" class="ready-bar" title="현재 준비 상태 — 엔진 / 모델 / Brain / Agent 범위 / 메모리 / 컨텍스트 창">
<span class="rb-dot" id="rbDot"></span>
<span class="rb-content" id="rbContent">준비 상태 확인 중…</span>
<div class="records-line">
<div class="rl-summary">
<span class="status-dot ready"></span>
<span id="chronicleAutoStatus" title="Project records are saved automatically after meaningful project turns.">Auto Records</span>
<span class="rl-latest" id="recordsLatest"></span>
</div>
<div class="hdr-dropdown" data-dd>
<button class="icon-btn" id="recordsMenuBtn" data-dd-trigger data-tooltip="Chronicle records">Records ▾</button>
<div class="hdr-menu hdr-menu-wide" id="recordsMenu" data-dd-menu>
<div class="hdr-menu-label">Chronicle Records</div>
<div class="select-wrap"><select id="chronicleRecordSel" title="Select Chronicle Record"></select></div>
<button class="hdr-menu-item" id="openChronicleRecordBtn" data-tooltip="Open Selected Record">Open Selected Record</button>
<button class="hdr-menu-item" id="refreshChronicleRecordsBtn" data-tooltip="Refresh Records">Refresh Records</button>
<button class="hdr-menu-item" id="openDesignerBtn" data-tooltip="Open Record Folder">Open Record Folder</button>
</div>
</div>
</div>
<div id="historyOverlay" class="history-overlay">
@@ -161,10 +190,10 @@
<div id="agentConfigPanel" class="panel">
<div class="field-label">Agent Persona/Instructions</div>
<textarea id="agentPrompt" rows="5" placeholder="Agent Persona & Instructions..."></textarea>
<div class="field-label">Negative Prompt (Strict Rules)</div>
<textarea id="negativePrompt" rows="2" placeholder="What NOT to do..."></textarea>
<button id="updateAgentBtn" class="secondary-btn">Update Agent Skill</button>
</div>
<div class="input-box">
+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' });