mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-02-12 22:44:45 +00:00
1. Keep source in a "details" block after rendered so it does not overwhelm users 2. Ensure artifacts are never indexed by robots 3. Cache break our CSS that changed recently
142 lines
4.2 KiB
Ruby
142 lines
4.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module AiBot
|
|
module Tools
|
|
class CreateArtifact < Tool
|
|
def self.name
|
|
"create_artifact"
|
|
end
|
|
|
|
def self.signature
|
|
{
|
|
name: "create_artifact",
|
|
description:
|
|
"Creates a web artifact with HTML, CSS, and JavaScript that can be displayed in an iframe",
|
|
parameters: [
|
|
{
|
|
name: "name",
|
|
description: "A name for the artifact (max 255 chars)",
|
|
type: "string",
|
|
required: true,
|
|
},
|
|
{
|
|
name: "html_body",
|
|
description: "The HTML content for the BODY tag (do not include the BODY tag)",
|
|
type: "string",
|
|
required: true,
|
|
},
|
|
{ name: "css", description: "Optional CSS styles for the artifact", type: "string" },
|
|
{
|
|
name: "js",
|
|
description:
|
|
"Optional
|
|
JavaScript code for the artifact",
|
|
type: "string",
|
|
},
|
|
],
|
|
}
|
|
end
|
|
|
|
def self.allow_partial_tool_calls?
|
|
true
|
|
end
|
|
|
|
def partial_invoke
|
|
@selected_tab = :html_body
|
|
if @prev_parameters
|
|
@selected_tab = parameters.keys.find { |k| @prev_parameters[k] != parameters[k] }
|
|
end
|
|
update_custom_html
|
|
@prev_parameters = parameters.dup
|
|
end
|
|
|
|
def invoke
|
|
yield parameters[:name] || "Web Artifact"
|
|
# Get the current post from context
|
|
post = Post.find_by(id: context[:post_id])
|
|
return error_response("No post context found") unless post
|
|
|
|
html = parameters[:html_body].to_s
|
|
css = parameters[:css].to_s
|
|
js = parameters[:js].to_s
|
|
|
|
# Create the artifact
|
|
artifact =
|
|
AiArtifact.new(
|
|
user_id: bot_user.id,
|
|
post_id: post.id,
|
|
name: parameters[:name].to_s[0...255],
|
|
html: html,
|
|
css: css,
|
|
js: js,
|
|
metadata: parameters[:metadata],
|
|
)
|
|
|
|
if artifact.save
|
|
update_custom_html(artifact)
|
|
success_response(artifact)
|
|
else
|
|
error_response(artifact.errors.full_messages.join(", "))
|
|
end
|
|
end
|
|
|
|
def chain_next_response?
|
|
@chain_next_response
|
|
end
|
|
|
|
private
|
|
|
|
def update_custom_html(artifact = nil)
|
|
html = parameters[:html_body].to_s
|
|
css = parameters[:css].to_s
|
|
js = parameters[:js].to_s
|
|
|
|
artifact_div =
|
|
"<div class=\"ai-artifact\" data-ai-artifact-id=\"#{artifact.id}\"></div>" if artifact
|
|
|
|
content = []
|
|
|
|
content << [:html_body, "### HTML\n\n```html\n#{html}\n```"] if html.present?
|
|
|
|
content << [:css, "### CSS\n\n```css\n#{css}\n```"] if css.present?
|
|
|
|
content << [:js, "### JavaScript\n\n```javascript\n#{js}\n```"] if js.present?
|
|
|
|
content.sort_by! { |c| c[0] === @selected_tab ? 1 : 0 } if !artifact
|
|
|
|
if artifact
|
|
content.unshift([nil, "[details='#{I18n.t("discourse_ai.ai_artifact.view_source")}']"])
|
|
content << [nil, "[/details]"]
|
|
end
|
|
|
|
content << [:preview, "### Preview\n\n#{artifact_div}"] if artifact_div
|
|
self.custom_raw = content.map { |c| c[1] }.join("\n\n")
|
|
end
|
|
|
|
def success_response(artifact)
|
|
@chain_next_response = false
|
|
|
|
{
|
|
status: "success",
|
|
artifact_id: artifact.id,
|
|
message: "Artifact created successfully and rendered to user.",
|
|
}
|
|
end
|
|
|
|
def error_response(message)
|
|
@chain_next_response = false
|
|
|
|
{ status: "error", error: message }
|
|
end
|
|
|
|
def help
|
|
"Creates a web artifact with HTML, CSS, and JavaScript that can be displayed in an iframe. " \
|
|
"Requires a name and HTML content. CSS and JavaScript are optional. " \
|
|
"The artifact will be associated with the current post and can be displayed using an iframe."
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|