Skip to content

✨(back) add pptx generation tool#624

Open
natoromano wants to merge 3 commits into
mainfrom
nr/generation-pptx
Open

✨(back) add pptx generation tool#624
natoromano wants to merge 3 commits into
mainfrom
nr/generation-pptx

Conversation

@natoromano

@natoromano natoromano commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Purpose

This PR adds a pptx generation tool. The code is largely (and voluntarily) derived from PIAG's implementation, in light of the convergence efforts. The same template is used, for now, to keep a feature parity.

Summary by CodeRabbit

  • New Features

    • Added AI-powered slide deck generation from a brief.
    • Generated presentations are available as downloadable PowerPoint files with time-limited links.
    • Supports structured layouts, formatted text, lists, tables, and speaker notes.
    • Added a feature toggle to control presentation generation.
    • Added a progress message while slides are being created.
  • Bug Fixes

    • Added support for custom download-link expiration times.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@natoromano, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 7 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a3c35f2-0df7-44c1-8006-be95ee59137f

📥 Commits

Reviewing files that changed from the base of the PR and between 3d1cd48 and 4ecd64a.

⛔ Files ignored due to path filters (2)
  • src/backend/chat/file_generation/templates/generic.pptx is excluded by !**/*.pptx
  • src/backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • CHANGELOG.md
  • src/backend/chat/agents/presentation.py
  • src/backend/chat/clients/pydantic_ai.py
  • src/backend/chat/file_generation/__init__.py
  • src/backend/chat/file_generation/builder.py
  • src/backend/chat/file_generation/entities.py
  • src/backend/chat/file_generation/renderer.py
  • src/backend/chat/file_generation/templates/__init__.py
  • src/backend/chat/file_generation/templates/generic.py
  • src/backend/chat/tests/file_generation/__init__.py
  • src/backend/chat/tests/file_generation/test_builder.py
  • src/backend/chat/tests/file_generation/test_renderer.py
  • src/backend/chat/tests/tools/test_generate_presentation.py
  • src/backend/chat/tests/views/chat/conversations/test_conversation.py
  • src/backend/chat/tools/descriptions.py
  • src/backend/chat/tools/generate_presentation.py
  • src/backend/core/feature_flags/flags.py
  • src/backend/core/file_upload/utils.py
  • src/backend/core/tests/feature_flags/test_flags.py
  • src/backend/core/tests/file_upload/test_utils.py
  • src/backend/core/tests/test_api_config.py
  • src/backend/pyproject.toml
  • src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx
  • src/frontend/apps/conversations/src/i18n/translations.json
  • src/frontend/apps/e2e/__tests__/app-conversations/common.ts

Walkthrough

Adds an AI-powered slide deck tool that generates structured presentations, renders them into PPTX files using a bundled template, stores downloadable files, gates tool registration behind a feature flag, and updates chat progress labels.

Changes

Presentation generation

Layer / File(s) Summary
Presentation contracts and template prompts
src/backend/chat/file_generation/entities.py, src/backend/chat/file_generation/templates/*, src/backend/chat/agents/presentation.py
Defines slide and presentation models, template layout mappings, package exports, and the structured-output agent prompt.
PPTX rendering and deck assembly
src/backend/chat/file_generation/{renderer,builder}.py, src/backend/chat/tests/file_generation/*
Renders Markdown content into PowerPoint elements, builds decks from validated layouts, supports notes, and tests layouts, formatting, lists, tables, and validation failures.
Generation tool and agent integration
src/backend/chat/tools/*, src/backend/chat/clients/pydantic_ai.py, src/backend/core/{feature_flags,file_upload}/*, src/backend/tests/*, src/backend/pyproject.toml
Registers the feature-gated tool, generates and stores PPTX files with expiring URLs, handles retry paths, adds expiration support, and tests end-to-end generation.
Chat feedback and release entry
CHANGELOG.md, src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx, src/frontend/apps/conversations/src/i18n/translations.json
Adds the changelog entry and localized loader text for slide generation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatAgent
  participant PresentationAgent
  participant build_presentation
  participant ObjectStorage
  ChatAgent->>PresentationAgent: generate structured Presentation
  PresentationAgent-->>ChatAgent: Presentation with slides
  ChatAgent->>build_presentation: build PPTX from template
  build_presentation-->>ChatAgent: PPTX bytes
  ChatAgent->>ObjectStorage: store presentation and request expiring URL
  ObjectStorage-->>ChatAgent: signed download URL
Loading

Suggested reviewers: providenz, eliott07, copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 98.46% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding a PPTX slide deck generation tool.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nr/generation-pptx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
src/backend/chat/file_generation/renderer.py (2)

166-215: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Consider restricting hyperlink schemes.

run.hyperlink.address = state.link writes the Markdown href straight through with no scheme allowlist. Since slide content is model-generated, a manipulated brief could embed a non-http(s) link (e.g. javascript:/data:) that ends up clickable in the delivered deck. Worth restricting to http(s) (and maybe mailto:) before setting the address.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/chat/file_generation/renderer.py` around lines 166 - 215,
Restrict hyperlink assignment in _apply_run_style to an allowlist of safe URI
schemes, permitting http and https (and mailto only if supported by the existing
contract) while rejecting javascript, data, and other schemes. Set
run.hyperlink.address only after validation, preserving the current behavior for
valid links.

141-164: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce _render_list's cognitive complexity.

SonarCloud reports this function at 18 vs. the 15 allowed. Extracting the per-item non-list-child handling (paragraph creation + inline rendering) into a small helper would bring it under the threshold without behavior change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/chat/file_generation/renderer.py` around lines 141 - 164, Reduce
cognitive complexity in _render_list by extracting handling of each
non-nested-list child into a small helper responsible for paragraph creation,
ordered/bullet formatting, and _add_inline. Keep _render_list focused on
iterating items, recursing into LIST_TAGS, and skipping whitespace, preserving
the current rendering behavior and ordering.

Source: Linters/SAST tools

src/backend/chat/tools/generate_presentation.py (2)

63-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Minor: align with logging.exception() convention flagged by SonarCloud.

exc_info=True is already passed at lines 91 and 99, so the traceback isn't actually lost — but SonarCloud's rule (also flagged in builder.py) prefers logging.exception() for this pattern. Purely cosmetic since behavior is already equivalent.

🩹 Proposed fix
-        logger.error("Failed to build the presentation: %s", exc, exc_info=True)
+        logger.exception("Failed to build the presentation: %s", exc)
-        logger.error("Failed to store the generated presentation: %s", exc, exc_info=True)
+        logger.exception("Failed to store the generated presentation: %s", exc)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/chat/tools/generate_presentation.py` around lines 63 - 115,
Update the exception handlers in generate_presentation, specifically the
PresentationAgent.run and store_presentation failure paths, to use
logger.exception(...) instead of logger.warning/error(..., exc_info=True).
Preserve the existing log messages, exception chaining, and
ModelRetry/ModelCannotRetry behavior; leave the PresentationBuildError handler
unchanged unless the same convention is required there.

Source: Linters/SAST tools


46-60: 🚀 Performance & Scalability | 🔵 Trivial

No cleanup path for the stored blob.

No attachment row is created by design, but the underlying object still gets written to default_storage permanently — only the presigned URL expires after an hour, not the object itself. Without an S3 lifecycle rule (or equivalent) scoped to these keys, every generated deck accumulates in storage indefinitely.

Please confirm whether a lifecycle policy already prunes objects under {conversation.pk}/{AttachmentMixin.ATTACHMENTS_FOLDER}/ that aren't backed by an attachment row, or whether one should be added for this new artifact type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/chat/tools/generate_presentation.py` around lines 46 - 60, The
store_presentation flow currently writes permanent objects without an attachment
row or cleanup mechanism. Verify whether default_storage already applies a
lifecycle policy to keys under the conversation and
AttachmentMixin.ATTACHMENTS_FOLDER prefix; if not, add an equivalent policy for
these generated presentation artifacts so unreferenced blobs expire, while
preserving the existing presigned URL behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/backend/chat/agents/presentation.py`:
- Around line 26-35: Update the REQUIREMENTS instructions to explicitly allow
the slide_notes field on every slide, alongside each layout’s declared fields.
Preserve the existing guidance to add presenter notes where context helps, while
clarifying that slide_notes is the permitted field name for those notes.

In `@src/backend/chat/file_generation/builder.py`:
- Around line 20-26: Update the exception handler around pptx.Presentation in
the template-opening flow to preserve the traceback by using logger.exception or
passing exc_info=True to logger.error, while retaining the existing contextual
message and PresentationBuildError chaining.

In `@src/backend/chat/file_generation/entities.py`:
- Around line 43-44: Update the Presentation model validation for slides so the
collection must be non-empty and its first Slide has type “cover”; reject any
other first-slide type at schema validation time before rendering.

In
`@src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx`:
- Line 423: Update the value used by getStreamingToolLabel in MessageItem so it
selects the currently pending/current tool invocation rather than an earlier
completed result from find(). Use the newest eligible invocation from
message.parts when parts are chronological, ensuring a following presentation
tool displays its own label such as “Generating the slides...” instead of the
previous tool’s label.

---

Nitpick comments:
In `@src/backend/chat/file_generation/renderer.py`:
- Around line 166-215: Restrict hyperlink assignment in _apply_run_style to an
allowlist of safe URI schemes, permitting http and https (and mailto only if
supported by the existing contract) while rejecting javascript, data, and other
schemes. Set run.hyperlink.address only after validation, preserving the current
behavior for valid links.
- Around line 141-164: Reduce cognitive complexity in _render_list by extracting
handling of each non-nested-list child into a small helper responsible for
paragraph creation, ordered/bullet formatting, and _add_inline. Keep
_render_list focused on iterating items, recursing into LIST_TAGS, and skipping
whitespace, preserving the current rendering behavior and ordering.

In `@src/backend/chat/tools/generate_presentation.py`:
- Around line 63-115: Update the exception handlers in generate_presentation,
specifically the PresentationAgent.run and store_presentation failure paths, to
use logger.exception(...) instead of logger.warning/error(..., exc_info=True).
Preserve the existing log messages, exception chaining, and
ModelRetry/ModelCannotRetry behavior; leave the PresentationBuildError handler
unchanged unless the same convention is required there.
- Around line 46-60: The store_presentation flow currently writes permanent
objects without an attachment row or cleanup mechanism. Verify whether
default_storage already applies a lifecycle policy to keys under the
conversation and AttachmentMixin.ATTACHMENTS_FOLDER prefix; if not, add an
equivalent policy for these generated presentation artifacts so unreferenced
blobs expire, while preserving the existing presigned URL behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 280763e4-6f0c-408a-bb8e-67fdc6c78e10

📥 Commits

Reviewing files that changed from the base of the PR and between a2af746 and 3d1cd48.

⛔ Files ignored due to path filters (2)
  • src/backend/chat/file_generation/templates/generic.pptx is excluded by !**/*.pptx
  • src/backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • CHANGELOG.md
  • src/backend/chat/agents/presentation.py
  • src/backend/chat/clients/pydantic_ai.py
  • src/backend/chat/file_generation/__init__.py
  • src/backend/chat/file_generation/builder.py
  • src/backend/chat/file_generation/entities.py
  • src/backend/chat/file_generation/renderer.py
  • src/backend/chat/file_generation/templates/__init__.py
  • src/backend/chat/file_generation/templates/generic.py
  • src/backend/chat/tests/file_generation/__init__.py
  • src/backend/chat/tests/file_generation/test_builder.py
  • src/backend/chat/tests/file_generation/test_renderer.py
  • src/backend/chat/tests/tools/test_generate_presentation.py
  • src/backend/chat/tools/descriptions.py
  • src/backend/chat/tools/generate_presentation.py
  • src/backend/core/feature_flags/flags.py
  • src/backend/core/file_upload/utils.py
  • src/backend/core/tests/file_upload/test_utils.py
  • src/backend/pyproject.toml
  • src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx
  • src/frontend/apps/conversations/src/i18n/translations.json

Comment on lines +26 to +35
REQUIREMENTS:
- Only use the slide types listed above, and only fill the fields each one declares.
- Open with a `cover` slide, and use `section` slides to separate the main parts.
- Vary the layouts to keep the deck readable; avoid repeating one type throughout.
- Aim for several slides, each carrying one idea rather than a wall of text.
- Write field content in Markdown. Inline styles, links, nested lists (indent
with 4 spaces) and tables are supported. A table takes over its whole field,
so do not put other content alongside it.
- Add presenter notes on slides where context helps the speaker.
- Write in the language of the brief.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Explicitly permit slide_notes on every slide.

Line 27 limits output to fields declared by a layout, but no layout declares slide_notes; Line 34 simultaneously requests notes. Clarify that slide_notes is allowed for every slide, otherwise the model is steered away from producing them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/chat/agents/presentation.py` around lines 26 - 35, Update the
REQUIREMENTS instructions to explicitly allow the slide_notes field on every
slide, alongside each layout’s declared fields. Preserve the existing guidance
to add presenter notes where context helps, while clarifying that slide_notes is
the permitted field name for those notes.

Comment thread src/backend/chat/file_generation/builder.py
Comment on lines +43 to +44
title: str
slides: list[Slide] = Field(min_length=1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg 'src/backend/chat/file_generation/entities.py|file_generation|presentation|generation' || true

echo "== target outline =="
ast-grep outline src/backend/chat/file_generation/entities.py --view expanded || true

echo "== target lines =="
cat -n src/backend/chat/file_generation/entities.py

echo "== searches for Presentation/SlideType/constraints =="
rg -n "class Presentation|class Slide|SlideType|cover|cover_first|first slide|slides\\[0\\]|model_validator|hypothesis|validator" src/backend/chat src/backend -S || true

Repository: suitenumerique/conversations

Length of output: 38944


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== presentation agent lines =="
cat -n src/backend/chat/agents/presentation.py | sed -n '1,110p'

echo "== generate presentation lines =="
cat -n src/backend/chat/tools/generate_presentation.py | sed -n '40,90p'

echo "== builder lines =="
cat -n src/backend/chat/file_generation/builder.py | sed -n '1,70p'

echo "== tests around validation/use =="
cat -n src/backend/chat/tests/file_generation/test_builder.py | sed -n '140,160p'
cat -n src/backend/chat/tests/tools/test_generate_presentation.py | sed -n '55,85p'

echo "== pydantic availability =="
python3 - <<'PY'
try:
    import pydantic, pydantic.version
    print("pydantic", pydantic.__version__, pydantic.version.VERSION)
except Exception as e:
    print("pydantic unavailable:", type(e).__name__, e)
else:
    from pydantic import BaseModel, Field, model_validator
    from enum import StrEnum
    class SlideType(StrEnum):
        COVER = "cover"
        OTHER = "other"
    class Slide(BaseModel):
        type: SlideType
    class WeakPresentation(BaseModel):
        title: str
        slides: list[str] = Field(min_length=1)
    class StrongPresentation(BaseModel):
        title: str
        slides: list[Slide] = Field(min_length=1)
        `@model_validator`(mode="after")
        def validate_structure(self):
            if self.slides[0].type != "cover":
                raise ValueError("The first slide must be a cover slide")
    print("weak valid non-cover", WeakPresentation(title="x", slides=["other"]).slides)
    try:
        StrongPresentation(title="x", slides=[{"type": "cover"}])
        print("strong valid cover allowed")
    except Exception as e:
        print("strong cover error", e)
    try:
        StrongPresentation(title="x", slides=[{"type": "other"}])
    except Exception as e:
        print("strong non-cover error", type(e).__name__)
PY

Repository: suitenumerique/conversations

Length of output: 10604


Enforce the cover-first deck contract in Presentation.

The agent contract requires the first slide to be cover, but Presentation only rejects empty decks. A non-cover first slide is accepted via structured output and passed to deck rendering; add a Pydantic validator so this violates the schema boundary instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/chat/file_generation/entities.py` around lines 43 - 44, Update
the Presentation model validation for slides so the collection must be non-empty
and its first Slide has type “cover”; reject any other first-slide type at
schema validation time before rendering.

{activeToolInvocation?.toolName === 'summarize'
? t('Summarizing...')
: t('Search...')}
{getStreamingToolLabel(activeToolInvocation?.toolName, t)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Select the currently streaming tool before displaying its label.

Line [423] uses activeToolInvocation, but that value is derived with find() and can refer to an earlier completed invocation. When a presentation tool follows another tool in the same message, the loader may still show “Search...” instead of “Generating the slides...”. Select the pending/current invocation (or the newest eligible invocation if message.parts is chronological).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/apps/conversations/src/features/chat/components/MessageItem.tsx`
at line 423, Update the value used by getStreamingToolLabel in MessageItem so it
selects the currently pending/current tool invocation rather than an earlier
completed result from find(). Use the newest eligible invocation from
message.parts when parts are chronological, ensuring a following presentation
tool displays its own label such as “Generating the slides...” instead of the
previous tool’s label.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Add a pptx generation tool
@natoromano
natoromano force-pushed the nr/generation-pptx branch from 3d1cd48 to b4f749e Compare July 24, 2026 16:42
There was a code spell deteciton on acount
Was missing a new config
@sonarqubecloud

Copy link
Copy Markdown

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.

2 participants