Skip to content

feat: /image attachment, /browse browser-view, and vision-message support#2

Merged
Rahulchaube1 merged 1 commit intomainfrom
copilot/add-chat-interface-and-browser-view
Mar 29, 2026
Merged

feat: /image attachment, /browse browser-view, and vision-message support#2
Rahulchaube1 merged 1 commit intomainfrom
copilot/add-chat-interface-and-browser-view

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 29, 2026

Adds image attachment, a rich terminal browser view, and wires vision-capable multi-part message formatting into the REPL — none of which existed before.

Changes

/image <path|url> …

  • Queues local files (base64-encoded data URLs) or remote HTTPS image URLs onto BaseCoder.pending_images
  • On the next run() call, images are bundled into the user turn as OpenAI/Anthropic multi-part content, then cleared
[QGo] > /image screenshot.png https://example.com/diagram.png
🖼  Image #1 queued: screenshot.png
🖼  Image #2 queued: https://example.com/diagram.png
  2 image(s) queued — they will be sent with your next message.
[QGo] > what does this architecture diagram show?

/browse <url>

  • Renders a structured terminal browser-view: address bar (title + status), Table of Contents (h1–h3), and a numbered Links panel
  • Backed by new fetch_page_info() in web_scraper.py returning {title, description, status_code, headings, links, content}
  • Page text is injected into conversation context identically to /web

Vision message building (base_coder.py)

  • BaseCoder gains pending_images: list[str]
  • _build_messages accepts images= and emits the multi-part content array when populated:
    [{"type": "text", "text": "…"}, {"type": "image_url", "image_url": {"url": "…"}}]

Supporting additions

  • QGoIO.print_browse() — Rich panels for browser view
  • QGoIO.print_image_added() — per-image queue confirmation
  • /browse and /image registered in dispatch table and tab-completion
  • /help table updated

…pport

- BaseCoder: add pending_images list; run() captures/clears images and passes
  them to _build_messages; _build_messages builds multi-part vision content
  when images are present (OpenAI/Anthropic vision API format)
- web_scraper: add fetch_page_info() returning structured page data
  (title, description, status_code, headings, links, content)
- terminal: add print_browse() rich browser-view panel and
  print_image_added() confirmation; update /help table with new commands
- commands: add _cmd_browse() and _cmd_image() with full dispatch
- repl: add /browse and /image to tab-completion list

Agent-Logs-Url: https://github.com/Rahulchaube1/QGo/sessions/50b8c034-e924-4434-912d-f950eba6066b

Co-authored-by: Rahulchaube1 <157899057+Rahulchaube1@users.noreply.github.com>
@Rahulchaube1 Rahulchaube1 marked this pull request as ready for review March 29, 2026 00:13
Copilot AI review requested due to automatic review settings March 29, 2026 00:14
@Rahulchaube1 Rahulchaube1 merged commit 8d929b6 into main Mar 29, 2026
4 of 10 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new REPL capabilities for (1) attaching images to the next user turn for vision-capable models and (2) browsing a URL with a richer terminal “browser view”, plus wiring needed to format multi-part (text + image) messages.

Changes:

  • Added /browse command + fetch_page_info() for structured page metadata (title/TOC/links) and terminal rendering.
  • Added /image command to queue local/remote images and send them with the next message.
  • Updated BaseCoder message building to support multi-part vision message payloads.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
qgo/utils/web_scraper.py Adds fetch_page_info() to return structured page data for /browse.
qgo/ui/terminal.py Adds rich rendering for browser view + image-queued confirmation; updates /help.
qgo/ui/repl.py Registers /browse and /image for tab completion.
qgo/ui/commands.py Registers and implements /browse and /image commands.
qgo/coders/base_coder.py Adds pending_images queue and builds multi-part user messages when images are present.
Comments suppressed due to low confidence (1)

qgo/utils/web_scraper.py:166

  • There is an unexpected indent starting at the """Minimal fallback using only urllib...""" line; it looks like the def _fetch_plain(...): wrapper was accidentally removed. As written, this will raise IndentationError on import and also breaks fetch_url()'s except ImportError: return _fetch_plain(...) fallback path. Reintroduce def _fetch_plain(url: str, timeout: int = 15) -> str: at module scope and indent this block under it.

    """Minimal fallback using only urllib (no requests/bs4)."""
    try:
        import urllib.request


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread qgo/utils/web_scraper.py
Comment on lines +122 to +123
response.raise_for_status()
result["status_code"] = response.status_code
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch_page_info() calls response.raise_for_status() before recording the HTTP status. For non-2xx responses this will jump to the exception handler and status_code will remain 0, so the browser view can’t display the actual status. Set result["status_code"] = response.status_code before raise_for_status(), or avoid raising and instead handle non-2xx while still parsing title/headings.

Suggested change
response.raise_for_status()
result["status_code"] = response.status_code
result["status_code"] = response.status_code
response.raise_for_status()

Copilot uses AI. Check for mistakes.
Comment thread qgo/ui/commands.py
Comment on lines +231 to +234
self.coder.messages.append({
"role": "user",
"content": f"Web page content from {url}:\n\n{content[:8000]}",
})
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseCoder.messages is a list[Message] and _build_messages() calls msg.to_dict() on history items. Here /browse appends a plain dict into self.coder.messages, which will raise AttributeError: 'dict' object has no attribute 'to_dict' on the next run(). Append a qgo.models.Message(...) (or provide a helper on BaseCoder to add context messages) instead of a raw dict.

Copilot uses AI. Check for mistakes.
Comment thread qgo/ui/commands.py
Comment on lines +267 to +270
elif src.startswith(("http://", "https://")):
# Remote image — pass URL directly (vision models support this)
self.coder.pending_images.append(src)
self.io.print_image_added(src, len(self.coder.pending_images))
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says remote HTTPS image URLs are supported, but /image currently accepts both http:// and https://. If HTTPS-only is intended, restrict to https:// (and consider warning on http:// so behavior matches the documented contract).

Copilot uses AI. Check for mistakes.
Comment thread qgo/coders/base_coder.py
Comment on lines +168 to +170
# Capture and clear any pending images
images = self.pending_images[:]
self.pending_images.clear()
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pending_images is cleared before _send() is called. If the LLM request fails/raises, the images are lost and won’t be attached on retry. Clear pending_images only after a successful send (or restore them in an exception handler) so queued attachments aren’t dropped on transient errors.

Copilot uses AI. Check for mistakes.
Comment thread qgo/coders/base_coder.py
Comment thread qgo/utils/web_scraper.py
Comment on lines +153 to +154
# Readable content (reuse existing fetch_url)
result["content"] = fetch_url(url, timeout)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch_page_info() fetches the page via requests.get(...) and then immediately calls fetch_url(url, ...), which performs a second HTTP request for the same URL. This doubles latency and load for /browse. Consider refactoring fetch_url to accept already-fetched HTML (or extracting the HTML→readable-text logic) so fetch_page_info() can reuse response.text/soup without another network call.

Copilot uses AI. Check for mistakes.
Comment thread qgo/utils/web_scraper.py
Comment on lines +133 to +134
if meta and isinstance(meta, object) and hasattr(meta, "get"):
result["description"] = meta.get("content", "") # type: ignore[union-attr]
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The meta description extraction has redundant checks (isinstance(meta, object) and hasattr(meta, "get")) and a # type: ignore. Since soup.find(...) returns a BeautifulSoup Tag (or None), you can simplify this to if meta: result["description"] = meta.get("content", "") to improve readability and remove the ignore.

Suggested change
if meta and isinstance(meta, object) and hasattr(meta, "get"):
result["description"] = meta.get("content", "") # type: ignore[union-attr]
if meta:
result["description"] = meta.get("content", "")

Copilot uses AI. Check for mistakes.
Comment thread qgo/ui/commands.py
Comment thread qgo/coders/base_coder.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants