Skip to content

Annotate reports with an AI-written story - #1923

Merged
ahuang11 merged 25 commits into
holoviz:mainfrom
ghostiee-11:feat/report-story-annotation
Aug 14, 2026
Merged

Annotate reports with an AI-written story#1923
ahuang11 merged 25 commits into
holoviz:mainfrom
ghostiee-11:feat/report-story-annotation

Conversation

@ghostiee-11

@ghostiee-11 ghostiee-11 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Adds an Annotate button to the report toolbar that uses an LLM to write a story about the selected tables and charts: an overall summary at the top plus a short note under each section. The narrative renders in the report and flows into both the Notebook and HTML export. Closes #1912.

Note: this stacks on #1911 (#1922); the first commits belong to that PR, so please review after it merges.

Untitled.-.09.July.2026.at.20.50.01.1.mp4

Add a Section.include_in_export flag and route to_notebook/to_html
through _export_views/_export_view so a report can be exported with a
subset of its sections. Defaults keep every section included, so
existing exports are unchanged.
Render each report accordion card header as a checkbox bound to the
section include_in_export flag next to its title, so users can pick
which sections to keep before exporting. The checkbox appears once the
report has outputs and leaves the rendered report unchanged.
Apply the HoloViz cleanup review: replace the list-building loop in
Report._export_views with a comprehension. No behavior change.
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.54896% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.47%. Comparing base (90facdf) to head (8774862).
⚠️ Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
lumen/ai/story.py 90.87% 24 Missing ⚠️
lumen/ai/agents/story.py 92.30% 4 Missing ⚠️
lumen/tests/ai/test_story.py 99.17% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1923      +/-   ##
==========================================
+ Coverage   72.48%   73.47%   +0.98%     
==========================================
  Files         203      208       +5     
  Lines       35960    37534    +1574     
==========================================
+ Hits        26066    27578    +1512     
- Misses       9894     9956      +62     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ahuang11

ahuang11 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Can the story interweave plots and tables like a blog post? Also, do you have an updated video?

Also can user write a prompt to ask the LLM to focus on a specific angle?

@ghostiee-11

Copy link
Copy Markdown
Collaborator Author

Yup.. sharing the video only compressing that

@ghostiee-11

Copy link
Copy Markdown
Collaborator Author
Untitled.-.10.July.2026.at.02.51.11.3.mp4

Only Report is ever exported, so the base TaskGroup _export_views /
_export_view wrappers (which just returned self.views / self._view) were
dead code. Drop them and move to_notebook / to_html onto Report next to
its _export_views / _export_view overrides.
@ahuang11

ahuang11 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Okay thanks for sharing the video.

Ideally, the story interweaves pictures and texts. Also, on clicking the annotate, it should show a popup to enter text to guide the story.

@ghostiee-11

Copy link
Copy Markdown
Collaborator Author

Hmm Okay Andrew, will refactor this and add this

@ghostiee-11
ghostiee-11 force-pushed the feat/report-story-annotation branch from 3f567c6 to 6c01e04 Compare July 10, 2026 13:04
@ghostiee-11

Copy link
Copy Markdown
Collaborator Author
Screen.Recording.2026-07-10.at.6.25.28.PM.mp4

@ahuang11

Copy link
Copy Markdown
Contributor

I like it (poor low performer 😭)!

Let's think about UI/UX. The current implementation is a bit weird ongoing back and forth on base report vs story report. I wonder if we can use Tabs or some other layout to differentiate. Also, what if user wants to tweak a specific section, or make manual edits (should this be supported)? Lastly, what if user simply wants to regenerate the full story? Or wants to create a new version?

@ghostiee-11

Copy link
Copy Markdown
Collaborator Author

Haha 😅

Yupp, need suggestions over the UI/UX, planning it, I too feel back and forth weird.

if user wants to tweak a specific section, or make manual edits (should this be supported)?

For this, I was thinking to add some button edit with AI.

Lastly, what if user simply wants to regenerate the full story? Or wants to create a new version?

Hmm, Better UX needed for this....

@ahuang11

ahuang11 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Hmm, Better UX needed for this....

If it can work like Google docs, that'd be cool
https://discourse.holoviz.org/t/how-to-bind-html-contents-using-reactivehtml/5444/2?u=ahuang11

(ReactiveHTML is outdated now, use JSComponent)

Nested sections rendered without a checkbox because only the report drew section headers, and the export read each top level section's aggregated views, so a nested section's selection was ignored. Render a heading and checkbox for nested sections, and walk the tasks when collecting the views so a discarded section is left out of the notebook and the HTML at any depth.
Selection only reached whole sections, so two charts made in one session could not be kept or dropped separately. Move include_in_export onto Task and give every task its own checkbox, and walk the tasks when collecting the views so discarding one chart leaves the rest of its section intact. The report explains the checkboxes at the top rather than on a per-checkbox info icon.
Add a StoryAgent that turns the report's charts and tables into ordered prose and view blocks, plus the catalog builder that describes each view for the prompt.
Add an Annotate action that opens a guidance popup (tone presets plus free text) and writes an AI story that interleaves prose with the charts and tables it discusses. The story is a separate view reachable from the report, an Arrange dialog reorders sections via a JSONEditor, and the notebook and HTML exports follow whichever view is shown.
Replace the toggle that swapped the report view for the story view with a Tabs layout, so the two are told apart at a glance instead of by flipping back and forth. The Story tab appears once a story is generated and is removed when it is discarded, and the exports follow whichever tab is selected.
Render each prose block as a contenteditable JSComponent so the story can be tweaked by hand like a document, and keep the prose as text on the story blocks so an edit flows into the notebook, HTML and Word exports. The text is only pushed back into the element when it changes elsewhere, otherwise the caret would jump to the start while typing.
Add a Regenerate action on the story that reopens the guidance popup so the whole story can be written again with a different angle, and give each paragraph an edit action that asks the LLM to rewrite just that paragraph from an instruction, leaving the charts and the rest of the story untouched.
Regenerating now adds a version instead of replacing the story, and a picker on the story switches between them, so a version the user has edited by hand is never thrown away by asking for a fresh take. Each version owns its blocks, so edits stay with the version they were made on.
The editable paragraphs showed the raw Markdown the model writes, so the story read as source rather than prose. Render each paragraph and swap to the source only while it is being edited, rendering again on blur, using the same Markdown engine the Markdown pane uses.
@ghostiee-11
ghostiee-11 force-pushed the feat/report-story-annotation branch from 6c01e04 to 6bd11e3 Compare July 17, 2026 16:24
The report was vertically centred in the main area, so a tall report or story never received a bounded height and could not scroll, while a short one left a gap below it. Stretch the report to fill the area and scroll its own content instead. Story charts and tables now render as fixed-height figures so they flow like blog-post images rather than stretching to fill and swallowing the scroll, and the export hint is shown only on the report tab where the checkboxes it describes live.
The story catalog labelled each figure with the view's title, which is the pipeline step name (e.g. "Prepare data", "Create pie chart"), so those names leaked into the prose. Drop the title from the catalog and let the data summary describe each figure, and instruct the model to refer to figures by what they show.
@ghostiee-11

Copy link
Copy Markdown
Collaborator Author

@ahuang11, How about this?

Screen.Recording.2026-07-19.at.1.44.19.PM.mp4

fix: conflicts report - keep the story feature (tabs, editable prose, versions) and the fill/scroll fix layered on main's export-selection base, which is identical to ours

fix: conflicts ui - take main's hsplit navigation; the report now fills and scrolls through _nav_content (which stretches, unlike the old centered _main), so our ui.py stretch toggle is no longer needed; kept the story's llm and jsoneditor wiring
Add a Report mode section covering the Annotate feature: generating an interleaved prose-and-chart story, editing paragraphs by hand or with AI, keeping each generation as a version, and exporting whichever tab is shown, with a short worked example.
Comment thread lumen/ai/report.py Outdated
)


class EditableProse(JSComponent):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be in report.py? Or story.py? Or even a panel-extension package https://github.com/panel-extensions/copier-template-panel-extension?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

moved to editor only

Comment thread lumen/ai/report.py Outdated
Comment on lines +977 to +979
_STORY_TAB = 1

_STORY_PRESETS = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should these be in CAPS? Maybe use https://holoviz-dev.github.io/holoviz-skills/contributing-to-holoviz/cleanup/ (and if it doesn't fix this, please update that skill to reflect)

Comment thread lumen/ai/report.py Outdated
self._story_blocks = []
self._story_versions = []
self._story_version = 0
self._story_outline = []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If there's so many _story related variables, should this be a dict or a Story param class?

Comment thread lumen/ai/report.py Outdated
instruction = (self._edit_instruction.value or "").strip()
if self.llm is None or self._edit_index is None or not instruction:
return
from .agents.story import StoryAgent, build_catalog

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Imports

- Move EditableProse out of report.py into editors.py, alongside LumenEditor, so the JSComponent lives with the other editor components.

- Import StoryAgent and build_catalog at the module top rather than inline, now that there is no circular import to avoid.

- Group the report's story state (title, blocks, versions, version, outline) into a StoryState param class instead of five loose instance attributes.
Comment thread lumen/ai/report.py Outdated
Comment thread lumen/ai/report.py Outdated
Comment thread lumen/ai/report.py Outdated
Comment on lines +1082 to +1093
self._story_generate = Button(
label="Generate", variant="contained", icon="auto_stories",
on_click=self._generate_story,
)
self._story_dialog = Dialog(
Typography("Pick a tone or write your own guidance, then generate the story.", variant="body2"),
self._story_presets,
self._story_guidance,
Row(self._story_generate, align="end", margin=(10, 0, 0, 0), sizing_mode="stretch_width"),
show_close_button=True,
title="Annotate Report",
width_option="sm",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm beginning to think we should do inheritance to have a StoryReport(Report) and migrate this to story.py

These are private class attributes rather than public constants, so drop the all-caps naming; give the preset list its own name so it no longer collides with the presets Row.
StoryState is story domain state, so it belongs in agents/story.py with the other story models rather than in report.py.
Move the AI story-annotation tab off the base Report onto a StoryReport
subclass so the base report stays free of story state and UI. Reports in
the app now instantiate StoryReport, base Report keeps only the core
report/export behaviour.
@ghostiee-11
ghostiee-11 requested a review from ahuang11 July 28, 2026 03:28
@ahuang11

Copy link
Copy Markdown
Contributor

Exciting feature! However a few bugs:

  1. Empty white space has its own checkbox
image
  1. The last plot is not showing up in report?

  2. Story tab disappears after going back to chat
    https://github.com/user-attachments/assets/f60372e9-8971-4047-8c28-3ba3403e7f79

  3. Inconsistent icon (turns from a chart to a line)
    https://github.com/user-attachments/assets/d1ed42c9-6066-47e5-8d94-63bf3d8f8596

  4. when clicking menu item, it should navigate back to the chat and exit report mode

ghostiee-11 added a commit to ghostiee-11/lumen that referenced this pull request Aug 12, 2026
Hide the row of a task that renders nothing, so an unviewable output no
longer leaves a lone export checkbox in empty space, and skip the header
of an untitled nested section for the same reason.

Walk the whole exploration tree when building the report. Followups nest
arbitrarily deep, and descending a single level dropped the newest
exploration from the report.

Reuse the report between visits instead of rebuilding it, so the Story
tab and its prose survive a trip back to the chat.

Leave the Explore menu item inactive and outlined while report mode is
on, rather than flipping it to an active timeline icon.

Leave report mode when an exploration is picked in the navigation.
Hide the row of a task that renders nothing, so an unviewable output no
longer leaves a lone export checkbox in empty space, and skip the header
of an untitled nested section for the same reason.

Walk the whole exploration tree when building the report. Followups nest
arbitrarily deep, and descending a single level dropped the newest
exploration from the report.

Reuse the report between visits instead of rebuilding it, so the Story
tab and its prose survive a trip back to the chat.

Leave the Explore menu item inactive and outlined while report mode is
on, rather than flipping it to an active timeline icon.

Leave report mode when an exploration is picked in the navigation.
@ghostiee-11
ghostiee-11 force-pushed the feat/report-story-annotation branch from 776ed00 to 8774862 Compare August 12, 2026 22:34
@ghostiee-11

Copy link
Copy Markdown
Collaborator Author

@ahuang11, I think this can be merged now!! tested this :)

@ahuang11
ahuang11 merged commit 24d36e3 into holoviz:main Aug 14, 2026
14 checks passed
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.

Annotate charts / tables

2 participants