@@ -2,6 +2,7 @@ package tui
22
33import (
44 "fmt"
5+ "slices"
56 "strings"
67 "time"
78
@@ -86,6 +87,7 @@ type Model struct {
8687 ShowSummary bool
8788 RunStats runStats
8889 Images []agentcore.ContentBlock // attached images (from Ctrl+V clipboard paste)
90+ ImageCursor int // -1 = not selecting; 0+ = selected image index
8991 Pasting int // number of async image reads in progress (clipboard paste or drag-drop)
9092
9193 Glamour * glamour.TermRenderer
@@ -148,6 +150,7 @@ func New(driver Driver, modelName string, cfg ...Config) Model {
148150 Cwd : c .Cwd ,
149151 GitBranch : c .GitBranch ,
150152 ShowWelcome : true ,
153+ ImageCursor : - 1 ,
151154 config : c ,
152155 }
153156}
@@ -282,9 +285,20 @@ func (m Model) View() string {
282285 if len (m .Images ) > 0 {
283286 var tags []string
284287 for i := range m .Images {
285- tags = append (tags , fmt .Sprintf ("[Image #%d]" , i + 1 ))
288+ tag := fmt .Sprintf ("[Image #%d]" , i + 1 )
289+ if m .ImageCursor == i {
290+ tags = append (tags , ImageSelectedStyle .Render (tag ))
291+ } else {
292+ tags = append (tags , CommandStyle .Render (tag ))
293+ }
286294 }
287- parts = append (parts , CommandStyle .Render (strings .Join (tags , " " )))
295+ line := strings .Join (tags , " " )
296+ if m .ImageCursor >= 0 {
297+ line += " " + MutedStyle .Render ("Delete to remove · Esc to cancel" )
298+ } else {
299+ line += " " + MutedStyle .Render ("(↑ to select)" )
300+ }
301+ parts = append (parts , line )
288302 }
289303 parts = append (parts , SeparatorStyle .Render (strings .Repeat ("─" , m .Width )))
290304 parts = append (parts , m .Input .View ())
@@ -343,6 +357,35 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
343357 }
344358 }
345359
360+ // Image selection mode: navigate and delete attached images.
361+ if m .ImageCursor >= 0 {
362+ switch msg .String () {
363+ case "left" , "h" :
364+ if m .ImageCursor > 0 {
365+ m .ImageCursor --
366+ }
367+ return m , nil
368+ case "right" , "l" :
369+ if m .ImageCursor < len (m .Images )- 1 {
370+ m .ImageCursor ++
371+ }
372+ return m , nil
373+ case "delete" , "backspace" :
374+ m .Images = slices .Delete (m .Images , m .ImageCursor , m .ImageCursor + 1 )
375+ if len (m .Images ) == 0 {
376+ m .ImageCursor = - 1
377+ } else if m .ImageCursor >= len (m .Images ) {
378+ m .ImageCursor = len (m .Images ) - 1
379+ }
380+ return m , nil
381+ case "esc" , "down" :
382+ m .ImageCursor = - 1
383+ return m , nil
384+ }
385+ // Other keys (ctrl+c, enter, etc.): exit selection, fall through to normal handling.
386+ m .ImageCursor = - 1
387+ }
388+
346389 switch msg .String () {
347390 case "ctrl+c" :
348391 if m .QuitPending {
@@ -393,6 +436,7 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
393436 }
394437 images := m .Images
395438 m .Images = nil
439+ m .ImageCursor = - 1
396440 m .Input .Reset ()
397441 m .Input .SetHeight (1 )
398442 m .ShowSummary = false
@@ -422,6 +466,13 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
422466 output += "\n " + ErrorStyle .Render (" error: " + err .Error ())
423467 }
424468 return m , tea .Println (output )
469+
470+ case "up" :
471+ // Enter image selection when cursor is on the first line of input.
472+ if len (m .Images ) > 0 && ! m .Running && m .Input .Line () == 0 {
473+ m .ImageCursor = len (m .Images ) - 1
474+ return m , nil
475+ }
425476 }
426477
427478 var cmd tea.Cmd
0 commit comments