Skip to content

Feat art#198

Merged
ryansurf merged 3 commits into
mainfrom
feat-art
Apr 25, 2026
Merged

Feat art#198
ryansurf merged 3 commits into
mainfrom
feat-art

Conversation

@ryansurf
Copy link
Copy Markdown
Owner

@ryansurf ryansurf commented Apr 25, 2026

General:

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

Code:

  1. Does your submission pass tests?
  2. Have you run the linter/formatter on your code locally before submission?
  3. Have you updated the documentation/README to reflect your changes, as applicable?
  4. Have you added an explanation of what your changes do?
  5. Have you written new tests for your changes, as applicable?

Summary by Sourcery

Introduce a shared LLM abstraction, improve GPT integration usage, and add HTML rendering for colored CLI output in the server response.

New Features:

  • Add ANSI-to-HTML conversion to render colored CLI output as styled HTML when the server is accessed with an HTML Accept header.

Enhancements:

  • Refactor GPT handling into reusable Llm, FreeGpt, and OpenAILlm classes and update helpers to use these abstractions.
  • Extend the FastAPI server default route to return either HTML or plain text responses based on the request Accept header.

Build:

  • Bump project version to 2.4.0 and add nest-asyncio dependency.

Tests:

  • Update and expand GPT-related tests to cover the new LLM classes and HTML/text response behavior.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 25, 2026

Reviewer's Guide

Refactors GPT integration into reusable LLM classes and updates callers/tests, and adds ANSI-to-HTML conversion so server responses can return styled HTML when requested, along with a minor dependency and version bump.

Sequence diagram for handling surf report HTTP request with HTML/ANSI conversion

sequenceDiagram
    actor User
    participant Browser
    participant FastAPIApp
    participant default_route
    participant SurfCli
    participant ansi_to_html

    User->>Browser: Request surf report
    Browser->>FastAPIApp: GET / (Accept: text/html or text/plain)
    FastAPIApp->>default_route: Call with Request
    default_route->>SurfCli: run(args)
    SurfCli-->>default_route: ANSI colored text output
    default_route->>default_route: Read Accept header
    alt Accept includes text/html
        default_route->>ansi_to_html: ansi_to_html(output)
        ansi_to_html-->>default_route: HTML string
        default_route-->>Browser: HTMLResponse
    else Accept does not include text/html
        default_route-->>Browser: PlainTextResponse(output)
    end
    Browser-->>User: Rendered surf report
Loading

Updated class diagram for LLM abstraction and helper usage

classDiagram
    class Llm {
        - Any client
        - str model
        + Llm(model)
        + call_llm(surf_summary, gpt_prompt) str
    }

    class FreeGpt {
        + FreeGpt(model)
    }

    class OpenAILlm {
        + OpenAILlm(api_key, model)
    }

    class Helper {
        + print_gpt(surf_data, gpt_prompt, gpt_info) str
    }

    Llm <|-- FreeGpt
    Llm <|-- OpenAILlm

    Helper ..> FreeGpt : uses
    Helper ..> OpenAILlm : uses
Loading

File-Level Changes

Change Details Files
Refactor GPT helpers into reusable LLM classes and update helper usage.
  • Replace simple_gpt and openai_gpt functions with an abstract Llm base class that centralizes chat.completions invocation and error handling.
  • Introduce FreeGpt and OpenAILlm concrete implementations to configure appropriate clients (gpt4free Client and OpenAI) and pass models/api keys.
  • Update print_gpt to instantiate FreeGpt or OpenAILlm based on API key presence/length and delegate to call_llm.
src/gpt.py
src/helper.py
Align GPT-related tests with new LLM class API.
  • Rename tests to target FreeGpt and OpenAILlm and exercise the new call_llm method.
  • Switch mocking from function-level helpers (simple_gpt/openai_gpt) to class instantiation and client attributes, verifying fallback behavior on exceptions.
  • Update helper test to mock OpenAILlm instance and assert call_llm usage instead of direct openai_gpt call.
tests/test_gpt.py
tests/test_helper.py
Support HTML output for ANSI-colored CLI art in the server.
  • Add ANSI_TO_CSS mapping and an ansi_to_html utility that escapes HTML, converts ANSI color codes to styled tags, and wraps output in a
     block.
  • Import ansi_to_html into the server and conditionally return HTMLResponse when the request Accept header includes text/html, otherwise PlainTextResponse.
  • Ensure server route captures CLI output, converts it as needed, and preserves existing text behavior for non-HTML clients.
src/art.py
src/server.py
Update project metadata and dependencies.
  • Bump package version from 2.3.1 to 2.4.0 to reflect new features.
  • Add nest-asyncio as a runtime dependency in pyproject.toml; lockfile updated accordingly.
pyproject.toml
poetry.lock

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 25, 2026

Codecov Report

❌ Patch coverage is 78.94737% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/art.py 25.00% 6 Missing ⚠️
src/helper.py 50.00% 1 Missing ⚠️
src/server.py 85.71% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/gpt.py 100.00% <100.00%> (ø)
src/helper.py 98.14% <50.00%> (ø)
src/server.py 97.56% <85.71%> (-2.44%) ⬇️
src/art.py 68.42% <25.00%> (-31.58%) ⬇️
🚀 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.

@ryansurf ryansurf merged commit 463693e into main Apr 25, 2026
11 of 12 checks passed
@ryansurf ryansurf deleted the feat-art branch April 25, 2026 00:59
Copy link
Copy Markdown
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 - I've found 2 issues, and left some high level feedback:

  • In ansi_to_html, the naive str.replace over the entire string can lead to incorrect or unbalanced <span> tags when multiple ANSI codes occur or when a reset is missing; consider parsing the string sequentially (e.g., with regex and a small state machine) to build properly nested HTML.
  • The Llm.call_llm method always returns a string (either the model content or the fallback message), so the return type hint str | None is misleading; tightening this to str will make downstream usage clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `ansi_to_html`, the naive `str.replace` over the entire string can lead to incorrect or unbalanced `<span>` tags when multiple ANSI codes occur or when a reset is missing; consider parsing the string sequentially (e.g., with regex and a small state machine) to build properly nested HTML.
- The `Llm.call_llm` method always returns a string (either the model content or the fallback message), so the return type hint `str | None` is misleading; tightening this to `str` will make downstream usage clearer.

## Individual Comments

### Comment 1
<location path="src/gpt.py" line_range="15-20" />
<code_context>
-    except Exception as e:
-        logger.error("OpenAI request failed: %s", e)
-        return "Unable to generate GPT response."
+class Llm(ABC):
+    def __init__(self, model):
+        self.model = model
+        self.client: Any = None
+
+    def call_llm(self, surf_summary, gpt_prompt) -> str | None:
+        try:
+            response = self.client.chat.completions.create(
</code_context>
<issue_to_address>
**issue:** The return type annotation for `call_llm` doesn’t match the actual behavior.

Since this function always returns a string (either model content or the fallback message) and never `None`, the annotation should be tightened to `str`. If you intend to allow `None`, please add explicit `None` returns in the appropriate paths so the hint matches actual behavior.
</issue_to_address>

### Comment 2
<location path="src/art.py" line_range="51" />
<code_context>
+}
+
+
+def ansi_to_html(text: str) -> str:
+    html = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+    for code, css in ANSI_TO_CSS.items():
</code_context>
<issue_to_address>
**suggestion (bug_risk):** ANSI spans are never explicitly closed if the text ends without a reset code.

Because `</span>` is only emitted on `�[0m`, any final color/bold sequence without a reset leaves the last `<span>` open, producing invalid HTML that can affect subsequent styling. Please track when a span is opened and emit a closing `</span>` at the end if one is still active.
</issue_to_address>

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.

Comment thread src/gpt.py
Comment on lines +15 to +20
class Llm(ABC):
def __init__(self, model):
self.model = model
self.client: Any = None

def call_llm(self, surf_summary, gpt_prompt) -> str | None:
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.

issue: The return type annotation for call_llm doesn’t match the actual behavior.

Since this function always returns a string (either model content or the fallback message) and never None, the annotation should be tightened to str. If you intend to allow None, please add explicit None returns in the appropriate paths so the hint matches actual behavior.

Comment thread src/art.py
}


def ansi_to_html(text: str) -> str:
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.

suggestion (bug_risk): ANSI spans are never explicitly closed if the text ends without a reset code.

Because </span> is only emitted on �[0m, any final color/bold sequence without a reset leaves the last <span> open, producing invalid HTML that can affect subsequent styling. Please track when a span is opened and emit a closing </span> at the end if one is still active.

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.

1 participant