Merge pull request #38306 from NousResearch/bb/desktop-clipboard-image-double-paste

fix(desktop): dedupe clipboard image paste
This commit is contained in:
brooklyn! 2026-06-03 10:28:21 -05:00 committed by GitHub
commit ecac659d7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 73 additions and 5 deletions

View file

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { detectTrigger } from './text-utils'
import { blobDedupeKey, detectTrigger, extractClipboardImageBlobs } from './text-utils'
describe('detectTrigger', () => {
it('detects a bare slash trigger with an empty query', () => {
@ -23,3 +23,55 @@ describe('detectTrigger', () => {
expect(detectTrigger('hello there')).toBeNull()
})
})
describe('extractClipboardImageBlobs', () => {
it('dedupes the same image exposed on both items and files', () => {
const image = new File([new Uint8Array([1, 2, 3])], 'paste.png', {
type: 'image/png',
lastModified: 1_700_000_000_000
})
const clipboard = {
files: {
length: 1,
item: (index: number) => (index === 0 ? image : null)
},
getData: () => '',
items: [
{
kind: 'file',
type: 'image/png',
getAsFile: () => image
}
]
} as unknown as DataTransfer
expect(extractClipboardImageBlobs(clipboard)).toEqual([image])
})
it('falls back to files when items has no image', () => {
const image = new File([new Uint8Array([4, 5])], 'shot.jpg', {
type: 'image/jpeg',
lastModified: 1_700_000_000_001
})
const clipboard = {
files: {
length: 1,
item: (index: number) => (index === 0 ? image : null)
},
getData: () => '',
items: []
} as unknown as DataTransfer
expect(extractClipboardImageBlobs(clipboard)).toEqual([image])
})
})
describe('blobDedupeKey', () => {
it('uses file metadata for File blobs', () => {
const file = new File([], 'a.png', { type: 'image/png', lastModified: 42 })
expect(blobDedupeKey(file)).toBe('file:a.png:0:image/png:42')
})
})

View file

@ -8,16 +8,31 @@ export interface TriggerState {
const TRIGGER_RE = /(?:^|[\s])([@/])([^\s@/]*)$/
/** Stable key for paste dedupe — `items` and `files` often mirror the same image as different objects. */
export function blobDedupeKey(blob: Blob): string {
if (blob instanceof File) {
return `file:${blob.name}:${blob.size}:${blob.type}:${blob.lastModified}`
}
return `blob:${blob.size}:${blob.type}`
}
export function extractClipboardImageBlobs(clipboard: DataTransfer): Blob[] {
const blobs: Blob[] = []
const seen = new Set<Blob>()
const seen = new Set<string>()
const push = (blob: Blob | null) => {
if (!blob || blob.size === 0 || seen.has(blob)) {
if (!blob || blob.size === 0) {
return
}
seen.add(blob)
const key = blobDedupeKey(blob)
if (seen.has(key)) {
return
}
seen.add(key)
blobs.push(blob)
}
@ -29,7 +44,8 @@ export function extractClipboardImageBlobs(clipboard: DataTransfer): Blob[] {
}
}
if (clipboard.files?.length) {
// Chromium/Electron expose the same pasted image on both `items` and `files`.
if (blobs.length === 0 && clipboard.files?.length) {
for (let i = 0; i < clipboard.files.length; i += 1) {
const file = clipboard.files.item(i)