mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-10 08:32:09 +00:00
fix(desktop): dedupe clipboard image paste
Chromium exposes the same pasted image on both DataTransfer.items and .files as distinct Blob objects, which attached twice. Prefer items and skip the files mirror when items already yielded images.
This commit is contained in:
parent
e02a6038a4
commit
c711146ad4
2 changed files with 73 additions and 5 deletions
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue