@@ -10,52 +10,39 @@ def self.name
1010
1111 def self . diff_examples
1212 <<~EXAMPLES
13- Example 1 - Adding a new button:
14- Original HTML:
15- <div class="calculator">
16- <div class="display">0</div>
17- <button>1</button>
18- </div>
19-
20- Diff to add a new button:
21- <button>1</button>
22- + <button>2</button>
23-
24- Example 2 - Modifying styles:
25- Original CSS:
26- .button {
27- background: blue;
28- color: white;
29- }
30-
31- Diff to change colors:
32- .button {
33- - background: blue;
34- - color: white;
35- + background: #333;
36- + color: #fff;
37-
38- Example 3 - Updating JavaScript:
39- Original JavaScript:
40- function ignore() {
41- // some function that is not part of diff
42- }
43- function calculate() {
44- return a + b;
45- }
46-
47- Diff to add multiplication:
48- function calculate() {
49- - return a + b;
50- + return operation === 'multiply' ? a * b : a + b;
51- }
13+ Example - Multiple changes in one file:
14+ --- JavaScript ---
15+ <<<<<<< SEARCH
16+ console.log('old1');
17+ =======
18+ console.log('new1');
19+ >>>>>>> REPLACE
20+ <<<<<<< SEARCH
21+ console.log('old2');
22+ =======
23+ console.log('new2');
24+ >>>>>>> REPLACE
25+
26+ Example - CSS with multiple blocks:
27+ --- CSS ---
28+ <<<<<<< SEARCH
29+ .button { color: blue; }
30+ =======
31+ .button { color: red; }
32+ >>>>>>> REPLACE
33+ <<<<<<< SEARCH
34+ .text { font-size: 12px; }
35+ =======
36+ .text { font-size: 16px; }
37+ >>>>>>> REPLACE
5238 EXAMPLES
5339 end
5440
5541 def self . signature
5642 {
5743 name : "update_artifact" ,
58- description : "Updates an existing web artifact by generating precise diffs" ,
44+ description :
45+ "Updates an existing web artifact using search/replace operations. Supports multiple changes per section." ,
5946 parameters : [
6047 {
6148 name : "artifact_id" ,
@@ -86,160 +73,172 @@ def invoke
8673 return error_response ( "Attempting to update an artifact you are not allowed to" )
8774 end
8875
89- diffs = generate_diffs ( post : post , user : post . user , artifact : artifact )
90- return error_response ( diffs [ :error ] ) if diffs [ :error ]
91-
92- p "here"
76+ changes = generate_changes ( post : post , user : post . user , artifact : artifact )
77+ return error_response ( changes [ :error ] ) if changes [ :error ]
9378
9479 begin
95- version =
96- artifact . apply_diff (
97- html_diff : diffs [ :html_diff ] ,
98- css_diff : diffs [ :css_diff ] ,
99- js_diff : diffs [ :js_diff ] ,
100- change_description : parameters [ :instructions ] ,
101- )
102-
103- p "good"
80+ version = apply_changes ( artifact , changes )
10481 update_custom_html ( artifact , version )
10582 success_response ( artifact , version )
106- rescue DiscourseAi ::Utils ::DiffUtils ::DiffError => e
107- p e
108- error_response ( e . to_llm_message )
10983 rescue => e
110- p e
11184 error_response ( e . message )
11285 end
11386 end
11487
11588 private
11689
117- def generate_diffs ( post :, user :, artifact :)
118- prompt = build_diff_prompt ( post : post , artifact : artifact )
90+ def generate_changes ( post :, user :, artifact :)
91+ prompt = build_changes_prompt ( post : post , artifact : artifact )
11992 response = +""
12093
121- llm . generate ( prompt , user : user , feature_name : "update_artifact" ) do |partial_response |
122- response << partial_response
94+ llm . generate ( prompt , user : user , feature_name : "update_artifact" ) do |partial |
95+ response << partial
96+ end
97+
98+ parse_changes ( response )
99+ end
100+
101+ def parse_changes ( response )
102+ sections = { html : nil , css : nil , javascript : nil }
103+ current_section = nil
104+ lines = [ ]
105+
106+ response . each_line do |line |
107+ case line
108+ when /^--- (HTML|CSS|JavaScript) ---$/
109+ sections [ current_section ] = lines . join if current_section && !lines . empty?
110+ current_section = line . match ( /^--- (.+) ---$/ ) [ 1 ] . downcase . to_sym
111+ lines = [ ]
112+ else
113+ lines << line if current_section
114+ end
115+ end
116+
117+ sections [ current_section ] = lines . join if current_section && !lines . empty?
118+
119+ # Validate and extract all search/replace blocks
120+ sections . transform_values do |content |
121+ next nil if content . nil?
122+
123+ puts content
124+
125+ blocks = extract_search_replace_blocks ( content )
126+ return { error : "Invalid format in #{ current_section } section" } if blocks . nil?
127+
128+ puts "GOOD"
129+ blocks
130+ end
131+ end
132+
133+ def extract_search_replace_blocks ( content )
134+ return nil if content . blank?
135+
136+ blocks = [ ]
137+ remaining = content
138+
139+ while remaining =~ /<<<<<<< SEARCH\n (.*?)\n =======\n (.*?)\n >>>>>>> REPLACE/m
140+ blocks << { search : $1, replace : $2 }
141+ remaining = $'
123142 end
124143
125- sections = parse_diff_sections ( response )
126-
127- if valid_diff_sections? ( sections )
128- html_diff , css_diff , js_diff = sections
129- {
130- html_diff : html_diff . presence ,
131- css_diff : css_diff . presence ,
132- js_diff : js_diff . presence ,
133- }
134- else
135- { error : "Failed to generate valid diffs" , response : response }
144+ blocks . empty? ? nil : blocks
145+ end
146+
147+ def apply_changes ( artifact , changes )
148+ updated_content = { }
149+
150+ %i[ html css javascript ] . each do |section |
151+ blocks = changes [ section ]
152+ next unless blocks
153+
154+ content = artifact . send ( section == :javascript ? :js : section )
155+ blocks . each do |block |
156+ content =
157+ DiscourseAi ::Utils ::DiffUtils ::SimpleDiff . apply (
158+ content ,
159+ block [ :search ] ,
160+ block [ :replace ] ,
161+ )
162+ end
163+ updated_content [ section == :javascript ? :js : section ] = content
136164 end
165+
166+ artifact . create_new_version (
167+ html : updated_content [ :html ] ,
168+ css : updated_content [ :css ] ,
169+ js : updated_content [ :js ] ,
170+ change_description : parameters [ :instructions ] ,
171+ )
137172 end
138173
139- def build_diff_prompt ( post :, artifact :)
174+ def build_changes_prompt ( post :, artifact :)
140175 DiscourseAi ::Completions ::Prompt . new (
141- diff_system_prompt ,
176+ changes_system_prompt ,
142177 messages : [
143- {
144- type : :user ,
145- content :
146- "Current artifact code:\n \n " \
147- "--- HTML ---\n #{ artifact . html } \n " \
148- "--- CSS ---\n #{ artifact . css } \n " \
149- "--- JavaScript ---\n #{ artifact . js } \n " ,
150- } ,
151- { type : :model , content : "Please explain the diffs you would like to generate:" } ,
178+ { type : :user , content : <<~CONTENT } ,
179+ Current artifact code:
180+
181+ --- HTML ---
182+ #{ artifact . html }
183+
184+ --- CSS ---
185+ #{ artifact . css }
186+
187+ --- JavaScript ---
188+ #{ artifact . js }
189+ CONTENT
190+ { type : :model , content : "Please explain the changes you would like to generate:" } ,
152191 { type : :user , content : parameters [ :instructions ] } ,
153192 ] ,
154193 post_id : post . id ,
155194 topic_id : post . topic_id ,
156195 )
157196 end
158197
159- def diff_system_prompt
198+ def changes_system_prompt
160199 <<~PROMPT
161- You are a web development expert generating precise diffs for updating HTML, CSS, and JavaScript code.
200+ You are a web development expert generating precise search/replace changes for updating HTML, CSS, and JavaScript code.
162201
163202 Important rules:
164- 1. Only output changes using - for removals and + for additions
165- 2. Include 1-2 lines of context around changes
166- 3. Generate three sections: HTML_DIFF, CSS_DIFF , and JS_DIFF
203+ 1. Use the format <<<<<<< SEARCH / ======= / >>>>>>> REPLACE for each change
204+ 2. You can specify multiple search/replace blocks per section
205+ 3. Generate three sections: HTML, CSS , and JavaScript
167206 4. Only include sections that have changes
168- 5. Use exact line matches for context
169- 6. Keep diffs minimal and focused
207+ 5. Keep changes minimal and focused
208+ 6. Use exact matches for the search content
170209
171210 Format:
172- --- HTML_DIFF ---
173- (diff or empty if no changes)
174- --- CSS_DIFF ---
175- (diff or empty if no changes)
176- --- JS_DIFF ---
177- (diff or empty if no changes)
178-
179- --------------
180- When supplying diffs, use a unified diff format. For example:
181-
211+ --- HTML ---
212+ (changes or empty if no changes)
213+ --- CSS ---
214+ (changes or empty if no changes)
215+ --- JavaScript ---
216+ (changes or empty if no changes)
217+
218+ Example changes:
182219 #{ self . class . diff_examples }
183220 PROMPT
184221 end
185222
186- def parse_diff_sections ( response )
187- html = +""
188- css = +""
189- javascript = +""
190- current_section = nil
191-
192- response . each_line do |line |
193- case line
194- when /--- (HTML_DIFF|CSS_DIFF|JS_DIFF) ---/
195- current_section = Regexp . last_match ( 1 )
196- else
197- case current_section
198- when "HTML_DIFF"
199- html << line
200- when "CSS_DIFF"
201- css << line
202- when "JS_DIFF"
203- javascript << line
204- end
205- end
206- end
207-
208- [ html . strip , css . strip , javascript . strip ]
209- end
210-
211- def valid_diff_sections? ( sections )
212- return false if sections . empty?
213-
214- sections . any? do |section |
215- next true if section . blank?
216- section . include? ( "-" ) || section . include? ( "+" )
217- end
218- end
219-
220223 def update_custom_html ( artifact , version )
221224 content = [ ]
222225
223226 if version . change_description . present?
224227 content << [ :description , "### Change Description\n \n #{ version . change_description } " ]
225228 end
226-
227- diffs = [ ]
228- diffs << [ "HTML Changes" , version . html ] if version . html != artifact . html
229- diffs << [ "CSS Changes" , version . css ] if version . css != artifact . css
230- diffs << [ "JavaScript Changes" , version . js ] if version . js != artifact . js
231-
232229 content << [ nil , "[details='#{ I18n . t ( "discourse_ai.ai_artifact.view_changes" ) } ']" ]
233230
234- diffs . each do |title , new_content |
235- old_content = artifact . send ( title . downcase . split . first )
236- #diff = generate_readable_diff(old_content, new_content)
237- diff = "TODO"
238- content << [ nil , "### #{ title } \n ```diff\n #{ diff } \n ```" ]
231+ %w[ html css js ] . each do |type |
232+ old_content = artifact . public_send ( type )
233+ new_content = version . public_send ( type )
234+
235+ if old_content != new_content
236+ diff = "xxx" # Placeholder for actual diff implementation
237+ content << [ nil , "### #{ type . upcase } Changes\n ```diff\n #{ diff } \n ```" ]
238+ end
239239 end
240240
241241 content << [ nil , "[/details]" ]
242-
243242 content << [
244243 :preview ,
245244 "### Preview\n \n <div class=\" ai-artifact\" data-ai-artifact-version=\" #{ version . version_number } \" data-ai-artifact-id=\" #{ artifact . id } \" ></div>" ,
@@ -248,10 +247,6 @@ def update_custom_html(artifact, version)
248247 self . custom_raw = content . map { |c | c [ 1 ] } . join ( "\n \n " )
249248 end
250249
251- def generate_readable_diff ( old_content , new_content )
252- #Diffy::Diff.new(old_content, new_content, context: 2).to_s(:text)
253- end
254-
255250 def success_response ( artifact , version )
256251 {
257252 status : "success" ,
0 commit comments