Class: Rubino::CLI::Chat::ImageInbox
- Inherits:
-
Object
- Object
- Rubino::CLI::Chat::ImageInbox
- Defined in:
- lib/rubino/cli/chat/image_inbox.rb
Overview
The REPL’s image-attachment inbox (attach an image from the terminal), extracted from ChatCommand (#17).
Attachments live in #pending_image_paths between the prompt read and the turn; run_turn consumes + clears them via #take! so each image is sent once into the native vision slot (image_paths →Lifecycle#execute → adapter ‘with:`).
Class Method Summary collapse
-
.resolve_oneshot(query, flag_values) ⇒ Object
Builds the [text, image_paths] pair for a one-shot turn.
Instance Method Summary collapse
-
#extract_images!(input, ui) ⇒ Object
Parses the line for image references (@image, dropped/quoted/escaped path), moves any into @pending_image_paths and returns the cleaned text.
-
#handle_image_command(input, ui) ⇒ Object
Handles the REPL-local image commands.
- #pending_image_paths ⇒ Object
-
#stage_flag_images(flag_values, ui) ⇒ Object
Seeds the interactive pending-images inbox from –image/-i flag paths (#160), through the SAME attachment gate every other staging surface uses (Attachments::Classify + Policy via ImageInput#attachment_error).
-
#take! ⇒ Object
Consumes the turn’s queued image attachments (the native vision slot) and resets so they’re attached exactly once, not re-sent next turn.
Class Method Details
.resolve_oneshot(query, flag_values) ⇒ Object
Builds the [text, image_paths] pair for a one-shot turn. Pulls @image / dropped-path tokens out of the prompt (so they hit the vision slot, not the literal text) and prepends any paths given via –image. Flag paths are expanded the same way as in-line tokens; a flag path that isn’t a readable image is reported and skipped rather than silently dropped.
Every candidate then passes the SAME secure-by-default attachment gate as the server/run path (Attachments::Classify + Policy, via ImageInput#attachment_error) — a policy rejection is a clean one-line error BEFORE any network call, not five provider retries (#98).
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/rubino/cli/chat/image_inbox.rb', line 24 def self.resolve_oneshot(query, flag_values) flag_paths = Array(flag_values).map { |p| Interaction::ImageInput.(p) } flag_paths.each do |p| next if LLM::ContentBuilder.image_file?(p) && File.file?(p) warn "rubino: ignoring --image #{p} (not a readable image file)" end valid_flags = flag_paths.select { |p| LLM::ContentBuilder.image_file?(p) && File.file?(p) } valid_flags.each do |p| reason = Interaction::ImageInput.(p) raise Rubino::Error, "--image #{p}: #{reason}" if reason end result = Interaction::ImageInput.parse(query, existing: valid_flags) if (rejection = result.rejected.first) raise Rubino::Error, "#{rejection[:path]}: #{rejection[:reason]}" end [result.text, result.image_paths] end |
Instance Method Details
#extract_images!(input, ui) ⇒ Object
Parses the line for image references (@image, dropped/quoted/escaped path), moves any into @pending_image_paths and returns the cleaned text. Non-image references are left in the text (current behaviour). Shows an in-prompt indicator for whatever is now attached. A candidate the attachment policy rejects (oversize / spoofed extension / unsafe) is dropped with a one-line warning instead of being shipped (#98).
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/rubino/cli/chat/image_inbox.rb', line 85 def extract_images!(input, ui) result = Interaction::ImageInput.parse(input, existing: pending_image_paths) result.rejected.each do |rejection| ui.warning("not attached — #{File.basename(rejection[:path])}: #{rejection[:reason]}") end newly = result.image_paths - pending_image_paths @pending_image_paths = result.image_paths # A line with text AND an @image sends BOTH on THIS turn (the cleaned # text is non-empty, so the main loop submits now); an image-only line # stages for the next message. The indicator must match that # disposition — saying "sent with your next message" on a text+image # line is wrong (#225). unless newly.empty? attached_now = !result.text.strip.empty? show_image_indicator(ui, newly, attached_now: attached_now) end result.text end |
#handle_image_command(input, ui) ⇒ Object
Handles the REPL-local image commands. Returns true when it consumed the input (so the main loop should ‘next`), false otherwise.
/paste — grab an image from the clipboard into image_paths
/clear-images — drop all pending attachments
rubocop:disable Naming/PredicateMethod – “did I consume the line”, not a pure predicate
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/rubino/cli/chat/image_inbox.rb', line 110 def handle_image_command(input, ui) case input.strip.downcase when "/clear-images", "/clear-image" if pending_image_paths.empty? ui.info("No attached images to clear.") else ui.info("Cleared #{pending_image_paths.size} attached image(s).") @pending_image_paths = [] end true when "/paste" paste_clipboard_image(ui) true else false end end |
#pending_image_paths ⇒ Object
45 46 47 |
# File 'lib/rubino/cli/chat/image_inbox.rb', line 45 def pending_image_paths @pending_image_paths ||= [] end |
#stage_flag_images(flag_values, ui) ⇒ Object
Seeds the interactive pending-images inbox from –image/-i flag paths (#160), through the SAME attachment gate every other staging surface uses (Attachments::Classify + Policy via ImageInput#attachment_error). A bad flag path warns and is skipped — interactive startup must not die on it the way one-shot raises. Staged images show the usual indicator and are covered by /clear-images, as documented.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/rubino/cli/chat/image_inbox.rb', line 63 def stage_flag_images(flag_values, ui) Array(flag_values).each do |raw| path = Interaction::ImageInput.(raw) unless LLM::ContentBuilder.image_file?(path) && File.file?(path) ui.warning("not attached — #{raw}: not a readable image file") next end if (reason = Interaction::ImageInput.(path)) ui.warning("not attached — #{File.basename(path)}: #{reason}") next end pending_image_paths << path unless pending_image_paths.include?(path) end show_image_indicator(ui, pending_image_paths) unless pending_image_paths.empty? end |
#take! ⇒ Object
Consumes the turn’s queued image attachments (the native vision slot) and resets so they’re attached exactly once, not re-sent next turn.
51 52 53 54 55 |
# File 'lib/rubino/cli/chat/image_inbox.rb', line 51 def take! paths = pending_image_paths @pending_image_paths = [] paths end |