mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
feat(desktop): bundle element selection into composer prefill
Wire createPicker into the preview-agent IIFE: enter/exit picker via host messages, bind DOM mousemove/click (capture phase) to picker hover/select, and on click emit a selection event carrying ElementMetadata + annotated screenshot. Route the selection event in previewEvents.ts through buildSelectionComposerText into queueComposerPrefill with the screenshot attachment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
96a288425f
commit
dbef9f2d0a
File diff suppressed because one or more lines are too long
@ -26,4 +26,14 @@ describe('subscribePreviewEvents', () => {
|
||||
attachments: [expect.objectContaining({ type: 'image', data: 'data:image/png;base64,AAAA' })],
|
||||
}))
|
||||
})
|
||||
|
||||
it('selection event prefills composer with text + annotated screenshot', async () => {
|
||||
await subscribePreviewEvents('s1')
|
||||
const payload = { pageUrl: 'http://x/', element: { selector: '#t', tag: 'h1', classes: [] }, change: { description: '改一下' }, screenshot: { dataUrl: 'data:image/png;base64,AAAA', kind: 'element' } }
|
||||
listeners['preview://event']!({ payload: JSON.stringify({ v: 1, type: 'selection', payload }) })
|
||||
expect(prefill).toHaveBeenCalledWith('s1', expect.objectContaining({
|
||||
text: expect.stringContaining('#t'),
|
||||
attachments: [expect.objectContaining({ type: 'image', data: 'data:image/png;base64,AAAA' })],
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { useBrowserPanelStore } from '../stores/browserPanelStore'
|
||||
import { useChatStore } from '../stores/chatStore'
|
||||
import { buildSelectionComposerText, type SelectionPayload } from './selectionComposer'
|
||||
|
||||
function kindLabel(kind?: string): string {
|
||||
if (kind === 'viewport') return 'viewport'
|
||||
@ -10,7 +11,7 @@ function kindLabel(kind?: string): string {
|
||||
|
||||
export async function subscribePreviewEvents(sessionId: string): Promise<() => void> {
|
||||
return listen<string>('preview://event', (e) => {
|
||||
let msg: { type?: string; url?: string; title?: string; dataUrl?: string; kind?: string }
|
||||
let msg: { type?: string; url?: string; title?: string; dataUrl?: string; kind?: string; payload?: unknown }
|
||||
try { msg = JSON.parse(e.payload) } catch { return }
|
||||
const store = useBrowserPanelStore.getState()
|
||||
if (msg.type === 'navigated' && msg.url) store.setNavigated(sessionId, msg.url, msg.title ?? '')
|
||||
@ -21,5 +22,14 @@ export async function subscribePreviewEvents(sessionId: string): Promise<() => v
|
||||
attachments: [{ type: 'image', name: `screenshot-${kindLabel(msg.kind)}.png`, mimeType: 'image/png', data: msg.dataUrl }],
|
||||
})
|
||||
}
|
||||
else if (msg.type === 'selection' && msg.payload) {
|
||||
const p = msg.payload as SelectionPayload & { screenshot?: { dataUrl?: string; kind?: string } }
|
||||
useChatStore.getState().queueComposerPrefill(sessionId, {
|
||||
text: buildSelectionComposerText(p),
|
||||
attachments: p.screenshot?.dataUrl
|
||||
? [{ type: 'image', name: 'selection.png', mimeType: 'image/png', data: p.screenshot.dataUrl }]
|
||||
: [],
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { createBridge } from './bridge'
|
||||
import { captureToDataUrl } from './screenshot'
|
||||
import { createPicker } from './picker'
|
||||
import { buildElementMetadata } from './metadata'
|
||||
|
||||
;(() => {
|
||||
;(window as unknown as { __PREVIEW_AGENT__?: boolean }).__PREVIEW_AGENT__ = true
|
||||
@ -19,6 +21,43 @@ import { captureToDataUrl } from './screenshot'
|
||||
catch (e) { bridge.reportError(String(e)) }
|
||||
})
|
||||
|
||||
let pickerOn = false
|
||||
const picker = createPicker({ onSelect: () => {} })
|
||||
|
||||
const emitSelection = async (el: Element) => {
|
||||
try {
|
||||
const dataUrl = await captureToDataUrl('element', el)
|
||||
bridge.send({
|
||||
type: 'selection',
|
||||
payload: {
|
||||
pageUrl: window.location.href,
|
||||
sourceHint: document.title || undefined,
|
||||
element: buildElementMetadata(el),
|
||||
screenshot: { dataUrl, kind: 'element' },
|
||||
},
|
||||
})
|
||||
} catch (e) { bridge.reportError(String(e)) }
|
||||
}
|
||||
|
||||
bridge.on('enter-picker', () => { pickerOn = true; picker.enter() })
|
||||
bridge.on('exit-picker', () => { pickerOn = false; picker.exit() })
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!pickerOn) return
|
||||
const t = e.target
|
||||
if (t instanceof Element) picker.hover(t)
|
||||
}, true)
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!pickerOn) return
|
||||
e.preventDefault(); e.stopPropagation()
|
||||
picker.select()
|
||||
const el = picker.current() // capture BEFORE exit (exit clears current)
|
||||
pickerOn = false
|
||||
picker.exit()
|
||||
if (el) void emitSelection(el)
|
||||
}, true)
|
||||
|
||||
const onReady = () => { bridge.reportReady(); bridge.reportNavigated() }
|
||||
if (document.readyState !== 'loading') onReady()
|
||||
else document.addEventListener('DOMContentLoaded', onReady)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user