v2.2.267: 채팅 사이드바 Pixel Office 배너 제거 (화면 점유 해소)
채팅 위에 항상 떠 있던 Pixel Office 카드(캐릭터·Agent/Status/Task 표)가 화면을 과도하게 가리는 문제 — 시각 레이어만 제거, 기능은 보존: 제거 (media 웹뷰 시각 레이어): - sidebar.html: #pixelOffice 배너 DOM 블록 - sidebar.js: Pixel Office 렌더러 IIFE + pixelOfficeUpdate/officeSnapshot 핸들러 no-op 화 - sidebar.css: .pixel-office / .po-* 스타일 전부 보존 (백엔드·별도 패널): - 파이프라인 상태 추적(pixelOfficeOn*) 및 브로드캐스트 전부 유지 — 에이전트 동작 무영향 - 풀스크린 Astra Office 패널(Astra: Open Pixel Office (Full Screen)) 그대로 동작 - 설정 pixelOffice.enabled/bubbles 유지, 설명만 풀스크린 패널 전용으로 정정 (검증: node --check ✓ · 잔여 참조 grep 0 ✓ · tsc ✓ · jest 728/730 ✓) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+3
-250
@@ -1102,50 +1102,10 @@
|
||||
chatEl.scrollTop = chatEl.scrollHeight;
|
||||
break;
|
||||
}
|
||||
case 'pixelOfficeUpdate': {
|
||||
// 새 path (officeSnapshot) 가 한 번이라도 도착했다면 옛 message 는 무시.
|
||||
if (window.__officeSnapshotSeen) break;
|
||||
if (typeof window.__pixelOfficeApply === 'function') {
|
||||
window.__pixelOfficeApply(msg.value || {});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pixelOfficeUpdate':
|
||||
case 'officeSnapshot': {
|
||||
// refactor #E — mini view 도 OfficeSnapshot 수신.
|
||||
// OfficeSnapshot 을 옛 {state, bubbles, config} payload 모양으로 변환 후
|
||||
// 기존 __pixelOfficeApply 재사용. dual-mode 안전 전환.
|
||||
window.__officeSnapshotSeen = true;
|
||||
const snap = msg.value;
|
||||
if (!snap || typeof window.__pixelOfficeApply !== 'function') break;
|
||||
const roster = Array.isArray(snap.roster) ? snap.roster : [];
|
||||
const active = (snap.activeAgentId && roster.find((a) => a.agentId === snap.activeAgentId)) || roster[0];
|
||||
const phaseToStatus = (p) => {
|
||||
if (p === 'awaiting-approval') return 'waiting_approval';
|
||||
if (p === 'reporting') return 'done';
|
||||
if (p === 'intake') return 'analyzing';
|
||||
return p || 'idle';
|
||||
};
|
||||
const synthetic = {
|
||||
agentId: snap.activeAgentId || (active && active.agentId) || 'main',
|
||||
agentName: (active && active.agentName) || 'Agent',
|
||||
status: (active && active.status) || phaseToStatus(snap.phase),
|
||||
currentTask: snap.task && snap.task.goal,
|
||||
currentStep: active && active.currentStep,
|
||||
message: snap.activeAgentId || '',
|
||||
recentLogs: (active && active.lastLog) ? [active.lastLog] : [],
|
||||
progress: snap.pipeline ? (snap.pipeline.index / Math.max(1, snap.pipeline.stages.length)) : 0,
|
||||
pipelineStages: snap.pipeline && snap.pipeline.stages,
|
||||
needUserInput: (snap.awaiting && snap.awaiting.kind === 'clarification') ? snap.awaiting.questions : undefined,
|
||||
awaitingApproval: (snap.awaiting && snap.awaiting.kind === 'approval') ? snap.awaiting.questions[0] : undefined,
|
||||
requirementContract: snap.task,
|
||||
updatedAt: snap.updatedAt,
|
||||
};
|
||||
window.__pixelOfficeApply({
|
||||
state: synthetic,
|
||||
bubbles: Array.isArray(snap.newBubbles) ? snap.newBubbles : [],
|
||||
// config 는 그대로 유지 — snapshot 에는 enabled 만 함의적, 옛 cfg 가 살아있음.
|
||||
config: undefined,
|
||||
});
|
||||
// Pixel Office 채팅 배너는 v2.2.267 에서 제거 — 백엔드가 여전히 보내는
|
||||
// 상태 메시지는 조용히 무시 (풀스크린 Astra Office 패널은 별도 webview 로 수신).
|
||||
break;
|
||||
}
|
||||
case 'devilRebuttal': {
|
||||
@@ -3032,213 +2992,6 @@
|
||||
window.__renderCompanyPipelines = renderCompanyPipelines;
|
||||
window.__closePipelineEditor = _closePipelineEditor;
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Pixel Office 렌더러 — 백엔드의 pixelOfficeUpdate 메시지를 받아 캐릭터,
|
||||
// 정보 패널, 말풍선 큐를 갱신. 어떤 상태도 그대로 그리기만 함 (read-only).
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
(function setupPixelOffice() {
|
||||
const root = document.getElementById('pixelOffice');
|
||||
if (!root) return;
|
||||
const collapseBtn = document.getElementById('poCollapseBtn');
|
||||
const expandBtn = document.getElementById('poExpandBtn');
|
||||
const head = document.querySelector('#pixelOffice .po-head');
|
||||
const charEl = document.getElementById('poChar');
|
||||
const charEmoji = document.getElementById('poCharEmoji');
|
||||
const charProp = document.getElementById('poCharProp');
|
||||
const bubblesEl = document.getElementById('poBubbles');
|
||||
const progressBar = document.getElementById('poProgressBar');
|
||||
const statusLabel = document.getElementById('poStatusLabel');
|
||||
const statusVal = document.getElementById('poStatusVal');
|
||||
const agentName = document.getElementById('poAgentName');
|
||||
const taskEl = document.getElementById('poTask');
|
||||
const stepEl = document.getElementById('poStep');
|
||||
const nextStepRow = document.getElementById('poNextStepRow');
|
||||
const nextStepEl = document.getElementById('poNextStep');
|
||||
const messageRow = document.getElementById('poMessageRow');
|
||||
const messageEl = document.getElementById('poMessage');
|
||||
const needInputSection = document.getElementById('poNeedInputSection');
|
||||
const needInputList = document.getElementById('poNeedInputList');
|
||||
const approvalSection = document.getElementById('poApprovalSection');
|
||||
const approvalText = document.getElementById('poApprovalText');
|
||||
const contractSection = document.getElementById('poContractSection');
|
||||
const contractEl = document.getElementById('poContract');
|
||||
const logsSection = document.getElementById('poLogsSection');
|
||||
const logsEl = document.getElementById('poLogs');
|
||||
|
||||
// 상태별 캐릭터·소품 매핑 — 픽셀아트 대신 이모지 조합으로.
|
||||
const STATUS_VIS = {
|
||||
idle: { emoji: '🧑💼', prop: '' },
|
||||
intake: { emoji: '🧑💼', prop: '📨' },
|
||||
analyzing: { emoji: '🧐', prop: '🔍' },
|
||||
need_clarification: { emoji: '🤔', prop: '❓' },
|
||||
contract_ready: { emoji: '🧑💼', prop: '📋' },
|
||||
planning: { emoji: '🧑💼', prop: '📝' },
|
||||
executing: { emoji: '🧑💻', prop: '⚙️' },
|
||||
reviewing: { emoji: '🧐', prop: '✅' },
|
||||
waiting_approval: { emoji: '🧑💼', prop: '🛑' },
|
||||
error: { emoji: '😵', prop: '⚠️' },
|
||||
done: { emoji: '😎', prop: '☕' },
|
||||
};
|
||||
|
||||
// ── 말풍선 큐 ──
|
||||
// 큐 크기 제한 + 자동 사라짐 타이머. 같은 텍스트 연속 등장 시 1개로 합침.
|
||||
let cfg = { enabled: true, bubblesEnabled: true, maxVisibleBubbles: 3, bubbleDurationMs: 4500 };
|
||||
const bubbleQueue = []; // { el, timer }
|
||||
let lastBubbleText = '';
|
||||
|
||||
const collapseToggle = () => {
|
||||
const cur = root.getAttribute('data-collapsed') === 'true';
|
||||
root.setAttribute('data-collapsed', cur ? 'false' : 'true');
|
||||
};
|
||||
if (collapseBtn) collapseBtn.onclick = (e) => { e.stopPropagation(); collapseToggle(); };
|
||||
if (expandBtn) expandBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
// 백엔드에 전체보기 panel 열기 요청.
|
||||
vscode.postMessage({ type: 'openPixelOfficePanel' });
|
||||
};
|
||||
// head 영역 자체 클릭으로도 토글 (버튼 외 영역).
|
||||
if (head) head.addEventListener('click', (e) => {
|
||||
if (e.target instanceof HTMLElement && (e.target.closest('.po-collapse') || e.target.closest('.po-expand'))) return;
|
||||
collapseToggle();
|
||||
});
|
||||
|
||||
const dropOldestBubble = () => {
|
||||
const first = bubbleQueue.shift();
|
||||
if (!first) return;
|
||||
if (first.timer) clearTimeout(first.timer);
|
||||
first.el.classList.add('po-bubble-fading');
|
||||
setTimeout(() => { try { first.el.remove(); } catch {} }, 300);
|
||||
};
|
||||
|
||||
const pushBubble = (b) => {
|
||||
if (!cfg.bubblesEnabled) return;
|
||||
if (!b || !b.text) return;
|
||||
if (b.text === lastBubbleText) return; // 연속 중복 차단
|
||||
lastBubbleText = b.text;
|
||||
const el = document.createElement('div');
|
||||
el.className = 'po-bubble po-bubble-' + (b.type || 'status');
|
||||
el.textContent = b.text;
|
||||
bubblesEl.appendChild(el);
|
||||
const duration = b.durationMs || cfg.bubbleDurationMs || 4500;
|
||||
const timer = setTimeout(() => {
|
||||
const idx = bubbleQueue.findIndex((x) => x.el === el);
|
||||
if (idx >= 0) {
|
||||
bubbleQueue.splice(idx, 1);
|
||||
el.classList.add('po-bubble-fading');
|
||||
setTimeout(() => { try { el.remove(); } catch {} }, 300);
|
||||
}
|
||||
}, duration);
|
||||
bubbleQueue.push({ el, timer });
|
||||
while (bubbleQueue.length > Math.max(1, cfg.maxVisibleBubbles)) {
|
||||
dropOldestBubble();
|
||||
}
|
||||
};
|
||||
|
||||
const setText = (el, val) => { if (el) el.textContent = (val == null || val === '') ? '—' : String(val); };
|
||||
|
||||
const apply = (payload) => {
|
||||
cfg = Object.assign(cfg, payload && payload.config ? payload.config : {});
|
||||
root.setAttribute('data-enabled', cfg.enabled ? 'true' : 'false');
|
||||
if (!cfg.enabled) return;
|
||||
const state = payload && payload.state;
|
||||
if (state) {
|
||||
const vis = STATUS_VIS[state.status] || STATUS_VIS.idle;
|
||||
if (charEmoji) charEmoji.textContent = vis.emoji;
|
||||
if (charProp) charProp.textContent = vis.prop;
|
||||
root.setAttribute('data-status', state.status || 'idle');
|
||||
// 상태 라벨 색상 클래스 새로.
|
||||
if (statusLabel) {
|
||||
statusLabel.className = 'po-status-label po-status-' + (state.status || 'idle');
|
||||
statusLabel.textContent = state.status || 'idle';
|
||||
}
|
||||
setText(statusVal, state.status);
|
||||
setText(agentName, state.agentName);
|
||||
setText(taskEl, state.currentTask);
|
||||
setText(stepEl, state.currentStep);
|
||||
if (state.nextStep) {
|
||||
nextStepRow.style.display = '';
|
||||
setText(nextStepEl, state.nextStep);
|
||||
} else {
|
||||
nextStepRow.style.display = 'none';
|
||||
}
|
||||
if (state.message) {
|
||||
messageRow.style.display = '';
|
||||
setText(messageEl, state.message);
|
||||
} else {
|
||||
messageRow.style.display = 'none';
|
||||
}
|
||||
// Progress
|
||||
if (progressBar) {
|
||||
const pct = typeof state.progress === 'number'
|
||||
? Math.round(Math.max(0, Math.min(1, state.progress)) * 100)
|
||||
: 0;
|
||||
progressBar.style.width = pct + '%';
|
||||
}
|
||||
// Need input
|
||||
if (Array.isArray(state.needUserInput) && state.needUserInput.length > 0) {
|
||||
needInputSection.style.display = '';
|
||||
needInputList.innerHTML = '';
|
||||
for (const q of state.needUserInput) {
|
||||
const li = document.createElement('li'); li.textContent = q;
|
||||
needInputList.appendChild(li);
|
||||
}
|
||||
} else {
|
||||
needInputSection.style.display = 'none';
|
||||
}
|
||||
// Approval
|
||||
if (state.awaitingApproval) {
|
||||
approvalSection.style.display = '';
|
||||
setText(approvalText, state.awaitingApproval);
|
||||
} else {
|
||||
approvalSection.style.display = 'none';
|
||||
}
|
||||
// Contract
|
||||
const c = state.requirementContract;
|
||||
if (c && (c.goal || c.context || (c.criteria && c.criteria.length) || c.format)) {
|
||||
contractSection.style.display = '';
|
||||
contractEl.innerHTML = '';
|
||||
const addRow = (k, v) => {
|
||||
if (!v || (Array.isArray(v) && v.length === 0)) return;
|
||||
const ke = document.createElement('div');
|
||||
ke.className = 'po-contract-key'; ke.textContent = k;
|
||||
const ve = document.createElement('div');
|
||||
ve.className = 'po-contract-val';
|
||||
ve.textContent = Array.isArray(v) ? v.map((x) => '• ' + x).join('\n') : String(v);
|
||||
ve.style.whiteSpace = 'pre-line';
|
||||
contractEl.appendChild(ke);
|
||||
contractEl.appendChild(ve);
|
||||
};
|
||||
addRow('Goal', c.goal);
|
||||
addRow('Ctx', c.context);
|
||||
addRow('Crit', c.criteria);
|
||||
addRow('Fmt', c.format);
|
||||
if (c.confidence) addRow('Conf', c.confidence);
|
||||
} else {
|
||||
contractSection.style.display = 'none';
|
||||
}
|
||||
// Recent logs
|
||||
if (Array.isArray(state.recentLogs) && state.recentLogs.length > 0) {
|
||||
logsSection.style.display = '';
|
||||
logsEl.innerHTML = '';
|
||||
for (const line of state.recentLogs) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = line;
|
||||
logsEl.appendChild(d);
|
||||
}
|
||||
} else {
|
||||
logsSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
// Bubbles
|
||||
if (Array.isArray(payload?.bubbles)) {
|
||||
for (const b of payload.bubbles) pushBubble(b);
|
||||
}
|
||||
};
|
||||
window.__pixelOfficeApply = apply;
|
||||
|
||||
// webview 로드 직후 백엔드 캐시 상태 요청.
|
||||
try { vscode.postMessage({ type: 'getPixelOfficeState' }); } catch {}
|
||||
})();
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// 이어서 진행 가능 세션 렌더링.
|
||||
|
||||
Reference in New Issue
Block a user