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

Commit a82e012

Browse files
committed
work in progress move artifact creation to background llm job
1 parent 99e73f0 commit a82e012

File tree

5 files changed

+404
-250
lines changed

5 files changed

+404
-250
lines changed

config/locales/server.en.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ en:
359359
javascript_evaluator: "Evaluate JavaScript"
360360
tool_description:
361361
update_artifact: "Updated a web artifact using the AI Bot"
362-
create_artifact: "Created a web artifact using the AI Bot"
362+
create_artifact: "Created a web artifact: %{name} - %{specification}"
363363
web_browser: "Reading <a href='%{url}'>%{url}</a>"
364364
github_search_files: "Searched for '%{keywords}' in %{repo}/%{branch}"
365365
github_search_code: "Searched for '%{query}' in %{repo}"

lib/ai_bot/tools/create_artifact.rb

Lines changed: 174 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,47 @@ def self.name
88
"create_artifact"
99
end
1010

11-
def self.js_dependency_tip
12-
<<~TIP
13-
If you need to include a JavaScript library, you may include assets from:
14-
- unpkg.com
15-
- cdnjs.com
16-
- jsdelivr.com
17-
- ajax.googleapis.com
18-
19-
To include them ensure they are the last tag in your HTML body.
20-
Example: <script crossorigin src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
21-
TIP
22-
end
23-
24-
def self.js_script_tag_tip
25-
<<~TIP
26-
if you need a custom script tag, you can use the following format:
27-
28-
<script type="module">
29-
// your script here
30-
</script>
31-
32-
If you only need a regular script tag, you can use the following format:
33-
34-
// your script here
35-
TIP
11+
def self.specification_description
12+
<<~DESC
13+
A detailed description of the web artifact you want to create. Your specification should include:
14+
15+
1. Purpose and functionality
16+
2. Visual design requirements
17+
3. Interactive elements and behavior
18+
4. Data handling (if applicable)
19+
5. Specific requirements or constraints
20+
21+
Good specification examples:
22+
23+
Example: (Calculator):
24+
"Create a modern calculator with a dark theme. It should:
25+
- Have a large display area showing current and previous calculations
26+
- Include buttons for numbers 0-9, basic operations (+,-,*,/), and clear
27+
- Use a grid layout with subtle hover effects on buttons
28+
- Show button press animations
29+
- Keep calculation history visible above current input
30+
- Use a monospace font for numbers
31+
- Support keyboard input for numbers and operations"
32+
33+
Poor specification example:
34+
"Make a website that looks nice and does cool stuff"
35+
(Too vague, lacks specific requirements and functionality details)
36+
37+
Tips for good specifications:
38+
- Be specific about layout and design preferences
39+
- Describe all interactive elements and their behavior
40+
- Include any specific visual effects or animations
41+
- Mention responsive design requirements if needed
42+
- List any specific libraries or frameworks to use/avoid
43+
- Describe error states and edge cases
44+
- Include accessibility requirements
45+
DESC
3646
end
3747

3848
def self.signature
3949
{
4050
name: "create_artifact",
41-
description:
42-
"Creates a web artifact with HTML, CSS, and JavaScript that can be displayed in an iframe",
51+
description: "Creates a web artifact based on a specification",
4352
parameters: [
4453
{
4554
name: "name",
@@ -48,58 +57,25 @@ def self.signature
4857
required: true,
4958
},
5059
{
51-
name: "html_body",
52-
description:
53-
"The HTML content for the BODY tag (do not include the BODY tag). #{js_dependency_tip}",
60+
name: "specification",
5461
type: "string",
62+
description: specification_description,
5563
required: true,
5664
},
57-
{ name: "css", description: "Optional CSS styles for the artifact", type: "string" },
58-
{
59-
name: "js",
60-
description:
61-
"Optional
62-
JavaScript code for the artifact, this will be the last <script> tag in the BODY. #{js_script_tag_tip}",
63-
type: "string",
64-
},
6565
],
6666
}
6767
end
6868

69-
def self.allow_partial_tool_calls?
70-
true
71-
end
72-
73-
def partial_invoke
74-
@selected_tab = :html_body
75-
if @prev_parameters
76-
@selected_tab = parameters.keys.find { |k| @prev_parameters[k] != parameters[k] }
77-
end
78-
update_custom_html
79-
@prev_parameters = parameters.dup
80-
end
81-
8269
def invoke
83-
yield parameters[:name] || "Web Artifact"
84-
# Get the current post from context
70+
yield parameters[:name] || "New Artifact"
71+
8572
post = Post.find_by(id: context[:post_id])
8673
return error_response("No post context found") unless post
8774

88-
html = parameters[:html_body].to_s
89-
css = parameters[:css].to_s
90-
js = parameters[:js].to_s
91-
92-
# Create the artifact
93-
artifact =
94-
AiArtifact.new(
95-
user_id: bot_user.id,
96-
post_id: post.id,
97-
name: parameters[:name].to_s[0...255],
98-
html: html,
99-
css: css,
100-
js: js,
101-
metadata: parameters[:metadata],
102-
)
75+
artifact_code = generate_artifact_code(post: post, user: post.user)
76+
return error_response(artifact_code[:error]) if artifact_code[:error]
77+
78+
artifact = create_artifact(post, artifact_code)
10379

10480
if artifact.save
10581
update_custom_html(artifact)
@@ -109,59 +85,156 @@ def invoke
10985
end
11086
end
11187

112-
def chain_next_response?
113-
@chain_next_response
88+
def description_args
89+
{ name: parameters[:name], specification: parameters[:specification] }
11490
end
11591

11692
private
11793

118-
def update_custom_html(artifact = nil)
119-
html = parameters[:html_body].to_s
120-
css = parameters[:css].to_s
121-
js = parameters[:js].to_s
94+
def generate_artifact_code(post:, user:)
95+
prompt = build_artifact_prompt(post: post)
96+
response = +""
97+
llm.generate(prompt, user: user, feature_name: "create_artifact") do |partial_response|
98+
response << partial_response
99+
end
122100

123-
artifact_div =
124-
"<div class=\"ai-artifact\" data-ai-artifact-id=\"#{artifact.id}\"></div>" if artifact
101+
sections = parse_sections(response)
125102

126-
content = []
103+
if valid_sections?(sections)
104+
html, css, js = sections
105+
{ html: html, css: css, js: js }
106+
else
107+
{ error: "Failed to generate valid artifact code", response: response }
108+
end
109+
end
127110

128-
content << [:html_body, "### HTML\n\n```html\n#{html}\n```"] if html.present?
111+
def build_artifact_prompt(post:)
112+
DiscourseAi::Completions::Prompt.new(
113+
artifact_system_prompt,
114+
messages: [{ type: :user, content: parameters[:specification] }],
115+
post_id: post.id,
116+
topic_id: post.topic_id,
117+
)
118+
end
129119

130-
content << [:css, "### CSS\n\n```css\n#{css}\n```"] if css.present?
120+
def parse_sections(response)
121+
html = +""
122+
css = +""
123+
javascript = +""
124+
current_section = nil
125+
126+
response.each_line do |line|
127+
case line
128+
when /--- (HTML|CSS|JavaScript) ---/
129+
current_section = Regexp.last_match(1)
130+
else
131+
case current_section
132+
when "HTML"
133+
html << line
134+
when "CSS"
135+
css << line
136+
when "JavaScript"
137+
javascript << line
138+
end
139+
end
140+
end
131141

132-
content << [:js, "### JavaScript\n\n```javascript\n#{js}\n```"] if js.present?
142+
[html.strip, css.strip, javascript.strip]
143+
end
133144

134-
content.sort_by! { |c| c[0] === @selected_tab ? 1 : 0 } if !artifact
145+
def valid_sections?(sections)
146+
return false if sections.empty?
135147

136-
if artifact
137-
content.unshift([nil, "[details='#{I18n.t("discourse_ai.ai_artifact.view_source")}']"])
138-
content << [nil, "[/details]"]
139-
end
148+
# Basic validation of sections
149+
has_html = sections[0].include?("<") && sections[0].include?(">")
150+
has_css = sections[1].include?("{") && sections[1].include?("}")
151+
has_js = sections[2].present?
140152

141-
content << [:preview, "### Preview\n\n#{artifact_div}"] if artifact_div
142-
self.custom_raw = content.map { |c| c[1] }.join("\n\n")
153+
has_html || has_css || has_js
143154
end
144155

145-
def success_response(artifact)
146-
@chain_next_response = false
156+
def create_artifact(post, code)
157+
AiArtifact.new(
158+
user_id: bot_user.id,
159+
post_id: post.id,
160+
name: parameters[:name].to_s[0...255],
161+
html: code[:html],
162+
css: code[:css],
163+
js: code[:js],
164+
metadata: {
165+
specification: parameters[:specification],
166+
},
167+
)
168+
end
147169

148-
{
149-
status: "success",
150-
artifact_id: artifact.id,
151-
message: "Artifact created successfully and rendered to user.",
152-
}
170+
def artifact_system_prompt
171+
<<~PROMPT
172+
You are a web development expert creating HTML, CSS, and JavaScript code.
173+
Follow these instructions precisely:
174+
175+
1. Output exactly three sections separated by section delimiters
176+
2. Section order must be: HTML, CSS, JavaScript
177+
3. Format requirements:
178+
- HTML: No <html>, <head>, or <body> tags
179+
- CSS: Valid CSS rules
180+
- JavaScript: Clean, working code
181+
4. TAKE NO SHORTCUTS - generate all of the code needed for a complete web artifact. Do not leave any placeholders in the code.
182+
183+
External libraries allowed only from:
184+
- unpkg.com
185+
- cdnjs.com
186+
- jsdelivr.net
187+
- ajax.googleapis.com
188+
189+
Example format:
190+
--- HTML ---
191+
<div id="app"><!-- Your HTML here --></div>
192+
--- CSS ---
193+
#app { /* Your CSS here */ }
194+
--- JavaScript ---
195+
// Your JavaScript here
196+
197+
Important:
198+
- Adhere striclty to the format (--- HTML ---, --- CSS ---, --- JavaScript ---)
199+
- Focus on simplicity and reliability
200+
- Include basic error handling
201+
- Follow accessibility guidelines
202+
- No explanatory text, only code
203+
PROMPT
153204
end
154205

155-
def error_response(message)
156-
@chain_next_response = false
206+
def update_custom_html(artifact)
207+
html_preview = <<~MD
208+
[details="View Source"]
209+
### HTML
210+
```html
211+
#{artifact.html}
212+
```
213+
214+
### CSS
215+
```css
216+
#{artifact.css}
217+
```
218+
219+
### JavaScript
220+
```javascript
221+
#{artifact.js}
222+
```
223+
[/details]
224+
225+
### Preview
226+
<div class="ai-artifact" data-ai-artifact-id="#{artifact.id}"></div>
227+
MD
228+
229+
self.custom_raw = html_preview
230+
end
157231

158-
{ status: "error", error: message }
232+
def success_response(artifact)
233+
{ status: "success", artifact_id: artifact.id, message: "Artifact created successfully." }
159234
end
160235

161-
def help
162-
"Creates a web artifact with HTML, CSS, and JavaScript that can be displayed in an iframe. " \
163-
"Requires a name and HTML content. CSS and JavaScript are optional. " \
164-
"The artifact will be associated with the current post and can be displayed using an iframe."
236+
def error_response(message)
237+
{ status: "error", error: message }
165238
end
166239
end
167240
end

0 commit comments

Comments
 (0)