systemprompt_models/artifacts/card/
mod.rs1use crate::artifacts::metadata::ExecutionMetadata;
9use crate::artifacts::traits::Artifact;
10use crate::artifacts::types::ArtifactType;
11use crate::execution::context::RequestContext;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use serde_json::{Value as JsonValue, json};
15use systemprompt_identifiers::SkillId;
16
17#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
18pub struct PresentationCardResponse {
19 #[serde(rename = "x-artifact-type")]
20 pub artifact_type: String,
21 pub title: String,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub subtitle: Option<String>,
24 pub sections: Vec<CardSection>,
25 #[serde(skip_serializing_if = "Vec::is_empty", default)]
26 pub ctas: Vec<CardCta>,
27 pub theme: String,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub execution_id: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub skill_id: Option<SkillId>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub skill_name: Option<String>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
37pub struct CardSection {
38 pub heading: String,
39 pub content: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub icon: Option<String>,
42}
43
44impl CardSection {
45 pub fn new(heading: impl Into<String>, content: impl Into<String>) -> Self {
46 Self {
47 heading: heading.into(),
48 content: content.into(),
49 icon: None,
50 }
51 }
52
53 pub fn with_icon(mut self, icon: impl Into<String>) -> Self {
54 self.icon = Some(icon.into());
55 self
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60pub struct CardCta {
61 pub id: String,
62 pub label: String,
63 pub message: String,
64 pub variant: String,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub icon: Option<String>,
67}
68
69impl CardCta {
70 pub fn new(
71 id: impl Into<String>,
72 label: impl Into<String>,
73 message: impl Into<String>,
74 variant: impl Into<String>,
75 ) -> Self {
76 Self {
77 id: id.into(),
78 label: label.into(),
79 message: message.into(),
80 variant: variant.into(),
81 icon: None,
82 }
83 }
84
85 pub fn with_icon(mut self, icon: impl Into<String>) -> Self {
86 self.icon = Some(icon.into());
87 self
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
92pub struct PresentationCardArtifact {
93 #[serde(rename = "x-artifact-type")]
94 #[serde(default = "default_card_artifact_type")]
95 pub artifact_type: String,
96 pub title: String,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub subtitle: Option<String>,
99 pub sections: Vec<CardSection>,
100 #[serde(default, skip_serializing_if = "Vec::is_empty")]
101 pub ctas: Vec<CardCta>,
102 #[serde(default = "default_theme")]
103 pub theme: String,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub execution_id: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub skill_id: Option<SkillId>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub skill_name: Option<String>,
110 #[serde(skip)]
111 #[schemars(skip)]
112 metadata: ExecutionMetadata,
113}
114
115fn default_theme() -> String {
116 "gradient".to_owned()
117}
118
119fn default_card_artifact_type() -> String {
120 "presentation_card".to_owned()
121}
122
123impl PresentationCardArtifact {
124 pub const ARTIFACT_TYPE_STR: &'static str = "presentation_card";
125
126 pub fn new(title: impl Into<String>, ctx: &RequestContext) -> Self {
127 Self {
128 artifact_type: "presentation_card".to_owned(),
129 title: title.into(),
130 subtitle: None,
131 sections: Vec::new(),
132 ctas: Vec::new(),
133 theme: default_theme(),
134 execution_id: None,
135 skill_id: None,
136 skill_name: None,
137 metadata: ExecutionMetadata::with_request(ctx),
138 }
139 }
140
141 pub fn with_subtitle(mut self, subtitle: impl Into<String>) -> Self {
142 self.subtitle = Some(subtitle.into());
143 self
144 }
145
146 pub fn with_sections(mut self, sections: Vec<CardSection>) -> Self {
147 self.sections = sections;
148 self
149 }
150
151 pub fn add_section(mut self, section: CardSection) -> Self {
152 self.sections.push(section);
153 self
154 }
155
156 pub fn with_ctas(mut self, ctas: Vec<CardCta>) -> Self {
157 self.ctas = ctas;
158 self
159 }
160
161 pub fn add_cta(mut self, cta: CardCta) -> Self {
162 self.ctas.push(cta);
163 self
164 }
165
166 pub fn with_theme(mut self, theme: impl Into<String>) -> Self {
167 self.theme = theme.into();
168 self
169 }
170
171 pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
172 let id_str = id.into();
173 self.execution_id = Some(id_str.clone());
174 self.metadata.execution_id = Some(id_str);
175 self
176 }
177
178 pub fn with_skill(
179 mut self,
180 skill_id: impl Into<SkillId>,
181 skill_name: impl Into<String>,
182 ) -> Self {
183 let id = skill_id.into();
184 self.skill_id = Some(id.clone());
185 self.skill_name = Some(skill_name.into());
186 self.metadata.skill_id = Some(id);
187 self
188 }
189}
190
191impl Artifact for PresentationCardArtifact {
192 fn artifact_type(&self) -> ArtifactType {
193 ArtifactType::PresentationCard
194 }
195
196 fn to_schema(&self) -> JsonValue {
197 json!({
198 "type": "object",
199 "properties": {
200 "title": {
201 "type": "string",
202 "description": "Card title"
203 },
204 "subtitle": {
205 "type": "string",
206 "description": "Card subtitle"
207 },
208 "sections": {
209 "type": "array",
210 "description": "Content sections",
211 "items": {
212 "type": "object",
213 "properties": {
214 "heading": {"type": "string"},
215 "content": {"type": "string"},
216 "icon": {"type": "string"}
217 },
218 "required": ["heading", "content"]
219 }
220 },
221 "ctas": {
222 "type": "array",
223 "description": "Call-to-action buttons",
224 "items": {
225 "type": "object",
226 "properties": {
227 "id": {"type": "string"},
228 "label": {"type": "string"},
229 "message": {"type": "string"},
230 "variant": {"type": "string"},
231 "icon": {"type": "string"}
232 },
233 "required": ["id", "label", "message", "variant"]
234 }
235 },
236 "theme": {
237 "type": "string",
238 "description": "Card theme",
239 "default": "gradient"
240 },
241 "_execution_id": {
242 "type": "string",
243 "description": "Execution ID for tracking"
244 }
245 },
246 "required": ["title", "sections"],
247 "x-artifact-type": "presentation_card",
248 "x-presentation-hints": {
249 "theme": self.theme
250 }
251 })
252 }
253}