1use serde::{Deserialize, Serialize};
4use yewdux::prelude::*;
5
6#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
8pub enum SkillStatus {
9 Configured,
10 Unconfigured,
11 Error,
12 Loading,
13}
14
15#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
17pub enum SkillRuntime {
18 #[default]
19 Wasm,
20 Docker,
21 Native,
22}
23
24#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26pub struct SkillSummary {
27 pub name: String,
28 pub version: String,
29 pub description: String,
30 pub source: String,
31 pub runtime: SkillRuntime,
32 pub tools_count: usize,
33 pub instances_count: usize,
34 pub status: SkillStatus,
35 pub last_used: Option<String>,
36 pub execution_count: u64,
37}
38
39#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
41pub struct ToolInfo {
42 pub name: String,
43 pub description: String,
44 pub parameters: Vec<ParameterInfo>,
45 pub streaming: bool,
46}
47
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
50pub struct ParameterInfo {
51 pub name: String,
52 pub param_type: String,
53 pub description: String,
54 pub required: bool,
55 pub default_value: Option<String>,
56}
57
58#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
60pub struct InstanceInfo {
61 pub name: String,
62 pub description: Option<String>,
63 pub is_default: bool,
64 pub config_keys: Vec<String>,
65}
66
67#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
69pub struct SkillDetail {
70 pub summary: SkillSummary,
71 pub full_description: Option<String>,
72 pub author: Option<String>,
73 pub repository: Option<String>,
74 pub license: Option<String>,
75 pub tools: Vec<ToolInfo>,
76 pub instances: Vec<InstanceInfo>,
77}
78
79#[derive(Clone, Debug, Default, PartialEq, Store)]
81pub struct SkillsStore {
82 pub skills: Vec<SkillSummary>,
84 pub selected_skill: Option<SkillDetail>,
86 pub loading: bool,
88 pub detail_loading: bool,
90 pub error: Option<String>,
92 pub search_query: String,
94 pub status_filter: Option<SkillStatus>,
96 pub source_filter: Option<String>,
97 pub runtime_filter: Option<SkillRuntime>,
98 pub sort_by: SkillSortBy,
100 pub sort_ascending: bool,
101}
102
103#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
105pub enum SkillSortBy {
106 #[default]
107 Name,
108 LastUsed,
109 ExecutionCount,
110 ToolsCount,
111}
112
113impl SkillsStore {
114 pub fn filtered_skills(&self) -> Vec<&SkillSummary> {
116 let mut skills: Vec<&SkillSummary> = self.skills
117 .iter()
118 .filter(|skill| {
119 if !self.search_query.is_empty() {
121 let query = self.search_query.to_lowercase();
122 if !skill.name.to_lowercase().contains(&query)
123 && !skill.description.to_lowercase().contains(&query)
124 {
125 return false;
126 }
127 }
128
129 if let Some(ref status) = self.status_filter {
131 if &skill.status != status {
132 return false;
133 }
134 }
135
136 if let Some(ref source) = self.source_filter {
138 if !skill.source.contains(source) {
139 return false;
140 }
141 }
142
143 if let Some(ref runtime) = self.runtime_filter {
145 if &skill.runtime != runtime {
146 return false;
147 }
148 }
149
150 true
151 })
152 .collect();
153
154 skills.sort_by(|a, b| {
156 let cmp = match self.sort_by {
157 SkillSortBy::Name => a.name.cmp(&b.name),
158 SkillSortBy::LastUsed => a.last_used.cmp(&b.last_used),
159 SkillSortBy::ExecutionCount => a.execution_count.cmp(&b.execution_count),
160 SkillSortBy::ToolsCount => a.tools_count.cmp(&b.tools_count),
161 };
162 if self.sort_ascending { cmp } else { cmp.reverse() }
163 });
164
165 skills
166 }
167
168 pub fn get_skill(&self, name: &str) -> Option<&SkillSummary> {
170 self.skills.iter().find(|s| s.name == name)
171 }
172
173 pub fn total_count(&self) -> usize {
175 self.skills.len()
176 }
177
178 pub fn filtered_count(&self) -> usize {
180 self.filtered_skills().len()
181 }
182}
183
184pub enum SkillsAction {
186 SetSkills(Vec<SkillSummary>),
187 AddSkill(SkillSummary),
188 RemoveSkill(String),
189 UpdateSkill(SkillSummary),
190 SetSelectedSkill(Option<SkillDetail>),
191 SetLoading(bool),
192 SetDetailLoading(bool),
193 SetError(Option<String>),
194 SetSearchQuery(String),
195 SetStatusFilter(Option<SkillStatus>),
196 SetSourceFilter(Option<String>),
197 SetRuntimeFilter(Option<SkillRuntime>),
198 SetSortBy(SkillSortBy),
199 ToggleSortOrder,
200 ClearFilters,
201}
202
203impl Reducer<SkillsStore> for SkillsAction {
204 fn apply(self, mut store: std::rc::Rc<SkillsStore>) -> std::rc::Rc<SkillsStore> {
205 let state = std::rc::Rc::make_mut(&mut store);
206
207 match self {
208 SkillsAction::SetSkills(skills) => {
209 state.skills = skills;
210 state.loading = false;
211 state.error = None;
212 }
213 SkillsAction::AddSkill(skill) => {
214 state.skills.retain(|s| s.name != skill.name);
216 state.skills.push(skill);
217 }
218 SkillsAction::RemoveSkill(name) => {
219 state.skills.retain(|s| s.name != name);
220 if let Some(ref selected) = state.selected_skill {
222 if selected.summary.name == name {
223 state.selected_skill = None;
224 }
225 }
226 }
227 SkillsAction::UpdateSkill(skill) => {
228 if let Some(existing) = state.skills.iter_mut().find(|s| s.name == skill.name) {
229 *existing = skill;
230 }
231 }
232 SkillsAction::SetSelectedSkill(skill) => {
233 state.selected_skill = skill;
234 state.detail_loading = false;
235 }
236 SkillsAction::SetLoading(loading) => {
237 state.loading = loading;
238 }
239 SkillsAction::SetDetailLoading(loading) => {
240 state.detail_loading = loading;
241 }
242 SkillsAction::SetError(error) => {
243 state.error = error;
244 state.loading = false;
245 state.detail_loading = false;
246 }
247 SkillsAction::SetSearchQuery(query) => {
248 state.search_query = query;
249 }
250 SkillsAction::SetStatusFilter(filter) => {
251 state.status_filter = filter;
252 }
253 SkillsAction::SetSourceFilter(filter) => {
254 state.source_filter = filter;
255 }
256 SkillsAction::SetRuntimeFilter(filter) => {
257 state.runtime_filter = filter;
258 }
259 SkillsAction::SetSortBy(sort_by) => {
260 state.sort_by = sort_by;
261 }
262 SkillsAction::ToggleSortOrder => {
263 state.sort_ascending = !state.sort_ascending;
264 }
265 SkillsAction::ClearFilters => {
266 state.search_query = String::new();
267 state.status_filter = None;
268 state.source_filter = None;
269 state.runtime_filter = None;
270 }
271 }
272
273 store
274 }
275}