don't blow up metadata when sharing an artifact
This commit is contained in:
parent
6e4222c1c6
commit
75e667423f
|
@ -38,7 +38,11 @@ class AiArtifact < ActiveRecord::Base
|
|||
|
||||
def self.share_publicly(id:, post:)
|
||||
artifact = AiArtifact.find_by(id: id)
|
||||
artifact.update!(metadata: { public: true }) if artifact&.post&.topic&.id == post.topic.id
|
||||
if artifact&.post&.topic&.id == post.topic.id
|
||||
artifact.metadata ||= {}
|
||||
artifact.metadata[:public] = true
|
||||
artifact.save!
|
||||
end
|
||||
end
|
||||
|
||||
def self.unshare_publicly(id:)
|
||||
|
|
|
@ -37,7 +37,9 @@ class SharedAiConversation < ActiveRecord::Base
|
|||
|
||||
maybe_topic = conversation.target
|
||||
if maybe_topic.is_a?(Topic)
|
||||
AiArtifact.where(post: maybe_topic.posts).update_all(metadata: { public: false })
|
||||
AiArtifact.where(post: maybe_topic.posts).update_all(
|
||||
"metadata = jsonb_set(COALESCE(metadata, '{}'), '{public}', 'false')",
|
||||
)
|
||||
end
|
||||
|
||||
::Jobs.enqueue(
|
||||
|
|
|
@ -64,6 +64,13 @@ module DiscourseAi
|
|||
description: specification_description,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "requires_storage",
|
||||
description:
|
||||
"Does the artifact require storage for data? (e.g., user input, settings)",
|
||||
type: "boolean",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
@ -223,6 +230,7 @@ module DiscourseAi
|
|||
js: code[:js],
|
||||
metadata: {
|
||||
specification: parameters[:specification],
|
||||
requires_storage: !!parameters[:requires_storage],
|
||||
},
|
||||
)
|
||||
end
|
||||
|
@ -265,9 +273,62 @@ module DiscourseAi
|
|||
- Include basic error handling
|
||||
- Follow accessibility guidelines
|
||||
- No explanatory text, only code
|
||||
|
||||
#{storage_api}
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def storage_api
|
||||
return if !parameters[:requires_storage]
|
||||
self.class.storage_api
|
||||
end
|
||||
|
||||
def self.storage_api
|
||||
<<~API
|
||||
## Storage API
|
||||
|
||||
Your artifact has access to a persistent key-value storage system via `window.discourseArtifact`:
|
||||
|
||||
### Methods Available:
|
||||
|
||||
**get(key)**
|
||||
- Parameters: key (string) - The key to retrieve
|
||||
- Returns: Promise<string|null> - The stored value or null if not found
|
||||
- Example: `const value = await window.discourseArtifact.get('user_name');`
|
||||
|
||||
**set(key, value, options)**
|
||||
- Parameters:
|
||||
- key (string) - The key to store (max 50 characters)
|
||||
- value (string) - The value to store (max 5000 characters)
|
||||
- options (object, optional) - { public: boolean } - Whether other users can read this value
|
||||
- Returns: Promise<object> - The created/updated key-value record
|
||||
- Example: `await window.discourseArtifact.set('score', '100', { public: true });`
|
||||
|
||||
**delete(key)**
|
||||
- Parameters: key (string) - The key to delete
|
||||
- Returns: Promise<boolean> - true if successful
|
||||
- Example: `await window.discourseArtifact.delete('temp_data');`
|
||||
|
||||
**index(filter)**
|
||||
- Parameters: filter (object, optional) - Filtering options:
|
||||
- key (string) - Filter by specific key
|
||||
- all_users (boolean) - Include other users' public values
|
||||
- keys_only (boolean) - Return only keys, not values
|
||||
- page (number) - Page number for pagination
|
||||
- per_page (number) - Items per page (max 100, default 100)
|
||||
- Returns: Promise<object> - { key_values: Array(key, value), has_more: boolean, total_count: number }
|
||||
- Example: `const result = await window.discourseArtifact.index({ keys_only: true });`
|
||||
|
||||
### Storage Rules:
|
||||
- Each user can store up to 100 keys per artifact
|
||||
- Keys are scoped to the current user and artifact
|
||||
- Private values are only accessible to the user who created them
|
||||
- Public values can be read by anyone who can view the artifact
|
||||
- All operations are asynchronous and return Promises
|
||||
```
|
||||
API
|
||||
end
|
||||
|
||||
def update_custom_html(artifact)
|
||||
html_preview = <<~MD
|
||||
[details="View Source"]
|
||||
|
|
|
@ -20,7 +20,8 @@ module DiscourseAi
|
|||
- Focus on visual appeal and smooth animations
|
||||
- Write clean, efficient code
|
||||
- Build progressively (HTML structure → CSS styling → JavaScript interactivity)
|
||||
- Keep components focused and purposeful
|
||||
- Artifacts run in a sandboxed IFRAME environmment
|
||||
- Artifacts optionally have support for a Discourse user persistent storage
|
||||
|
||||
When creating:
|
||||
1. Understand the desired user experience
|
||||
|
|
|
@ -74,6 +74,44 @@ RSpec.describe SharedAiConversation, type: :model do
|
|||
expect(populated_context[1].user.id).to eq(post2.user.id)
|
||||
end
|
||||
|
||||
it "shares artifacts publicly when conversation is shared" do
|
||||
# Create a post with an AI artifact
|
||||
artifact =
|
||||
Fabricate(
|
||||
:ai_artifact,
|
||||
post: post1,
|
||||
user: user,
|
||||
metadata: {
|
||||
public: false,
|
||||
something: "good",
|
||||
},
|
||||
)
|
||||
|
||||
_post_with_artifact =
|
||||
Fabricate(
|
||||
:post,
|
||||
topic: topic,
|
||||
post_number: 3,
|
||||
raw: "Here's an artifact",
|
||||
cooked:
|
||||
"<div class='ai-artifact' data-ai-artifact-id='#{artifact.id}' data-ai-artifact-version='1'></div>",
|
||||
)
|
||||
|
||||
expect(artifact.public?).to be_falsey
|
||||
|
||||
conversation = described_class.share_conversation(user, topic)
|
||||
artifact.reload
|
||||
|
||||
expect(artifact.metadata["something"]).to eq("good")
|
||||
expect(artifact.public?).to be_truthy
|
||||
|
||||
described_class.destroy_conversation(conversation)
|
||||
artifact.reload
|
||||
|
||||
expect(artifact.metadata["something"]).to eq("good")
|
||||
expect(artifact.public?).to be_falsey
|
||||
end
|
||||
|
||||
it "escapes HTML" do
|
||||
conversation = described_class.share_conversation(user, topic)
|
||||
onebox = conversation.onebox
|
||||
|
|
Loading…
Reference in New Issue