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

Commit ec3ecc0

Browse files
committed
update artifact tool
1 parent 6cbfcac commit ec3ecc0

File tree

6 files changed

+371
-3
lines changed

6 files changed

+371
-3
lines changed

app/models/ai_artifact_version.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
class AiArtifactVersion < ActiveRecord::Base
3+
belongs_to :ai_artifact
4+
validates :html, length: { maximum: 65_535 }
5+
validates :css, length: { maximum: 65_535 }
6+
validates :js, length: { maximum: 65_535 }
7+
end
8+
9+
# == Schema Information
10+
#
11+
# Table name: ai_artifact_versions
12+
#
13+
# id :bigint not null, primary key
14+
# ai_artifact_id :bigint not null
15+
# version_number :integer not null
16+
# html :string(65535)
17+
# css :string(65535)
18+
# js :string(65535)
19+
# metadata :jsonb
20+
# change_description :string
21+
# created_at :datetime not null
22+
# updated_at :datetime not null
23+
#
24+
# Indexes
25+
#
26+
# index_ai_artifact_versions_on_ai_artifact_id_and_version_number (ai_artifact_id,version_number) UNIQUE
27+
#

config/locales/server.en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ en:
205205
ai_artifact:
206206
link: "Show Artifact in new tab"
207207
view_source: "View Source"
208+
view_changes: "View Changes"
208209
unknown_model: "Unknown AI model"
209210

210211
tools:

lib/ai_bot/personas/persona.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,11 @@ def all_available_tools
9999
Tools::JavascriptEvaluator,
100100
]
101101

102-
tools << Tools::CreateArtifact if SiteSetting.ai_artifact_security.in?(%w[lax strict])
102+
if SiteSetting.ai_artifact_security.in?(%w[lax strict])
103+
tools << Tools::CreateArtifact
104+
tools << Tools::UpdateArtifact
105+
end
106+
103107
tools << Tools::GithubSearchCode if SiteSetting.ai_bot_github_access_token.present?
104108

105109
tools << Tools::ListTags if SiteSetting.tagging_enabled

lib/ai_bot/personas/web_artifact_creator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ module AiBot
55
module Personas
66
class WebArtifactCreator < Persona
77
def tools
8-
[Tools::CreateArtifact]
8+
[Tools::CreateArtifact, Tools::UpdateArtifact]
99
end
1010

1111
def required_tools
12-
[Tools::CreateArtifact]
12+
[Tools::CreateArtifact, Tools::UpdateArtifact]
1313
end
1414

1515
def system_prompt
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module AiBot
5+
module Tools
6+
class UpdateArtifact < Tool
7+
def self.name
8+
"update_artifact"
9+
end
10+
11+
def self.unified_diff_tip
12+
<<~TIP
13+
When updating and artifact provied unified diffs, for example:
14+
15+
If editing:
16+
17+
<div>
18+
<p>Some text</p>
19+
</div>
20+
21+
You can provide a diff like:
22+
23+
<div>
24+
- <p>Some text</p>
25+
+ <p>Some new text</p>
26+
</div>
27+
28+
This will result in:
29+
30+
<div>
31+
<p>Some new text</p>
32+
</div>
33+
34+
If you need to supply multiple hunks for a diff use the @@@ separator between hunks.
35+
TIP
36+
end
37+
38+
def self.signature
39+
{
40+
name: "update_artifact",
41+
description:
42+
"Updates an existing web artifact with new HTML, CSS, or JavaScript content.\n#{unified_diff_tip}",
43+
parameters: [
44+
{
45+
name: "artifact_id",
46+
description: "The ID of the artifact to update",
47+
type: "integer",
48+
required: true,
49+
},
50+
{
51+
name: "html_diff",
52+
description: "A unified diff of HTML changes to apply",
53+
type: "string",
54+
},
55+
{
56+
name: "css_diff",
57+
description: "A unified diff of CSS changes to apply",
58+
type: "string",
59+
},
60+
{
61+
name: "js_diff",
62+
description: "A unified diff of JavaScript changes to apply",
63+
type: "string",
64+
},
65+
{
66+
name: "change_description",
67+
description: "A brief description of the changes being made",
68+
type: "string",
69+
required: true,
70+
},
71+
],
72+
}
73+
end
74+
75+
def self.allow_partial_tool_calls?
76+
true
77+
end
78+
79+
def partial_invoke
80+
@selected_tab = :html_diff
81+
if @prev_parameters
82+
@selected_tab = parameters.keys.find { |k| @prev_parameters[k] != parameters[k] }
83+
end
84+
update_custom_html
85+
@prev_parameters = parameters.dup
86+
end
87+
88+
def invoke
89+
yield "Updating Artifact"
90+
91+
post = Post.find_by(id: context[:post_id])
92+
return error_response("No post context found") unless post
93+
94+
artifact = AiArtifact.find_by(id: parameters[:artifact_id])
95+
return error_response("Artifact not found") unless artifact
96+
97+
if artifact.post.topic.id != post.topic.id
98+
return error_response("Attempting to update an artifact you are not allowed to")
99+
end
100+
101+
begin
102+
artifact.apply_diff(
103+
html_diff: parameters[:html_diff],
104+
css_diff: parameters[:css_diff],
105+
js_diff: parameters[:js_diff],
106+
change_description: parameters[:change_description],
107+
)
108+
109+
update_custom_html(artifact)
110+
success_response(artifact)
111+
rescue DiscourseAi::Utils::DiffUtils::DiffError => e
112+
error_response(e.to_llm_message)
113+
rescue => e
114+
error_response(e.message)
115+
end
116+
end
117+
118+
private
119+
120+
def update_custom_html(artifact = nil)
121+
content = []
122+
123+
if parameters[:html_diff].present?
124+
content << [:html_diff, "### HTML Changes\n\n```diff\n#{parameters[:html_diff]}\n```"]
125+
end
126+
127+
if parameters[:css_diff].present?
128+
content << [:css_diff, "### CSS Changes\n\n```diff\n#{parameters[:css_diff]}\n```"]
129+
end
130+
131+
if parameters[:js_diff].present?
132+
content << [:js_diff, "### JavaScript Changes\n\n```diff\n#{parameters[:js_diff]}\n```"]
133+
end
134+
135+
if parameters[:change_description].present?
136+
content.unshift(
137+
[:description, "### Change Description\n\n#{parameters[:change_description]}"],
138+
)
139+
end
140+
141+
content.sort_by! { |c| c[0] === @selected_tab ? 1 : 0 } if !artifact
142+
143+
if artifact
144+
content.unshift([nil, "[details='#{I18n.t("discourse_ai.ai_artifact.view_changes")}']"])
145+
content << [nil, "[/details]"]
146+
content << [
147+
:preview,
148+
"### Preview\n\n<div class=\"ai-artifact\" data-ai-artifact-id=\"#{artifact.id}\"></div>",
149+
]
150+
end
151+
152+
self.custom_raw = content.map { |c| c[1] }.join("\n\n")
153+
end
154+
155+
def success_response(artifact)
156+
@chain_next_response = false
157+
158+
{
159+
status: "success",
160+
artifact_id: artifact.id,
161+
version: artifact.versions.last.version_number,
162+
message: "Artifact updated successfully and rendered to user.",
163+
}
164+
end
165+
166+
def error_response(message)
167+
@chain_next_response = false
168+
169+
{ status: "error", error: message }
170+
end
171+
172+
def help
173+
"Updates an existing web artifact with changes to its HTML, CSS, or JavaScript content. " \
174+
"Requires the artifact ID and at least one change diff. " \
175+
"Changes are applied using unified diff format. " \
176+
"A description of the changes is required for version history."
177+
end
178+
end
179+
end
180+
end
181+
end

0 commit comments

Comments
 (0)