mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-06-29 11:02:17 +00:00
FEATURE: hybrid artifact security mode (#1431)
In hybrid mode ai artifacts can optionally automatically run. This is useful for cases where you may want to embed a survey and so on. Additionally, artifacts now allow for better fidelity around display: <div class="ai-artifact" data-ai-artifact-id="501" data-ai-artifact-height="300px" data-ai-artifact-autorun data-ai-artifact-seamless></div> User can supply height and seamless mode to be seamlessly rendered with no box shadow and show full screen button.
This commit is contained in:
parent
b5a2ee31ab
commit
02bc9f645e
@ -335,7 +335,7 @@ module DiscourseAi
|
|||||||
|
|
||||||
def require_site_settings!
|
def require_site_settings!
|
||||||
if !SiteSetting.discourse_ai_enabled ||
|
if !SiteSetting.discourse_ai_enabled ||
|
||||||
!SiteSetting.ai_artifact_security.in?(%w[lax strict])
|
!SiteSetting.ai_artifact_security.in?(%w[lax hybrid strict])
|
||||||
raise Discourse::NotFound
|
raise Discourse::NotFound
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -178,7 +178,7 @@ class SharedAiConversation < ActiveRecord::Base
|
|||||||
|
|
||||||
def self.cook_artifacts(post)
|
def self.cook_artifacts(post)
|
||||||
html = post.cooked
|
html = post.cooked
|
||||||
return html if !%w[lax strict].include?(SiteSetting.ai_artifact_security)
|
return html if !%w[lax hybrid strict].include?(SiteSetting.ai_artifact_security)
|
||||||
|
|
||||||
doc = Nokogiri::HTML5.fragment(html)
|
doc = Nokogiri::HTML5.fragment(html)
|
||||||
doc
|
doc
|
||||||
|
@ -3,6 +3,7 @@ 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 didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
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";
|
||||||
import getURL from "discourse/lib/get-url";
|
import getURL from "discourse/lib/get-url";
|
||||||
@ -51,7 +52,21 @@ export default class AiArtifactComponent extends Component {
|
|||||||
if (this.showingArtifact) {
|
if (this.showingArtifact) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.siteSettings.ai_artifact_security === "strict";
|
|
||||||
|
if (this.siteSettings.ai_artifact_security === "strict") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.siteSettings.ai_artifact_security === "hybrid") {
|
||||||
|
const shouldAutorun =
|
||||||
|
this.args.autorun === "true" ||
|
||||||
|
this.args.autorun === true ||
|
||||||
|
this.args.autorun === "1";
|
||||||
|
|
||||||
|
return !shouldAutorun;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.siteSettings.ai_artifact_security !== "lax";
|
||||||
}
|
}
|
||||||
|
|
||||||
get artifactUrl() {
|
get artifactUrl() {
|
||||||
@ -86,7 +101,7 @@ export default class AiArtifactComponent extends Component {
|
|||||||
get wrapperClasses() {
|
get wrapperClasses() {
|
||||||
return `ai-artifact__wrapper ${
|
return `ai-artifact__wrapper ${
|
||||||
this.expanded ? "ai-artifact__expanded" : ""
|
this.expanded ? "ai-artifact__expanded" : ""
|
||||||
}`;
|
} ${this.seamless ? "ai-artifact__seamless" : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -98,11 +113,38 @@ export default class AiArtifactComponent extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get heightStyle() {
|
||||||
|
if (this.args.artifactHeight) {
|
||||||
|
let height = parseInt(this.args.artifactHeight, 10);
|
||||||
|
if (isNaN(height) || height <= 0) {
|
||||||
|
height = 500; // default height if the provided value is invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height > 2000) {
|
||||||
|
height = 2000; // cap the height to a maximum of 2000px
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlSafe(`height: ${height}px;`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get seamless() {
|
||||||
|
return (
|
||||||
|
this.args.seamless === "true" ||
|
||||||
|
this.args.seamless === true ||
|
||||||
|
this.args.seamless === "1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showFooter() {
|
||||||
|
return !this.seamless && !this.requireClickToRun;
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{#if this.expanded}}
|
{{#if this.expanded}}
|
||||||
{{htmlClass "ai-artifact-expanded"}}
|
{{htmlClass "ai-artifact-expanded"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class={{this.wrapperClasses}}>
|
<div class={{this.wrapperClasses}} style={{this.heightStyle}}>
|
||||||
<div class="ai-artifact__panel--wrapper">
|
<div class="ai-artifact__panel--wrapper">
|
||||||
<div class="ai-artifact__panel">
|
<div class="ai-artifact__panel">
|
||||||
<DButton
|
<DButton
|
||||||
@ -131,7 +173,7 @@ export default class AiArtifactComponent extends Component {
|
|||||||
{{didInsert this.setDataAttributes}}
|
{{didInsert this.setDataAttributes}}
|
||||||
></iframe>
|
></iframe>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless this.requireClickToRun}}
|
{{#if this.showFooter}}
|
||||||
<div class="ai-artifact__footer">
|
<div class="ai-artifact__footer">
|
||||||
<DButton
|
<DButton
|
||||||
class="btn-transparent btn-icon-text ai-artifact__expand-button"
|
class="btn-transparent btn-icon-text ai-artifact__expand-button"
|
||||||
@ -140,7 +182,7 @@ export default class AiArtifactComponent extends Component {
|
|||||||
@action={{this.toggleView}}
|
@action={{this.toggleView}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,27 @@ function initializeAiArtifacts(api) {
|
|||||||
"data-ai-artifact-version"
|
"data-ai-artifact-version"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const artifactHeight = artifactElement.getAttribute(
|
||||||
|
"data-ai-artifact-height"
|
||||||
|
);
|
||||||
|
|
||||||
|
const autorun =
|
||||||
|
artifactElement.getAttribute("data-ai-artifact-autorun") ||
|
||||||
|
artifactElement.hasAttribute("data-ai-artifact-autorun");
|
||||||
|
|
||||||
|
const seamless =
|
||||||
|
artifactElement.getAttribute("data-ai-artifact-seamless") ||
|
||||||
|
artifactElement.hasAttribute("data-ai-artifact-seamless");
|
||||||
|
|
||||||
const dataAttributes = {};
|
const dataAttributes = {};
|
||||||
for (const attr of artifactElement.attributes) {
|
for (const attr of artifactElement.attributes) {
|
||||||
if (
|
if (
|
||||||
attr.name.startsWith("data-") &&
|
attr.name.startsWith("data-") &&
|
||||||
attr.name !== "data-ai-artifact-id" &&
|
attr.name !== "data-ai-artifact-id" &&
|
||||||
attr.name !== "data-ai-artifact-version"
|
attr.name !== "data-ai-artifact-version" &&
|
||||||
|
attr.name !== "data-ai-artifact-height" &&
|
||||||
|
attr.name !== "data-ai-artifact-autorun" &&
|
||||||
|
attr.name !== "data-ai-artifact-seamless"
|
||||||
) {
|
) {
|
||||||
dataAttributes[attr.name] = attr.value;
|
dataAttributes[attr.name] = attr.value;
|
||||||
}
|
}
|
||||||
@ -35,6 +50,9 @@ function initializeAiArtifacts(api) {
|
|||||||
<AiArtifact
|
<AiArtifact
|
||||||
@artifactId={{artifactId}}
|
@artifactId={{artifactId}}
|
||||||
@artifactVersion={{artifactVersion}}
|
@artifactVersion={{artifactVersion}}
|
||||||
|
@artifactHeight={{artifactHeight}}
|
||||||
|
@autorun={{autorun}}
|
||||||
|
@seamless={{seamless}}
|
||||||
@dataAttributes={{dataAttributes}}
|
@dataAttributes={{dataAttributes}}
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -4,5 +4,8 @@ export function setup(helper) {
|
|||||||
"div[class=ai-artifact]",
|
"div[class=ai-artifact]",
|
||||||
"div[data-ai-artifact-id]",
|
"div[data-ai-artifact-id]",
|
||||||
"div[data-ai-artifact-version]",
|
"div[data-ai-artifact-version]",
|
||||||
|
"div[data-ai-artifact-autorun]",
|
||||||
|
"div[data-ai-artifact-height]",
|
||||||
|
"div[data-ai-artifact-width]",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,15 @@
|
|||||||
height: calc(100% - 2em);
|
height: calc(100% - 2em);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.ai-artifact__expanded) {
|
&.ai-artifact__seamless {
|
||||||
|
padding-bottom: 1em;
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.ai-artifact__expanded, .ai-artifact__seamless) {
|
||||||
iframe {
|
iframe {
|
||||||
box-shadow: var(--shadow-card);
|
box-shadow: var(--shadow-card);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ en:
|
|||||||
description: "Periodic report based on a large language model"
|
description: "Periodic report based on a large language model"
|
||||||
site_settings:
|
site_settings:
|
||||||
discourse_ai_enabled: "Enable the discourse AI plugin."
|
discourse_ai_enabled: "Enable the discourse AI plugin."
|
||||||
ai_artifact_security: "The AI artifact system generates IFRAMEs with runnable code. Strict mode disables sharing and forces an extra click to run code. Lax mode allows sharing of artifacts and runs code directly. Disabled mode disables the artifact system."
|
ai_artifact_security: "The AI artifact system generates IFRAMEs with runnable code. Strict mode forces an extra click to run code. Lax mode runs code immediately. Hybrid mode allows user to supply data-ai-artifact-autorun to show right away. Disabled mode disables the artifact system."
|
||||||
ai_toxicity_enabled: "Enable the toxicity module."
|
ai_toxicity_enabled: "Enable the toxicity module."
|
||||||
ai_toxicity_inference_service_api_endpoint: "URL where the API is running for the toxicity module"
|
ai_toxicity_inference_service_api_endpoint: "URL where the API is running for the toxicity module"
|
||||||
ai_toxicity_inference_service_api_key: "API key for the toxicity API"
|
ai_toxicity_inference_service_api_key: "API key for the toxicity API"
|
||||||
|
@ -9,6 +9,7 @@ discourse_ai:
|
|||||||
choices:
|
choices:
|
||||||
- "disabled"
|
- "disabled"
|
||||||
- "lax"
|
- "lax"
|
||||||
|
- "hybrid"
|
||||||
- "strict"
|
- "strict"
|
||||||
|
|
||||||
ai_sentiment_enabled:
|
ai_sentiment_enabled:
|
||||||
|
@ -119,7 +119,7 @@ module DiscourseAi
|
|||||||
Tools::Researcher,
|
Tools::Researcher,
|
||||||
]
|
]
|
||||||
|
|
||||||
if SiteSetting.ai_artifact_security.in?(%w[lax strict])
|
if SiteSetting.ai_artifact_security.in?(%w[lax hybrid strict])
|
||||||
tools << Tools::CreateArtifact
|
tools << Tools::CreateArtifact
|
||||||
tools << Tools::UpdateArtifact
|
tools << Tools::UpdateArtifact
|
||||||
tools << Tools::ReadArtifact
|
tools << Tools::ReadArtifact
|
||||||
|
Loading…
x
Reference in New Issue
Block a user