Skip to main content

systemprompt_cli/shared/command_result/
render.rs

1//! Terminal rendering for [`CommandOutput`].
2//!
3//! `json`/`yaml` formats emit the [`CliArtifact`] verbatim; `table` renders per
4//! artifact variant for an interactive terminal.
5
6use systemprompt_logging::CliService;
7use systemprompt_models::artifacts::{
8    ChartArtifact, CliArtifact, ListArtifact, PresentationCardArtifact, TableArtifact,
9};
10
11use super::{CommandOutput, value_to_display};
12use crate::cli_settings::{OutputFormat, get_global_config};
13
14pub fn render_result(result: &CommandOutput) {
15    if result.should_skip_render() {
16        return;
17    }
18
19    match get_global_config().output_format() {
20        OutputFormat::Json => CliService::json(result.artifact()),
21        OutputFormat::Yaml => CliService::yaml(result.artifact()),
22        OutputFormat::Table => render_terminal(result),
23    }
24}
25
26fn render_terminal(result: &CommandOutput) {
27    if let Some(title) = result.title() {
28        CliService::section(title);
29    }
30
31    match result.artifact() {
32        CliArtifact::Text { artifact } => {
33            if result.title().is_none() {
34                if let Some(title) = &artifact.title {
35                    CliService::section(title);
36                }
37            }
38            CliService::output(&artifact.content);
39        },
40        CliArtifact::CopyPasteText { artifact } => {
41            if result.title().is_none() {
42                if let Some(title) = &artifact.title {
43                    CliService::section(title);
44                }
45            }
46            CliService::output(&artifact.content);
47        },
48        CliArtifact::Table { artifact } => render_table(artifact),
49        CliArtifact::List { artifact } => render_list(artifact),
50        CliArtifact::PresentationCard { artifact } => render_card(artifact),
51        CliArtifact::Dashboard { artifact } => {
52            if result.title().is_none() {
53                CliService::section(&artifact.title);
54            }
55            if let Some(description) = &artifact.description {
56                CliService::output(description);
57            }
58        },
59        CliArtifact::Chart { artifact } => render_chart(artifact),
60        CliArtifact::Audio { artifact } => CliService::output(&artifact.src),
61        CliArtifact::Image { artifact } => CliService::output(&artifact.src),
62        CliArtifact::Video { artifact } => CliService::output(&artifact.src),
63    }
64}
65
66fn render_table(artifact: &TableArtifact) {
67    let headers: Vec<&str> = artifact
68        .columns
69        .iter()
70        .map(|c| c.label.as_deref().unwrap_or(&c.name))
71        .collect();
72
73    let rows: Vec<Vec<String>> = artifact
74        .items
75        .iter()
76        .map(|item| {
77            artifact
78                .columns
79                .iter()
80                .map(|col| {
81                    item.get(&col.name)
82                        .map_or_else(String::new, value_to_display)
83                })
84                .collect()
85        })
86        .collect();
87
88    CliService::table(&headers, &rows);
89}
90
91fn render_list(artifact: &ListArtifact) {
92    for item in &artifact.items {
93        CliService::subsection(&item.title);
94        if !item.summary.is_empty() {
95            CliService::output(&item.summary);
96        }
97        if !item.link.is_empty() {
98            CliService::output(&item.link);
99        }
100    }
101}
102
103fn render_card(artifact: &PresentationCardArtifact) {
104    CliService::section(&artifact.title);
105    if let Some(subtitle) = &artifact.subtitle {
106        CliService::output(subtitle);
107    }
108    for section in &artifact.sections {
109        CliService::subsection(&section.heading);
110        CliService::output(&section.content);
111    }
112}
113
114fn render_chart(artifact: &ChartArtifact) {
115    let mut headers: Vec<&str> = vec!["label"];
116    for dataset in &artifact.datasets {
117        headers.push(&dataset.label);
118    }
119
120    let rows: Vec<Vec<String>> = artifact
121        .labels
122        .iter()
123        .enumerate()
124        .map(|(row, label)| {
125            let mut cells = vec![label.clone()];
126            for dataset in &artifact.datasets {
127                cells.push(
128                    dataset
129                        .data
130                        .get(row)
131                        .map_or_else(String::new, ToString::to_string),
132                );
133            }
134            cells
135        })
136        .collect();
137
138    CliService::table(&headers, &rows);
139}