fc_dioxus/components/
context_strip.rs1use crate::{
4 behavior::context::{ContextModel, ContextState, Effort, ProviderChoice},
5 core::{
6 assets,
7 attrs::{applicable_attribute, component_attributes, context_attributes},
8 },
9 prelude::*,
10 theme::use_stylesheets,
11};
12const BEM: Bem = Bem::new("fc-context-strip");
13
14#[component]
20pub fn ContextStrip(
21 providers: Vec<ProviderChoice>,
22 #[props(default)] onchange: Option<EventHandler<ContextState>>,
23 #[props(default = true)]
25 show_provider: bool,
26 #[props(default)] class: Option<String>,
27 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
28) -> Element {
29 let mut strip = use_signal(|| ContextModel::new(providers.clone()));
30 if strip.peek().providers() != providers.as_slice() {
31 strip.set(ContextModel::new(providers.clone()));
32 }
33 let model = strip.read().clone();
34 let state = model.state();
35 let reasoning = model.model().is_some_and(|m| m.reasoning);
36 let mut attributes = component_attributes(
37 attributes,
38 ClassList::new(BEM.block()).extend(class.as_deref()),
39 &[],
40 );
41 attributes.extend(context_attributes(
42 &state.provider,
43 &state.model,
44 state.effort.map(Effort::as_str),
45 ));
46 let mut update = move |change: &dyn Fn(&mut ContextModel)| {
47 let before = strip.peek().state();
48 change(&mut strip.write());
49 let after = strip.peek().state();
50 if after != before {
51 if let Some(onchange) = onchange {
52 onchange.call(after);
53 }
54 }
55 };
56 let provider_options: Vec<SelectChoice> = model
57 .providers()
58 .iter()
59 .map(|p| SelectChoice::new(p.id.clone(), p.name.clone()))
60 .collect();
61 let model_options: Vec<SelectChoice> = model
62 .provider()
63 .map(|p| {
64 p.models
65 .iter()
66 .map(|m| SelectChoice::new(m.id.clone(), m.name.clone()))
67 .collect()
68 })
69 .unwrap_or_default();
70 let effort_options: Vec<SelectChoice> = Effort::ALL
71 .iter()
72 .map(|e| SelectChoice::new(e.as_str(), e.label()))
73 .collect();
74 let provider_value = use_memo(move || Some(strip.read().state().provider));
76 let model_value = use_memo(move || Some(strip.read().state().model));
77 let effort_value =
78 use_memo(move || strip.read().stored_effort().map(|e| e.as_str().to_owned()));
79 rsx! {
80 if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_CONTEXT_STRIP_CSS } }
81 div { ..attributes,
82 if show_provider {
83 div { class: BEM.element("field"),
84 span { class: BEM.element("label"), "Provider" }
85 Select { label: crate::name!("Provider"), options: provider_options,
86 value: ReadSignal::from(provider_value),
87 onchange: move |v: String| update(&|m| m.set_provider(&v)) }
88 }
89 }
90 div { class: BEM.element("field"),
91 span { class: BEM.element("label"), "Model" }
92 Select { label: crate::name!("Model"), options: model_options, value: ReadSignal::from(model_value),
93 onchange: move |v: String| update(&|m| m.set_model(&v)) }
94 }
95 div { class: BEM.element("field"), ..crate::spread![applicable_attribute(reasoning)],
98 span { class: BEM.element("label"), "Effort" }
99 Select { label: crate::name!("Effort"), options: effort_options, placeholder: "n/a",
100 value: ReadSignal::from(effort_value),
101 availability: if reasoning { Availability::Enabled } else { Availability::Disabled },
102 onchange: move |v: String| if let Some(e) = Effort::from_value(&v) { update(&|m| m.set_effort(e)) } }
103 }
104 span { class: BEM.element("spacer") }
105 div { class: BEM.element("params"),
106 for (name, value) in state.parameters.iter() {
107 span { key: "{name}", class: BEM.element("param"),
108 span { class: BEM.element("param-name"), "{name}" }
109 span { class: BEM.element("param-value"), "{value}" }
110 }
111 }
112 }
113 span { class: BEM.element("notice"), role: "status", hidden: model.notice().is_none(),
114 "{model.notice().unwrap_or_default()}"
115 }
116 }
117 }
118}