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,496 @@
|
|
|
1
|
+
"""An app to retrieve complete details about a Panel component.
|
|
2
|
+
|
|
3
|
+
An interactive version of the holoviz_mcp.panel_mcp.server.get_component 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 Get Component Tool
|
|
21
|
+
|
|
22
|
+
This tool provides detailed information about a **single** Panel component, including its complete docstring,
|
|
23
|
+
initialization signature, and parameter specifications.
|
|
24
|
+
|
|
25
|
+
### How to Use This Tool
|
|
26
|
+
|
|
27
|
+
1. **Component Name**: Enter the component name (e.g., "Button", "TextInput", "Slider")
|
|
28
|
+
2. **Module Path** (optional): Enter exact module path for precision (e.g., "panel.widgets.Button")
|
|
29
|
+
3. **Package** (optional): Select a package to disambiguate components with same name
|
|
30
|
+
4. **Click Get Component**: Retrieve complete component details
|
|
31
|
+
|
|
32
|
+
**Note**: Component names are case-insensitive.
|
|
33
|
+
|
|
34
|
+
### Key Features
|
|
35
|
+
|
|
36
|
+
Unlike `list_components` which returns summaries, this tool:
|
|
37
|
+
- Returns **complete details** for exactly ONE component
|
|
38
|
+
- Includes **full docstring** with usage examples
|
|
39
|
+
- Shows **initialization signature** with all parameters
|
|
40
|
+
- Provides **detailed parameter specifications** (types, defaults, constraints)
|
|
41
|
+
- Best for **understanding how to use** a specific component
|
|
42
|
+
|
|
43
|
+
### Handling Ambiguous Names
|
|
44
|
+
|
|
45
|
+
When a component name matches multiple packages (e.g., "Button" exists in both "panel" and "panel_material_ui"):
|
|
46
|
+
- The tool will **error** and show all matching module paths
|
|
47
|
+
- Use the **Package filter** to specify which one you want
|
|
48
|
+
- Or use the exact **Module Path** for precision
|
|
49
|
+
|
|
50
|
+
### Results Display
|
|
51
|
+
|
|
52
|
+
The component details are organized into expandable sections:
|
|
53
|
+
- **Overview**: Name, package, module path, and description
|
|
54
|
+
- **Docstring**: Complete documentation with usage examples
|
|
55
|
+
- **Parameters**: Table of all parameters with types, defaults, and documentation
|
|
56
|
+
- **Signature**: Initialization method signature
|
|
57
|
+
|
|
58
|
+
### Example Usage
|
|
59
|
+
|
|
60
|
+
**Get Panel's Button**:
|
|
61
|
+
- Component Name: `Button`
|
|
62
|
+
- Package: `panel`
|
|
63
|
+
|
|
64
|
+
**Get Material UI Button**:
|
|
65
|
+
- Component Name: `Button`
|
|
66
|
+
- Package: `panel_material_ui`
|
|
67
|
+
|
|
68
|
+
**Get by exact path**:
|
|
69
|
+
- Module Path: `panel.widgets.button.Button`
|
|
70
|
+
- (Leave other fields empty)
|
|
71
|
+
|
|
72
|
+
### Learn More
|
|
73
|
+
|
|
74
|
+
For more information visit: [HoloViz MCP](https://marcskovmadsen.github.io/holoviz-mcp/).
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class GetComponentConfiguration(param.Parameterized):
|
|
79
|
+
"""Configuration for Panel get_component tool."""
|
|
80
|
+
|
|
81
|
+
component_name = param.String(default="Button", label="Component Name")
|
|
82
|
+
module_path = param.String(default="", label="Module Path")
|
|
83
|
+
package = param.Selector(default="panel", objects=[ALL, "panel"], label="Package")
|
|
84
|
+
|
|
85
|
+
get_component = param.Event(label="Get Component")
|
|
86
|
+
|
|
87
|
+
result = param.Dict(default={}, doc="Component details result")
|
|
88
|
+
loading = param.Boolean(default=False, doc="Loading state")
|
|
89
|
+
error_message = param.String(default="", doc="Error message if request fails")
|
|
90
|
+
|
|
91
|
+
def __init__(self, **params):
|
|
92
|
+
super().__init__(**params)
|
|
93
|
+
if pn.state.location:
|
|
94
|
+
pn.state.location.sync(self, ["component_name", "module_path", "package"])
|
|
95
|
+
# Initialize with available packages
|
|
96
|
+
pn.state.execute(self._update_packages)
|
|
97
|
+
# Load default component on startup
|
|
98
|
+
pn.state.execute(self._update_result)
|
|
99
|
+
|
|
100
|
+
async def _update_packages(self):
|
|
101
|
+
"""Update the available Panel packages."""
|
|
102
|
+
try:
|
|
103
|
+
result = await call_tool("panel_list_packages", {})
|
|
104
|
+
packages = sorted([p for p in result.data])
|
|
105
|
+
self.param.package.objects = [ALL] + packages
|
|
106
|
+
except Exception as e:
|
|
107
|
+
self.error_message = f"Failed to load packages: {str(e)}"
|
|
108
|
+
|
|
109
|
+
@param.depends("get_component", watch=True)
|
|
110
|
+
async def _update_result(self):
|
|
111
|
+
"""Execute get_component and update result."""
|
|
112
|
+
self.loading = True
|
|
113
|
+
self.error_message = ""
|
|
114
|
+
self.result = {}
|
|
115
|
+
|
|
116
|
+
params = {}
|
|
117
|
+
|
|
118
|
+
# Add filters based on what's provided
|
|
119
|
+
if self.component_name.strip():
|
|
120
|
+
params["name"] = self.component_name.strip()
|
|
121
|
+
if self.module_path.strip():
|
|
122
|
+
params["module_path"] = self.module_path.strip()
|
|
123
|
+
if self.package != ALL:
|
|
124
|
+
params["package"] = self.package
|
|
125
|
+
|
|
126
|
+
# At least one filter must be provided
|
|
127
|
+
if not params:
|
|
128
|
+
self.error_message = "Please provide at least a component name, module path, or package"
|
|
129
|
+
self.loading = False
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
result = await call_tool("panel_get_component", params)
|
|
134
|
+
if result and hasattr(result, "structured_content"):
|
|
135
|
+
# get_component returns a single ComponentDetails object, not a list
|
|
136
|
+
if result.structured_content:
|
|
137
|
+
self.result = result.structured_content
|
|
138
|
+
else:
|
|
139
|
+
self.error_message = "No component found matching the criteria"
|
|
140
|
+
else:
|
|
141
|
+
self.error_message = "Request returned no data"
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
error_str = str(e)
|
|
145
|
+
# Make error messages more user-friendly
|
|
146
|
+
if "Multiple components found" in error_str:
|
|
147
|
+
self.error_message = f"⚠️ Ambiguous component name.\n\n{error_str}\n\nPlease specify the package or use exact module path."
|
|
148
|
+
elif "No components found" in error_str:
|
|
149
|
+
self.error_message = f"❌ Component not found.\n\n{error_str}"
|
|
150
|
+
else:
|
|
151
|
+
self.error_message = f"Request failed: {error_str}"
|
|
152
|
+
finally:
|
|
153
|
+
self.loading = False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class ComponentDetailsViewer(pn.viewable.Viewer):
|
|
157
|
+
"""Viewer for displaying complete component details."""
|
|
158
|
+
|
|
159
|
+
result = param.Dict(default={}, allow_refs=True, doc="Component details")
|
|
160
|
+
|
|
161
|
+
def __init__(self, **params):
|
|
162
|
+
super().__init__(**params)
|
|
163
|
+
|
|
164
|
+
# Empty state message
|
|
165
|
+
no_component_message = pn.pane.Markdown(
|
|
166
|
+
"### No component details to display.\n\n"
|
|
167
|
+
"Click 'Get Component' to retrieve information about a Panel component.\n\n"
|
|
168
|
+
"The default 'Button' component will be loaded automatically.",
|
|
169
|
+
sizing_mode="stretch_width",
|
|
170
|
+
visible=self.is_empty,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Create accordion sections with reactive content
|
|
174
|
+
self._overview_section = self._create_overview_section()
|
|
175
|
+
self._docstring_section = self._create_docstring_section()
|
|
176
|
+
self._parameters_section = self._create_parameters_section()
|
|
177
|
+
self._signature_section = self._create_signature_section()
|
|
178
|
+
|
|
179
|
+
accordion = pmui.Accordion(
|
|
180
|
+
("📋 Overview", self._overview_section),
|
|
181
|
+
("📖 Docstring", self._docstring_section),
|
|
182
|
+
("🔧 Signature", self._signature_section),
|
|
183
|
+
("⚙️ Parameters", self._parameters_section),
|
|
184
|
+
active=[0, 1], # Open first two sections by default
|
|
185
|
+
visible=self.is_not_empty,
|
|
186
|
+
sizing_mode="stretch_width",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
formatted_view = pmui.Column(
|
|
190
|
+
no_component_message,
|
|
191
|
+
accordion,
|
|
192
|
+
name="Human Readable View",
|
|
193
|
+
sizing_mode="stretch_width",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
response_pane = pn.pane.JSON(
|
|
197
|
+
self.param.result,
|
|
198
|
+
theme="dark",
|
|
199
|
+
name="Raw Response",
|
|
200
|
+
depth=2,
|
|
201
|
+
sizing_mode="stretch_width",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
self._layout = pn.Tabs(formatted_view, response_pane)
|
|
205
|
+
|
|
206
|
+
def _create_overview_section(self):
|
|
207
|
+
"""Create overview section with component metadata."""
|
|
208
|
+
return pmui.Column(
|
|
209
|
+
# Component Name
|
|
210
|
+
pmui.Typography("Component Name", variant="subtitle2", sx={"color": "text.secondary", "marginBottom": "5px"}),
|
|
211
|
+
pmui.Typography(self._get_name, variant="h6", sx={"color": "primary.main", "marginBottom": "15px"}),
|
|
212
|
+
pmui.Divider(),
|
|
213
|
+
# Package
|
|
214
|
+
pmui.Typography("Package", variant="subtitle2", sx={"color": "text.secondary", "marginTop": "15px", "marginBottom": "5px"}),
|
|
215
|
+
pmui.Typography(self._get_package, variant="body1", sx={"marginBottom": "15px"}),
|
|
216
|
+
pmui.Divider(),
|
|
217
|
+
# Module Path
|
|
218
|
+
pmui.Typography("Module Path", variant="subtitle2", sx={"color": "text.secondary", "marginTop": "15px", "marginBottom": "5px"}),
|
|
219
|
+
pmui.Typography(self._get_module_path, variant="body2", sx={"fontFamily": "monospace", "marginBottom": "15px"}),
|
|
220
|
+
pmui.Divider(),
|
|
221
|
+
# Description
|
|
222
|
+
pmui.Typography("Description", variant="subtitle2", sx={"color": "text.secondary", "marginTop": "15px", "marginBottom": "5px"}),
|
|
223
|
+
pn.pane.Markdown(self._get_description),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def _create_docstring_section(self):
|
|
227
|
+
"""Create docstring section."""
|
|
228
|
+
return pn.pane.Markdown(
|
|
229
|
+
self._get_docstring,
|
|
230
|
+
sizing_mode="stretch_width",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def _create_parameters_section(self):
|
|
234
|
+
"""Create parameters section with tabulator."""
|
|
235
|
+
return pmui.Column(
|
|
236
|
+
pn.pane.Markdown(self._get_param_count, sizing_mode="stretch_width"),
|
|
237
|
+
pn.widgets.Tabulator(
|
|
238
|
+
self._get_parameters_df,
|
|
239
|
+
sizing_mode="stretch_width",
|
|
240
|
+
show_index=False,
|
|
241
|
+
disabled=True,
|
|
242
|
+
sortable=True,
|
|
243
|
+
titles={
|
|
244
|
+
"name": "Parameter",
|
|
245
|
+
"type": "Type",
|
|
246
|
+
"default": "Default",
|
|
247
|
+
"doc": "Documentation",
|
|
248
|
+
"constraints": "Constraints",
|
|
249
|
+
},
|
|
250
|
+
formatters={"doc": "textarea"},
|
|
251
|
+
header_filters=True,
|
|
252
|
+
),
|
|
253
|
+
sizing_mode="stretch_width",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def _create_signature_section(self):
|
|
257
|
+
"""Create signature section."""
|
|
258
|
+
return pmui.Column(
|
|
259
|
+
pmui.Typography("Initialization Signature", variant="subtitle2", sx={"color": "text.secondary", "marginBottom": "10px"}),
|
|
260
|
+
pn.pane.Markdown(
|
|
261
|
+
self._get_signature_markdown,
|
|
262
|
+
sizing_mode="stretch_width",
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
@param.depends("result")
|
|
267
|
+
def is_empty(self):
|
|
268
|
+
"""Check if there is no result."""
|
|
269
|
+
return not bool(self.result)
|
|
270
|
+
|
|
271
|
+
@param.depends("result")
|
|
272
|
+
def is_not_empty(self):
|
|
273
|
+
"""Check if there is a result."""
|
|
274
|
+
return bool(self.result)
|
|
275
|
+
|
|
276
|
+
@param.depends("result")
|
|
277
|
+
def _get_name(self):
|
|
278
|
+
"""Get component name."""
|
|
279
|
+
return self.result.get("name", "N/A")
|
|
280
|
+
|
|
281
|
+
@param.depends("result")
|
|
282
|
+
def _get_package(self):
|
|
283
|
+
"""Get component package."""
|
|
284
|
+
return self.result.get("package", "N/A")
|
|
285
|
+
|
|
286
|
+
@param.depends("result")
|
|
287
|
+
def _get_module_path(self):
|
|
288
|
+
"""Get component module path."""
|
|
289
|
+
return self.result.get("module_path", "N/A")
|
|
290
|
+
|
|
291
|
+
@param.depends("result")
|
|
292
|
+
def _get_description(self):
|
|
293
|
+
"""Get component description."""
|
|
294
|
+
return self.result.get("description", "_No description available._")
|
|
295
|
+
|
|
296
|
+
@param.depends("result")
|
|
297
|
+
def _get_docstring(self):
|
|
298
|
+
"""Get component docstring."""
|
|
299
|
+
docstring = self.result.get("docstring", "")
|
|
300
|
+
if not docstring:
|
|
301
|
+
return "_No docstring available._"
|
|
302
|
+
# Wrap in code block if it looks like plain text
|
|
303
|
+
if not docstring.strip().startswith("#"):
|
|
304
|
+
return f"```markdown\n{docstring}\n```"
|
|
305
|
+
return docstring
|
|
306
|
+
|
|
307
|
+
@param.depends("result")
|
|
308
|
+
def _get_param_count(self):
|
|
309
|
+
"""Get parameter count message."""
|
|
310
|
+
params = self.result.get("parameters", {})
|
|
311
|
+
count = len(params)
|
|
312
|
+
return f"**{count} parameter(s)** defined for this component:"
|
|
313
|
+
|
|
314
|
+
@param.depends("result")
|
|
315
|
+
def _get_parameters_df(self):
|
|
316
|
+
"""Convert parameters dict to DataFrame."""
|
|
317
|
+
params = self.result.get("parameters", {})
|
|
318
|
+
if not params:
|
|
319
|
+
return pd.DataFrame(columns=["name", "type", "default", "doc", "constraints"])
|
|
320
|
+
|
|
321
|
+
rows = []
|
|
322
|
+
for param_name, param_info in params.items():
|
|
323
|
+
# Extract common fields
|
|
324
|
+
param_type = param_info.get("type", "Unknown")
|
|
325
|
+
default = param_info.get("default", "")
|
|
326
|
+
doc = param_info.get("doc", "")
|
|
327
|
+
|
|
328
|
+
# Build constraints string from bounds, objects, etc.
|
|
329
|
+
constraints = []
|
|
330
|
+
if "bounds" in param_info and param_info["bounds"] is not None:
|
|
331
|
+
constraints.append(f"bounds: {param_info['bounds']}")
|
|
332
|
+
if "objects" in param_info and param_info["objects"] is not None:
|
|
333
|
+
objects = param_info["objects"]
|
|
334
|
+
if isinstance(objects, list) and len(objects) > 5:
|
|
335
|
+
constraints.append(f"objects: {len(objects)} options")
|
|
336
|
+
else:
|
|
337
|
+
constraints.append(f"objects: {objects}")
|
|
338
|
+
if "regex" in param_info and param_info["regex"] is not None:
|
|
339
|
+
constraints.append(f"regex: {param_info['regex']}")
|
|
340
|
+
if "allow_None" in param_info and param_info["allow_None"]:
|
|
341
|
+
constraints.append("allow_None: True")
|
|
342
|
+
if "readonly" in param_info and param_info["readonly"]:
|
|
343
|
+
constraints.append("readonly: True")
|
|
344
|
+
if "constant" in param_info and param_info["constant"]:
|
|
345
|
+
constraints.append("constant: True")
|
|
346
|
+
|
|
347
|
+
constraint_str = ", ".join(constraints) if constraints else ""
|
|
348
|
+
|
|
349
|
+
rows.append(
|
|
350
|
+
{
|
|
351
|
+
"name": param_name,
|
|
352
|
+
"type": param_type,
|
|
353
|
+
"default": str(default) if default is not None else "",
|
|
354
|
+
"doc": doc or "",
|
|
355
|
+
"constraints": constraint_str,
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
return pd.DataFrame(rows)
|
|
360
|
+
|
|
361
|
+
@param.depends("result")
|
|
362
|
+
def _get_signature_markdown(self):
|
|
363
|
+
"""Get signature as markdown code block."""
|
|
364
|
+
signature = self.result.get("init_signature", "")
|
|
365
|
+
if not signature:
|
|
366
|
+
return "_No signature available._"
|
|
367
|
+
return f"```python\n{signature}\n```"
|
|
368
|
+
|
|
369
|
+
def __panel__(self):
|
|
370
|
+
"""Return the Panel layout."""
|
|
371
|
+
return self._layout
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class PanelGetComponentApp(pn.viewable.Viewer):
|
|
375
|
+
"""Main application for retrieving Panel component details."""
|
|
376
|
+
|
|
377
|
+
title = param.String(default="HoloViz MCP - panel_get_component Tool Demo")
|
|
378
|
+
|
|
379
|
+
def __init__(self, **params):
|
|
380
|
+
super().__init__(**params)
|
|
381
|
+
|
|
382
|
+
# Create configuration and viewer
|
|
383
|
+
self._config = GetComponentConfiguration()
|
|
384
|
+
self._component_viewer = ComponentDetailsViewer(result=self._config.param.result)
|
|
385
|
+
|
|
386
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
387
|
+
self._name_input = pmui.TextInput.from_param(
|
|
388
|
+
self._config.param.component_name,
|
|
389
|
+
label="Component Name",
|
|
390
|
+
placeholder="e.g., Button, TextInput, Slider...",
|
|
391
|
+
variant="outlined",
|
|
392
|
+
sx={"width": "100%"},
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
self._module_path_input = pmui.TextInput.from_param(
|
|
396
|
+
self._config.param.module_path,
|
|
397
|
+
label="Module Path (optional)",
|
|
398
|
+
placeholder="e.g., panel.widgets.Button...",
|
|
399
|
+
variant="outlined",
|
|
400
|
+
sx={"width": "100%"},
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
self._package_select = pmui.Select.from_param(
|
|
404
|
+
self._config.param.package,
|
|
405
|
+
label="Package (optional)",
|
|
406
|
+
variant="outlined",
|
|
407
|
+
sx={"width": "100%"},
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
self._get_button = pmui.Button.from_param(
|
|
411
|
+
self._config.param.get_component,
|
|
412
|
+
label="Get Component",
|
|
413
|
+
color="primary",
|
|
414
|
+
variant="contained",
|
|
415
|
+
sx={"width": "100%", "marginTop": "10px"},
|
|
416
|
+
on_click=lambda e: self._config.param.trigger("get_component"),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Status indicators
|
|
420
|
+
self._status_pane = pn.pane.Markdown(self._status_text, sizing_mode="stretch_width")
|
|
421
|
+
self._error_pane = pmui.Alert(
|
|
422
|
+
self._error_text,
|
|
423
|
+
alert_type="error",
|
|
424
|
+
visible=pn.rx(bool)(self._config.param.error_message),
|
|
425
|
+
sizing_mode="stretch_width",
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Create static layout structure
|
|
429
|
+
self._sidebar = pn.Column(
|
|
430
|
+
pmui.Typography("## Component Lookup", variant="h6"),
|
|
431
|
+
self._name_input,
|
|
432
|
+
self._module_path_input,
|
|
433
|
+
self._package_select,
|
|
434
|
+
self._get_button,
|
|
435
|
+
self._error_pane,
|
|
436
|
+
self._status_pane,
|
|
437
|
+
sizing_mode="stretch_width",
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
self._main = pmui.Container(self._component_viewer, width_option="xl", margin=10)
|
|
441
|
+
|
|
442
|
+
@param.depends("_config.loading", "_config.result")
|
|
443
|
+
def _status_text(self):
|
|
444
|
+
"""Generate status message."""
|
|
445
|
+
if self._config.loading:
|
|
446
|
+
return "_Loading component details..._"
|
|
447
|
+
elif self._config.result:
|
|
448
|
+
name = self._config.result.get("name", "Component")
|
|
449
|
+
package = self._config.result.get("package", "")
|
|
450
|
+
return f"_Successfully loaded **{name}** from **{package}**_"
|
|
451
|
+
return ""
|
|
452
|
+
|
|
453
|
+
@param.depends("_config.error_message")
|
|
454
|
+
def _error_text(self):
|
|
455
|
+
"""Get error message."""
|
|
456
|
+
return self._config.error_message
|
|
457
|
+
|
|
458
|
+
def __panel__(self):
|
|
459
|
+
"""Return the main page layout."""
|
|
460
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
461
|
+
# About button and dialog
|
|
462
|
+
about_button = pmui.IconButton(
|
|
463
|
+
label="About",
|
|
464
|
+
icon="info",
|
|
465
|
+
description="Click to learn about the Panel Get Component Tool.",
|
|
466
|
+
sizing_mode="fixed",
|
|
467
|
+
color="light",
|
|
468
|
+
margin=(10, 0),
|
|
469
|
+
)
|
|
470
|
+
about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
|
|
471
|
+
about_button.js_on_click(args={"about": about}, code="about.data.open = true")
|
|
472
|
+
|
|
473
|
+
# GitHub button
|
|
474
|
+
github_button = pmui.IconButton(
|
|
475
|
+
label="Github",
|
|
476
|
+
icon="star",
|
|
477
|
+
description="Give HoloViz-MCP a star on GitHub",
|
|
478
|
+
sizing_mode="fixed",
|
|
479
|
+
color="light",
|
|
480
|
+
margin=(10, 0),
|
|
481
|
+
href="https://github.com/MarcSkovMadsen/holoviz-mcp",
|
|
482
|
+
target="_blank",
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
return pmui.Page(
|
|
486
|
+
title=self.title,
|
|
487
|
+
site_url="./",
|
|
488
|
+
sidebar=[self._sidebar],
|
|
489
|
+
header=[pn.Row(pn.Spacer(), about_button, github_button, align="end")],
|
|
490
|
+
main=[about, self._main],
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
if pn.state.served:
|
|
495
|
+
pn.extension("tabulator")
|
|
496
|
+
PanelGetComponentApp().servable()
|