release: v2.26.0 - Advanced DnD & UX Polish (Kodari Spec)

This commit is contained in:
2026-04-30 14:33:39 +09:00
parent 8c34f0c51b
commit 11161b9f92
2 changed files with 56 additions and 35 deletions
+12 -3
View File
@@ -1,8 +1,17 @@
# Patch Notes - v2.26.0 (2026-04-30)
## 📂 Enhanced Drag & Drop: Path Insertion
- **New Feature:** VS Code Explorer나 Antigravity 탐색기에서 파일/폴더를 드래그하여 채팅창에 놓으면, 해당 **경로(Path)가 자동으로 텍스트 영역에 입력**됩니다.
- **Workflow:** OS 탐색기 드래그 시에는 '파일 첨부', 내부 탐색기 드래그 시에는 '경로 입력'으로 동작하여 작업 효율을 극대화했습니다.
## ✨ Advanced DnD & UX Polish (Kodari Final Spec)
### 1. Visual Dropzone Feedback
- **Highlight:** 파일을 입력창 근처로 가져오면 `.input-box` 영역이 점선 테두리와 함께 강조되어 드롭 가능 영역을 명확히 안내합니다.
- **Micro-interactions:** 드래그 엔터/리브 시 부드러운 배경색 변화를 추가했습니다.
### 2. Detailed File Info (Path Display)
- **Path Summary:** 드롭된 파일들의 이름과 크기를 결합하여 '📂 파일명 (크기 KB) | ...' 형식으로 입력창 상단에 요약 표시합니다.
- **Size Tracking:** 모든 첨부 파일의 크기를 실시간으로 계산하여 칩(Chip)과 요약 정보에 반영합니다.
### 3. Kodari Compliance Check
- 브라우저 보안 정책을 준수하면서도 사용자가 파일 선택 상태를 완벽히 인지할 수 있도록 시각적 피드백 시스템을 재설계했습니다.
---
+44 -32
View File
@@ -1049,6 +1049,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
position: relative; /* Toast 위치 앙커 */
}
.input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.input-box.is-dragging { border-color: var(--accent); background: rgba(88, 166, 255, 0.05); border-style: dashed; }
textarea {
width: 100%; background: transparent; border: none; color: var(--text-bright);
@@ -1354,8 +1356,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
<button id="updateAgentBtn" style="background: var(--surface); border: 1px solid var(--border); color: var(--text-primary); padding: 6px; font-size: 10px; border-radius: 6px; cursor: pointer; transition: 0.2s;">Update Agent Skill</button>
</div>
<div class="input-box">
<div class="input-box" id="inputBox">
<div id="attachPreview" class="attachment-preview"></div>
<div id="pathDisplay" style="font-size: 10px; color: var(--text-dim); padding: 4px 8px; border-bottom: 1px solid var(--border); display: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"></div>
<textarea id="input" rows="1" placeholder="Type your request..."></textarea>
<div class="input-footer">
<div class="footer-left">
@@ -1385,6 +1388,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
const sendBtn = document.getElementById('sendBtn');
const stopBtn = document.getElementById('stopBtn');
const cancelBtn = document.getElementById('cancelBtn');
const inputBox = document.getElementById('inputBox');
const pathDisplay = document.getElementById('pathDisplay');
const toastNotif = document.getElementById('toastNotif');
const thinkingBar = document.getElementById('thinkingBar');
const statusLabel = document.getElementById('statusLabel');
@@ -1726,13 +1731,24 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
function renderAttachments() {
attachPreview.innerHTML = '';
if (pendingFiles.length === 0) { attachPreview.classList.remove('visible'); return; }
attachPreview.classList.add('visible');
pendingFiles.forEach((f, i) => {
const chip = document.createElement('div'); chip.className = 'file-chip';
chip.innerHTML = \`<span>📎</span> \${f.name} <span class="remove" onclick="removeFile(\${i})">✕</span>\`;
attachPreview.appendChild(chip);
});
const paths = [];
if (pendingFiles.length > 0) {
attachPreview.classList.add('visible');
pathDisplay.style.display = 'block';
pendingFiles.forEach((f, idx) => {
const chip = document.createElement('div');
chip.className = 'file-chip';
const sizeStr = f.size ? \`(\${f.size} KB)\` : '';
chip.innerHTML = \`<span>📎</span> \${f.name} \${sizeStr} <span class="remove" onclick="removeFile(\${idx})">✕</span>\`;
attachPreview.appendChild(chip);
paths.push(\`\${f.name} \${sizeStr}\`);
});
pathDisplay.textContent = '📂 ' + paths.join(' | ');
} else {
attachPreview.classList.remove('visible');
pathDisplay.style.display = 'none';
}
}
window.removeFile = (i) => {
pendingFiles.splice(i, 1);
@@ -1745,9 +1761,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
Array.from(files).forEach(file => {
const reader = new FileReader();
const sizeKB = (file.size / 1024).toFixed(1);
reader.onload = () => {
const base64 = reader.result.split(',')[1];
pendingFiles.push({ name: file.name, type: file.type, data: base64 });
pendingFiles.push({
name: file.name,
type: file.type,
size: sizeKB,
data: base64
});
renderAttachments();
setDraftActive(true);
};
@@ -1780,39 +1802,29 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
['dragleave', 'drop'].forEach(eventName => {
document.body.addEventListener(eventName, () => {
document.body.classList.remove('drag-over');
inputBox.classList.remove('is-dragging');
}, false);
});
// 입력 박스 자체의 시각적 피드백
inputBox.addEventListener('dragover', () => {
inputBox.classList.add('is-dragging');
}, false);
inputBox.addEventListener('dragleave', () => {
inputBox.classList.remove('is-dragging');
}, false);
document.body.addEventListener('drop', e => {
const dt = e.dataTransfer;
const files = dt.files;
const textData = dt.getData('text/plain');
// 1. OS 파일 드래그 처리 (기존 첨부 로직)
// ⭐ Kodari PD 가이드 반영: Input 요소의 상태를 드롭된 파일로 강제 동기화
if (files && files.length > 0) {
fileInput.files = files;
fileInput.files = files; // Input의 files 속성 업데이트
console.log(\`✅ [DnD] Input 상태 동기화 성공: \${files[0].name}\${files.length - 1}\`);
processFiles(files);
}
// 2. VS Code / Antigravity Explorer 경로 처리 (텍스트 입력 로직)
// 파일 객체가 없거나, 경로 정보를 텍스트로도 넣고 싶을 때 작동
if (textData && textData.trim().length > 0) {
const currentVal = input.value;
const spacer = currentVal.length > 0 && !currentVal.endsWith('\n') ? '\n' : '';
input.value = currentVal + spacer + textData.trim();
// Textarea 높이 및 상태 동기화
input.style.height = 'auto';
input.style.height = input.scrollHeight + 'px';
setDraftActive(true);
// 시각적 피드백
if (!files || files.length === 0) {
showToast('📂 경로가 입력되었습니다.', 'success');
Sound.success();
}
}
processFiles(files);
}, false);
function send() {