fix: convert dragged file paths to @mentions for attachment

When non-image files are dragged into the terminal, the file path was
inserted as plain text and never attached. Now detected absolute paths
are converted to @mentions so they get picked up by the attachment system.
This commit is contained in:
Paulo Reis
2026-04-05 04:53:07 -03:00
parent 5ef79546e9
commit 6bb7d44f3f
2 changed files with 64 additions and 0 deletions

View File

@@ -67,6 +67,7 @@ import { isBilledAsExtraUsage } from '../../utils/extraUsage.js';
import { getFastModeUnavailableReason, isFastModeAvailable, isFastModeCooldown, isFastModeEnabled, isFastModeSupportedByModel } from '../../utils/fastMode.js'; import { getFastModeUnavailableReason, isFastModeAvailable, isFastModeCooldown, isFastModeEnabled, isFastModeSupportedByModel } from '../../utils/fastMode.js';
import { isFullscreenEnvEnabled } from '../../utils/fullscreen.js'; import { isFullscreenEnvEnabled } from '../../utils/fullscreen.js';
import type { PromptInputHelpers } from '../../utils/handlePromptSubmit.js'; import type { PromptInputHelpers } from '../../utils/handlePromptSubmit.js';
import { extractDraggedFilePaths } from '../../utils/dragDropPaths.js';
import { getImageFromClipboard, PASTE_THRESHOLD } from '../../utils/imagePaste.js'; import { getImageFromClipboard, PASTE_THRESHOLD } from '../../utils/imagePaste.js';
import type { ImageDimensions } from '../../utils/imageResizer.js'; import type { ImageDimensions } from '../../utils/imageResizer.js';
import { cacheImagePath, storeImage } from '../../utils/imageStore.js'; import { cacheImagePath, storeImage } from '../../utils/imageStore.js';
@@ -1204,6 +1205,22 @@ function PromptInput({
// Clean up pasted text - strip ANSI escape codes and normalize line endings and tabs // Clean up pasted text - strip ANSI escape codes and normalize line endings and tabs
let text = stripAnsi(rawText).replace(/\r/g, '\n').replaceAll('\t', ' '); let text = stripAnsi(rawText).replace(/\r/g, '\n').replaceAll('\t', ' ');
// Detect file paths from drag-and-drop and convert to @mentions.
// When files are dragged into the terminal, the terminal sends their
// absolute paths via bracketed paste. Image files are handled by the
// image paste handler upstream; here we handle non-image files by
// converting them to @mentions so they get attached on submit.
const draggedPaths = extractDraggedFilePaths(text);
if (draggedPaths.length > 0) {
const mentions = draggedPaths
.map(p => (p.includes(' ') ? `@"${p}"` : `@${p}`))
.join(' ');
// Ensure spacing around the mention(s) relative to existing input
const charBefore = input[cursorOffset - 1];
const prefix = charBefore && !/\s/.test(charBefore) ? ' ' : '';
text = prefix + mentions + ' ';
}
// Match typed/auto-suggest: `!cmd` pasted into empty input enters bash mode. // Match typed/auto-suggest: `!cmd` pasted into empty input enters bash mode.
if (input.length === 0) { if (input.length === 0) {
const pastedMode = getModeFromInput(text); const pastedMode = getModeFromInput(text);

View File

@@ -0,0 +1,47 @@
import { isAbsolute } from 'path'
import { getFsImplementation } from './fsOperations.js'
import { isImageFilePath } from './imagePaste.js'
/**
* Detect absolute file paths in pasted text (typically from drag-and-drop).
* Returns the cleaned paths if ALL segments are existing non-image files,
* or an empty array otherwise.
*
* Splitting logic mirrors usePasteHandler: space preceding `/` or a Windows
* drive letter, plus newline separators.
*/
export function extractDraggedFilePaths(text: string): string[] {
const segments = text
.split(/ (?=\/|[A-Za-z]:\\)/)
.flatMap(part => part.split('\n'))
.map(s => s.trim())
.filter(Boolean)
if (segments.length === 0) return []
const fs = getFsImplementation()
const cleaned: string[] = []
for (const raw of segments) {
// Strip outer quotes and shell-escape backslashes
let p = raw
if (
(p.startsWith('"') && p.endsWith('"')) ||
(p.startsWith("'") && p.endsWith("'"))
) {
p = p.slice(1, -1)
}
if (process.platform !== 'win32') {
p = p.replace(/\\(.)/g, '$1')
}
if (!isAbsolute(p)) return []
// Image files are handled by the upstream image paste handler
if (isImageFilePath(raw)) return []
// Verify the path actually exists on disk
if (!fs.existsSync(p)) return []
cleaned.push(p)
}
return cleaned
}