@@ -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