FEATURE: persistent key-value storage for AI Artifacts (#1417)
Introduces a persistent, user-scoped key-value storage system for
AI Artifacts, enabling them to be stateful and interactive. This
transforms artifacts from static content into mini-applications that can
save user input, preferences, and other data.
The core components of this feature are:
1. **Model and API**:
- A new `AiArtifactKeyValue` model and corresponding database table to
store data associated with a user and an artifact.
- A new `ArtifactKeyValuesController` provides a RESTful API for
CRUD operations (`index`, `set`, `destroy`) on the key-value data.
- Permissions are enforced: users can only modify their own data but
can view public data from other users.
2. **Secure JavaScript Bridge**:
- A `postMessage` communication bridge is established between the
sandboxed artifact `iframe` and the parent Discourse window.
- A JavaScript API is exposed to the artifact as `window.discourseArtifact`
with async methods: `get(key)`, `set(key, value, options)`,
`delete(key)`, and `index(filter)`.
- The parent window handles these requests, makes authenticated calls to the
new controller, and returns the results to the iframe. This ensures
security by keeping untrusted JS isolated.
3. **AI Tool Integration**:
- The `create_artifact` tool is updated with a `requires_storage`
boolean parameter.
- If an artifact requires storage, its metadata is flagged, and the
system prompt for the code-generating AI is augmented with detailed
documentation for the new storage API.
4. **Configuration**:
- Adds hidden site settings `ai_artifact_kv_value_max_length` and
`ai_artifact_max_keys_per_user_per_artifact` for throttling.
This also includes a minor fix to use `jsonb_set` when updating
artifact metadata, ensuring other metadata fields are preserved.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
@@ -88,6 +89,33 @@ export default class ShareModal extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
async maybeCopyEmbed(event) {
|
||||
if (!event.target.classList.contains("copy-embed")) {
|
||||
return true;
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
let version = "";
|
||||
if (event.target.dataset.artifactVersion) {
|
||||
version = `data-ai-artifact-version="${event.target.dataset.artifactVersion}"`;
|
||||
}
|
||||
const artifactEmbed = `<div class="ai-artifact" ${version} data-ai-artifact-id="${event.target.dataset.artifactId}"></div>`;
|
||||
const promise = new Promise((resolve) => {
|
||||
resolve(artifactEmbed);
|
||||
});
|
||||
|
||||
await clipboardCopyAsync(() => promise);
|
||||
|
||||
this.toasts.success({
|
||||
duration: 3000,
|
||||
data: {
|
||||
message: i18n("discourse_ai.ai_bot.embed_copied"),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
<template>
|
||||
<DModal
|
||||
class="ai-share-full-topic-modal"
|
||||
@@ -95,9 +123,14 @@ export default class ShareModal extends Component {
|
||||
@closeModal={{@closeModal}}
|
||||
>
|
||||
<:body>
|
||||
<div class="ai-share-full-topic-modal__body">
|
||||
{{! template-lint-disable no-invalid-interactive }}
|
||||
<div
|
||||
class="ai-share-full-topic-modal__body"
|
||||
{{on "click" this.maybeCopyEmbed}}
|
||||
>
|
||||
{{this.htmlContext}}
|
||||
</div>
|
||||
{{! template-lint-enable}}
|
||||
</:body>
|
||||
|
||||
<:footer>
|
||||
|
||||
Reference in New Issue
Block a user