use serde::{Deserialize, Serialize};
use yewdux::prelude::*;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum SkillStatus {
Configured,
Unconfigured,
Error,
Loading,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum SkillRuntime {
#[default]
Wasm,
Docker,
Native,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SkillSummary {
pub name: String,
pub version: String,
pub description: String,
pub source: String,
pub runtime: SkillRuntime,
pub tools_count: usize,
pub instances_count: usize,
pub status: SkillStatus,
pub last_used: Option<String>,
pub execution_count: u64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ToolInfo {
pub name: String,
pub description: String,
pub parameters: Vec<ParameterInfo>,
pub streaming: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ParameterInfo {
pub name: String,
pub param_type: String,
pub description: String,
pub required: bool,
pub default_value: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct InstanceInfo {
pub name: String,
pub description: Option<String>,
pub is_default: bool,
pub config_keys: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SkillDetail {
pub summary: SkillSummary,
pub full_description: Option<String>,
pub author: Option<String>,
pub repository: Option<String>,
pub license: Option<String>,
pub tools: Vec<ToolInfo>,
pub instances: Vec<InstanceInfo>,
}
#[derive(Clone, Debug, Default, PartialEq, Store)]
pub struct SkillsStore {
pub skills: Vec<SkillSummary>,
pub selected_skill: Option<SkillDetail>,
pub loading: bool,
pub detail_loading: bool,
pub error: Option<String>,
pub search_query: String,
pub status_filter: Option<SkillStatus>,
pub source_filter: Option<String>,
pub runtime_filter: Option<SkillRuntime>,
pub sort_by: SkillSortBy,
pub sort_ascending: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum SkillSortBy {
#[default]
Name,
LastUsed,
ExecutionCount,
ToolsCount,
}
impl SkillsStore {
pub fn filtered_skills(&self) -> Vec<&SkillSummary> {
let mut skills: Vec<&SkillSummary> = self.skills
.iter()
.filter(|skill| {
if !self.search_query.is_empty() {
let query = self.search_query.to_lowercase();
if !skill.name.to_lowercase().contains(&query)
&& !skill.description.to_lowercase().contains(&query)
{
return false;
}
}
if let Some(ref status) = self.status_filter {
if &skill.status != status {
return false;
}
}
if let Some(ref source) = self.source_filter {
if !skill.source.contains(source) {
return false;
}
}
if let Some(ref runtime) = self.runtime_filter {
if &skill.runtime != runtime {
return false;
}
}
true
})
.collect();
skills.sort_by(|a, b| {
let cmp = match self.sort_by {
SkillSortBy::Name => a.name.cmp(&b.name),
SkillSortBy::LastUsed => a.last_used.cmp(&b.last_used),
SkillSortBy::ExecutionCount => a.execution_count.cmp(&b.execution_count),
SkillSortBy::ToolsCount => a.tools_count.cmp(&b.tools_count),
};
if self.sort_ascending { cmp } else { cmp.reverse() }
});
skills
}
pub fn get_skill(&self, name: &str) -> Option<&SkillSummary> {
self.skills.iter().find(|s| s.name == name)
}
pub fn total_count(&self) -> usize {
self.skills.len()
}
pub fn filtered_count(&self) -> usize {
self.filtered_skills().len()
}
}
pub enum SkillsAction {
SetSkills(Vec<SkillSummary>),
AddSkill(SkillSummary),
RemoveSkill(String),
UpdateSkill(SkillSummary),
SetSelectedSkill(Option<SkillDetail>),
SetLoading(bool),
SetDetailLoading(bool),
SetError(Option<String>),
SetSearchQuery(String),
SetStatusFilter(Option<SkillStatus>),
SetSourceFilter(Option<String>),
SetRuntimeFilter(Option<SkillRuntime>),
SetSortBy(SkillSortBy),
ToggleSortOrder,
ClearFilters,
}
impl Reducer<SkillsStore> for SkillsAction {
fn apply(self, mut store: std::rc::Rc<SkillsStore>) -> std::rc::Rc<SkillsStore> {
let state = std::rc::Rc::make_mut(&mut store);
match self {
SkillsAction::SetSkills(skills) => {
state.skills = skills;
state.loading = false;
state.error = None;
}
SkillsAction::AddSkill(skill) => {
state.skills.retain(|s| s.name != skill.name);
state.skills.push(skill);
}
SkillsAction::RemoveSkill(name) => {
state.skills.retain(|s| s.name != name);
if let Some(ref selected) = state.selected_skill {
if selected.summary.name == name {
state.selected_skill = None;
}
}
}
SkillsAction::UpdateSkill(skill) => {
if let Some(existing) = state.skills.iter_mut().find(|s| s.name == skill.name) {
*existing = skill;
}
}
SkillsAction::SetSelectedSkill(skill) => {
state.selected_skill = skill;
state.detail_loading = false;
}
SkillsAction::SetLoading(loading) => {
state.loading = loading;
}
SkillsAction::SetDetailLoading(loading) => {
state.detail_loading = loading;
}
SkillsAction::SetError(error) => {
state.error = error;
state.loading = false;
state.detail_loading = false;
}
SkillsAction::SetSearchQuery(query) => {
state.search_query = query;
}
SkillsAction::SetStatusFilter(filter) => {
state.status_filter = filter;
}
SkillsAction::SetSourceFilter(filter) => {
state.source_filter = filter;
}
SkillsAction::SetRuntimeFilter(filter) => {
state.runtime_filter = filter;
}
SkillsAction::SetSortBy(sort_by) => {
state.sort_by = sort_by;
}
SkillsAction::ToggleSortOrder => {
state.sort_ascending = !state.sort_ascending;
}
SkillsAction::ClearFilters => {
state.search_query = String::new();
state.status_filter = None;
state.source_filter = None;
state.runtime_filter = None;
}
}
store
}
}