Skip to main content

systemprompt_models/artifacts/chart/
mod.rs

1//! Chart artifact.
2//!
3//! A [`ChartArtifact`] carries labelled axes and one or more [`ChartDataset`]s
4//! for a given [`ChartType`]. Chart type, title, and axis configuration
5//! serialize with the payload so renderers consume them directly; the artifact
6//! implements [`Artifact`].
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use crate::artifacts::metadata::ExecutionMetadata;
12use crate::artifacts::traits::Artifact;
13use crate::artifacts::types::{ArtifactType, AxisType, ChartType};
14use crate::execution::context::RequestContext;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use serde_json::{Value as JsonValue, json};
18use systemprompt_identifiers::SkillId;
19
20fn default_artifact_type() -> String {
21    "chart".to_owned()
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
25pub struct ChartDataset {
26    pub label: String,
27    pub data: Vec<f64>,
28}
29
30impl ChartDataset {
31    pub fn new(label: impl Into<String>, data: Vec<f64>) -> Self {
32        Self {
33            label: label.into(),
34            data,
35        }
36    }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
40pub struct ChartArtifact {
41    #[serde(rename = "x-artifact-type")]
42    #[serde(default = "default_artifact_type")]
43    pub artifact_type: String,
44    pub labels: Vec<String>,
45    pub datasets: Vec<ChartDataset>,
46    #[serde(default)]
47    pub chart_type: ChartType,
48    #[serde(default)]
49    pub title: String,
50    #[serde(default)]
51    pub x_axis_label: String,
52    #[serde(default)]
53    pub y_axis_label: String,
54    #[serde(default)]
55    pub x_axis_type: AxisType,
56    #[serde(default)]
57    pub y_axis_type: AxisType,
58    #[serde(skip)]
59    #[schemars(skip)]
60    metadata: ExecutionMetadata,
61}
62
63impl ChartArtifact {
64    pub const ARTIFACT_TYPE_STR: &'static str = "chart";
65
66    pub fn new(title: impl Into<String>, chart_type: ChartType) -> Self {
67        Self {
68            artifact_type: "chart".to_owned(),
69            labels: Vec::new(),
70            datasets: Vec::new(),
71            chart_type,
72            title: title.into(),
73            x_axis_label: "X".to_owned(),
74            y_axis_label: "Y".to_owned(),
75            x_axis_type: AxisType::Category,
76            y_axis_type: AxisType::Linear,
77            metadata: ExecutionMetadata::default(),
78        }
79    }
80
81    pub fn with_request(mut self, ctx: &RequestContext) -> Self {
82        self.metadata = ExecutionMetadata::with_request(ctx);
83        self
84    }
85
86    pub fn with_x_axis_labels(mut self, labels: Vec<String>) -> Self {
87        self.labels = labels;
88        self
89    }
90
91    pub fn with_labels(self, labels: Vec<String>) -> Self {
92        self.with_x_axis_labels(labels)
93    }
94
95    pub fn with_datasets(mut self, datasets: Vec<ChartDataset>) -> Self {
96        self.datasets = datasets;
97        self
98    }
99
100    pub fn add_dataset(mut self, dataset: ChartDataset) -> Self {
101        self.datasets.push(dataset);
102        self
103    }
104
105    pub const fn with_x_axis_type(mut self, axis_type: AxisType) -> Self {
106        self.x_axis_type = axis_type;
107        self
108    }
109
110    pub const fn with_y_axis_type(mut self, axis_type: AxisType) -> Self {
111        self.y_axis_type = axis_type;
112        self
113    }
114
115    pub fn with_axes(mut self, x_label: impl Into<String>, y_label: impl Into<String>) -> Self {
116        self.x_axis_label = x_label.into();
117        self.y_axis_label = y_label.into();
118        self
119    }
120
121    pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
122        self.metadata.execution_id = Some(id.into());
123        self
124    }
125
126    pub fn with_skill(
127        mut self,
128        skill_id: impl Into<SkillId>,
129        skill_name: impl Into<String>,
130    ) -> Self {
131        self.metadata.skill_id = Some(skill_id.into());
132        self.metadata.skill_name = Some(skill_name.into());
133        self
134    }
135}
136
137impl Artifact for ChartArtifact {
138    fn artifact_type(&self) -> ArtifactType {
139        ArtifactType::Chart
140    }
141
142    fn to_schema(&self) -> JsonValue {
143        json!({
144            "type": "object",
145            "properties": {
146                "labels": {
147                    "type": "array",
148                    "items": {"type": "string"}
149                },
150                "datasets": {
151                    "type": "array",
152                    "items": {
153                        "type": "object",
154                        "properties": {
155                            "label": {"type": "string"},
156                            "data": {"type": "array", "items": {"type": "number"}}
157                        }
158                    }
159                },
160                "chart_type": {"type": "string"},
161                "title": {"type": "string"},
162                "x_axis_label": {"type": "string"},
163                "y_axis_label": {"type": "string"},
164                "x_axis_type": {"type": "string"},
165                "y_axis_type": {"type": "string"},
166                "_execution_id": {"type": "string"}
167            },
168            "required": ["labels", "datasets"],
169            "x-artifact-type": "chart"
170        })
171    }
172}