Skip to content

Commit 64247ca

Browse files
committed
fix: optimistically show image banner
1 parent 994fa5b commit 64247ca

File tree

5 files changed

+50
-8
lines changed

5 files changed

+50
-8
lines changed

cli/src/chat.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,15 +1015,40 @@ export const Chat = ({
10151015
onBashHistoryUp: navigateUp,
10161016
onBashHistoryDown: navigateDown,
10171017
onPasteImage: () => {
1018-
// Show placeholder immediately so user sees the banner right away
1018+
// Show placeholder immediately for instant feedback
10191019
const placeholderPath = addClipboardPlaceholder()
10201020

1021-
// Check and process clipboard image in background
1021+
// Check and process clipboard in background
10221022
setTimeout(() => {
1023-
// Check if clipboard actually has an image
10241023
if (!hasClipboardImage()) {
1025-
// No image - quietly remove placeholder (brief flash is acceptable)
1024+
// No image - remove placeholder and simulate text paste
10261025
useChatStore.getState().removePendingImage(placeholderPath)
1026+
1027+
// Read text from clipboard and insert it
1028+
try {
1029+
const { spawnSync } = require('child_process')
1030+
const textResult = spawnSync(
1031+
process.platform === 'darwin' ? 'pbpaste' :
1032+
process.platform === 'win32' ? 'powershell' : 'xclip',
1033+
process.platform === 'win32' ? ['-Command', 'Get-Clipboard'] :
1034+
process.platform === 'linux' ? ['-selection', 'clipboard', '-o'] : [],
1035+
{ encoding: 'utf-8', timeout: 1000 }
1036+
)
1037+
if (textResult.status === 0 && textResult.stdout) {
1038+
const text = textResult.stdout
1039+
setInputValue((prev) => {
1040+
const before = prev.text.slice(0, prev.cursorPosition)
1041+
const after = prev.text.slice(prev.cursorPosition)
1042+
return {
1043+
text: before + text + after,
1044+
cursorPosition: before.length + text.length,
1045+
lastEditDueToNav: false,
1046+
}
1047+
})
1048+
}
1049+
} catch {
1050+
// Ignore errors - text paste just won't work
1051+
}
10271052
return
10281053
}
10291054

@@ -1040,7 +1065,7 @@ export const Chat = ({
10401065
void addPendingImageFromFile(result.imagePath, cwd, placeholderPath)
10411066
}, 0)
10421067

1043-
return true
1068+
return true // Always consume - we handle text paste ourselves if needed
10441069
},
10451070
}),
10461071
[

cli/src/commands/router.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { getProjectRoot } from '../project-files'
1717
import { useChatStore } from '../state/chat-store'
1818
import { getSystemMessage, getUserMessage } from '../utils/message-history'
19-
import { capturePendingImages, validateAndAddImage } from '../utils/add-pending-image'
19+
import { capturePendingImages, hasProcessingImages, validateAndAddImage } from '../utils/add-pending-image'
2020
import {
2121
buildBashHistoryMessages,
2222
createRunTerminalToolResult,
@@ -355,6 +355,13 @@ export async function routeUserPrompt(
355355
}
356356

357357
// Regular message or unknown slash command - send to agent
358+
359+
// Block sending if images are still processing
360+
if (hasProcessingImages()) {
361+
// Don't send - images are still processing
362+
return
363+
}
364+
358365
saveToHistory(trimmed)
359366
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
360367

cli/src/components/pending-images-banner.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export const PendingImagesBanner = () => {
8888
{readyCount > 0 && `${pluralize(readyCount, 'image')} attached`}
8989
{readyCount > 0 && processingCount > 0 && ', '}
9090
{processingCount > 0 && `${pluralize(processingCount, 'image')} processing`}
91+
{processingCount > 0 && ' (wait to send)'}
9192
</text>
9293

9394
{/* Image cards in a horizontal row - only valid images */}

cli/src/utils/add-pending-image.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@ export async function validateAndAddImage(
168168
return { success: true }
169169
}
170170

171+
/**
172+
* Check if any pending images are still processing.
173+
*/
174+
export function hasProcessingImages(): boolean {
175+
return useChatStore.getState().pendingImages.some(
176+
(img) => img.status === 'processing',
177+
)
178+
}
179+
171180
/**
172181
* Capture and clear pending images so they can be passed to the queue without
173182
* duplicating state handling logic in multiple callers.

cli/src/utils/clipboard-image.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ function generateImageFilename(): string {
3131

3232
/**
3333
* Check if clipboard contains an image (macOS)
34+
* Uses 'clipboard info' which is the fastest way to check clipboard types
3435
*/
3536
function hasImageMacOS(): boolean {
3637
try {
37-
// Use osascript to check clipboard type
3838
const result = spawnSync('osascript', [
3939
'-e',
4040
'clipboard info',
41-
], { encoding: 'utf-8', timeout: 5000 })
41+
], { encoding: 'utf-8', timeout: 1000 })
4242

4343
if (result.status !== 0) {
4444
return false

0 commit comments

Comments
 (0)