holoviz-mcp 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- holoviz_mcp/__init__.py +18 -0
- holoviz_mcp/apps/__init__.py +1 -0
- holoviz_mcp/apps/configuration_viewer.py +116 -0
- holoviz_mcp/apps/holoviz_get_best_practices.py +173 -0
- holoviz_mcp/apps/holoviz_search.py +319 -0
- holoviz_mcp/apps/hvplot_get_docstring.py +255 -0
- holoviz_mcp/apps/hvplot_get_signature.py +252 -0
- holoviz_mcp/apps/hvplot_list_plot_types.py +83 -0
- holoviz_mcp/apps/panel_get_component.py +496 -0
- holoviz_mcp/apps/panel_get_component_parameters.py +467 -0
- holoviz_mcp/apps/panel_list_components.py +311 -0
- holoviz_mcp/apps/panel_list_packages.py +71 -0
- holoviz_mcp/apps/panel_search_components.py +312 -0
- holoviz_mcp/cli.py +75 -0
- holoviz_mcp/client.py +94 -0
- holoviz_mcp/config/__init__.py +29 -0
- holoviz_mcp/config/config.yaml +178 -0
- holoviz_mcp/config/loader.py +316 -0
- holoviz_mcp/config/models.py +208 -0
- holoviz_mcp/config/resources/best-practices/holoviews.md +423 -0
- holoviz_mcp/config/resources/best-practices/hvplot.md +465 -0
- holoviz_mcp/config/resources/best-practices/panel-material-ui.md +318 -0
- holoviz_mcp/config/resources/best-practices/panel.md +562 -0
- holoviz_mcp/config/schema.json +228 -0
- holoviz_mcp/holoviz_mcp/__init__.py +1 -0
- holoviz_mcp/holoviz_mcp/data.py +970 -0
- holoviz_mcp/holoviz_mcp/models.py +21 -0
- holoviz_mcp/holoviz_mcp/pages_design.md +407 -0
- holoviz_mcp/holoviz_mcp/server.py +220 -0
- holoviz_mcp/hvplot_mcp/__init__.py +1 -0
- holoviz_mcp/hvplot_mcp/server.py +146 -0
- holoviz_mcp/panel_mcp/__init__.py +17 -0
- holoviz_mcp/panel_mcp/data.py +319 -0
- holoviz_mcp/panel_mcp/models.py +124 -0
- holoviz_mcp/panel_mcp/server.py +443 -0
- holoviz_mcp/py.typed +0 -0
- holoviz_mcp/serve.py +36 -0
- holoviz_mcp/server.py +86 -0
- holoviz_mcp/shared/__init__.py +1 -0
- holoviz_mcp/shared/extract_tools.py +74 -0
- holoviz_mcp/thumbnails/configuration_viewer.png +0 -0
- holoviz_mcp/thumbnails/holoviz_get_best_practices.png +0 -0
- holoviz_mcp/thumbnails/holoviz_search.png +0 -0
- holoviz_mcp/thumbnails/hvplot_get_docstring.png +0 -0
- holoviz_mcp/thumbnails/hvplot_get_signature.png +0 -0
- holoviz_mcp/thumbnails/hvplot_list_plot_types.png +0 -0
- holoviz_mcp/thumbnails/panel_get_component.png +0 -0
- holoviz_mcp/thumbnails/panel_get_component_parameters.png +0 -0
- holoviz_mcp/thumbnails/panel_list_components.png +0 -0
- holoviz_mcp/thumbnails/panel_list_packages.png +0 -0
- holoviz_mcp/thumbnails/panel_search_components.png +0 -0
- holoviz_mcp-0.4.0.dist-info/METADATA +216 -0
- holoviz_mcp-0.4.0.dist-info/RECORD +56 -0
- holoviz_mcp-0.4.0.dist-info/WHEEL +4 -0
- holoviz_mcp-0.4.0.dist-info/entry_points.txt +2 -0
- holoviz_mcp-0.4.0.dist-info/licenses/LICENSE.txt +30 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""An app to list and filter Panel components.
|
|
2
|
+
|
|
3
|
+
An interactive version of the holoviz_mcp.panel_mcp.server.list_components tool.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import panel as pn
|
|
8
|
+
import panel_material_ui as pmui
|
|
9
|
+
import param
|
|
10
|
+
|
|
11
|
+
from holoviz_mcp.client import call_tool
|
|
12
|
+
|
|
13
|
+
pn.extension()
|
|
14
|
+
|
|
15
|
+
pn.pane.Markdown.disable_anchors = True
|
|
16
|
+
|
|
17
|
+
ALL = "All Packages"
|
|
18
|
+
|
|
19
|
+
ABOUT = """
|
|
20
|
+
## Panel Component List Tool
|
|
21
|
+
|
|
22
|
+
This tool provides an interactive interface for listing and filtering Panel components.
|
|
23
|
+
|
|
24
|
+
### How to Use This Tool
|
|
25
|
+
|
|
26
|
+
1. **Filter by Name**: Enter a component name (e.g., "Button") for exact case-insensitive matching
|
|
27
|
+
2. **Filter by Module Path**: Enter a module path prefix (e.g., "panel.widgets") to list all components in that module
|
|
28
|
+
3. **Filter by Package**: Select a specific package or "All Packages" to see components from all packages
|
|
29
|
+
4. **Click List Components**: View all matching components
|
|
30
|
+
|
|
31
|
+
Leave filters empty to see all components.
|
|
32
|
+
|
|
33
|
+
### Key Differences from Search Tool
|
|
34
|
+
|
|
35
|
+
Unlike the search tool which uses fuzzy matching and relevance scoring, this tool:
|
|
36
|
+
- Uses **exact/prefix matching** for precise filtering
|
|
37
|
+
- Returns **ALL matching components** (no result limit)
|
|
38
|
+
- Provides **faster responses** (no docstring content included)
|
|
39
|
+
- Best for **browsing** when you know what category you're looking for
|
|
40
|
+
|
|
41
|
+
### Results Display
|
|
42
|
+
|
|
43
|
+
Each result includes:
|
|
44
|
+
- **Component Name**: The class name of the component
|
|
45
|
+
- **Package**: Which Panel package contains it (e.g., "panel", "panel_material_ui")
|
|
46
|
+
- **Module Path**: The full Python import path
|
|
47
|
+
- **Description**: A brief summary of the component's functionality
|
|
48
|
+
|
|
49
|
+
You can click column headers to sort the results alphabetically.
|
|
50
|
+
|
|
51
|
+
### Learn More
|
|
52
|
+
|
|
53
|
+
For more information about this project, including setup instructions and advanced configuration options,
|
|
54
|
+
visit: [HoloViz MCP](https://marcskovmadsen.github.io/holoviz-mcp/).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ListComponentsConfiguration(param.Parameterized):
|
|
59
|
+
"""Configuration for Panel component list tool."""
|
|
60
|
+
|
|
61
|
+
component_name = param.String(default="Button", label="Component Name")
|
|
62
|
+
module_path = param.String(default="", label="Module Path")
|
|
63
|
+
package = param.Selector(default=ALL, objects=[ALL], label="Package")
|
|
64
|
+
|
|
65
|
+
list_components = param.Event(label="List Components")
|
|
66
|
+
|
|
67
|
+
results = param.List(default=[], doc="List results")
|
|
68
|
+
loading = param.Boolean(default=False, doc="Loading state")
|
|
69
|
+
error_message = param.String(default="", doc="Error message if listing fails")
|
|
70
|
+
|
|
71
|
+
def __init__(self, **params):
|
|
72
|
+
super().__init__(**params)
|
|
73
|
+
# Initialize with available packages
|
|
74
|
+
pn.state.execute(self._update_packages)
|
|
75
|
+
|
|
76
|
+
if pn.state.location:
|
|
77
|
+
pn.state.location.sync(self, parameters=["component_name", "module_path", "package"])
|
|
78
|
+
|
|
79
|
+
async def _update_packages(self):
|
|
80
|
+
"""Update the available Panel packages."""
|
|
81
|
+
result = await call_tool("panel_list_packages", {})
|
|
82
|
+
packages = [p for p in result.data]
|
|
83
|
+
self.param.package.objects = [ALL] + sorted(packages)
|
|
84
|
+
|
|
85
|
+
@param.depends("list_components", watch=True)
|
|
86
|
+
async def _update_results(self):
|
|
87
|
+
"""Execute list_components and update results."""
|
|
88
|
+
self.loading = True
|
|
89
|
+
self.error_message = ""
|
|
90
|
+
self.results = []
|
|
91
|
+
|
|
92
|
+
params = {}
|
|
93
|
+
|
|
94
|
+
# Add filters only if they have values
|
|
95
|
+
if self.component_name.strip():
|
|
96
|
+
params["name"] = self.component_name.strip()
|
|
97
|
+
if self.module_path.strip():
|
|
98
|
+
params["module_path"] = self.module_path.strip()
|
|
99
|
+
if self.package != ALL:
|
|
100
|
+
params["package"] = self.package
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
result = await call_tool("panel_list_components", params)
|
|
104
|
+
|
|
105
|
+
if result and hasattr(result, "data"):
|
|
106
|
+
self.results = result.data if result.data else []
|
|
107
|
+
if not self.results:
|
|
108
|
+
self.error_message = "No components found matching the filters"
|
|
109
|
+
else:
|
|
110
|
+
self.error_message = "List returned no data"
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
self.error_message = f"List failed: {str(e)}"
|
|
114
|
+
finally:
|
|
115
|
+
self.loading = False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ComponentsListViewer(pn.viewable.Viewer):
|
|
119
|
+
"""Viewer for displaying component list as a table."""
|
|
120
|
+
|
|
121
|
+
results = param.List(default=[], allow_refs=True, doc="List of components")
|
|
122
|
+
|
|
123
|
+
data = param.DataFrame(doc="DataFrame of components")
|
|
124
|
+
|
|
125
|
+
def __init__(self, **params):
|
|
126
|
+
super().__init__(**params)
|
|
127
|
+
|
|
128
|
+
no_components_message = pn.pane.Markdown(
|
|
129
|
+
"### No results to display.\n" "Click 'List Components' to see all available components, or use filters to narrow your search.",
|
|
130
|
+
sizing_mode="stretch_width",
|
|
131
|
+
visible=self.is_empty,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
titles = {
|
|
135
|
+
"name": "Component",
|
|
136
|
+
"package": "Package",
|
|
137
|
+
"module_path": "Path",
|
|
138
|
+
"description": "Description",
|
|
139
|
+
}
|
|
140
|
+
formatters = {"description": "textarea"}
|
|
141
|
+
table = pn.widgets.Tabulator(
|
|
142
|
+
self.param.data,
|
|
143
|
+
titles=titles,
|
|
144
|
+
formatters=formatters,
|
|
145
|
+
sizing_mode="stretch_width",
|
|
146
|
+
show_index=False,
|
|
147
|
+
name="Human Readable View",
|
|
148
|
+
disabled=True,
|
|
149
|
+
sortable=True,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
response = pn.pane.JSON(
|
|
153
|
+
self.param.results,
|
|
154
|
+
depth=3,
|
|
155
|
+
sizing_mode="stretch_width",
|
|
156
|
+
theme="dark",
|
|
157
|
+
name="Raw Response",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
tabs = pn.Tabs(table, response, visible=self.is_not_empty, sizing_mode="stretch_width")
|
|
161
|
+
|
|
162
|
+
self._layout = pmui.Column(
|
|
163
|
+
no_components_message,
|
|
164
|
+
tabs,
|
|
165
|
+
sizing_mode="stretch_width",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
@param.depends("results")
|
|
169
|
+
def is_empty(self):
|
|
170
|
+
"""Check if there are no results."""
|
|
171
|
+
return not bool(self.results)
|
|
172
|
+
|
|
173
|
+
@param.depends("results")
|
|
174
|
+
def is_not_empty(self):
|
|
175
|
+
"""Check if there are results."""
|
|
176
|
+
return bool(self.results)
|
|
177
|
+
|
|
178
|
+
@param.depends("results", watch=True)
|
|
179
|
+
def _update_data(self):
|
|
180
|
+
if not self.results:
|
|
181
|
+
data = pd.DataFrame(columns=["name", "package", "module_path", "description"])
|
|
182
|
+
else:
|
|
183
|
+
data = pd.DataFrame(self.results)[["name", "package", "module_path", "description"]]
|
|
184
|
+
self.data = data
|
|
185
|
+
|
|
186
|
+
def __panel__(self):
|
|
187
|
+
"""Return the Panel layout."""
|
|
188
|
+
return self._layout
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class PanelListComponentsApp(pn.viewable.Viewer):
|
|
192
|
+
"""Main application for listing and filtering Panel components."""
|
|
193
|
+
|
|
194
|
+
title = param.String(default="HoloViz MCP - panel_list_components Tool Demo")
|
|
195
|
+
|
|
196
|
+
def __init__(self, **params):
|
|
197
|
+
super().__init__(**params)
|
|
198
|
+
|
|
199
|
+
# Create configuration and components
|
|
200
|
+
self._config = ListComponentsConfiguration()
|
|
201
|
+
self._components_list = ComponentsListViewer(results=self._config.param.results)
|
|
202
|
+
|
|
203
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
204
|
+
self._name_input = pmui.TextInput.from_param(
|
|
205
|
+
self._config.param.component_name,
|
|
206
|
+
label="Component Name",
|
|
207
|
+
placeholder="e.g., Button, TextInput, Slider...",
|
|
208
|
+
variant="outlined",
|
|
209
|
+
sx={"width": "100%"},
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
self._module_path_input = pmui.TextInput.from_param(
|
|
213
|
+
self._config.param.module_path,
|
|
214
|
+
label="Module Path",
|
|
215
|
+
placeholder="e.g., panel.widgets, panel.pane...",
|
|
216
|
+
variant="outlined",
|
|
217
|
+
sx={"width": "100%"},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
self._package_select = pmui.Select.from_param(
|
|
221
|
+
self._config.param.package,
|
|
222
|
+
label="Package",
|
|
223
|
+
variant="outlined",
|
|
224
|
+
sx={"width": "100%"},
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
self._list_button = pmui.Button.from_param(
|
|
228
|
+
self._config.param.list_components,
|
|
229
|
+
label="List Components",
|
|
230
|
+
color="primary",
|
|
231
|
+
variant="contained",
|
|
232
|
+
sx={"width": "100%", "marginTop": "10px"},
|
|
233
|
+
on_click=lambda e: self._config.param.trigger("list_components"),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Status indicators
|
|
237
|
+
self._status_pane = pn.pane.Markdown(self._status_text, sizing_mode="stretch_width")
|
|
238
|
+
self._error_pane = pmui.Alert(
|
|
239
|
+
self._error_text,
|
|
240
|
+
alert_type="error",
|
|
241
|
+
visible=pn.rx(bool)(self._config.param.error_message),
|
|
242
|
+
sizing_mode="stretch_width",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Create static layout structure
|
|
246
|
+
self._sidebar = pn.Column(
|
|
247
|
+
pmui.Typography("## Filter Options", variant="h6"),
|
|
248
|
+
self._name_input,
|
|
249
|
+
self._module_path_input,
|
|
250
|
+
self._package_select,
|
|
251
|
+
self._list_button,
|
|
252
|
+
self._error_pane,
|
|
253
|
+
self._status_pane,
|
|
254
|
+
sizing_mode="stretch_width",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
self._main = pmui.Container(self._components_list, width_option="xl")
|
|
258
|
+
|
|
259
|
+
@param.depends("_config.loading", "_config.results")
|
|
260
|
+
def _status_text(self):
|
|
261
|
+
"""Generate status message."""
|
|
262
|
+
if self._config.loading:
|
|
263
|
+
return "_Loading components..._"
|
|
264
|
+
elif self._config.results:
|
|
265
|
+
return f"_Found {len(self._config.results)} component(s)_"
|
|
266
|
+
return ""
|
|
267
|
+
|
|
268
|
+
@param.depends("_config.error_message")
|
|
269
|
+
def _error_text(self):
|
|
270
|
+
"""Get error message."""
|
|
271
|
+
return self._config.error_message
|
|
272
|
+
|
|
273
|
+
def __panel__(self):
|
|
274
|
+
"""Return the main page layout."""
|
|
275
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
276
|
+
# About button and dialog
|
|
277
|
+
about_button = pmui.IconButton(
|
|
278
|
+
label="About",
|
|
279
|
+
icon="info",
|
|
280
|
+
description="Click to learn about the Panel List Components Tool.",
|
|
281
|
+
sizing_mode="fixed",
|
|
282
|
+
color="light",
|
|
283
|
+
margin=(10, 0),
|
|
284
|
+
)
|
|
285
|
+
about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
|
|
286
|
+
about_button.js_on_click(args={"about": about}, code="about.data.open = true")
|
|
287
|
+
|
|
288
|
+
# GitHub button
|
|
289
|
+
github_button = pmui.IconButton(
|
|
290
|
+
label="Github",
|
|
291
|
+
icon="star",
|
|
292
|
+
description="Give HoloViz-MCP a star on GitHub",
|
|
293
|
+
sizing_mode="fixed",
|
|
294
|
+
color="light",
|
|
295
|
+
margin=(10, 0),
|
|
296
|
+
href="https://github.com/MarcSkovMadsen/holoviz-mcp",
|
|
297
|
+
target="_blank",
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return pmui.Page(
|
|
301
|
+
title=self.title,
|
|
302
|
+
site_url="./",
|
|
303
|
+
sidebar=[self._sidebar],
|
|
304
|
+
header=[pn.Row(pn.Spacer(), about_button, github_button, align="end")],
|
|
305
|
+
main=[about, self._main],
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
if pn.state.served:
|
|
310
|
+
pn.extension("tabulator")
|
|
311
|
+
PanelListComponentsApp().servable()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""An app to demo the usage and responses of the panel_list_packages tool."""
|
|
2
|
+
|
|
3
|
+
import panel as pn
|
|
4
|
+
import panel_material_ui as pmui
|
|
5
|
+
|
|
6
|
+
from holoviz_mcp.client import call_tool
|
|
7
|
+
|
|
8
|
+
ABOUT = """
|
|
9
|
+
# Panel List Packages Tool
|
|
10
|
+
|
|
11
|
+
The `panel_list_packages` tool lists all installed packages that provide Panel `Viewable` components.
|
|
12
|
+
|
|
13
|
+
## Purpose
|
|
14
|
+
|
|
15
|
+
Discover what Panel-related packages are available in your environment.
|
|
16
|
+
This helps you understand which packages you can use in the 'package' parameter of other tools.
|
|
17
|
+
|
|
18
|
+
## Returns
|
|
19
|
+
|
|
20
|
+
A list of package names that provide Panel components, sorted alphabetically.
|
|
21
|
+
|
|
22
|
+
**Examples:** `["panel"]` or `["panel", "panel_material_ui"]`
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pn.cache
|
|
27
|
+
async def panel_list_packages() -> list[str]:
|
|
28
|
+
"""Demo the usage and responses of the panel_list_packages tool."""
|
|
29
|
+
response = await call_tool(
|
|
30
|
+
tool_name="panel_list_packages",
|
|
31
|
+
parameters={},
|
|
32
|
+
)
|
|
33
|
+
return response.data
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_app():
|
|
37
|
+
"""Create the Panel Material UI app for demoing the panel_list_packages tool."""
|
|
38
|
+
about_button = pmui.IconButton(
|
|
39
|
+
label="About",
|
|
40
|
+
icon="info",
|
|
41
|
+
description="Click to learn about the Panel List Packages Tool.",
|
|
42
|
+
sizing_mode="fixed",
|
|
43
|
+
color="light",
|
|
44
|
+
margin=(10, 0),
|
|
45
|
+
)
|
|
46
|
+
about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
|
|
47
|
+
about_button.js_on_click(args={"about": about}, code="about.data.open = true")
|
|
48
|
+
|
|
49
|
+
# GitHub button
|
|
50
|
+
github_button = pmui.IconButton(
|
|
51
|
+
label="Github",
|
|
52
|
+
icon="star",
|
|
53
|
+
description="Give HoloViz-MCP a star on GitHub",
|
|
54
|
+
sizing_mode="fixed",
|
|
55
|
+
color="light",
|
|
56
|
+
margin=(10, 0),
|
|
57
|
+
href="https://github.com/MarcSkovMadsen/holoviz-mcp",
|
|
58
|
+
target="_blank",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
main = pmui.Container(about, pn.pane.JSON(panel_list_packages, theme="dark", depth=3, sizing_mode="stretch_width"))
|
|
62
|
+
|
|
63
|
+
return pmui.Page(
|
|
64
|
+
title="HoloViz-MCP: panel_list_packages Tool Demo",
|
|
65
|
+
header=[pmui.Row(pn.HSpacer(), about_button, github_button, sizing_mode="stretch_width")],
|
|
66
|
+
main=[main],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if pn.state.served:
|
|
71
|
+
create_app().servable()
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""An app to search for Panel components and examples.
|
|
2
|
+
|
|
3
|
+
An interactive version of the holoviz_mcp.panel_mcp.server.search tool.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import panel as pn
|
|
8
|
+
import panel_material_ui as pmui
|
|
9
|
+
import param
|
|
10
|
+
|
|
11
|
+
from holoviz_mcp.client import call_tool
|
|
12
|
+
|
|
13
|
+
pn.extension()
|
|
14
|
+
|
|
15
|
+
pn.pane.Markdown.disable_anchors = True
|
|
16
|
+
|
|
17
|
+
ALL = "All Packages"
|
|
18
|
+
|
|
19
|
+
ABOUT = """
|
|
20
|
+
## Panel Component Search Tool
|
|
21
|
+
|
|
22
|
+
The `panel_search_components` tool provides an interactive interface for searching Panel components and discovering their capabilities.
|
|
23
|
+
|
|
24
|
+
### How to Use This Search Tool
|
|
25
|
+
|
|
26
|
+
1. **Enter a Query**: Type component names, features, or descriptions (e.g., "Button", "input", "layout")
|
|
27
|
+
2. **Filter by Package**: Narrow your search to specific Panel packages (or search all)
|
|
28
|
+
3. **Set Max Results**: Control how many results to display
|
|
29
|
+
4. **Click Search**: View matching components with relevance scores
|
|
30
|
+
|
|
31
|
+
### How the Search Works
|
|
32
|
+
|
|
33
|
+
The search looks through component names, module paths, and docstrings to find matches to the *Search Query*.
|
|
34
|
+
|
|
35
|
+
### Search Results
|
|
36
|
+
|
|
37
|
+
Each result includes:
|
|
38
|
+
|
|
39
|
+
- **Component Name**: The class name of the component
|
|
40
|
+
- **Relevance Score**: How closely it matches your query
|
|
41
|
+
- **Package**: Which Panel package contains it
|
|
42
|
+
- **Module Path**: The full Python import path
|
|
43
|
+
- **Description**: A brief summary of the component's functionality
|
|
44
|
+
|
|
45
|
+
### Learn More
|
|
46
|
+
|
|
47
|
+
For more information about this project, including setup instructions and advanced configuration options,
|
|
48
|
+
visit: [HoloViz MCP](https://marcskovmadsen.github.io/holoviz-mcp/).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SearchConfiguration(param.Parameterized):
|
|
53
|
+
"""Configuration for Panel component search tool."""
|
|
54
|
+
|
|
55
|
+
query = param.String(default="Button", label="Search Query")
|
|
56
|
+
package = param.Selector(default=ALL, objects=[ALL], label="Package")
|
|
57
|
+
limit = param.Integer(default=10, bounds=(1, 50), label="Max Results")
|
|
58
|
+
|
|
59
|
+
search = param.Event(label="Search")
|
|
60
|
+
|
|
61
|
+
results = param.List(default=[], doc="Search results")
|
|
62
|
+
loading = param.Boolean(default=False, doc="Loading state")
|
|
63
|
+
error_message = param.String(default="", doc="Error message if search fails")
|
|
64
|
+
|
|
65
|
+
def __init__(self, **params):
|
|
66
|
+
super().__init__(**params)
|
|
67
|
+
if pn.state.location:
|
|
68
|
+
pn.state.location.sync(self, ["query", "package", "limit"])
|
|
69
|
+
|
|
70
|
+
# Initialize with available packages
|
|
71
|
+
pn.state.execute(self._update_packages)
|
|
72
|
+
|
|
73
|
+
async def _update_packages(self):
|
|
74
|
+
"""Update the available Panel packages."""
|
|
75
|
+
result = await call_tool("panel_list_packages", {})
|
|
76
|
+
packages = [p for p in result.data]
|
|
77
|
+
self.param.package.objects = [ALL] + sorted(packages)
|
|
78
|
+
|
|
79
|
+
@param.depends("search", watch=True)
|
|
80
|
+
async def _update_results(self):
|
|
81
|
+
"""Execute search and update results."""
|
|
82
|
+
if not self.query.strip():
|
|
83
|
+
self.error_message = "Please enter a search query"
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
self.loading = True
|
|
87
|
+
self.error_message = ""
|
|
88
|
+
self.results = []
|
|
89
|
+
|
|
90
|
+
params = {
|
|
91
|
+
"query": self.query,
|
|
92
|
+
"limit": self.limit,
|
|
93
|
+
}
|
|
94
|
+
# Add package filter if not "All"
|
|
95
|
+
if self.package != ALL:
|
|
96
|
+
params["package"] = self.package
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
result = await call_tool("panel_search_components", params)
|
|
100
|
+
|
|
101
|
+
if result and hasattr(result, "data"):
|
|
102
|
+
self.results = result.data if result.data else []
|
|
103
|
+
if not self.results:
|
|
104
|
+
self.error_message = "No results found"
|
|
105
|
+
else:
|
|
106
|
+
self.error_message = "Search returned no data"
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
self.error_message = f"Search failed: {str(e)}"
|
|
110
|
+
finally:
|
|
111
|
+
self.loading = False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class SearchResultsViewer(pn.viewable.Viewer):
|
|
115
|
+
"""Viewer for displaying search results as a menu list."""
|
|
116
|
+
|
|
117
|
+
results = param.List(default=[], allow_refs=True, doc="List of search results")
|
|
118
|
+
|
|
119
|
+
data = param.DataFrame(doc="DataFrame of search results")
|
|
120
|
+
|
|
121
|
+
def __init__(self, **params):
|
|
122
|
+
super().__init__(**params)
|
|
123
|
+
|
|
124
|
+
no_components_message = pn.pane.Markdown(
|
|
125
|
+
"### No results to display.\n" "Please enter a search query and click 'Search' to find Panel components.",
|
|
126
|
+
sizing_mode="stretch_width",
|
|
127
|
+
visible=self.is_empty,
|
|
128
|
+
)
|
|
129
|
+
titles = {
|
|
130
|
+
"name": "Component",
|
|
131
|
+
"relevance_score": "Relevance",
|
|
132
|
+
"package": "Package",
|
|
133
|
+
"module_path": "Path",
|
|
134
|
+
"description": "Description",
|
|
135
|
+
}
|
|
136
|
+
formatters = {"description": "textarea"}
|
|
137
|
+
table = pn.widgets.Tabulator(
|
|
138
|
+
self.param.data,
|
|
139
|
+
titles=titles,
|
|
140
|
+
formatters=formatters,
|
|
141
|
+
sizing_mode="stretch_width",
|
|
142
|
+
show_index=False,
|
|
143
|
+
name="Human Readable View",
|
|
144
|
+
disabled=True,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
raw = pn.pane.JSON(
|
|
148
|
+
self.param.results,
|
|
149
|
+
depth=3,
|
|
150
|
+
theme="dark",
|
|
151
|
+
sizing_mode="stretch_width",
|
|
152
|
+
name="Raw Response",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
tabs = pn.Tabs(table, raw, visible=self.is_not_empty, sizing_mode="stretch_width")
|
|
156
|
+
|
|
157
|
+
self._layout = pmui.Column(
|
|
158
|
+
no_components_message,
|
|
159
|
+
tabs,
|
|
160
|
+
sizing_mode="stretch_width",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
@param.depends("results")
|
|
164
|
+
def is_empty(self):
|
|
165
|
+
"""Check if there are no results."""
|
|
166
|
+
return not bool(self.results)
|
|
167
|
+
|
|
168
|
+
@param.depends("results")
|
|
169
|
+
def is_not_empty(self):
|
|
170
|
+
"""Check if there are results."""
|
|
171
|
+
return bool(self.results)
|
|
172
|
+
|
|
173
|
+
@param.depends("results", watch=True)
|
|
174
|
+
def _update_data(self):
|
|
175
|
+
if not self.results:
|
|
176
|
+
data = pd.DataFrame(columns=["name", "relevance_score", "package", "module_path", "description"])
|
|
177
|
+
else:
|
|
178
|
+
data = pd.DataFrame(self.results)[["name", "relevance_score", "package", "module_path", "description"]]
|
|
179
|
+
self.data = data
|
|
180
|
+
|
|
181
|
+
def __panel__(self):
|
|
182
|
+
"""Return the Panel layout."""
|
|
183
|
+
return self._layout
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class PanelSearchApp(pn.viewable.Viewer):
|
|
187
|
+
"""Main application for exploring Panel component search."""
|
|
188
|
+
|
|
189
|
+
title = param.String(default="HoloViz MCP - panel_search_components Tool Demo")
|
|
190
|
+
|
|
191
|
+
def __init__(self, **params):
|
|
192
|
+
super().__init__(**params)
|
|
193
|
+
|
|
194
|
+
# Create configuration and components
|
|
195
|
+
self._config = SearchConfiguration()
|
|
196
|
+
self._search_results = SearchResultsViewer(results=self._config.param.results)
|
|
197
|
+
|
|
198
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
199
|
+
self._search_input = pmui.TextInput.from_param(
|
|
200
|
+
self._config.param.query,
|
|
201
|
+
label="Search Query",
|
|
202
|
+
placeholder="e.g., Button, TextInput, layout...",
|
|
203
|
+
variant="outlined",
|
|
204
|
+
sx={"width": "100%"},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
self._package_select = pmui.Select.from_param(
|
|
208
|
+
self._config.param.package,
|
|
209
|
+
label="Package",
|
|
210
|
+
variant="outlined",
|
|
211
|
+
sx={"width": "100%"},
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
self._limit_input = pmui.IntInput.from_param(
|
|
215
|
+
self._config.param.limit,
|
|
216
|
+
label="Max Results",
|
|
217
|
+
variant="outlined",
|
|
218
|
+
sx={"width": "100%"},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
self._search_button = pmui.Button.from_param(
|
|
222
|
+
self._config.param.search,
|
|
223
|
+
label="Search",
|
|
224
|
+
color="primary",
|
|
225
|
+
variant="contained",
|
|
226
|
+
sx={"width": "100%", "marginTop": "10px"},
|
|
227
|
+
on_click=lambda e: self._config.param.trigger("search"),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Status indicators
|
|
231
|
+
self._status_pane = pn.pane.Markdown(self._status_text, sizing_mode="stretch_width")
|
|
232
|
+
self._error_pane = pmui.Alert(
|
|
233
|
+
self._error_text,
|
|
234
|
+
alert_type="error",
|
|
235
|
+
visible=pn.rx(bool)(self._config.param.error_message),
|
|
236
|
+
sizing_mode="stretch_width",
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Create static layout structure
|
|
240
|
+
self._sidebar = pn.Column(
|
|
241
|
+
pmui.Typography("## Search Filters", variant="h6"),
|
|
242
|
+
self._search_input,
|
|
243
|
+
self._package_select,
|
|
244
|
+
self._limit_input,
|
|
245
|
+
self._search_button,
|
|
246
|
+
self._error_pane,
|
|
247
|
+
self._status_pane,
|
|
248
|
+
sizing_mode="stretch_width",
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
self._main = pmui.Container(self._search_results, width_option="xl")
|
|
252
|
+
|
|
253
|
+
@param.depends("_config.loading", "_config.results")
|
|
254
|
+
def _status_text(self):
|
|
255
|
+
"""Generate status message."""
|
|
256
|
+
if self._config.loading:
|
|
257
|
+
return "_Searching..._"
|
|
258
|
+
elif self._config.results:
|
|
259
|
+
return f"_Found {len(self._config.results)} result(s)_"
|
|
260
|
+
return ""
|
|
261
|
+
|
|
262
|
+
@param.depends("_config.error_message")
|
|
263
|
+
def _error_text(self):
|
|
264
|
+
"""Get error message."""
|
|
265
|
+
return self._config.error_message
|
|
266
|
+
|
|
267
|
+
def __panel__(self):
|
|
268
|
+
"""Return the main page layout."""
|
|
269
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
270
|
+
# About button and dialog
|
|
271
|
+
about_button = pmui.IconButton(
|
|
272
|
+
label="About",
|
|
273
|
+
icon="info",
|
|
274
|
+
description="Click to learn about the Panel Search Tool.",
|
|
275
|
+
sizing_mode="fixed",
|
|
276
|
+
color="light",
|
|
277
|
+
margin=(10, 0),
|
|
278
|
+
)
|
|
279
|
+
about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
|
|
280
|
+
about_button.js_on_click(args={"about": about}, code="about.data.open = true")
|
|
281
|
+
|
|
282
|
+
# GitHub button
|
|
283
|
+
github_button = pmui.IconButton(
|
|
284
|
+
label="GitHub",
|
|
285
|
+
icon="star",
|
|
286
|
+
description="Give HoloViz-MCP a star on GitHub",
|
|
287
|
+
sizing_mode="fixed",
|
|
288
|
+
color="light",
|
|
289
|
+
margin=(10, 0),
|
|
290
|
+
href="https://github.com/MarcSkovMadsen/holoviz-mcp",
|
|
291
|
+
target="_blank",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return pmui.Page(
|
|
295
|
+
title=self.title,
|
|
296
|
+
site_url="./",
|
|
297
|
+
sidebar=[self._sidebar],
|
|
298
|
+
header=[pn.Row(pn.Spacer(), about_button, github_button, align="end")],
|
|
299
|
+
main=[about, self._main],
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def create_app(**params):
|
|
304
|
+
"""Create and return the servable Panel Search app."""
|
|
305
|
+
app = PanelSearchApp(**params)
|
|
306
|
+
return app.__panel__()
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# Make the app servable
|
|
310
|
+
if pn.state.served:
|
|
311
|
+
pn.extension("tabulator")
|
|
312
|
+
create_app().servable()
|