Skip to main content

fc_dioxus/components/
tool_call.rs

1//! A recorded or running operation, from tool-call.css.
2use crate::{
3    core::{
4        assets,
5        attrs::{clipped_attributes, component_attributes, FcData, Kind},
6        measure::{custom_property, CustomProperty},
7    },
8    prelude::*,
9    theme::use_stylesheets,
10};
11const BEM: Bem = Bem::new("fc-tool");
12
13/// How far a running tool call has got.
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum ToolProgress {
16    /// Working, extent unknown.
17    Indeterminate,
18    /// This much is done.
19    Done(Percent),
20}
21
22/// One tool invocation: its name, state, and duration, then progress, its payload, or why
23/// it failed. Every field is real text and the state is `data-fc-state`, so the record
24/// reads the same to a person and to automation, and survives greyscale.
25#[component]
26pub fn ToolCall(
27    name: String,
28    state: Lifecycle,
29    /// Elapsed time, e.g. "412 ms".
30    #[props(default)]
31    duration: Option<String>,
32    #[props(default)] progress: Option<ToolProgress>,
33    /// A small label above the payload, e.g. "Arguments".
34    #[props(default)]
35    payload_label: Option<Element>,
36    /// Arguments or result. Truncation is declared, and the full value kept in `title`.
37    #[props(default)]
38    payload: Option<Clipped>,
39    #[props(default)] error: Option<Element>,
40    #[props(default)] record: Record,
41    #[props(default)] class: Option<String>,
42    #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
43) -> Element {
44    let mut attributes = component_attributes(
45        attributes,
46        ClassList::new(BEM.block()).extend(class.as_deref()),
47        &[],
48    );
49    attributes.extend(
50        FcData {
51            kind: Some(Kind::ToolCall),
52            id: record.id.clone(),
53            seq: record.seq,
54            state: Some(state),
55            ts: record.ts.clone(),
56            ..FcData::default()
57        }
58        .attributes(),
59    );
60    let state_attr = FcData {
61        state: Some(state),
62        ..FcData::default()
63    }
64    .attributes();
65    rsx! {
66        if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_TOOL_CALL_CSS } }
67        div { ..attributes,
68            div { class: BEM.element("header"),
69                span { class: BEM.element("name"), "{name}" }
70                span { class: BEM.element("state"), ..state_attr, "{state.as_str()}" }
71                if let Some(duration) = duration { span { class: BEM.element("duration"), "{duration}" } }
72            }
73            if let Some(progress) = progress {
74                div { class: BEM.element("progress"),
75                    div { class: BEM.element("progress-track"),
76                        match progress {
77                            ToolProgress::Indeterminate => rsx! {
78                                div { class: BEM.element("progress-bar"), role: "progressbar", aria_label: "{name} progress" }
79                            },
80                            ToolProgress::Done(percent) => {
81                                let width = custom_property(CustomProperty::ToolProgress, format!("{percent}%"));
82                                rsx! {
83                                    div { class: BEM.element("progress-bar"), role: "progressbar", aria_label: "{name} progress",
84                                        aria_valuemin: "0", aria_valuemax: "100", aria_valuenow: "{percent}", ..crate::spread![width] }
85                                }
86                            }
87                        }
88                    }
89                }
90            }
91            if let Some(label) = payload_label { span { class: BEM.element("label"), {label} } }
92            if let Some(payload) = payload {
93                div { class: BEM.element("payload"), ..clipped_attributes(&payload), "{payload.shown()}" }
94            }
95            if let Some(error) = error { div { class: BEM.element("error"), {error} } }
96        }
97    }
98}