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
holoviz_mcp/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Accessible imports for the holoviz_mcp package."""
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from holoviz_mcp.server import main
|
|
7
|
+
from holoviz_mcp.server import mcp
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = importlib.metadata.version(__name__)
|
|
11
|
+
except importlib.metadata.PackageNotFoundError as e: # pragma: no cover
|
|
12
|
+
warnings.warn(f"Could not determine version of {__name__}\n{e!s}", stacklevel=2)
|
|
13
|
+
__version__ = "unknown"
|
|
14
|
+
|
|
15
|
+
__all__: list[str] = ["mcp"]
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Here for technical reasons."""
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""An app to view the holoviz-mcp configuration using Panel and Panel Material UI best practices.
|
|
2
|
+
|
|
3
|
+
- Uses parameter-driven architecture for reactivity and maintainability
|
|
4
|
+
- Uses a Panel template for layout and branding
|
|
5
|
+
- Uses only Panel Material UI components for UI widgets
|
|
6
|
+
- Separates config loading logic from UI logic
|
|
7
|
+
- Responsive and modern design
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
import panel as pn
|
|
13
|
+
import panel_material_ui as pmui
|
|
14
|
+
import param
|
|
15
|
+
|
|
16
|
+
from holoviz_mcp.config.loader import HoloVizMCPConfig
|
|
17
|
+
from holoviz_mcp.config.loader import get_config
|
|
18
|
+
from holoviz_mcp.config.loader import get_config_loader
|
|
19
|
+
|
|
20
|
+
pn.extension("jsoneditor")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConfigViewer(param.Parameterized):
|
|
24
|
+
"""
|
|
25
|
+
A Panel Material UI app for viewing holoviz-mcp configuration.
|
|
26
|
+
|
|
27
|
+
Features:
|
|
28
|
+
- Parameter-driven reactivity
|
|
29
|
+
- Modern, responsive UI using Panel Material UI
|
|
30
|
+
- Separation of config loading and UI logic
|
|
31
|
+
- Supports dark and light themes
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
config_source = param.Selector(objects=["Combined", "Default", "User"], default="Combined", doc="Which configuration to show")
|
|
35
|
+
dark_theme = param.Boolean(default=False, doc="Use dark theme for the JSON editor", allow_refs=True)
|
|
36
|
+
|
|
37
|
+
def __init__(self, **params):
|
|
38
|
+
"""
|
|
39
|
+
Initialize the ConfigViewer.
|
|
40
|
+
|
|
41
|
+
Loads environment config and config loader.
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(**params)
|
|
44
|
+
self._env_config = HoloVizMCPConfig()
|
|
45
|
+
self._loader = get_config_loader()
|
|
46
|
+
|
|
47
|
+
@param.depends("config_source")
|
|
48
|
+
def _config_json(self):
|
|
49
|
+
"""Get the selected configuration as a JSON string."""
|
|
50
|
+
if self.config_source == "Combined":
|
|
51
|
+
return get_config().model_dump_json(indent=2)
|
|
52
|
+
elif self.config_source == "Default":
|
|
53
|
+
default_config_file = self._env_config.default_dir / "config.yaml"
|
|
54
|
+
default_config = self._loader._load_yaml_file(default_config_file)
|
|
55
|
+
return json.dumps(default_config, indent=2)
|
|
56
|
+
elif self.config_source == "User":
|
|
57
|
+
user_config_file = self._env_config.user_dir / "config.yaml"
|
|
58
|
+
user_config = self._loader._load_yaml_file(user_config_file)
|
|
59
|
+
return json.dumps(user_config, indent=2)
|
|
60
|
+
return json.dumps({"error": "Unknown config source"}, indent=2)
|
|
61
|
+
|
|
62
|
+
@param.depends("dark_theme")
|
|
63
|
+
def _theme(self):
|
|
64
|
+
"""Get the theme for the JSON editor ('dark' or 'light')."""
|
|
65
|
+
return "dark" if self.dark_theme else "light"
|
|
66
|
+
|
|
67
|
+
def view(self):
|
|
68
|
+
"""Get the main view for the configuration viewer app."""
|
|
69
|
+
selector = pmui.RadioButtonGroup.from_param(
|
|
70
|
+
self.param.config_source,
|
|
71
|
+
color="primary",
|
|
72
|
+
orientation="horizontal",
|
|
73
|
+
sx={"mb": 2},
|
|
74
|
+
)
|
|
75
|
+
json_pane = pn.pane.JSON(
|
|
76
|
+
self._config_json,
|
|
77
|
+
name="holoviz-mcp config",
|
|
78
|
+
theme=self._theme,
|
|
79
|
+
sizing_mode="stretch_width",
|
|
80
|
+
)
|
|
81
|
+
card = pmui.Paper(
|
|
82
|
+
pmui.Column(
|
|
83
|
+
pmui.Typography(
|
|
84
|
+
"HoloViz MCP Configuration Viewer",
|
|
85
|
+
variant="h5",
|
|
86
|
+
sx={"fontWeight": 700, "mb": 2},
|
|
87
|
+
),
|
|
88
|
+
selector,
|
|
89
|
+
json_pane,
|
|
90
|
+
),
|
|
91
|
+
elevation=3,
|
|
92
|
+
sx={"maxWidth": "900px", "margin": "auto", "padding": "32px"},
|
|
93
|
+
)
|
|
94
|
+
return pmui.Container(card)
|
|
95
|
+
|
|
96
|
+
def _update_source(self, selector):
|
|
97
|
+
"""Update the config source from the selector widget."""
|
|
98
|
+
self.config_source = selector.value
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def create_app(cls):
|
|
102
|
+
"""Create and return the Panel Material UI app page."""
|
|
103
|
+
viewer = cls()
|
|
104
|
+
page = pmui.Page(
|
|
105
|
+
title="HoloViz MCP - Configuration Viewer",
|
|
106
|
+
main=[viewer.view()],
|
|
107
|
+
site_url="./",
|
|
108
|
+
)
|
|
109
|
+
viewer.dark_theme = page.param.dark_theme
|
|
110
|
+
return page
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
ConfigViewer.create_app().show(port=5007, open=True, dev=True)
|
|
115
|
+
elif pn.state.served:
|
|
116
|
+
ConfigViewer.create_app().servable()
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Panel app for exploring the HoloViz MCP holoviz_get_best_practices tool.
|
|
2
|
+
|
|
3
|
+
Uses panel-material-ui widgets and Page layout.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import panel as pn
|
|
7
|
+
import panel_material_ui as pmui
|
|
8
|
+
import param
|
|
9
|
+
|
|
10
|
+
from holoviz_mcp.holoviz_mcp.data import get_best_practices
|
|
11
|
+
from holoviz_mcp.holoviz_mcp.data import list_best_practices
|
|
12
|
+
|
|
13
|
+
pn.extension()
|
|
14
|
+
|
|
15
|
+
ABOUT = """
|
|
16
|
+
## Best Practices Tool
|
|
17
|
+
|
|
18
|
+
This tool provides best practices and guidelines for using HoloViz projects with Large Language Models (LLMs).
|
|
19
|
+
|
|
20
|
+
### What Are Best Practices?
|
|
21
|
+
|
|
22
|
+
Best practices are curated guidelines that help LLMs (and developers) write better code using HoloViz libraries. Each best practices document includes:
|
|
23
|
+
|
|
24
|
+
- **Hello World Examples**: Annotated starter code showing proper usage patterns
|
|
25
|
+
- **DO/DON'T Guidelines**: Clear rules for what to do and what to avoid
|
|
26
|
+
- **Code Patterns**: Common idioms and recommended approaches
|
|
27
|
+
- **LLM-Specific Guidance**: How to structure prompts and responses effectively
|
|
28
|
+
|
|
29
|
+
### Available Projects
|
|
30
|
+
|
|
31
|
+
This tool currently provides best practices for:
|
|
32
|
+
|
|
33
|
+
- **panel**: Core Panel library for building web apps and dashboards
|
|
34
|
+
- **panel-material-ui**: Material Design UI components for Panel applications
|
|
35
|
+
- **holoviz**: General HoloViz ecosystem guidelines
|
|
36
|
+
|
|
37
|
+
### How to Use
|
|
38
|
+
|
|
39
|
+
Select a project in the sidebar to view its best practices.
|
|
40
|
+
The content is displayed in Markdown format and includes code examples you can reference when building applications.
|
|
41
|
+
|
|
42
|
+
### Learn More
|
|
43
|
+
|
|
44
|
+
For more information about this project, including setup instructions and advanced configuration options,
|
|
45
|
+
visit: [HoloViz MCP](https://marcskovmadsen.github.io/holoviz-mcp/).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BestPracticesConfiguration(param.Parameterized):
|
|
50
|
+
"""
|
|
51
|
+
Configuration for the best practices viewer.
|
|
52
|
+
|
|
53
|
+
Parameters correspond to the project selection for viewing best practices.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
project = param.Selector(
|
|
57
|
+
default=None,
|
|
58
|
+
objects=[],
|
|
59
|
+
doc="Select a project to view its best practices",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
content = param.String(default="", doc="Markdown content of the selected best practices", precedence=-1)
|
|
63
|
+
|
|
64
|
+
def __init__(self, **params):
|
|
65
|
+
"""Initialize the BestPracticesConfiguration with available projects."""
|
|
66
|
+
super().__init__(**params)
|
|
67
|
+
self._load_projects()
|
|
68
|
+
|
|
69
|
+
if pn.state.location:
|
|
70
|
+
pn.state.location.sync(self, parameters=["project"])
|
|
71
|
+
|
|
72
|
+
def _load_projects(self):
|
|
73
|
+
"""Load available best practices projects."""
|
|
74
|
+
try:
|
|
75
|
+
projects = list_best_practices()
|
|
76
|
+
self.param.project.objects = projects
|
|
77
|
+
if projects and self.project is None:
|
|
78
|
+
self.project = projects[0] # Default to first project
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.param.project.objects = []
|
|
81
|
+
self.content = f"**Error loading projects:** {e}"
|
|
82
|
+
|
|
83
|
+
@param.depends("project", watch=True, on_init=True)
|
|
84
|
+
def _update_content(self):
|
|
85
|
+
"""Update content when project selection changes."""
|
|
86
|
+
if self.project is None or not isinstance(self.project, str):
|
|
87
|
+
self.content = "Please select a project to view its best practices."
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
self.content = get_best_practices(str(self.project))
|
|
92
|
+
except FileNotFoundError as e:
|
|
93
|
+
self.content = f"**Error:** {e}"
|
|
94
|
+
except Exception as e:
|
|
95
|
+
self.content = f"**Error loading best practices:** {e}"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class BestPracticesViewer(pn.viewable.Viewer):
|
|
99
|
+
"""
|
|
100
|
+
A Panel Material UI app for viewing HoloViz best practices.
|
|
101
|
+
|
|
102
|
+
Features:
|
|
103
|
+
- Parameter-driven reactivity
|
|
104
|
+
- Modern, responsive UI using Panel Material UI
|
|
105
|
+
- Integration with HoloViz MCP holoviz_get_best_practices tool
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
title = param.String(default="HoloViz MCP - holoviz_get_best_practices Tool Demo", doc="Title of the best practices viewer")
|
|
109
|
+
config: BestPracticesConfiguration = param.Parameter(doc="Configuration for the best practices viewer") # type: ignore
|
|
110
|
+
|
|
111
|
+
def __init__(self, **params):
|
|
112
|
+
"""Initialize the BestPracticesViewer with default configuration."""
|
|
113
|
+
params["config"] = params.get("config", BestPracticesConfiguration())
|
|
114
|
+
super().__init__(**params)
|
|
115
|
+
|
|
116
|
+
def _config_panel(self):
|
|
117
|
+
"""Create the configuration panel for the sidebar."""
|
|
118
|
+
return pmui.widgets.RadioButtonGroup.from_param(self.config.param.project, sizing_mode="stretch_width", orientation="vertical", button_style="outlined")
|
|
119
|
+
|
|
120
|
+
def __panel__(self):
|
|
121
|
+
"""Create the Panel layout for the best practices viewer."""
|
|
122
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
123
|
+
# About button and dialog
|
|
124
|
+
about_button = pmui.IconButton(
|
|
125
|
+
label="About",
|
|
126
|
+
icon="info",
|
|
127
|
+
description="Click to learn about the Best Practices Tool.",
|
|
128
|
+
sizing_mode="fixed",
|
|
129
|
+
color="light",
|
|
130
|
+
margin=(10, 0),
|
|
131
|
+
)
|
|
132
|
+
about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
|
|
133
|
+
about_button.js_on_click(args={"about": about}, code="about.data.open = true")
|
|
134
|
+
|
|
135
|
+
# GitHub button
|
|
136
|
+
github_button = pmui.IconButton(
|
|
137
|
+
label="Github",
|
|
138
|
+
icon="star",
|
|
139
|
+
description="Give HoloViz-MCP a star on GitHub",
|
|
140
|
+
sizing_mode="fixed",
|
|
141
|
+
color="light",
|
|
142
|
+
margin=(10, 0),
|
|
143
|
+
href="https://github.com/MarcSkovMadsen/holoviz-mcp",
|
|
144
|
+
target="_blank",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return pmui.Page(
|
|
148
|
+
title=self.title,
|
|
149
|
+
site_url="./",
|
|
150
|
+
sidebar=[self._config_panel()],
|
|
151
|
+
sidebar_width=350,
|
|
152
|
+
header=[pn.Row(pn.Spacer(), about_button, github_button, align="end")],
|
|
153
|
+
main=[
|
|
154
|
+
pmui.Container(
|
|
155
|
+
about,
|
|
156
|
+
pn.Column(
|
|
157
|
+
pn.pane.Markdown(
|
|
158
|
+
self.config.param.content,
|
|
159
|
+
sizing_mode="stretch_both",
|
|
160
|
+
styles={"padding": "20px"},
|
|
161
|
+
),
|
|
162
|
+
scroll=True,
|
|
163
|
+
sizing_mode="stretch_both",
|
|
164
|
+
),
|
|
165
|
+
width_option="xl",
|
|
166
|
+
sizing_mode="stretch_both",
|
|
167
|
+
)
|
|
168
|
+
],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if pn.state.served:
|
|
173
|
+
BestPracticesViewer().servable()
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""A search application for exploring the HoloViz MCP holoviz_search tool."""
|
|
2
|
+
|
|
3
|
+
import panel as pn
|
|
4
|
+
import panel_material_ui as pmui
|
|
5
|
+
import param
|
|
6
|
+
|
|
7
|
+
from holoviz_mcp.holoviz_mcp.data import DocumentationIndexer
|
|
8
|
+
from holoviz_mcp.holoviz_mcp.models import Document
|
|
9
|
+
|
|
10
|
+
URL_CSS = """
|
|
11
|
+
#url, .url {
|
|
12
|
+
display: inline-block;
|
|
13
|
+
margin-bottom: 8px;
|
|
14
|
+
border: 1.5px solid #e0e0e0;
|
|
15
|
+
border-radius: 10px;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
padding: 10px;
|
|
18
|
+
width: 100%;
|
|
19
|
+
margin-top: 10px;
|
|
20
|
+
}
|
|
21
|
+
#iframe {
|
|
22
|
+
height: calc(100% - 100px);
|
|
23
|
+
width: 100%;
|
|
24
|
+
border: 1.5px solid #e0e0e0;
|
|
25
|
+
border-radius: 10px;
|
|
26
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
}
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
ALL = "ALL"
|
|
32
|
+
|
|
33
|
+
ABOUT = """
|
|
34
|
+
## Documentation Search Tool
|
|
35
|
+
|
|
36
|
+
This tool provides powerful semantic search capabilities across the extended HoloViz documentation.
|
|
37
|
+
|
|
38
|
+
### Search Parameters
|
|
39
|
+
|
|
40
|
+
- **`query`**: Your search text - the tool uses semantic similarity to find the most relevant documents
|
|
41
|
+
- **`project`**: Filter results by specific project (e.g., "panel", "hvplot", "datashader") or search across all projects
|
|
42
|
+
- **`max_results`**: Control the number of results returned (1-50 documents)
|
|
43
|
+
- **`content`**: Choose whether to include full document content or just metadata for faster responses
|
|
44
|
+
|
|
45
|
+
### What's Indexed
|
|
46
|
+
|
|
47
|
+
By default, the complete HoloViz ecosystem documentation is indexed and searchable, including:
|
|
48
|
+
|
|
49
|
+
- [Panel](https://panel.holoviz.org), [HvPlot](https://hvplot.holoviz.org), [Datashader](https://datashader.holoviz.org), [HoloViews](https://holoviews.org), [GeoViews](https://geoviews.org)
|
|
50
|
+
- [Param](https://param.holoviz.org), [Colorcet](https://colorcet.holoviz.org), and core HoloViz guides
|
|
51
|
+
|
|
52
|
+
The system is extensible and can be configured to include additional projects like Altair, Bokeh, Pandas, Plotly, Polars or even your own custom documentation.
|
|
53
|
+
|
|
54
|
+
### Why Use This Tool?
|
|
55
|
+
|
|
56
|
+
Unlike simple keyword search, this tool understands context and meaning, helping you discover relevant information even when you don't know the exact terminology used in the documentation.
|
|
57
|
+
|
|
58
|
+
For LLMs this tool provides a structured way to access and retrieve relevant documentation, when using the HoloViz ecosystem.
|
|
59
|
+
|
|
60
|
+
### Learn More
|
|
61
|
+
|
|
62
|
+
For more information about this project, including setup instructions and advanced configuration options, visit: [HoloViz MCP](https://marcskovmadsen.github.io/holoviz-mcp/).
|
|
63
|
+
""" # noqa: E501
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pn.cache
|
|
67
|
+
def _get_indexer() -> DocumentationIndexer:
|
|
68
|
+
"""Get or create the global DocumentationIndexer instance."""
|
|
69
|
+
return DocumentationIndexer()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class SearchConfiguration(param.Parameterized):
|
|
73
|
+
"""
|
|
74
|
+
Configuration for the search application.
|
|
75
|
+
|
|
76
|
+
Parameters correspond to the arguments of the search_documentation function.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
query = param.String(default="What is HoloViz?", doc="Search text for semantic similarity search across the documentation")
|
|
80
|
+
|
|
81
|
+
project = param.Selector(
|
|
82
|
+
default=ALL,
|
|
83
|
+
objects=[ALL, "panel", "hvplot", "datashader", "holoviews", "geoviews", "param", "colorcet", "holoviz"],
|
|
84
|
+
doc="Filter results to a specific project. Select 'all' for all projects.",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
max_results = param.Integer(default=5, bounds=(1, 50), doc="Maximum number of search results to return")
|
|
88
|
+
|
|
89
|
+
content = param.Boolean(
|
|
90
|
+
default=True, label="Include Full Content", doc="Include full document content in results. Disable for faster and simpler responses with metadata only."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
search = param.Event(doc="Event to trigger search when parameters change")
|
|
94
|
+
|
|
95
|
+
results = param.List(item_type=Document, doc="Search results as a list of Documents", precedence=-1)
|
|
96
|
+
|
|
97
|
+
def __init__(self, **params):
|
|
98
|
+
"""Initialize the SearchConfiguration with default values."""
|
|
99
|
+
super().__init__(**params)
|
|
100
|
+
|
|
101
|
+
if pn.state.location:
|
|
102
|
+
pn.state.location.sync(self, parameters=["query", "project", "content", "max_results"])
|
|
103
|
+
|
|
104
|
+
if self.query:
|
|
105
|
+
self.param.trigger("search")
|
|
106
|
+
|
|
107
|
+
@param.depends("search", watch=True)
|
|
108
|
+
async def _update_results(self):
|
|
109
|
+
indexer = _get_indexer()
|
|
110
|
+
project = self.project if self.project != ALL else None
|
|
111
|
+
self.results = await indexer.search(self.query, project=project, content=self.content, max_results=self.max_results)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def _update_projects(self):
|
|
115
|
+
self.config.param.project.objects = [ALL] + await _get_indexer().list_projects() # Ensure indexer is initialized
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class DocumentsMenuList(pn.viewable.Viewer):
|
|
119
|
+
"""
|
|
120
|
+
A Menu for selecting a Document.
|
|
121
|
+
|
|
122
|
+
This menu allows users to select a Document from a list of Documents.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
value = param.ClassSelector(
|
|
126
|
+
default=None,
|
|
127
|
+
class_=Document,
|
|
128
|
+
allow_None=True,
|
|
129
|
+
doc="""
|
|
130
|
+
Last clicked Document.""",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
documents = param.List(item_type=Document, doc="List of Documents to display in the menu", allow_refs=True)
|
|
134
|
+
|
|
135
|
+
def __panel__(self):
|
|
136
|
+
"""Create the Panel layout."""
|
|
137
|
+
menu = pmui.MenuList(items=self._items)
|
|
138
|
+
pn.bind(self._update_value, menu.param.active, watch=True)
|
|
139
|
+
return menu
|
|
140
|
+
|
|
141
|
+
@param.depends("documents", watch=True)
|
|
142
|
+
def _reset_value(self):
|
|
143
|
+
"""Reset the value when the documents change."""
|
|
144
|
+
if self.documents:
|
|
145
|
+
self.value = self.documents[0]
|
|
146
|
+
else:
|
|
147
|
+
self.value = None
|
|
148
|
+
|
|
149
|
+
def _update_value(self, event):
|
|
150
|
+
if event and self.documents:
|
|
151
|
+
index = event[0]
|
|
152
|
+
self.value = self.documents[index]
|
|
153
|
+
else:
|
|
154
|
+
self.value = None
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def _to_secondary(document: Document):
|
|
158
|
+
"""Convert a Document to a secondary text for the menu item."""
|
|
159
|
+
return f"""{document.description}
|
|
160
|
+
|
|
161
|
+
Relevance Score: {document.relevance_score or 'N/A':0.2f}
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
@param.depends("documents")
|
|
165
|
+
def _items(self):
|
|
166
|
+
return [
|
|
167
|
+
{"label": f"{index+1}. {document.project}: {document.title}", "icon": None, "secondary": self._to_secondary(document)}
|
|
168
|
+
for index, document in enumerate(self.documents)
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class DocumentView(pn.viewable.Viewer):
|
|
173
|
+
"""
|
|
174
|
+
A Panel Material UI view for displaying a single Document.
|
|
175
|
+
|
|
176
|
+
This view renders the content of a Document in a tabbed interface.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
document = param.ClassSelector(class_=Document, doc="Document to display", allow_refs=True)
|
|
180
|
+
|
|
181
|
+
def __panel__(self):
|
|
182
|
+
"""Create the Panel layout."""
|
|
183
|
+
return pmui.Tabs(
|
|
184
|
+
("Human Readable View", pn.pane.HTML(self._url_view, sizing_mode="stretch_both", stylesheets=[URL_CSS])),
|
|
185
|
+
# Hack Column Scroll
|
|
186
|
+
(
|
|
187
|
+
"Markdown View",
|
|
188
|
+
pn.Column(pn.pane.Markdown(self._source_view, sizing_mode="stretch_width", stylesheets=[URL_CSS]), sizing_mode="stretch_both", scroll=True),
|
|
189
|
+
),
|
|
190
|
+
("Raw Response", pn.Column(pn.pane.JSON(self._json_view, sizing_mode="stretch_both"), scroll=True)),
|
|
191
|
+
dynamic=True,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
@param.depends("document")
|
|
195
|
+
def _js_copy_url_to_clipboard(self):
|
|
196
|
+
return f"navigator.clipboard.writeText('{self._url()}');"
|
|
197
|
+
|
|
198
|
+
@param.depends("document")
|
|
199
|
+
def _url(self):
|
|
200
|
+
"""Get the URL of the document."""
|
|
201
|
+
if not self.document:
|
|
202
|
+
return ""
|
|
203
|
+
url = self.document.url
|
|
204
|
+
return f"[{url}]({url})"
|
|
205
|
+
|
|
206
|
+
@param.depends("document")
|
|
207
|
+
def _source_url(self):
|
|
208
|
+
"""Get the source URL of the document."""
|
|
209
|
+
if not self.document:
|
|
210
|
+
return ""
|
|
211
|
+
return f"[{self.document.source_url}]({self.document.source_url})"
|
|
212
|
+
|
|
213
|
+
@param.depends("document")
|
|
214
|
+
def _json_view(self):
|
|
215
|
+
"""Create a JSON view for the document."""
|
|
216
|
+
if not self.document:
|
|
217
|
+
return None
|
|
218
|
+
return self.document.model_dump_json()
|
|
219
|
+
|
|
220
|
+
@param.depends("document")
|
|
221
|
+
def _source_view(self):
|
|
222
|
+
"""Create a source view for the document."""
|
|
223
|
+
if not self.document:
|
|
224
|
+
return "No document selected."
|
|
225
|
+
if not self.document.content:
|
|
226
|
+
return "No content available for this document."
|
|
227
|
+
if self.document.source_path.endswith(".rst"):
|
|
228
|
+
language = "restructuredtext"
|
|
229
|
+
else:
|
|
230
|
+
language = "markdown"
|
|
231
|
+
|
|
232
|
+
return f"""
|
|
233
|
+
<a class="url" href="{self.document.source_url}" target="_blank">{self.document.source_url}</a>
|
|
234
|
+
|
|
235
|
+
`````{language}
|
|
236
|
+
{self.document.content}
|
|
237
|
+
`````
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
@param.depends("document")
|
|
241
|
+
def _url_view(self):
|
|
242
|
+
"""Create a URL view for the document."""
|
|
243
|
+
if not self.document:
|
|
244
|
+
return "No document selected."
|
|
245
|
+
if not self.document.url:
|
|
246
|
+
return "No URL available for this document."
|
|
247
|
+
|
|
248
|
+
return f"""\
|
|
249
|
+
<a id="url" href="{self.document.url}" target="_blank">{self.document.url}</a>
|
|
250
|
+
<iframe id="iframe" src="{self.document.url}"></iframe>
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class SearchApp(pn.viewable.Viewer):
|
|
255
|
+
"""
|
|
256
|
+
A Panel Material UI app for searching HoloViz MCP documentation.
|
|
257
|
+
|
|
258
|
+
Features:
|
|
259
|
+
- Parameter-driven reactivity
|
|
260
|
+
- Modern, responsive UI using Panel Material UI
|
|
261
|
+
- Integration with HoloViz MCP holoviz_search tool
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
title = param.String(default="HoloViz MCP - holoviz_search Tool Demo", doc="Title of the search app")
|
|
265
|
+
config = param.ClassSelector(class_=SearchConfiguration, doc="Configuration for the search app")
|
|
266
|
+
|
|
267
|
+
def __init__(self, **params):
|
|
268
|
+
"""Initialize the SearchApp with default configuration."""
|
|
269
|
+
params["config"] = params.get("config", SearchConfiguration())
|
|
270
|
+
super().__init__(**params)
|
|
271
|
+
|
|
272
|
+
async def _config(self):
|
|
273
|
+
await _update_projects(self)
|
|
274
|
+
|
|
275
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
276
|
+
return pn.Param(
|
|
277
|
+
self.config,
|
|
278
|
+
name="Search",
|
|
279
|
+
widgets={
|
|
280
|
+
"query": {"type": pmui.TextAreaInput, "rows": 3, "placeholder": "Enter search query ..."},
|
|
281
|
+
"search": {"type": pmui.Button, "label": "Search", "button_type": "primary"},
|
|
282
|
+
},
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def __panel__(self):
|
|
286
|
+
"""Create the Panel layout for the search app."""
|
|
287
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
288
|
+
menu = DocumentsMenuList(documents=self.config.param.results)
|
|
289
|
+
|
|
290
|
+
about_button = pmui.IconButton(
|
|
291
|
+
label="About", icon="info", description="Click to learn about the Search Tool.", sizing_mode="fixed", color="light", margin=(10, 0)
|
|
292
|
+
)
|
|
293
|
+
about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
|
|
294
|
+
about_button.js_on_click(args={"about": about}, code="about.data.open = true")
|
|
295
|
+
|
|
296
|
+
github_button = pmui.IconButton(
|
|
297
|
+
label="Github",
|
|
298
|
+
icon="star",
|
|
299
|
+
description="Give HoloViz-MCP a star on GitHub",
|
|
300
|
+
sizing_mode="fixed",
|
|
301
|
+
color="light",
|
|
302
|
+
margin=(10, 0),
|
|
303
|
+
href="https://github.com/MarcSkovMadsen/holoviz-mcp",
|
|
304
|
+
target="_blank",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return pmui.Page(
|
|
308
|
+
title=self.title,
|
|
309
|
+
site_url="./",
|
|
310
|
+
sidebar=[self._config, menu],
|
|
311
|
+
sidebar_width=400,
|
|
312
|
+
header=[pn.Row(pn.Spacer(), about_button, github_button, align="end")],
|
|
313
|
+
main=[pmui.Container(about, DocumentView(document=menu.param.value), width_option="xl", sizing_mode="stretch_both")],
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if pn.state.served:
|
|
318
|
+
pn.extension("codeeditor")
|
|
319
|
+
SearchApp().servable()
|