skill_web/components/run/
tool_selector.rs

1//! Tool selector component - Step 2 of the execution workflow
2
3use yew::prelude::*;
4use crate::api::ToolInfo;
5
6#[derive(Properties, PartialEq)]
7pub struct ToolSelectorProps {
8    pub tools: Vec<ToolInfo>,
9    pub selected: Option<String>,
10    pub loading: bool,
11    pub enabled: bool,
12    pub on_select: Callback<String>,
13}
14
15#[function_component(ToolSelector)]
16pub fn tool_selector(props: &ToolSelectorProps) -> Html {
17    let max_visible = 8;
18
19    html! {
20        <div class="space-y-4">
21            // Step header
22            <div class="flex items-center gap-2 mb-2">
23                <div class={format!(
24                    "flex items-center justify-center w-8 h-8 rounded-full font-semibold text-sm {}",
25                    if props.enabled {
26                        "bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400"
27                    } else {
28                        "bg-gray-100 dark:bg-gray-800 text-gray-400"
29                    }
30                )}>
31                    { "2" }
32                </div>
33                <h3 class="text-lg font-semibold text-gray-900 dark:text-white">
34                    { "Select Tool" }
35                </h3>
36            </div>
37
38            <div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
39                <div class="p-4 space-y-3">
40                    if !props.enabled {
41                        <div class="text-center py-12 text-gray-400 dark:text-gray-500">
42                            <svg class="w-12 h-12 mx-auto mb-3 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
43                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 11.5V14m0-2.5v-6a1.5 1.5 0 113 0m-3 6a1.5 1.5 0 00-3 0v2a7.5 7.5 0 0015 0v-5a1.5 1.5 0 00-3 0m-6-3V11m0-5.5v-1a1.5 1.5 0 013 0v1m0 0V11m0-5.5a1.5 1.5 0 013 0v3m0 0V11" />
44                            </svg>
45                            <p class="text-sm font-medium">{ "Select a skill first" }</p>
46                            <p class="text-xs mt-1">{ "Choose a skill from the left to see available tools" }</p>
47                        </div>
48                    } else if props.loading {
49                        // Loading skeleton
50                        { for (0..4).map(|_| html! {
51                            <div class="animate-pulse">
52                                <div class="h-14 bg-gray-200 dark:bg-gray-700 rounded-lg"></div>
53                            </div>
54                        })}
55                    } else if props.tools.is_empty() {
56                        <div class="text-center py-8 text-gray-500">
57                            <svg class="w-12 h-12 mx-auto mb-3 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
58                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
59                            </svg>
60                            <p class="text-sm">{ "No tools available" }</p>
61                        </div>
62                    } else {
63                        // Tool cards
64                        { for props.tools.iter().take(max_visible).map(|tool| {
65                            let is_selected = props.selected.as_ref().map(|s| s == &tool.name).unwrap_or(false);
66                            let tool_name = tool.name.clone();
67                            let on_click = {
68                                let on_select = props.on_select.clone();
69                                Callback::from(move |_| {
70                                    on_select.emit(tool_name.clone());
71                                })
72                            };
73
74                            html! {
75                                <button
76                                    onclick={on_click}
77                                    class={format!(
78                                        "w-full text-left p-3 rounded-lg border-2 transition-all hover:border-primary-300 dark:hover:border-primary-700 {}",
79                                        if is_selected {
80                                            "border-primary-500 bg-primary-50 dark:bg-primary-900/20 dark:border-primary-500"
81                                        } else {
82                                            "border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/30"
83                                        }
84                                    )}
85                                >
86                                    <div class="flex items-start gap-3">
87                                        // Radio button
88                                        <div class={format!(
89                                            "w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0 mt-0.5 transition-colors {}",
90                                            if is_selected {
91                                                "border-primary-500 bg-primary-500"
92                                            } else {
93                                                "border-gray-300 dark:border-gray-600"
94                                            }
95                                        )}>
96                                            if is_selected {
97                                                <div class="w-2.5 h-2.5 bg-white rounded-full"></div>
98                                            }
99                                        </div>
100
101                                        // Tool info
102                                        <div class="flex-1 min-w-0">
103                                            <div class="flex items-center justify-between gap-2">
104                                                <span class="font-medium text-gray-900 dark:text-white">
105                                                    { &tool.name }
106                                                </span>
107                                                if !tool.parameters.is_empty() {
108                                                    <span class="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1">
109                                                        <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
110                                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
111                                                        </svg>
112                                                        { format!("{} params", tool.parameters.len()) }
113                                                    </span>
114                                                }
115                                            </div>
116                                            if !tool.description.is_empty() {
117                                                <p class="text-xs text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">
118                                                    { &tool.description }
119                                                </p>
120                                            }
121                                        </div>
122                                    </div>
123                                </button>
124                            }
125                        })}
126
127                        if props.tools.len() > max_visible {
128                            <button class="w-full text-center py-2 text-sm text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 hover:bg-primary-50 dark:hover:bg-primary-900/10 rounded-lg transition-colors">
129                                { format!("Show {} more tools...", props.tools.len() - max_visible) }
130                            </button>
131                        }
132                    }
133                </div>
134            </div>
135        </div>
136    }
137}