discourse-ai/assets/javascripts/discourse/components/ai-artifact.gjs

123 lines
3.3 KiB
Plaintext
Raw Normal View History

FEATURE: AI artifacts (#898) This is a significant PR that introduces AI Artifacts functionality to the discourse-ai plugin along with several other improvements. Here are the key changes: 1. AI Artifacts System: - Adds a new `AiArtifact` model and database migration - Allows creation of web artifacts with HTML, CSS, and JavaScript content - Introduces security settings (`strict`, `lax`, `disabled`) for controlling artifact execution - Implements artifact rendering in iframes with sandbox protection - New `CreateArtifact` tool for AI to generate interactive content 2. Tool System Improvements: - Adds support for partial tool calls, allowing incremental updates during generation - Better handling of tool call states and progress tracking - Improved XML tool processing with CDATA support - Fixes for tool parameter handling and duplicate invocations 3. LLM Provider Updates: - Updates for Anthropic Claude models with correct token limits - Adds support for native/XML tool modes in Gemini integration - Adds new model configurations including Llama 3.1 models - Improvements to streaming response handling 4. UI Enhancements: - New artifact viewer component with expand/collapse functionality - Security controls for artifact execution (click-to-run in strict mode) - Improved dialog and response handling - Better error management for tool execution 5. Security Improvements: - Sandbox controls for artifact execution - Public/private artifact sharing controls - Security settings to control artifact behavior - CSP and frame-options handling for artifacts 6. Technical Improvements: - Better post streaming implementation - Improved error handling in completions - Better memory management for partial tool calls - Enhanced testing coverage 7. Configuration: - New site settings for artifact security - Extended LLM model configurations - Additional tool configuration options This PR significantly enhances the plugin's capabilities for generating and displaying interactive content while maintaining security and providing flexible configuration options for administrators.
2024-11-18 17:22:39 -05:00
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import htmlClass from "discourse/helpers/html-class";
import getURL from "discourse-common/lib/get-url";
export default class AiArtifactComponent extends Component {
@service siteSettings;
@tracked expanded = false;
@tracked showingArtifact = false;
constructor() {
super(...arguments);
this.keydownHandler = this.handleKeydown.bind(this);
}
willDestroy() {
super.willDestroy(...arguments);
window.removeEventListener("keydown", this.keydownHandler);
}
@action
handleKeydown(event) {
if (event.key === "Escape" || event.key === "Esc") {
this.expanded = false;
}
}
get requireClickToRun() {
if (this.showingArtifact) {
return false;
}
return this.siteSettings.ai_artifact_security === "strict";
}
get artifactUrl() {
return getURL(`/discourse-ai/ai-bot/artifacts/${this.args.artifactId}`);
}
@action
showArtifact() {
this.showingArtifact = true;
}
@action
toggleView() {
this.expanded = !this.expanded;
if (this.expanded) {
window.addEventListener("keydown", this.keydownHandler);
} else {
window.removeEventListener("keydown", this.keydownHandler);
}
}
get wrapperClasses() {
return `ai-artifact__wrapper ${
this.expanded ? "ai-artifact__expanded" : ""
}`;
}
@action
artifactPanelHover() {
// retrrigger animation
const panel = document.querySelector(".ai-artifact__panel");
panel.style.animation = "none"; // Stop the animation
setTimeout(() => {
panel.style.animation = ""; // Re-trigger the animation by removing the none style
}, 0);
}
<template>
{{#if this.expanded}}
{{htmlClass "ai-artifact-expanded"}}
{{/if}}
<div class={{this.wrapperClasses}}>
<div
class="ai-artifact__panel--wrapper"
{{on "mouseleave" this.artifactPanelHover}}
>
<div class="ai-artifact__panel">
<DButton
class="btn-flat btn-icon-text"
@icon="discourse-compress"
@label="discourse_ai.ai_artifact.collapse_view_label"
@action={{this.toggleView}}
/>
</div>
</div>
{{#if this.requireClickToRun}}
<div class="ai-artifact__click-to-run">
<DButton
class="btn btn-primary"
@icon="play"
@label="discourse_ai.ai_artifact.click_to_run_label"
@action={{this.showArtifact}}
/>
</div>
{{else}}
<iframe
title="AI Artifact"
src={{this.artifactUrl}}
width="100%"
frameborder="0"
sandbox="allow-scripts allow-forms"
></iframe>
{{/if}}
{{#unless this.requireClickToRun}}
<div class="ai-artifact__footer">
<DButton
class="btn-flat btn-icon-text ai-artifact__expand-button"
@icon="discourse-expand"
@label="discourse_ai.ai_artifact.expand_view_label"
@action={{this.toggleView}}
/>
</div>
{{/unless}}
</div>
</template>
}