Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 17f04c7

Browse files
SamSaffrontgxworld
andauthored
FEATURE: add OpenAI image generation and editing capabilities (#1293)
This commit enhances the AI image generation functionality by adding support for: 1. OpenAI's GPT-based image generation model (gpt-image-1) 2. Image editing capabilities through the OpenAI API 3. A new "Designer" persona specialized in image generation and editing 4. Two new AI tools: CreateImage and EditImage Technical changes include: - Renaming `ai_openai_dall_e_3_url` to `ai_openai_image_generation_url` with a migration - Adding `ai_openai_image_edit_url` setting for the image edit API endpoint - Refactoring image generation code to handle both DALL-E and the newer GPT models - Supporting multipart/form-data for image editing requests * wild guess but maybe quantization is breaking the test sometimes this increases distance * Update lib/personas/designer.rb Co-authored-by: Alan Guo Xiang Tan <[email protected]> * simplify and de-flake code * fix, in chat we need enough context so we know exactly what uploads a user uploaded. * Update lib/personas/tools/edit_image.rb Co-authored-by: Alan Guo Xiang Tan <[email protected]> * cleanup downloaded files right away * fix implementation --------- Co-authored-by: Alan Guo Xiang Tan <[email protected]>
1 parent 8669e8a commit 17f04c7

File tree

18 files changed

+876
-100
lines changed

18 files changed

+876
-100
lines changed

config/locales/server.en.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ en:
5555
ai_nsfw_flag_threshold_sexy: "Threshold for an image classified as sexy to be considered NSFW."
5656
ai_nsfw_models: "Models to use for NSFW inference."
5757

58-
ai_openai_api_key: "API key for OpenAI API. ONLY used for Dall-E. For GPT use the LLM config tab"
58+
ai_openai_api_key: "API key for OpenAI API. ONLY used for Image creation and edits. For GPT use the LLM config tab"
59+
ai_openai_image_generation_url: "URL for OpenAI image generation API"
60+
ai_openai_image_edit_url: "URL for OpenAI image edit API"
5961

6062
ai_helper_enabled: "Enable the AI helper."
6163
composer_ai_helper_allowed_groups: "Users on these groups will see the AI helper button in the composer."
@@ -290,6 +292,9 @@ en:
290292
artist:
291293
name: Artist
292294
description: "AI Bot specialized in generating images"
295+
designer:
296+
name: Designer
297+
description: "AI Bot specialized in generating and editing images"
293298
sql_helper:
294299
name: SQL Helper
295300
description: "AI Bot specialized in helping craft SQL queries on this Discourse instance"
@@ -377,6 +382,8 @@ en:
377382
dall_e: "Generate image"
378383
search_meta_discourse: "Search Meta Discourse"
379384
javascript_evaluator: "Evaluate JavaScript"
385+
create_image: "Creating image"
386+
edit_image: "Editing image"
380387
tool_help:
381388
read_artifact: "Read a web artifact using the AI Bot"
382389
update_artifact: "Update a web artifact using the AI Bot"
@@ -393,6 +400,8 @@ en:
393400
time: "Find time in various time zones"
394401
summary: "Summarize a topic"
395402
image: "Generate image using Stable Diffusion"
403+
create_image: "Generate image using Open AI GPT image model"
404+
edit_image: "Edit image using Open AI GPT image model"
396405
google: "Search Google for a query"
397406
read: "Read public topic on the forum"
398407
setting_context: "Look up site setting context"
@@ -415,6 +424,8 @@ en:
415424
time: "Time in %{timezone} is %{time}"
416425
summarize: "Summarized <a href='%{url}'>%{title}</a>"
417426
dall_e: "%{prompt}"
427+
create_image: "%{prompt}"
428+
edit_image: "%{prompt}"
418429
image: "%{prompt}"
419430
categories:
420431
one: "Found %{count} category"

config/settings.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ discourse_ai:
2626
default: 60
2727
hidden: true
2828

29-
ai_openai_dall_e_3_url: "https://api.openai.com/v1/images/generations"
29+
ai_openai_image_generation_url: "https://api.openai.com/v1/images/generations"
30+
ai_openai_image_edit_url: "https://api.openai.com/v1/images/edits"
3031
ai_openai_embeddings_url:
3132
hidden: true
3233
default: "https://api.openai.com/v1/embeddings"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
class MoveDallEUrl < ActiveRecord::Migration[7.2]
3+
def up
4+
execute <<~SQL
5+
UPDATE site_settings
6+
SET name = 'ai_openai_image_generation_url'
7+
WHERE name = 'ai_openai_dall_e_3_url'
8+
AND NOT EXISTS (
9+
SELECT 1
10+
FROM site_settings
11+
WHERE name = 'ai_openai_image_generation_url')
12+
SQL
13+
14+
execute <<~SQL
15+
DELETE FROM site_settings
16+
WHERE name = 'ai_openai_dall_e_3_url'
17+
SQL
18+
end
19+
20+
def down
21+
raise ActiveRecord::IrreversibleMigration
22+
end
23+
end

lib/ai_helper/painter.rb

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@ def commission_thumbnails(input, user)
2121

2222
base64_to_image(artifacts, user.id)
2323
elsif model == "dall_e_3"
24-
api_key = SiteSetting.ai_openai_api_key
25-
api_url = SiteSetting.ai_openai_dall_e_3_url
26-
27-
artifacts =
28-
DiscourseAi::Inference::OpenAiImageGenerator
29-
.perform!(input, api_key: api_key, api_url: api_url)
30-
.dig(:data)
31-
.to_a
32-
.map { |art| art[:b64_json] }
33-
34-
base64_to_image(artifacts, user.id)
24+
attribution =
25+
I18n.t(
26+
"discourse_ai.ai_helper.painter.attribution.#{SiteSetting.ai_helper_illustrate_post_model}",
27+
)
28+
results =
29+
DiscourseAi::Inference::OpenAiImageGenerator.create_uploads!(
30+
input,
31+
model: "dall-e-3",
32+
user_id: user.id,
33+
title: attribution,
34+
)
35+
results.map { |result| UploadSerializer.new(result[:upload], root: false) }
3536
end
3637
end
3738

lib/completions/prompt_messages_builder.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ def self.messages_from_chat(
7171
thread_title = m.thread&.title if include_thread_titles && m.thread_id
7272
mapped_message = "(#{thread_title})\n#{m.message}" if thread_title
7373

74+
if m.uploads.present?
75+
mapped_message =
76+
"#{mapped_message} -- uploaded(#{m.uploads.map(&:short_url).join(", ")})"
77+
end
78+
7479
builder.push(
7580
type: :user,
7681
content: mapped_message,

0 commit comments

Comments
 (0)