use crate::{
behavior::context::{ContextModel, ContextState, Effort, ProviderChoice},
core::{
assets,
attrs::{applicable_attribute, component_attributes, context_attributes},
},
prelude::*,
theme::use_stylesheets,
};
const BEM: Bem = Bem::new("fc-context-strip");
#[component]
pub fn ContextStrip(
providers: Vec<ProviderChoice>,
#[props(default)] onchange: Option<EventHandler<ContextState>>,
#[props(default = true)]
show_provider: bool,
#[props(default)] class: Option<String>,
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
) -> Element {
let mut strip = use_signal(|| ContextModel::new(providers.clone()));
if strip.peek().providers() != providers.as_slice() {
strip.set(ContextModel::new(providers.clone()));
}
let model = strip.read().clone();
let state = model.state();
let reasoning = model.model().is_some_and(|m| m.reasoning);
let mut attributes = component_attributes(
attributes,
ClassList::new(BEM.block()).extend(class.as_deref()),
&[],
);
attributes.extend(context_attributes(
&state.provider,
&state.model,
state.effort.map(Effort::as_str),
));
let mut update = move |change: &dyn Fn(&mut ContextModel)| {
let before = strip.peek().state();
change(&mut strip.write());
let after = strip.peek().state();
if after != before {
if let Some(onchange) = onchange {
onchange.call(after);
}
}
};
let provider_options: Vec<SelectChoice> = model
.providers()
.iter()
.map(|p| SelectChoice::new(p.id.clone(), p.name.clone()))
.collect();
let model_options: Vec<SelectChoice> = model
.provider()
.map(|p| {
p.models
.iter()
.map(|m| SelectChoice::new(m.id.clone(), m.name.clone()))
.collect()
})
.unwrap_or_default();
let effort_options: Vec<SelectChoice> = Effort::ALL
.iter()
.map(|e| SelectChoice::new(e.as_str(), e.label()))
.collect();
let provider_value = use_memo(move || Some(strip.read().state().provider));
let model_value = use_memo(move || Some(strip.read().state().model));
let effort_value =
use_memo(move || strip.read().stored_effort().map(|e| e.as_str().to_owned()));
rsx! {
if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_CONTEXT_STRIP_CSS } }
div { ..attributes,
if show_provider {
div { class: BEM.element("field"),
span { class: BEM.element("label"), "Provider" }
Select { label: crate::name!("Provider"), options: provider_options,
value: ReadSignal::from(provider_value),
onchange: move |v: String| update(&|m| m.set_provider(&v)) }
}
}
div { class: BEM.element("field"),
span { class: BEM.element("label"), "Model" }
Select { label: crate::name!("Model"), options: model_options, value: ReadSignal::from(model_value),
onchange: move |v: String| update(&|m| m.set_model(&v)) }
}
div { class: BEM.element("field"), ..crate::spread![applicable_attribute(reasoning)],
span { class: BEM.element("label"), "Effort" }
Select { label: crate::name!("Effort"), options: effort_options, placeholder: "n/a",
value: ReadSignal::from(effort_value),
availability: if reasoning { Availability::Enabled } else { Availability::Disabled },
onchange: move |v: String| if let Some(e) = Effort::from_value(&v) { update(&|m| m.set_effort(e)) } }
}
span { class: BEM.element("spacer") }
div { class: BEM.element("params"),
for (name, value) in state.parameters.iter() {
span { key: "{name}", class: BEM.element("param"),
span { class: BEM.element("param-name"), "{name}" }
span { class: BEM.element("param-value"), "{value}" }
}
}
}
span { class: BEM.element("notice"), role: "status", hidden: model.notice().is_none(),
"{model.notice().unwrap_or_default()}"
}
}
}
}