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

Commit da74713

Browse files
committed
implement new designer persona and tools / spec
1 parent 9d6fa07 commit da74713

File tree

8 files changed

+419
-3
lines changed

8 files changed

+419
-3
lines changed

config/locales/server.en.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ en:
292292
artist:
293293
name: Artist
294294
description: "AI Bot specialized in generating images"
295+
designer:
296+
name: Designer
297+
description: "AI Bot specialized in generating and editing images"
295298
sql_helper:
296299
name: SQL Helper
297300
description: "AI Bot specialized in helping craft SQL queries on this Discourse instance"
@@ -379,6 +382,8 @@ en:
379382
dall_e: "Generate image"
380383
search_meta_discourse: "Search Meta Discourse"
381384
javascript_evaluator: "Evaluate JavaScript"
385+
create_image: "Creating image"
386+
edit_image: "Editing image"
382387
tool_help:
383388
read_artifact: "Read a web artifact using the AI Bot"
384389
update_artifact: "Update a web artifact using the AI Bot"
@@ -395,6 +400,8 @@ en:
395400
time: "Find time in various time zones"
396401
summary: "Summarize a topic"
397402
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"
398405
google: "Search Google for a query"
399406
read: "Read public topic on the forum"
400407
setting_context: "Look up site setting context"
@@ -417,6 +424,8 @@ en:
417424
time: "Time in %{timezone} is %{time}"
418425
summarize: "Summarized <a href='%{url}'>%{title}</a>"
419426
dall_e: "%{prompt}"
427+
create_image: "%{prompt}"
428+
edit_image: "%{prompt}"
420429
image: "%{prompt}"
421430
categories:
422431
one: "Found %{count} category"

lib/inference/open_ai_image_generator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ def self.create_edited_upload!(
6969
quality: quality,
7070
)
7171

72-
# Create uploads in the main thread
7372
create_uploads_from_responses([api_response], user_id, for_private_message).first
7473
end
7574

@@ -211,7 +210,7 @@ def self.edit_images(
211210
attempts += 1
212211
sleep 2
213212
retry if attempts < 3
214-
if Rails.env.development?
213+
if Rails.env.development? || Rails.env.test?
215214
puts "Error editing image(s) with prompt: #{prompt} #{e}"
216215
p e
217216
end
@@ -326,6 +325,7 @@ def self.perform_edit_api_call!(
326325
# Add model
327326
body << "--#{boundary}\r\n"
328327
body << "Content-Disposition: form-data; name=\"model\"\r\n\r\n"
328+
329329
body << "#{model}\r\n"
330330

331331
# Add images

lib/personas/designer.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Personas
5+
class Designer < Persona
6+
def tools
7+
[Tools::CreateImage, Tools::EditImage]
8+
end
9+
10+
def required_tools
11+
[Tools::CreateImage, Tools::EditImage]
12+
end
13+
14+
def system_prompt
15+
<<~PROMPT
16+
You are designerbot and you are here to help people generate and edit images.
17+
18+
- A good prompt needs to be detailed and specific.
19+
- You can specify subject, medium (e.g. oil on canvas), artist (person who drew it or photographed it)
20+
- You can specify details about lighting or time of day.
21+
- You can specify a particular website you would like to emulate (artstation or deviantart)
22+
- You can specify additional details such as "beautiful, dystopian, futuristic, etc."
23+
- Be extremely detailed with image prompts
24+
PROMPT
25+
end
26+
end
27+
end
28+
end

lib/personas/persona.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def system_personas
4646
WebArtifactCreator => -10,
4747
Summarizer => -11,
4848
ShortSummarizer => -12,
49+
Designer => -13,
4950
}
5051
end
5152

@@ -111,7 +112,12 @@ def all_available_tools
111112
tools << Tools::ListTags if SiteSetting.tagging_enabled
112113
tools << Tools::Image if SiteSetting.ai_stability_api_key.present?
113114

114-
tools << Tools::DallE if SiteSetting.ai_openai_api_key.present?
115+
if SiteSetting.ai_openai_api_key.present?
116+
tools << Tools::DallE
117+
tools << Tools::CreateImage
118+
tools << Tools::EditImage
119+
end
120+
115121
if SiteSetting.ai_google_custom_search_api_key.present? &&
116122
SiteSetting.ai_google_custom_search_cx.present?
117123
tools << Tools::Google

lib/personas/tools/create_image.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Personas
5+
module Tools
6+
class CreateImage < Tool
7+
def self.signature
8+
{
9+
name: name,
10+
description: "Renders images from supplied descriptions",
11+
parameters: [
12+
{
13+
name: "prompts",
14+
description:
15+
"The prompts used to generate or create or draw the image (5000 chars or less, be creative) up to 4 prompts, usually only supply a single prompt",
16+
type: "array",
17+
item_type: "string",
18+
required: true,
19+
},
20+
],
21+
}
22+
end
23+
24+
def self.name
25+
"create_image"
26+
end
27+
28+
def prompts
29+
parameters[:prompts]
30+
end
31+
32+
def chain_next_response?
33+
false
34+
end
35+
36+
def invoke
37+
# max 4 prompts
38+
max_prompts = prompts.take(4)
39+
progress = prompts.first
40+
41+
yield(progress)
42+
43+
results = nil
44+
45+
results =
46+
DiscourseAi::Inference::OpenAiImageGenerator.create_uploads!(
47+
max_prompts,
48+
model: "gpt-image-1",
49+
user_id: bot_user.id,
50+
)
51+
52+
if results.blank?
53+
return { prompts: max_prompts, error: "Something went wrong, could not generate image" }
54+
end
55+
56+
self.custom_raw = <<~RAW
57+
58+
[grid]
59+
#{
60+
results
61+
.map { |item| "![#{item[:prompt].gsub(/\|\'\"/, "")}](#{item[:upload].short_url})" }
62+
.join(" ")
63+
}
64+
[/grid]
65+
RAW
66+
67+
{
68+
prompts: results.map { |item| { prompt: item[:prompt], url: item[:upload].short_url } },
69+
}
70+
end
71+
72+
protected
73+
74+
def description_args
75+
{ prompt: prompts.first }
76+
end
77+
end
78+
end
79+
end
80+
end

lib/personas/tools/edit_image.rb

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Personas
5+
module Tools
6+
class EditImage < Tool
7+
def self.signature
8+
{
9+
name: name,
10+
description: "Renders images from supplied descriptions",
11+
parameters: [
12+
{
13+
name: "prompt",
14+
description:
15+
"instructions for the image to be edited (5000 chars or less, be creative)",
16+
type: "string",
17+
required: true,
18+
},
19+
{
20+
name: "image_urls",
21+
description:
22+
"The images to provides as context for the edit (minimum 1, maximum 10), use the short url eg: upload://qUm0DGR49PAZshIi7HxMd3cAlzn.png",
23+
type: "array",
24+
item_type: "string",
25+
required: true,
26+
},
27+
],
28+
}
29+
end
30+
31+
def self.name
32+
"edit_image"
33+
end
34+
35+
def prompt
36+
parameters[:prompt]
37+
end
38+
39+
def chain_next_response?
40+
false
41+
end
42+
43+
def image_urls
44+
parameters[:image_urls]
45+
end
46+
47+
def invoke
48+
yield(prompt)
49+
50+
return { prompt: prompt, error: "No valid images provided" } if image_urls.blank?
51+
52+
uploads =
53+
image_urls
54+
.map do |url|
55+
sha1 = Upload.sha1_from_short_url(url)
56+
Upload.find_by(sha1: sha1)
57+
end
58+
.compact
59+
.take(10)
60+
61+
return { prompt: prompt, error: "No valid images provided" } if uploads.blank?
62+
63+
result =
64+
DiscourseAi::Inference::OpenAiImageGenerator.create_edited_upload!(
65+
uploads,
66+
prompt,
67+
user_id: bot_user.id,
68+
)
69+
70+
if result.blank?
71+
return { prompt: prompt, error: "Something went wrong, could not generate image" }
72+
end
73+
74+
self.custom_raw = "![#{result[:prompt].gsub(/\|\'\"/, "")}](#{result[:upload].short_url})"
75+
76+
{ prompt: result[:prompt], url: result[:upload].short_url }
77+
end
78+
79+
protected
80+
81+
def description_args
82+
{ prompt: prompt }
83+
end
84+
end
85+
end
86+
end
87+
end

0 commit comments

Comments
 (0)