mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-06-26 17:42:15 +00:00
FEATURE: allow passing in data attributes to an artifact (#1346)
Also allow artifact access to current username Usage inside artifact is: 1. await window.discourseArtifactReady; 2. access data via window.discourseArtifactData;
This commit is contained in:
parent
925949de47
commit
3ac2359ff1
@ -42,6 +42,21 @@ module DiscourseAi
|
||||
<style>
|
||||
#{artifact.css}
|
||||
</style>
|
||||
<script>
|
||||
window._discourse_user_data = {
|
||||
#{current_user ? "username: #{current_user.username.to_json}" : "username: null"}
|
||||
};
|
||||
window.discourseArtifactReady = new Promise(resolve => {
|
||||
window._resolveArtifactData = resolve;
|
||||
});
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.data && event.data.type === 'discourse-artifact-data') {
|
||||
window.discourseArtifactData = event.data.dataset || {};
|
||||
Object.assign(window.discourseArtifactData, window._discourse_user_data);
|
||||
window._resolveArtifactData(window.discourseArtifactData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
#{artifact.html}
|
||||
@ -74,6 +89,19 @@ module DiscourseAi
|
||||
</head>
|
||||
<body>
|
||||
<iframe sandbox="allow-scripts allow-forms" height="100%" width="100%" srcdoc="#{ERB::Util.html_escape(untrusted_html)}" frameborder="0"></iframe>
|
||||
<script>
|
||||
document.querySelector('iframe').addEventListener('load', function() {
|
||||
try {
|
||||
const iframeWindow = this.contentWindow;
|
||||
const message = { type: 'discourse-artifact-data', dataset: {} };
|
||||
|
||||
if (window.frameElement && window.frameElement.dataset) {
|
||||
Object.assign(message.dataset, window.frameElement.dataset);
|
||||
}
|
||||
iframeWindow.postMessage(message, '*');
|
||||
} catch (e) { console.error('Error passing data to artifact:', e); }
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import { service } from "@ember/service";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import htmlClass from "discourse/helpers/html-class";
|
||||
@ -88,6 +89,15 @@ export default class AiArtifactComponent extends Component {
|
||||
}`;
|
||||
}
|
||||
|
||||
@action
|
||||
setDataAttributes(element) {
|
||||
if (this.args.dataAttributes) {
|
||||
Object.entries(this.args.dataAttributes).forEach(([key, value]) => {
|
||||
element.setAttribute(key, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.expanded}}
|
||||
{{htmlClass "ai-artifact-expanded"}}
|
||||
@ -118,6 +128,7 @@ export default class AiArtifactComponent extends Component {
|
||||
src={{this.artifactUrl}}
|
||||
width="100%"
|
||||
frameborder="0"
|
||||
{{didInsert this.setDataAttributes}}
|
||||
></iframe>
|
||||
{{/if}}
|
||||
{{#unless this.requireClickToRun}}
|
||||
|
@ -18,12 +18,24 @@ function initializeAiArtifacts(api) {
|
||||
"data-ai-artifact-version"
|
||||
);
|
||||
|
||||
const dataAttributes = {};
|
||||
for (const attr of artifactElement.attributes) {
|
||||
if (
|
||||
attr.name.startsWith("data-") &&
|
||||
attr.name !== "data-ai-artifact-id" &&
|
||||
attr.name !== "data-ai-artifact-version"
|
||||
) {
|
||||
dataAttributes[attr.name] = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
helper.renderGlimmer(
|
||||
artifactElement,
|
||||
<template>
|
||||
<AiArtifact
|
||||
@artifactId={{artifactId}}
|
||||
@artifactVersion={{artifactVersion}}
|
||||
@dataAttributes={{dataAttributes}}
|
||||
/>
|
||||
</template>
|
||||
);
|
||||
|
79
spec/system/ai_bot/artifact_spec.rb
Normal file
79
spec/system/ai_bot/artifact_spec.rb
Normal file
@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe "AI Artifact with Data Attributes", type: :system do
|
||||
fab!(:admin)
|
||||
fab!(:user)
|
||||
fab!(:author) { Fabricate(:user) }
|
||||
fab!(:category) { Fabricate(:category, user: admin, read_restricted: false) }
|
||||
fab!(:topic) { Fabricate(:topic, category: category, user: author) }
|
||||
fab!(:post) { Fabricate(:post, topic: topic, user: author) }
|
||||
|
||||
before { SiteSetting.discourse_ai_enabled = true }
|
||||
|
||||
it "correctly passes data attributes and user info to a public AI artifact embedded in a post" do
|
||||
artifact_js = <<~JS
|
||||
window.discourseArtifactReady.then(data => {
|
||||
const displayElement = document.getElementById('data-display');
|
||||
if (displayElement) {
|
||||
displayElement.innerText = JSON.stringify(data);
|
||||
}
|
||||
}).catch(err => {
|
||||
const displayElement = document.getElementById('data-display');
|
||||
if (displayElement) {
|
||||
displayElement.innerText = 'Error: ' + err.message;
|
||||
}
|
||||
console.error("Artifact JS Error:", err);
|
||||
});
|
||||
JS
|
||||
|
||||
ai_artifact =
|
||||
Fabricate(
|
||||
:ai_artifact,
|
||||
user: author,
|
||||
name: "Data Passing Test Artifact",
|
||||
html: "<div id='data-display'>Waiting for data...</div>",
|
||||
js: artifact_js.strip,
|
||||
metadata: {
|
||||
public: true,
|
||||
},
|
||||
)
|
||||
|
||||
raw_post_content =
|
||||
"<div class='ai-artifact' data-ai-artifact-id='#{ai_artifact.id}' data-custom-message='hello-from-post' data-post-author-id='#{author.id}'></div>"
|
||||
_post = Fabricate(:post, topic: topic, user: author, raw: raw_post_content)
|
||||
|
||||
sign_in(user)
|
||||
visit "/t/#{topic.slug}/#{topic.id}"
|
||||
|
||||
find(".ai-artifact__click-to-run button").click
|
||||
|
||||
artifact_element_selector = ".ai-artifact[data-ai-artifact-id='#{ai_artifact.id}']"
|
||||
iframe_selector = "#{artifact_element_selector} iframe"
|
||||
|
||||
expect(page).to have_css(iframe_selector)
|
||||
|
||||
iframe_element = find(iframe_selector)
|
||||
expect(iframe_element["data-custom-message"]).to eq("hello-from-post")
|
||||
expect(iframe_element["data-post-author-id"]).to eq(author.id.to_s)
|
||||
|
||||
# note: artifacts are within nested iframes for security reasons
|
||||
page.within_frame(iframe_element) do
|
||||
inner_iframe = find("iframe")
|
||||
page.within_frame(inner_iframe) do
|
||||
data_display_element = find("#data-display")
|
||||
|
||||
expect(data_display_element.text).not_to be_empty
|
||||
expect(data_display_element.text).not_to eq("Waiting for data...")
|
||||
expect(data_display_element.text).not_to include("Error:")
|
||||
|
||||
artifact_data_json = data_display_element.text
|
||||
artifact_data = JSON.parse(artifact_data_json)
|
||||
|
||||
expect(artifact_data["customMessage"]).to eq("hello-from-post")
|
||||
expect(artifact_data["postAuthorId"]).to eq(author.id.to_s)
|
||||
|
||||
expect(artifact_data["username"]).to eq(user.username)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user