Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/pr-assets/after-quiz-runs-in-iframe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/pr-assets/after-renders-as-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/pr-assets/before-renders-as-source.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 23 additions & 1 deletion app/agent_wiki_explorer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import streamlit as st
import streamlit.components.v1 as components

from app.utils.agent_wiki import (
WikiDocument,
Expand All @@ -17,6 +18,10 @@

st.set_page_config(page_title="Agent wiki explorer", page_icon="📚")

# Viewport height for embedded HTML artifacts. Tall enough that a report's first
# screen is visible without scrolling the iframe; the iframe scrolls beyond that.
HTML_ASSET_IFRAME_HEIGHT = 900

# Map extensions of non-markdown text documents to a syntax-highlighting language
# so artifacts like `repro_app.py` render inline as code.
CODE_LANGUAGE_BY_EXTENSION = {
Expand All @@ -30,7 +35,6 @@
".js": "javascript",
".ts": "typescript",
".css": "css",
".html": "html",
".sql": "sql",
}

Expand Down Expand Up @@ -80,6 +84,22 @@ def _render_code_asset(document: WikiDocument, language: str) -> None:
st.code(document_text, language=language)


def _render_html_asset(document: WikiDocument) -> None:
document_text, document_error = fetch_wiki_document_text(document["path"])
if document_error:
st.error(document_error)
return
if document_text is None:
st.warning("The selected asset could not be loaded.")
return
st.caption(document["path"])
# A sandboxed iframe, so the artifact's own stylesheet cannot leak into the
# app chrome and its scripts (e.g. an interactive quiz) still run.
components.html(document_text, height=HTML_ASSET_IFRAME_HEIGHT, scrolling=True)
with st.expander("View source"):
st.code(document_text, language="html")


def _render_other_asset() -> None:
st.info("This asset is not rendered inline.")

Expand Down Expand Up @@ -132,6 +152,8 @@ def _render_other_asset() -> None:
_render_markdown_document(selected_document, document_text)
elif selected_document["is_image"]:
_render_image_asset(selected_document)
elif selected_document["is_html"]:
_render_html_asset(selected_document)
elif selected_document["extension"] in CODE_LANGUAGE_BY_EXTENSION:
_render_code_asset(selected_document, CODE_LANGUAGE_BY_EXTENSION[selected_document["extension"]])
else:
Expand Down
9 changes: 9 additions & 0 deletions app/utils/agent_wiki.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
TEXT_DOCUMENT_EXTENSIONS = {".md", ".markdown", ".mdx", ".txt"}
MARKDOWN_EXTENSIONS = {".md", ".markdown", ".mdx"}
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"}
# Self-contained HTML artifacts (e.g. visual PR reports). Rendered in a sandboxed
# iframe rather than as source, so their own styling and scripts stay intact.
HTML_EXTENSIONS = {".html", ".htm"}

ISSUES_SECTION = "issues"
# Canonical filenames the agent wiki uses for runnable issue reproductions.
Expand All @@ -53,6 +56,7 @@ class WikiDocument(TypedDict):
source_url: str
is_markdown: bool
is_image: bool
is_html: bool


class WikiIssueRepro(TypedDict):
Expand Down Expand Up @@ -149,6 +153,10 @@ def is_image_path(path: str) -> bool:
return _get_extension(path) in IMAGE_EXTENSIONS


def is_html_path(path: str) -> bool:
return _get_extension(path) in HTML_EXTENSIONS


def build_wiki_raw_url(path: str) -> str:
return f"{WIKI_RAW_URL_PREFIX}/{quote(path, safe='/')}"

Expand Down Expand Up @@ -204,6 +212,7 @@ def build_wiki_documents(paths: list[str]) -> list[WikiDocument]:
"source_url": build_wiki_source_url(path),
"is_markdown": is_markdown_path(path),
"is_image": is_image_path(path),
"is_html": is_html_path(path),
}
)

Expand Down
17 changes: 17 additions & 0 deletions tests/test_agent_wiki.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@ def test_build_wiki_documents_includes_issue_artifacts() -> None:
assert documents[1]["folder"] == "issues/12345"


def test_build_wiki_documents_flags_html_artifacts_as_html() -> None:
documents = build_wiki_documents(
[
"pull-requests/12345/report.html",
"pull-requests/12345/notes.txt",
]
)

report = documents[1]
assert report["path"] == "pull-requests/12345/report.html"
assert report["is_html"] is True
assert report["is_markdown"] is False
assert report["is_image"] is False
# Plain text artifacts keep rendering as code, not in an iframe.
assert documents[0]["is_html"] is False


def test_get_wiki_folder_groups_issue_artifacts_by_issue_number() -> None:
assert get_wiki_folder("issues/12345/repro_app.py") == "issues/12345"
assert get_wiki_folder("issues/12345/nested/notes.md") == "issues/12345"
Expand Down