mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-26 11:12:03 +00:00
feat(desktop): drop files anywhere in the chat area (#36262)
* feat(desktop): drop files anywhere in the chat area File drops were only wired to the composer input. Add a reusable useFileDropZone hook (enter/leave depth counting + capture-phase reset so the affordance clears even when the composer claims the drop) and a pointer-events-none ChatDropOverlay, wired onto the conversation viewport. Drops funnel through the existing onAttachDroppedItems; composer drops keep their own inline-ref behavior. * fix(desktop): chat-area drops insert inline @file refs, not attachment cards Match the composer-input drop behavior — funnel dropped paths through droppedFileInlineRef + the composer insert bus so they render as inline ref chips instead of attachment cards. * fix(desktop): don't render bare file paths as tool images (404) vision_analyze reports its input image as a local filesystem path, which toolImageUrl handed straight to <img src>. In the renderer that resolves against the dev-server origin and 404s. Restrict inline tool images to fetchable sources (data: URLs and remote http(s)); bare paths now fall back to the tool's codicon.
This commit is contained in:
parent
e1eba6f8cc
commit
359f2be12e
5 changed files with 180 additions and 5 deletions
|
|
@ -0,0 +1,35 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { buildToolView, type ToolPart } from './tool-fallback-model'
|
||||
|
||||
const part = (overrides: Partial<ToolPart>): ToolPart => ({
|
||||
args: {},
|
||||
isError: false,
|
||||
result: {},
|
||||
toolCallId: 'call_1',
|
||||
toolName: 'vision_analyze',
|
||||
type: 'tool-call',
|
||||
...overrides
|
||||
})
|
||||
|
||||
describe('buildToolView image handling', () => {
|
||||
// vision_analyze reports the input image as a local path; an <img> pointed at
|
||||
// a bare path resolves against the renderer origin and 404s, so we render the
|
||||
// tool codicon instead of a broken image.
|
||||
it('drops bare filesystem paths', () => {
|
||||
expect(buildToolView(part({ args: { path: '/Users/me/shot.png' } }), '').imageUrl).toBe('')
|
||||
expect(buildToolView(part({ result: { image_path: '/tmp/out.jpg' } }), '').imageUrl).toBe('')
|
||||
})
|
||||
|
||||
it('keeps fetchable data URLs', () => {
|
||||
const dataUrl = 'data:image/png;base64,AAAA'
|
||||
|
||||
expect(buildToolView(part({ result: { image_url: dataUrl } }), '').imageUrl).toBe(dataUrl)
|
||||
})
|
||||
|
||||
it('keeps remote http(s) image URLs', () => {
|
||||
const url = 'https://example.com/pic.webp'
|
||||
|
||||
expect(buildToolView(part({ result: { url } }), '').imageUrl).toBe(url)
|
||||
})
|
||||
})
|
||||
|
|
@ -786,9 +786,14 @@ function toolImageUrl(args: Record<string, unknown>, result: Record<string, unkn
|
|||
return ''
|
||||
}
|
||||
|
||||
return candidate.toLowerCase().startsWith('data:image/') || /\.(png|jpe?g|gif|webp|bmp|svg)(\?|#|$)/i.test(candidate)
|
||||
? candidate
|
||||
: ''
|
||||
// Only inline-render images the renderer can actually fetch: data URLs or
|
||||
// remote http(s). A bare filesystem path (e.g. vision_analyze's input image)
|
||||
// resolves against the dev-server origin and 404s — fall back to the tool's
|
||||
// codicon instead of a broken <img>.
|
||||
const isDataImage = candidate.toLowerCase().startsWith('data:image/')
|
||||
const isRemoteImage = /^https?:\/\//i.test(candidate) && /\.(png|jpe?g|gif|webp|bmp|svg)(\?|#|$)/i.test(candidate)
|
||||
|
||||
return isDataImage || isRemoteImage ? candidate : ''
|
||||
}
|
||||
|
||||
function stripAnsi(value: string): string {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue