Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions src/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
const fileSearchInput = document.getElementById('fileSearchInput');
const fileList = document.getElementById('fileList');
const imageBtn = document.getElementById('imageBtn');
const inputContainer = document.getElementById('inputContainer');

let isProcessRunning = false;
let filteredFiles = [];
Expand Down Expand Up @@ -1334,6 +1335,148 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt
// but ensure paste will work when selected
});

// Drag & drop support for images (from OS) and files (from VS Code explorer/editor tabs)
let dragCounter = 0;

function resetDragState() {
dragCounter = 0;
inputContainer.classList.remove('drag-over');
}

function isRelevantDrag(dataTransfer) {
if (!dataTransfer || !dataTransfer.types) {
return false;
}
return dataTransfer.types.indexOf('Files') !== -1 || dataTransfer.types.indexOf('text/uri-list') !== -1;
}

function insertFileReference(filePath) {
const cursorPos = messageInput.selectionStart;
const textBefore = messageInput.value.substring(0, cursorPos);
const textAfter = messageInput.value.substring(cursorPos);
const newText = textBefore + '@' + filePath + ' ' + textAfter;

messageInput.value = newText;
messageInput.focus();

const newCursorPos = textBefore.length + filePath.length + 2;
messageInput.setSelectionRange(newCursorPos, newCursorPos);
adjustTextareaHeight();
}

function fileUriToPath(uri) {
if (!uri || uri.indexOf('file://') !== 0) {
return null;
}
try {
let filePath = decodeURIComponent(uri.substring('file://'.length));
// Strip leading slash from Windows drive paths like /c:/Users/...
if (/^\\/[a-zA-Z]:\\//.test(filePath)) {
filePath = filePath.substring(1);
}
return filePath;
} catch (error) {
console.error('Failed to decode dropped file URI:', uri, error);
return null;
}
}

document.body.addEventListener('dragenter', (e) => {
if (!isRelevantDrag(e.dataTransfer)) {
return;
}
e.preventDefault();
e.stopPropagation();
dragCounter++;
inputContainer.classList.add('drag-over');
});

document.body.addEventListener('dragover', (e) => {
if (!isRelevantDrag(e.dataTransfer)) {
return;
}
e.preventDefault();
e.stopPropagation();
});

document.body.addEventListener('dragleave', (e) => {
if (!isRelevantDrag(e.dataTransfer)) {
return;
}
e.preventDefault();
e.stopPropagation();
dragCounter = Math.max(0, dragCounter - 1);
if (dragCounter === 0) {
inputContainer.classList.remove('drag-over');
}
});

// Safety net: iframe boundaries in VS Code webviews often don't fire dragleave
// (e.g. dropping outside the webview, or aborting the drag with Esc)
window.addEventListener('dragend', resetDragState);
window.addEventListener('blur', resetDragState);

document.body.addEventListener('drop', (e) => {
if (!isRelevantDrag(e.dataTransfer)) {
return;
}
e.preventDefault();
e.stopPropagation();
resetDragState();

const dataTransfer = e.dataTransfer;
if (!dataTransfer) {
return;
}

// Case 1: files dropped from the OS (e.g. Explorer/Finder) - attach images
if (dataTransfer.files && dataTransfer.files.length > 0) {
let hasSkippedFile = false;
for (let i = 0; i < dataTransfer.files.length; i++) {
const file = dataTransfer.files[i];
if (file.type && file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = function(event) {
vscode.postMessage({
type: 'createImageFile',
imageData: event.target.result,
imageType: file.type
});
};
reader.readAsDataURL(file);
} else {
hasSkippedFile = true;
}
}
if (hasSkippedFile) {
showToast('Only image files can be dropped directly - use @ to reference other files');
}
return;
}

// Case 2: items dropped from VS Code (explorer/editor tabs) - insert as @ file references
const uriList = dataTransfer.getData('text/uri-list');
if (uriList) {
const uris = uriList.split('\\n')
.map(function(line) { return line.trim(); })
.filter(function(line) { return line && line.indexOf('#') !== 0; });

let insertedCount = 0;
uris.forEach(function(uri) {
const filePath = fileUriToPath(uri);
if (filePath) {
insertedCount++;
insertFileReference(filePath);
}
});

// uris were present but none were local file:// paths (e.g. untitled:, vscode-remote://)
if (uris.length > 0 && insertedCount === 0) {
showToast('Only local files can be dropped here');
}
}
});

// Initialize textarea height
adjustTextareaHeight();

Expand Down
6 changes: 6 additions & 0 deletions src/ui-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,12 @@ const styles = `
position: relative;
}

.input-container.drag-over {
outline: 2px dashed var(--vscode-focusBorder);
outline-offset: -2px;
background-color: rgba(139, 92, 246, 0.08);
}

.model-selector-row {
display: flex;
align-items: center;
Expand Down