mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-06-26 09:32:40 +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>
|
<style>
|
||||||
#{artifact.css}
|
#{artifact.css}
|
||||||
</style>
|
</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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
#{artifact.html}
|
#{artifact.html}
|
||||||
@ -74,6 +89,19 @@ module DiscourseAi
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<iframe sandbox="allow-scripts allow-forms" height="100%" width="100%" srcdoc="#{ERB::Util.html_escape(untrusted_html)}" frameborder="0"></iframe>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
HTML
|
HTML
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import DButton from "discourse/components/d-button";
|
import DButton from "discourse/components/d-button";
|
||||||
import htmlClass from "discourse/helpers/html-class";
|
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>
|
<template>
|
||||||
{{#if this.expanded}}
|
{{#if this.expanded}}
|
||||||
{{htmlClass "ai-artifact-expanded"}}
|
{{htmlClass "ai-artifact-expanded"}}
|
||||||
@ -118,6 +128,7 @@ export default class AiArtifactComponent extends Component {
|
|||||||
src={{this.artifactUrl}}
|
src={{this.artifactUrl}}
|
||||||
width="100%"
|
width="100%"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
|
{{didInsert this.setDataAttributes}}
|
||||||
></iframe>
|
></iframe>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless this.requireClickToRun}}
|
{{#unless this.requireClickToRun}}
|
||||||
|
@ -18,12 +18,24 @@ function initializeAiArtifacts(api) {
|
|||||||
"data-ai-artifact-version"
|
"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(
|
helper.renderGlimmer(
|
||||||
artifactElement,
|
artifactElement,
|
||||||
<template>
|
<template>
|
||||||
<AiArtifact
|
<AiArtifact
|
||||||
@artifactId={{artifactId}}
|
@artifactId={{artifactId}}
|
||||||
@artifactVersion={{artifactVersion}}
|
@artifactVersion={{artifactVersion}}
|
||||||
|
@dataAttributes={{dataAttributes}}
|
||||||
/>
|
/>
|
||||||
</template>
|
</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