diff --git a/public/css/style.css b/public/css/style.css index 83f7f2a..27ebbf4 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -433,6 +433,14 @@ body { padding: 8px 14px 10px; } +.msg-card-actions { + display: flex; + gap: 6px; + padding: 8px 14px; + border-top: 1px solid var(--border-subtle); + flex-wrap: wrap; +} + .msg-step { display: flex; align-items: flex-start; diff --git a/public/js/chat-panel.js b/public/js/chat-panel.js index fb70cfb..4e5dd52 100644 --- a/public/js/chat-panel.js +++ b/public/js/chat-panel.js @@ -173,6 +173,35 @@ class ChatPanel { }); } + // 카드 내부 액션 버튼 (Cancel, Review Changes 등) + if (msg.actions && msg.actions.length > 0) { + const actionsDiv = document.createElement('div'); + actionsDiv.className = 'msg-card-actions'; + + for (const btn of msg.actions) { + const el = document.createElement('button'); + el.className = 'msg-action-btn'; + el.textContent = btn.label; + + if (['Proceed', 'Approve', 'Accept', 'Yes', 'Allow'].some(k => btn.label.includes(k))) { + el.classList.add('msg-action-primary'); + } + + if (btn.x && btn.y) { + el.style.cursor = 'pointer'; + el.addEventListener('click', (e) => { + e.stopPropagation(); // 카드 토글 방지 + if (this.onActionClick) { + this.onActionClick({ label: btn.label, x: btn.x, y: btn.y }); + } + }); + } + + actionsDiv.appendChild(el); + } + card.appendChild(actionsDiv); + } + return card; } diff --git a/server/cdp-client.js b/server/cdp-client.js index 6ff7f08..867a75d 100644 --- a/server/cdp-client.js +++ b/server/cdp-client.js @@ -174,12 +174,27 @@ class CDPClient { } }); + // 카드 내부 액션 버튼 추출 (Cancel, Review Changes 등) + const actionKeywords = ['Proceed','Cancel','Open','View','Review','Approve','Reject','Yes','No','Accept','Deny','Allow','Skip']; + const cardBtns = Array.from(card.querySelectorAll('button')).map(b => { + const label = b.textContent.trim(); + const rect = b.getBoundingClientRect(); + return { + label, + x: Math.round(rect.left + rect.width / 2), + y: Math.round(rect.top + rect.height / 2), + w: Math.round(rect.width), + h: Math.round(rect.height), + }; + }).filter(b => b.label && b.w > 0 && actionKeywords.some(k => b.label.includes(k))); + messages.push({ type: 'task', title: titleEl ? titleEl.textContent.trim() : '', summary: summaryEl ? summaryEl.textContent.trim().substring(0, 500) : '', collapsed: expanded ? expanded.getAttribute('aria-expanded') === 'false' : true, steps: steps.slice(0, 20), + actions: cardBtns.slice(0, 5), }); continue; }