diff --git a/public/css/style.css b/public/css/style.css index 2b00ce5..d3eb44f 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -259,6 +259,44 @@ body { font-size: 11px; color: var(--text-muted); margin-top: 2px; + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.session-project { + display: inline-block; + font-size: 9px; + padding: 1px 6px; + border-radius: 8px; + color: #fff; + font-weight: 600; + letter-spacing: 0.3px; + line-height: 1.4; +} + +.running-dot { + color: #22c55e; + font-size: 10px; + margin-right: 3px; + animation: pulse 1.5s ease-in-out infinite; +} + +.session-card.is-running { + border-left: 2px solid #22c55e; +} + +@keyframes pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.3; + } } .session-remove { diff --git a/public/js/app.js b/public/js/app.js index cb4171d..96e25cd 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -92,19 +92,41 @@ async function loadBridgeSessions() { try { - const res = await fetch('/api/bridge/sessions'); - if (!res.ok) return; - const data = await res.json(); - bridgeSessions = (data.sessions || []).map(s => ({ - id: s.id, - name: s.title || s.id.substring(0, 8), - host: 'bridge', - cdpPort: 0, - status: 'connected', - title: s.title, - stepCount: s.stepCount, - isBridge: true, - })); + // 세션 목록 + cascades를 병렬로 가져오기 + const [sessRes, cascRes] = await Promise.all([ + fetch('/api/bridge/sessions'), + fetch('/api/bridge/cascades'), + ]); + if (!sessRes.ok) return; + const sessData = await sessRes.json(); + let cascadeMap = {}; + if (cascRes.ok) { + const cascData = await cascRes.json(); + cascadeMap = cascData.cascades || cascData; + } + + bridgeSessions = (sessData.sessions || []).map(s => { + const cascade = cascadeMap[s.id] || {}; + // 워크스페이스에서 프로젝트명 추출 + const wsUri = cascade.workspaces?.[0]?.workspaceFolderAbsoluteUri || ''; + const project = wsUri ? decodeURIComponent(wsUri.split('/').pop()) : ''; + // 상태 + const runStatus = cascade.status || ''; + const isRunning = runStatus.includes('RUNNING'); + return { + id: s.id, + name: s.title || s.id.substring(0, 8), + host: 'bridge', + cdpPort: 0, + status: isRunning ? 'running' : 'connected', + title: s.title, + stepCount: s.stepCount, + lastModified: s.lastModifiedTime, + project: project, + isRunning: isRunning, + isBridge: true, + }; + }); sessionPanel.update(bridgeSessions); // 활성 세션 없으면 가장 최근 대화 자동 선택 if (!sessionPanel.activeSessionId && bridgeSessions.length > 0) { diff --git a/public/js/session-panel.js b/public/js/session-panel.js index 934f23d..53424f2 100644 --- a/public/js/session-panel.js +++ b/public/js/session-panel.js @@ -43,17 +43,28 @@ class SessionPanel { return; } - this.listEl.innerHTML = this.sessions.map(s => ` -