Skip to content

Conversation

@MH0386
Copy link
Contributor

@MH0386 MH0386 commented Oct 1, 2025

Summary by Sourcery

Refactor code structure by moving core modules into the chattr.app package, reorder and simplify settings classes, and update imports accordingly.

Enhancements:

  • Restructure project layout by relocating builder, runner, state, settings, utils, and gui modules under chattr.app and adjusting imports.
  • Simplify DirectorySettings by reducing fields to base, assets, and log, adding a docstring, and enhancing create_missing_dirs with exception handling.
  • Move and document ModelSettings class below DirectorySettings, preserving its API key validation logic with class docstring.
  • Rename Graph class to App and update GUI and builder references to use app.generate_response instead of graph.generate_response.

Copilot AI review requested due to automatic review settings October 1, 2025 20:35
@gitnotebooks
Copy link

gitnotebooks bot commented Oct 1, 2025

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 1, 2025

Reviewer's Guide

This PR reorganizes the codebase into an app subpackage, renames and adjusts core classes and imports for consistency, refines directory-setting behavior with robust error handling, and tidies up obsolete IDE configuration files.

Class diagram for renamed App class (formerly Graph)

classDiagram
class App {
    +Settings settings
}
App --|> "Main application class for the Chattr Multi-agent system app"
Loading

File-Level Changes

Change Details Files
Restructure code into chattr.app package
  • Relocated key modules (settings, builder, runner, gui, utils) under src/chattr/app
  • Updated all import paths to reference chattr.app
  • Removed old module locations after migration
src/chattr/settings.py
src/chattr/app/settings.py
src/chattr/graph/builder.py
src/chattr/app/builder.py
src/chattr/graph/runner.py
src/chattr/app/runner.py
src/chattr/utils.py
src/chattr/app/utils.py
src/chattr/gui.py
src/chattr/app/gui.py
Enhance DirectorySettings implementation
  • Simplified default field definitions by dropping default_factory wrappers
  • Augmented create_missing_dirs to catch and log PermissionError and generic exceptions
  • Added class-level docstring for clarity
src/chattr/app/settings.py
Rename and update core application class
  • Renamed Graph class to App in builder
  • Swapped graph.generate_response calls to app.generate_response in GUI bindings
src/chattr/app/builder.py
src/chattr/app/gui.py
Clean up IDE configuration
  • Removed obsolete entries in .idea/.gitignore
  • Deleted outdated ruff.xml under .idea
.idea/.gitignore
.idea/ruff.xml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 1, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added voice and video generation support via integrated services
    • Implemented M3U8 playlist support for audio processing
    • Added Docker Compose configuration for local development with vector database and media generation services
    • Enhanced GUI with streaming response generation
  • Chores

    • Refactored application architecture for improved async handling
    • Expanded project directory structure for media assets and prompts

Walkthrough

Refactors App into a class-centered async factory with classmethods for graph/memory/LLM setup and a public Gradio entry (App.gui()); makes generate_response a classmethod with streaming, URL/M3U8 audio download, and enhanced error handling; updates settings, logging, CI, Docker, assets, and editor tooling.

Changes

Cohort / File(s) Summary
App Architecture Refactor
src/chattr/app/builder.py
Converts App to a class-centered async factory; moves memory/tools/LLM/graph setup to classmethods; adds gui() returning Gradio Blocks; implements generate_response as a classmethod with streaming, history handling, URL/M3U8 download helpers, and standardized error handling.
Settings & MCP
src/chattr/app/settings.py, mcp.json, assets/mcp-config.json (deleted)
Auto-creates default MCP config when missing, flattens MCP JSON structure, adds voice/video generator entries, removes old MCP schema file, updates directory fields (audio/video/prompts), and wires FileHandler logging for log dir creation.
Logging & Package Init
src/chattr/app/logger.py, src/chattr/__init__.py
Adds centralized logger using RichHandler and exposes logger; initializes package-level console and suppresses DeprecationWarning globally.
Entry Point & Launch
src/chattr/__main__.py
Replaces direct Blocks builder with app.gui() from chattr.app.runner, defers Gradio import via TYPE_CHECKING, and adjusts launch flags (adds show_error, pwa) and launch call.
Prompts & Assets
assets/prompts/template.poml
Adds a POML prompt template enforcing character-voice responses, mandatory audio generation, and a video tool call via MCP.
Docker & Builder
Dockerfile
Changes builder stage to install uv via ARG-driven INSTALL_SOURCE and PYTHON_VERSION, installs build dependencies, removes prebuilt uv copy step.
CI/CD & Workflows
.github/workflows/.docker.yaml, .github/workflows/build.yaml, .github/workflows/release.yaml, .github/workflows/test.yaml, .github/workflows/.lint.yaml, .github/workflows/version.yaml, .github/workflows/ci_tools.yaml, .github/mergify.yml
Adds workflow input install_source, dynamic registry handling, job summaries, version/tag propagation, opencode trigger, modifies mergify queue rules (removes a CodeRabbit check), and small lint/commit behavior changes.
Dev / Editor Tasks & Configs
.envrc, compose-dev.yaml, .idea/*, .vscode/*, .zed/tasks.json
Removes direnv/devenv wiring, adds compose-dev services (vector_database, voice_generator), adds IDE run configs and VSCode/Zed tasks for running/debugging and Docker.
Linting & Tooling Configs
.trunk/configs/*, pyproject.toml, .trunk/trunk.yaml
Adds/adjusts Checkov/Trivy/YAML/Ruff configs, points Ruff to trunk config, updates dependencies (adds poml, dev deps for doppler-env and uv-build, removes loguru).
Docs & Guidelines
AGENTS.md, assets/prompts/*
Adds AGENTS.md with development guidelines and prompt templates; removes old MCP JSON schema.
Misc: IDE & Project Files
.idea/chattr.iml, .idea/dictionaries/project.xml
Minor IDE config changes (exclude logs folder, add dictionary word).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant GUI as App.gui() (Gradio)
    participant App as App (class)
    participant Memory as cls._setup_memory()
    participant LLM as cls._setup_llm()
    participant Graph as cls._setup_graph()

    User->>GUI: open interface
    GUI->>App: App.create(settings)
    App->>Memory: cls._setup_memory()
    Memory-->>App: memory ready
    App->>LLM: cls._setup_llm()
    LLM-->>App: llm ready
    App->>Graph: cls._setup_graph()
    Graph-->>App: graph compiled
    GUI-->>User: interface ready

    User->>GUI: send message
    GUI->>App: cls.generate_response(message, history)
    App->>Graph: invoke compiled graph (stream)
    Graph->>LLM: request tokens / stream
    LLM-->>Graph: token chunks
    Graph-->>App: yield (chunk, updated_history, audio_path?, video_path?)
    App-->>GUI: stream updates to user
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Hop-hop, the App now class-bound and spry,

async factories hum and tokens fly.
GUI opens wide, streams dance on the wire,
audio playlists fetched, M3U8 in the choir.
Rabbit logs the run — build, try, and retry!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.95% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The PR title "Reorder code" is generic and lacks specificity. While the changeset does involve reordering and restructuring code (moving modules into chattr.app, reorganizing settings classes, etc.), the title does not clearly communicate the main scope or intent of the changes. A reader scanning commit history would struggle to understand what was actually reordered or restructured. The title uses a non-descriptive term without indicating the primary architectural refactoring, class renaming, or project layout restructuring that are central to this PR. Consider using a more descriptive title that captures the main architectural changes, such as "Refactor project structure and move modules into chattr.app package" or "Restructure app architecture with async factory pattern and rename Graph to App". This would provide clarity about the primary changes without requiring reviewers to examine the full changeset.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The PR description is well-related to the changeset and provides clear, specific details about the modifications. It accurately describes the restructuring of project layout, the changes to settings classes (DirectorySettings and ModelSettings), the renaming of Graph to App, and the relocation of core modules into the chattr.app package. All items mentioned in the description are reflected in the raw_summary of changes, confirming the description aligns with the actual implementation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enhance

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca536f2 and 5b5203a.

⛔ Files ignored due to path filters (1)
  • assets/graph.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • src/chattr/app/builder.py (5 hunks)
  • src/chattr/app/settings.py (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/chattr/app/builder.py (2)
src/chattr/app/settings.py (1)
  • Settings (155-170)
src/chattr/app/state.py (1)
  • State (4-7)
🪛 Ruff (0.14.1)
src/chattr/app/builder.py

83-83: Do not catch blind exception: Exception

(BLE001)


205-205: Do not catch blind exception: Exception

(BLE001)


462-462: Consider moving this statement to an else block

(TRY300)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Trunk Check
  • GitHub Check: Lint / Check
  • GitHub Check: Test Image / Build and push Docker image to ghcr.io
  • GitHub Check: Mergify Merge Protections
  • GitHub Check: Summary
🔇 Additional comments (1)
src/chattr/app/builder.py (1)

432-433: Audio path depends on settings.py circular dependency fix.

Lines 432-433 reference cls.settings.directory.audio, which is defined in settings.py but has a circular dependency issue (see settings.py lines 85-87). Ensure the settings.py fix is applied before this code can work correctly.


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 and usage tips.

This commit fixes the style issues introduced in 85dd2a5 according to the output
from Ruff Formatter.

Details: #388
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @MH0386, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request implements a significant refactoring of the chattr application's internal structure. The primary change involves renaming the graph module to app and updating the main orchestration class from Graph to App, aiming to enhance clarity and better represent the application's core components. Additionally, configuration files have been adjusted for broader compatibility, and the directory management logic has been made more robust with explicit error handling for directory creation.

Highlights

  • Module and Class Renaming: The chattr.graph module has been renamed to chattr.app, and the core orchestration class Graph within builder.py has been renamed to App to better reflect its role as the central application component.
  • Configuration Updates: The .gitignore file now includes an entry for copilot.data.migration.*.xml, and the ruff.xml configuration has been updated to use a more generic Unix-like path for the Ruff executable.
  • Enhanced Directory Management: The DirectorySettings in settings.py has been improved with more robust error handling for creating directories and has simplified the default asset directory structure.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

Refactors the codebase by moving components from graph module to app module and reorganizing class structure. The change consolidates the application's orchestration under a unified app namespace.

  • Moved Graph class to App class in the app module
  • Relocated runner functionality from graph/runner.py to app/runner.py
  • Updated import paths throughout the codebase to reflect the new module structure

Reviewed Changes

Copilot reviewed 6 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/chattr/graph/runner.py Removed entire file content as part of module reorganization
src/chattr/app/utils.py Updated import path for logger from settings module
src/chattr/app/settings.py Reorganized class definitions and improved DirectorySettings implementation
src/chattr/app/runner.py New file with app initialization logic moved from graph module
src/chattr/app/gui.py Updated imports and references from graph to app module
src/chattr/app/builder.py Renamed Graph class to App and updated related imports
Files not reviewed (2)
  • .idea/.gitignore: Language not supported
  • .idea/ruff.xml: Language not supported

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@deepsource-io
Copy link

deepsource-io bot commented Oct 1, 2025

Here's the code health analysis summary for commits 960ce8a..1875d0f. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Shell LogoShell✅ SuccessView Check ↗
DeepSource Python LogoPython❌ Failure
❗ 25 occurences introduced
🎯 7 occurences resolved
View Check ↗
DeepSource Docker LogoDocker✅ SuccessView Check ↗
DeepSource Secrets LogoSecrets✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the project structure by moving several modules into a new chattr.app package. The changes are well-organized and improve the project layout. I've found a couple of areas for improvement: one is a potential issue with how directory paths are initialized in the settings, and the other is a more architectural concern about running asynchronous code at the module level, which could cause issues in different environments. My detailed feedback is in the comments below.

from chattr.app.settings import Settings

settings: Settings = Settings()
app: App = run(App.create(settings))
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Running asyncio.run() at the module level is generally discouraged. It blocks the import process until App.create() completes, which can be slow. More importantly, it can cause conflicts if this module is imported into a larger application that already has a running asyncio event loop (e.g., a web server), as asyncio.run() creates a new event loop. This can lead to a RuntimeError: This event loop is already running. Consider using a lazy initialization pattern or an explicit initialization function that is called from your main application entry point to avoid running async code at import time.

Comment on lines 86 to 88
assets: DirectoryPath = Path.cwd() / "assets"
log: DirectoryPath = Path.cwd() / "logs"

Copy link
Contributor

Choose a reason for hiding this comment

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

medium

By assigning Path.cwd() directly as a default value, the current working directory is captured when the module is first imported. This can lead to unexpected behavior if the program's working directory changes before the DirectorySettings model is instantiated. The previous implementation using default_factory was safer as it determines the path at instantiation time.

Suggested change
assets: DirectoryPath = Path.cwd() / "assets"
log: DirectoryPath = Path.cwd() / "logs"
base: DirectoryPath = Field(default_factory=Path.cwd)
assets: DirectoryPath = Field(default_factory=lambda: Path.cwd() / "assets")
log: DirectoryPath = Field(default_factory=lambda: Path.cwd() / "logs")

@AlphaSphereDotAI AlphaSphereDotAI deleted a comment from Copilot AI Oct 1, 2025
@MH0386
Copy link
Contributor Author

MH0386 commented Oct 1, 2025

🔍 Vulnerabilities of ghcr.io/alphaspheredotai/chattr:4dde222-pr-388

📦 Image Reference ghcr.io/alphaspheredotai/chattr:4dde222-pr-388
digestsha256:86056f87ac4ca9cfde7c4767e924feb71a7519d191e8d2165bde919dd1b2a34e
vulnerabilitiescritical: 0 high: 2 medium: 1 low: 0
platformlinux/amd64
size325 MB
packages500
critical: 0 high: 1 medium: 0 low: 0 pdfjs-dist 3.11.174 (npm)

pkg:npm/[email protected]

# Dockerfile (28:28)
COPY --from=builder --chown=nonroot:nonroot --chmod=555 /home/nonroot/.local/ /home/nonroot/.local/

high 8.8: CVE--2024--4367 Improper Check for Unusual or Exceptional Conditions

Affected range<=4.1.392
Fixed version4.2.67
CVSS Score8.8
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Score35.103%
EPSS Percentile97th percentile
Description

Impact

If pdf.js is used to load a malicious PDF, and PDF.js is configured with isEvalSupported set to true (which is the default value), unrestricted attacker-controlled JavaScript will be executed in the context of the hosting domain.

Patches

The patch removes the use of eval:
mozilla/pdf.js#18015

Workarounds

Set the option isEvalSupported to false.

References

https://bugzilla.mozilla.org/show_bug.cgi?id=1893645

critical: 0 high: 1 medium: 0 low: 0 gradio 5.49.1 (pypi)

pkg:pypi/[email protected]

# Dockerfile (28:28)
COPY --from=builder --chown=nonroot:nonroot --chmod=555 /home/nonroot/.local/ /home/nonroot/.local/

high 8.1: CVE--2023--6572 OWASP Top Ten 2017 Category A9 - Using Components with Known Vulnerabilities

Affected range<2023-11-06
Fixed versionNot Fixed
CVSS Score8.1
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
EPSS Score1.662%
EPSS Percentile81st percentile
Description

Exposure of Sensitive Information to an Unauthorized Actor in GitHub repository gradio-app/gradio prior to main.

critical: 0 high: 0 medium: 1 low: 0 pip 25.2 (pypi)

pkg:pypi/[email protected]

# Dockerfile (28:28)
COPY --from=builder --chown=nonroot:nonroot --chmod=555 /home/nonroot/.local/ /home/nonroot/.local/

medium 5.9: CVE--2025--8869 Improper Link Resolution Before File Access ('Link Following')

Affected range<=25.2
Fixed versionNot Fixed
CVSS Score5.9
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
EPSS Score0.018%
EPSS Percentile3rd percentile
Description

Summary

In the fallback extraction path for source distributions, pip used Python’s tarfile module without verifying that symbolic/hard link targets resolve inside the intended extraction directory. A malicious sdist can include links that escape the target directory and overwrite arbitrary files on the invoking host during pip install.

Impact

Successful exploitation enables arbitrary file overwrite outside the build/extraction directory on the machine running pip. This can be leveraged to tamper with configuration or startup files and may lead to further code execution depending on the environment, but the direct, guaranteed impact is integrity compromise on the vulnerable system.

Conditions

The issue is triggered when installing an attacker-controlled sdist (e.g., from an index or URL) and the fallback extraction code path is used. No special privileges are required beyond running pip install; active user action is necessary.

Remediation

The fix, while available as a patch that can be manually applied, has not yet been put into a numbered version but is planned for 25.3. Using a Python interpreter that implements the safe-extraction behavior described by PEP 706 provides additional defense in depth for other tarfile issues but is not a substitute for upgrading pip for this specific flaw.

@mergify mergify bot temporarily deployed to code_quality October 1, 2025 20:38 Inactive
@mergify mergify bot temporarily deployed to code_quality October 1, 2025 20:38 Inactive
@mergify mergify bot temporarily deployed to code_quality October 1, 2025 20:38 Inactive
@AlphaSphereDotAI AlphaSphereDotAI deleted a comment from Copilot AI Oct 1, 2025
@mergify mergify bot temporarily deployed to docker_image October 1, 2025 20:39 Inactive
@mergify mergify bot temporarily deployed to code_quality October 1, 2025 20:39 Inactive
@mergify
Copy link
Contributor

mergify bot commented Oct 1, 2025

🧪 CI Insights

Here's what we observed from your CI run for 1875d0f.

🟢 All jobs passed!

But CI Insights is watching 👀

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/chattr/app/builder.py (2)

253-265: Restoring directory.audio is required
DirectorySettings no longer defines an audio path, but this method still calls self.settings.directory.audio. At runtime settings.directory won’t have that attribute, so the first tool response that hits this branch will crash with AttributeError. Reintroduce the audio (and any other referenced) directories in DirectorySettings before merging.


85-112: DirectoryPath defaults now fail validation
The new defaults set base, assets, and log to DirectoryPath, but if those folders don’t already exist Pydantic rejects the model before create_missing_dirs() runs. Fresh environments (CI, new installs) will now throw ValidationError. Please switch these fields to Path | None (or plain Path) with default_factory and keep the existence checks in the validator.

♻️ Duplicate comments (1)
src/chattr/app/runner.py (1)

7-7: Avoid running asyncio.run() at import time
Importing this module now executes asyncio.run(App.create(...)) immediately, which will raise RuntimeError: This event loop is already running whenever the importer already has an event loop (common in Gradio, notebooks, or any asyncio-based host). It also makes import slow and side-effectful. Please defer App creation to an explicit initializer (e.g., async def init_app() plus a cached accessor) instead of calling run() at module import.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f067392 and 93bf060.

📒 Files selected for processing (8)
  • .idea/.gitignore (1 hunks)
  • .idea/ruff.xml (1 hunks)
  • src/chattr/app/builder.py (2 hunks)
  • src/chattr/app/gui.py (2 hunks)
  • src/chattr/app/runner.py (1 hunks)
  • src/chattr/app/settings.py (2 hunks)
  • src/chattr/app/utils.py (1 hunks)
  • src/chattr/graph/runner.py (0 hunks)
💤 Files with no reviewable changes (1)
  • src/chattr/graph/runner.py
🧰 Additional context used
🧬 Code graph analysis (3)
src/chattr/app/gui.py (1)
src/chattr/app/builder.py (1)
  • generate_response (201-266)
src/chattr/app/runner.py (2)
src/chattr/app/builder.py (1)
  • create (39-52)
src/chattr/app/settings.py (1)
  • Settings (148-162)
src/chattr/app/builder.py (4)
src/chattr/app/settings.py (1)
  • Settings (148-162)
src/chattr/app/state.py (1)
  • State (4-7)
src/chattr/app/utils.py (3)
  • convert_audio_to_wav (61-75)
  • download_file (33-58)
  • is_url (13-30)
src/chattr/graph/builder.py (1)
  • Graph (29-273)
🪛 Ruff (0.13.2)
src/chattr/app/settings.py

110-110: Do not catch blind exception: Exception

(BLE001)


140-142: Avoid specifying long messages outside the exception class

(TRY003)


144-144: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Test Image / API Test
  • GitHub Check: Test Image / Docker Scout (recommendations)
  • GitHub Check: Test Image / Docker Scout (quickview)
  • GitHub Check: Test Image / Docker Scout (cves)
  • GitHub Check: Trunk Check
  • GitHub Check: Lint / Check
  • GitHub Check: Sourcery review
  • GitHub Check: Mergify Merge Protections
  • GitHub Check: Summary
🔇 Additional comments (1)
.idea/.gitignore (1)

9-10: LGTM—sensible IDE ignore.

Adding /copilot.data.migration.*.xml keeps transient Copilot migration artifacts out of version control. 👍

Comment on lines 118 to 145
url: HttpUrl = Field(default=None)
name: str = Field(default=None)
api_key: SecretStr = Field(default=None)
temperature: float = Field(default=0.0, ge=0.0, le=1.0)
system_message: str = Field(
default="You are a helpful assistant that can answer questions about the time and generate audio files from text."
)

@model_validator(mode="after")
def check_api_key_exist(self) -> Self:
"""
Ensure that an API key and model name are provided if a model URL is set.
This method validates the presence of required credentials for the model provider.
Returns:
Self: The validated ModelSettings instance.
Raises:
ValueError: If the API key or model name is missing when a model URL is provided.
"""
if self.url:
if not self.api_key or not self.api_key.get_secret_value():
raise ValueError(
"You need to provide API Key for the Model provider via `MODEL__API_KEY`"
)
if not self.name:
raise ValueError("You need to provide Model name via `MODEL__NAME`")
return self
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix optional field typing in ModelSettings
url, name, and api_key are annotated as mandatory (HttpUrl, str, SecretStr) but default to None. On instantiation Pydantic raises validation errors (“Input should be a valid URL/str”), so Settings() no longer builds in a fresh environment. Please mark these as optional (HttpUrl | None, str | None, SecretStr | None) or provide non-null defaults.

🧰 Tools
🪛 Ruff (0.13.2)

140-142: Avoid specifying long messages outside the exception class

(TRY003)


144-144: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In src/chattr/app/settings.py around lines 118 to 145, the fields url, name and
api_key are typed as non-optional but default to None which causes Pydantic
validation errors; change their type annotations to optional (e.g., HttpUrl |
None, str | None, SecretStr | None or use Optional[...] depending on project
typing style) so None is an acceptable value, and ensure any necessary typing
imports are added; keep the existing model_validator logic that enforces
presence when url is set.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (7)
src/chattr/app/builder.py (7)

205-210: Unwrap SecretStr and handle None for ChatOpenAI parameters.

cls.settings.model.api_key is likely a SecretStr | None, which ChatOpenAI cannot consume directly. Similarly, str(None) on an unset URL produces the literal string "None", causing connection attempts to a bogus host.

Apply this diff:

         try:
-            return ChatOpenAI(
-                base_url=str(cls.settings.model.url),
-                model=cls.settings.model.name,
-                api_key=cls.settings.model.api_key,
-                temperature=cls.settings.model.temperature,
-            )
+            init_kwargs = {
+                "model": cls.settings.model.name,
+                "temperature": cls.settings.model.temperature,
+            }
+            if cls.settings.model.url:
+                init_kwargs["base_url"] = str(cls.settings.model.url)
+            if cls.settings.model.api_key:
+                init_kwargs["api_key"] = cls.settings.model.api_key.get_secret_value()
+            return ChatOpenAI(**init_kwargs)

250-256: Clarify the OpenAI error message.

The error message is incomplete and doesn't indicate whether the API key is missing, invalid, or if there's a connectivity issue. Including the exception details will help users diagnose the problem.

Apply this diff:

         except OpenAIError as e:
             _msg = (
-                "Failed to connect to Chat Model server: "
-                "setting the `MODEL__API_KEY` environment variable"
+                "Failed to connect to Chat Model server. "
+                "Verify the MODEL__API_KEY environment variable is set correctly "
+                f"and the model URL is reachable. Error: {e}"
             )
             logger.error(_msg)
             raise Error(_msg) from e

414-420: Fix audio directory path and format conversion.

Two issues:

  1. Audio directory regression: cls.settings.directory.audio likely doesn't exist after the DirectorySettings simplification (per PR objectives: reduced to base, assets, log). This will raise AttributeError at runtime.

  2. Downloaded file isn't actual WAV: _download_file streams raw bytes to a .wav extension, but if the source is AAC, M3U8, or another format, Gradio will fail to play it.

Apply this diff:

                 if cls._is_url(last_tool_message.content):
                     logger.info(f"Downloading audio from {last_tool_message.content}")
-                    file_path: Path = (
-                        cls.settings.directory.audio / last_tool_message.id
-                    )
-                    audio_file = file_path.with_suffix(".wav")
-                    cls._download_file(last_tool_message.content, audio_file)
-                    logger.info(f"Audio downloaded to {audio_file}")
+                    audio_dir = cls.settings.directory.assets / "audio"
+                    audio_dir.mkdir(parents=True, exist_ok=True)
+                    file_path = audio_dir / last_tool_message.id
+                    # Download to original format first
+                    temp_file = file_path.with_suffix(".aac")
+                    cls._download_file(last_tool_message.content, temp_file)
+                    # Convert to WAV for Gradio
+                    audio_file = file_path.with_suffix(".wav")
+                    cls._convert_audio_to_wav(temp_file, audio_file)
+                    temp_file.unlink()  # Clean up temp file
+                    logger.info(f"Audio converted to {audio_file}")

Note: You'll need to restore the _convert_audio_to_wav method that was removed, or use a similar conversion approach.


464-466: Avoid variable shadowing for URL parameter.

The parameter url (type HttpUrl) is reassigned to a str at line 466, causing type confusion and shadowing the original parameter.

Apply this diff:

-        if str(url).endswith(".m3u8"):
-            _playlist: M3U8 = load(url)
-            url: str = str(url).replace("playlist.m3u8", _playlist.segments[0].uri)
-        logger.info(f"Downloading {url} to {path}")
+        download_url = str(url)
+        if download_url.endswith(".m3u8"):
+            _playlist: M3U8 = load(url)
+            download_url = download_url.replace("playlist.m3u8", _playlist.segments[0].uri)
+        logger.info(f"Downloading {download_url} to {path}")
         session = Session()
-        response = session.get(url, stream=True, timeout=30)
+        response = session.get(download_url, stream=True, timeout=30)

422-425: Use warning instead of raising for unexpected response types.

The state graph may legitimately produce other response types during streaming. Raising an Error will crash the chat and provide a poor user experience. Logging a warning and continuing is more appropriate.

Apply this diff:

             else:
-                _msg = f"Unsupported audio source: {response.keys()}"
+                _msg = f"Unsupported response type: {response.keys()}"
                 logger.warning(_msg)
-                raise Error(_msg)
+                continue

464-475: Add error handling for M3U8 playlist processing.

The M3U8 playlist handling lacks error handling and uses fragile string replacement logic. The playlist may fail to load, contain no segments, or have a different URL structure than expected.

Apply this diff:

+        from urllib.parse import urljoin
+
         download_url = str(url)
         if download_url.endswith(".m3u8"):
-            _playlist: M3U8 = load(url)
-            download_url = download_url.replace("playlist.m3u8", _playlist.segments[0].uri)
+            try:
+                _playlist: M3U8 = load(url)
+                if not _playlist.segments:
+                    raise ValueError("M3U8 playlist contains no segments")
+                # Resolve segment URI relative to playlist base
+                base_url = download_url.rsplit('/', 1)[0] + '/'
+                download_url = urljoin(base_url, _playlist.segments[0].uri)
+            except Exception as e:
+                _msg = f"Failed to process M3U8 playlist: {e}"
+                logger.error(_msg)
+                raise Error(_msg) from e
         logger.info(f"Downloading {download_url} to {path}")

421-426: Restructure to avoid duplicate yields.

Line 426 always executes after the conditional yields in lines 421. This causes redundant yields after audio generation. The generator should yield once per response update.

Consider restructuring with a single yield point:

                 if cls._is_url(last_tool_message.content):
                     logger.info(f"Downloading audio from {last_tool_message.content}")
                     # ... audio download logic ...
                     is_audio_generated = True
-                    yield "", history, audio_file, None
             else:
                 _msg = f"Unsupported response type: {response.keys()}"
                 logger.warning(_msg)
                 continue
             yield "", history, audio_file if is_audio_generated else None, None

Or use continue after the audio yield:

                     is_audio_generated = True
                     yield "", history, audio_file, None
+                    continue
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cf3342 and ca536f2.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • pyproject.toml (2 hunks)
  • src/chattr/app/builder.py (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/chattr/app/builder.py (2)
src/chattr/app/settings.py (1)
  • Settings (147-162)
src/chattr/app/state.py (1)
  • State (4-7)
🪛 Ruff (0.14.0)
src/chattr/app/builder.py

83-83: Do not catch blind exception: Exception

(BLE001)


187-187: Do not catch blind exception: Exception

(BLE001)


444-444: Consider moving this statement to an else block

(TRY300)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Trunk Check
  • GitHub Check: Lint / Check
  • GitHub Check: Test Image / Build and push Docker image to ghcr.io
  • GitHub Check: Mergify Merge Protections
  • GitHub Check: Summary
🔇 Additional comments (4)
pyproject.toml (4)

19-19: LGTM: poml addition is justified and version constraint is reasonable.

poml 0.0.8 includes numerous issue resolutions and is marked as a final pre-release, aligning with the PR's use of POML (Prompt Orchestration Markup Language) for bringing structure and maintainability to prompt engineering.


31-31: Dev dependencies are appropriately scoped.

The additions of doppler-env (environment configuration) and uv-build (supporting the uv_build backend) are reasonable and correctly placed in the dev dependency group.

Also applies to: 37-37


40-41: The Ruff configuration file is present and valid.

The referenced .trunk/configs/ruff.toml file exists and contains properly formatted Ruff configuration. The extend directive in pyproject.toml is correctly configured and will not cause build failures.


12-12: The original concern does not apply to the current constraint.

CVE-2023-6572 was patched in gradio 4.14.0 (or any later release), affecting only versions < 4.14.0. The constraint gradio>=5.48.0 already requires a version well above this patch threshold, meaning all versions that satisfy this constraint have the fix applied.

The Docker Scout vulnerability scan flagging version 5.49.1 appears to reflect stale vulnerability data. No changes to the version constraint are needed.

Likely an incorrect or invalid review comment.

cls._model = cls._llm.bind_tools(cls._tools, parallel_tool_calls=False)
cls._memory = await cls._setup_memory()
cls._graph = cls._setup_graph()
return cls()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix the factory return value.

Line 89 returns cls(), which creates a new instance with empty state, while all the setup work stored data in class-level attributes. This makes the returned instance unusable.

Apply this diff:

-        return cls()
+        return cls

Alternatively, if you intend proper instance isolation, convert all class-level attributes to instance attributes and change all cls.* references to self.* throughout the class (as previously suggested in past reviews).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return cls()
return cls
🤖 Prompt for AI Agents
In src/chattr/app/builder.py around line 89, the factory currently returns cls()
which instantiates a new object with empty instance state while setup populated
class-level attributes, so the returned object is unusable; fix by returning the
class itself (return cls) instead of instantiating it, OR if instance isolation
is required, convert all class-level attributes to instance attributes and
update every cls.* reference to self.* across the class so the factory can
continue to return an initialized instance.

@AlphaSphereDotAI AlphaSphereDotAI deleted a comment from coderabbitai bot Oct 20, 2025
Adds a new node to the state graph that proactively removes old audio and video files from temporary directories before processing a new agent request. This prevents the accumulation of unused files and helps manage disk space.

Also updates the memory update call to pass relevant messages and user ID, ensuring comprehensive context is stored for the conversation.
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot

See analysis details on SonarQube Cloud

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