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.
Files changed (56) hide show
  1. holoviz_mcp/__init__.py +18 -0
  2. holoviz_mcp/apps/__init__.py +1 -0
  3. holoviz_mcp/apps/configuration_viewer.py +116 -0
  4. holoviz_mcp/apps/holoviz_get_best_practices.py +173 -0
  5. holoviz_mcp/apps/holoviz_search.py +319 -0
  6. holoviz_mcp/apps/hvplot_get_docstring.py +255 -0
  7. holoviz_mcp/apps/hvplot_get_signature.py +252 -0
  8. holoviz_mcp/apps/hvplot_list_plot_types.py +83 -0
  9. holoviz_mcp/apps/panel_get_component.py +496 -0
  10. holoviz_mcp/apps/panel_get_component_parameters.py +467 -0
  11. holoviz_mcp/apps/panel_list_components.py +311 -0
  12. holoviz_mcp/apps/panel_list_packages.py +71 -0
  13. holoviz_mcp/apps/panel_search_components.py +312 -0
  14. holoviz_mcp/cli.py +75 -0
  15. holoviz_mcp/client.py +94 -0
  16. holoviz_mcp/config/__init__.py +29 -0
  17. holoviz_mcp/config/config.yaml +178 -0
  18. holoviz_mcp/config/loader.py +316 -0
  19. holoviz_mcp/config/models.py +208 -0
  20. holoviz_mcp/config/resources/best-practices/holoviews.md +423 -0
  21. holoviz_mcp/config/resources/best-practices/hvplot.md +465 -0
  22. holoviz_mcp/config/resources/best-practices/panel-material-ui.md +318 -0
  23. holoviz_mcp/config/resources/best-practices/panel.md +562 -0
  24. holoviz_mcp/config/schema.json +228 -0
  25. holoviz_mcp/holoviz_mcp/__init__.py +1 -0
  26. holoviz_mcp/holoviz_mcp/data.py +970 -0
  27. holoviz_mcp/holoviz_mcp/models.py +21 -0
  28. holoviz_mcp/holoviz_mcp/pages_design.md +407 -0
  29. holoviz_mcp/holoviz_mcp/server.py +220 -0
  30. holoviz_mcp/hvplot_mcp/__init__.py +1 -0
  31. holoviz_mcp/hvplot_mcp/server.py +146 -0
  32. holoviz_mcp/panel_mcp/__init__.py +17 -0
  33. holoviz_mcp/panel_mcp/data.py +319 -0
  34. holoviz_mcp/panel_mcp/models.py +124 -0
  35. holoviz_mcp/panel_mcp/server.py +443 -0
  36. holoviz_mcp/py.typed +0 -0
  37. holoviz_mcp/serve.py +36 -0
  38. holoviz_mcp/server.py +86 -0
  39. holoviz_mcp/shared/__init__.py +1 -0
  40. holoviz_mcp/shared/extract_tools.py +74 -0
  41. holoviz_mcp/thumbnails/configuration_viewer.png +0 -0
  42. holoviz_mcp/thumbnails/holoviz_get_best_practices.png +0 -0
  43. holoviz_mcp/thumbnails/holoviz_search.png +0 -0
  44. holoviz_mcp/thumbnails/hvplot_get_docstring.png +0 -0
  45. holoviz_mcp/thumbnails/hvplot_get_signature.png +0 -0
  46. holoviz_mcp/thumbnails/hvplot_list_plot_types.png +0 -0
  47. holoviz_mcp/thumbnails/panel_get_component.png +0 -0
  48. holoviz_mcp/thumbnails/panel_get_component_parameters.png +0 -0
  49. holoviz_mcp/thumbnails/panel_list_components.png +0 -0
  50. holoviz_mcp/thumbnails/panel_list_packages.png +0 -0
  51. holoviz_mcp/thumbnails/panel_search_components.png +0 -0
  52. holoviz_mcp-0.4.0.dist-info/METADATA +216 -0
  53. holoviz_mcp-0.4.0.dist-info/RECORD +56 -0
  54. holoviz_mcp-0.4.0.dist-info/WHEEL +4 -0
  55. holoviz_mcp-0.4.0.dist-info/entry_points.txt +2 -0
  56. holoviz_mcp-0.4.0.dist-info/licenses/LICENSE.txt +30 -0
@@ -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()