Skip to content

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Dec 4, 2025

User description

Benchmark PR discourse#36195

Type: Clean (correct implementation)

Original PR Title: DEV: Make persona image tools platform agnostic
Original PR Description: ## 🔍 Overview
This update updates existing persona tools: Image, CreateImage, and EditImage to make use of the new AI image generation tooling added in discourse@ce2e278 . It also updates the image generation tool scripts to support editing images on providers that support it.

📹 Preview

preview-gemini.mov

Original PR URL: discourse#36195


PR Type

Enhancement, Bug fix


Description

  • Migrate image generation from provider-specific APIs to platform-agnostic custom tools

  • Remove deprecated OpenAI and Stability image generation implementations

  • Add support for FLUX 2 Pro and enhance existing tools with image editing capabilities

  • Update personas to use configurable custom image tools instead of hardcoded providers


Diagram Walkthrough

flowchart LR
  A["Provider-Specific APIs<br/>OpenAI/Stability"] -->|Deprecated| B["Custom AiTool System"]
  B -->|Configurable| C["Image Generation Tools"]
  B -->|Configurable| D["Image Editing Tools"]
  C -->|Supports| E["FLUX 1.1 Pro<br/>FLUX 2 Pro<br/>Gemini<br/>OpenAI"]
  D -->|Supports| F["Multi-image Editing<br/>Permission Checks"]
  G["Personas<br/>Artist/Designer/General"] -->|Uses| B
Loading

File Walkthrough

Relevant files
Enhancement
13 files
ai_tool.rb
Add FLUX 2 Pro preset and update FLUX naming                         
+29/-3   
artist.rb
Update Artist persona to use custom image tools                   
+4/-3     
designer.rb
Update Designer persona to use custom image tools               
+7/-1     
general.rb
Update General persona to use custom image tools                 
+6/-2     
persona.rb
Remove DallE3 from system personas, update tool availability logic
+3/-4     
create_image.rb
Refactor to delegate to custom image generation tools       
+72/-19 
edit_image.rb
Refactor to use custom tools with permission checks           
+87/-19 
image.rb
Refactor to delegate to custom image generation tools       
+87/-57 
tool.rb
Add method to retrieve available custom image tools           
+7/-0     
flux_2_bfl.js
Add new FLUX 2 Pro Black Forest Labs script                           
+130/-0 
flux_together.js
Add FLUX 1.1 Pro Together.ai script with editing support 
+133/-0 
gemini.js
Add image editing support to Gemini script                             
+66/-1   
openai.js
Add image editing support to OpenAI script                             
+102/-1 
Bug fix
5 files
open_ai_image_generator.rb
Remove deprecated OpenAI image generation implementation 
+0/-469 
stability_generator.rb
Remove deprecated Stability image generation implementation
+0/-160 
dall_e_3.rb
Remove deprecated DallE3 persona class                                     
+0/-37   
dall_e.rb
Remove deprecated DallE tool implementation                           
+0/-97   
flux.js
Remove deprecated FLUX Together.ai script                               
+0/-41   
Tests
7 files
playground_spec.rb
Remove deprecated DallE bot test cases                                     
+0/-63   
persona_spec.rb
Update persona tests for custom tool availability               
+41/-35 
create_image_spec.rb
Refactor tests to use custom image generation tools           
+78/-109
dall_e_spec.rb
Remove deprecated DallE tool test file                                     
+0/-97   
edit_image_spec.rb
Refactor tests with permission checks and custom tools     
+145/-66
image_spec.rb
Refactor tests to use custom image generation tools           
+51/-23 
stability_generator_spec.rb
Remove deprecated Stability generator test file                   
+0/-81   
Documentation
2 files
preamble.js
Document upload.getBase64 for image editing support           
+15/-0   
server.en.yml
Update FLUX naming and add FLUX 2 Pro localization             
+3/-1     
Configuration changes
1 files
settings.yml
Deprecate provider-specific image generation settings       
+24/-28 

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Hardcoded API key

Description: The script embeds a hardcoded API key placeholder (const apiKey = "YOUR_BFL_API_KEY";),
which encourages storing secrets directly in code; tool presets should source credentials
from secure server-side configuration to prevent accidental exposure.
flux_2_bfl.js [1-5]

Referred Code
/* eslint-disable no-undef, no-unused-vars */
const apiKey = "YOUR_BFL_API_KEY";

function invoke(params) {
  const prompt = params.prompt;
Hardcoded API key

Description: The preset uses a hardcoded API key placeholder (const apiKey = "YOUR_KEY";), suggesting
client-side secret usage; credentials should be injected securely server-side to avoid
leakage.
flux_together.js [1-4]

Referred Code
/* eslint-disable no-undef, no-unused-vars */
const apiKey = "YOUR_KEY";
const model = "black-forest-labs/FLUX.1.1-pro";
Hardcoded API key

Description: The OpenAI preset defines const apiKey = "YOUR_OPENAI_API_KEY";, implying API keys inside
tool scripts; storing secrets in these scripts risks exposure if scripts are surfaced to
clients or logs.
openai.js [2-3]

Referred Code
const apiKey = "YOUR_OPENAI_API_KEY";
Hardcoded API key

Description: The Gemini preset includes const apiKey = "YOUR_GOOGLE_API_KEY"; as an inline secret
placeholder; API credentials should be retrieved from secure settings on the server and
not embedded in JavaScript tool code.
gemini.js [2-3]

Referred Code
const apiKey = "YOUR_GOOGLE_API_KEY";
Third-party data leak

Description: Image edit requests validate access for uploads linked to restricted posts but skip checks
for unrestricted uploads; if external image edit providers receive the raw content of
public uploads, additional content policy checks (e.g., NSFW/PII) may be required to avoid
unintentional data exfiltration to third parties.
edit_image.rb [63-81]

Referred Code
guardian = Guardian.new(context.user)
uploads = Upload.where(sha1: sha1s)

uploads.each do |upload|
  # Check if upload has access control
  if upload.access_control_post_id.present?
    post = Post.find_by(id: upload.access_control_post_id)
    if post && !guardian.can_see?(post)
      @error = true
      return(
        {
          prompt: prompt,
          error:
            "Access denied: You don't have permission to edit one or more of the provided images",
        }
      )
    end
  end
end
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Limited Auditing: New image edit flow performs permission checks and external tool invocation without
emitting structured audit logs tying actions to user, prompt, images, and outcome.

Referred Code
guardian = Guardian.new(context.user)
uploads = Upload.where(sha1: sha1s)

uploads.each do |upload|
  # Check if upload has access control
  if upload.access_control_post_id.present?
    post = Post.find_by(id: upload.access_control_post_id)
    if post && !guardian.can_see?(post)
      @error = true
      return(
        {
          prompt: prompt,
          error:
            "Access denied: You don't have permission to edit one or more of the provided images",
        }
      )
    end
  end
end

# Find available custom image generation tools


 ... (clipped 63 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Error Detail Exposure: User-visible error messages may include raw exception messages from external tool
invocations which could reveal internal details.

Referred Code
rescue => e
  Rails.logger.error(
    "CreateImage: Failed to generate image for prompt '#{prompt.truncate(50)}'. " \
      "Tool: #{tool_class.name}, Error: #{e.class.name} - #{e.message}",
  )
  errors << e.message
end

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Prompt In Logs: Logs include prompts and image URLs in error/warn messages which could leak user-provided
content into logs.

Referred Code
@error = true
Rails.logger.error(
  "EditImage: Failed to edit image. " \
    "Tool: #{tool_class.name}, Error: #{e.class.name} - #{e.message}. " \
    "Prompt: #{prompt.truncate(50)}, Image URLs: #{image_urls.join(", ")}",
)
{ prompt: prompt, error: e.message }

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Refactor tool invocation to avoid duplication

To improve maintainability, extract the duplicated logic for finding, invoking,
and parsing results from custom image tools in CreateImage, EditImage, and Image
into a shared helper method.

Examples:

plugins/discourse-ai/lib/personas/tools/create_image.rb [37-116]
          # Find available custom image generation tools
          custom_tools = self.class.available_custom_image_tools

          if custom_tools.empty?
            @error = true
            return(
              {
                prompts: prompts,
                error:
                  "No image generation tools configured. Please configure an image generation tool via the admin UI to use this feature.",

 ... (clipped 70 lines)
plugins/discourse-ai/lib/personas/tools/edit_image.rb [83-145]
          # Find available custom image generation tools
          custom_tools = self.class.available_custom_image_tools

          if custom_tools.empty?
            @error = true
            return(
              {
                prompt: prompt,
                error:
                  "No image generation tools configured. Please configure an image generation tool via the admin UI to use this feature.",

 ... (clipped 53 lines)

Solution Walkthrough:

Before:

# In plugins/discourse-ai/lib/personas/tools/create_image.rb
def invoke
  custom_tools = self.class.available_custom_image_tools
  if custom_tools.empty?
    # return error
  end
  tool_class = custom_tools.first

  prompts.each do |prompt|
    begin
      tool_instance = tool_class.new({ prompt: prompt }, ...)
      tool_instance.invoke { ... }
      # ... parse custom_raw to get upload ...
    rescue => e
      # ... handle error ...
    end
  end
  # ... format results ...
end

# Similar logic exists in EditImage.rb and Image.rb

After:

# In a new shared module, e.g., ImageToolHelper
module ImageToolHelper
  def invoke_first_available_image_tool(tool_params)
    custom_tools = self.class.available_custom_image_tools
    return { error: "No image tools configured." } if custom_tools.empty?

    tool_class = custom_tools.first
    tool_instance = tool_class.new(tool_params, ...)
    tool_instance.invoke { ... }

    # ... parse custom_raw and return result/error ...
  end
end

# In plugins/discourse-ai/lib/personas/tools/create_image.rb
class CreateImage < Tool
  include ImageToolHelper
  def invoke
    prompts.each do |prompt|
      result = invoke_first_available_image_tool({ prompt: prompt })
      # ... process result ...
    end
  end
end
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies significant code duplication across the CreateImage, EditImage, and Image tools for discovering, invoking, and parsing results from custom image tools, and addressing this would improve code maintainability.

Medium
  • More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants