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        CliArtifact::Message { artifact } => {
64            for line in &artifact.messages {
65                match line.level.as_str() {
66                    "success" => CliService::success(&line.text),
67                    "warning" => CliService::warning(&line.text),
68                    "error" => CliService::error(&line.text),
69                    _ => CliService::info(&line.text),
70                }
71            }
72        },
73    }
74}
75
76fn render_table(artifact: &TableArtifact) {
77    let headers: Vec<&str> = artifact
78        .columns
79        .iter()
80        .map(|c| c.label.as_deref().unwrap_or(&c.name))
81        .collect();
82
83    let rows: Vec<Vec<String>> = artifact
84        .items
85        .iter()
86        .map(|item| {
87            artifact
88                .columns
89                .iter()
90                .map(|col| {
91                    item.get(&col.name)
92                        .map_or_else(String::new, value_to_display)
93                })
94                .collect()
95        })
96        .collect();
97
98    CliService::table(&headers, &rows);
99}
100
101fn render_list(artifact: &ListArtifact) {
102    for item in &artifact.items {
103        CliService::subsection(&item.title);
104        if !item.summary.is_empty() {
105            CliService::output(&item.summary);
106        }
107        if !item.link.is_empty() {
108            CliService::output(&item.link);
109        }
110    }
111}
112
113fn render_card(artifact: &PresentationCardArtifact) {
114    CliService::section(&artifact.title);
115    if let Some(subtitle) = &artifact.subtitle {
116        CliService::output(subtitle);
117    }
118    for section in &artifact.sections {
119        CliService::subsection(&section.heading);
120        CliService::output(&section.content);
121    }
122}
123
124fn render_chart(artifact: &ChartArtifact) {
125    let mut headers: Vec<&str> = vec!["label"];
126    for dataset in &artifact.datasets {
127        headers.push(&dataset.label);
128    }
129
130    let rows: Vec<Vec<String>> = artifact
131        .labels
132        .iter()
133        .enumerate()
134        .map(|(row, label)| {
135            let mut cells = vec![label.clone()];
136            for dataset in &artifact.datasets {
137                cells.push(
138                    dataset
139                        .data
140                        .get(row)
141                        .map_or_else(String::new, ToString::to_string),
142                );
143            }
144            cells
145        })
146        .collect();
147
148    CliService::table(&headers, &rows);
149}