-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathsearch.py
More file actions
351 lines (273 loc) · 13.1 KB
/
Copy pathsearch.py
File metadata and controls
351 lines (273 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
"""A search application for exploring the HoloViz MCP search tool."""
import panel as pn
import panel_material_ui as pmui
import param
from holoviz_mcp.holoviz_mcp.data import DocumentationIndexer
from holoviz_mcp.holoviz_mcp.models import Document
URL_CSS = """
#url, .url {
display: inline-block;
margin-bottom: 8px;
border: 1.5px solid #e0e0e0;
border-radius: 10px;
overflow: hidden;
padding: 10px;
width: 100%;
margin-top: 10px;
}
#iframe {
height: calc(100% - 100px);
width: 100%;
border: 1.5px solid #e0e0e0;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
overflow: hidden;
}
"""
ALL = "ALL"
ABOUT = """
## Documentation Search Tool
This tool provides powerful semantic search capabilities across the extended HoloViz documentation.
### Search Parameters
- **`query`**: Your search text - the tool uses semantic similarity to find the most relevant documents
- **`project`**: Filter results by specific project (e.g., "panel", "hvplot", "datashader") or search across all projects
- **`max_results`**: Control the number of results returned (1-50 documents)
- **`content`**: Controls what content is returned per result:
- `"truncated"` (default): Full document content, smart-truncated around query keywords
- `"chunk"`: Only the best-matching chunk from the document (useful for quick snippets)
- `"full"`: Complete document content with no truncation (can be very large)
- `"none"`: No content, metadata only (fastest)
- **`max_content_chars`**: Maximum characters of content per result (100-50,000). Smaller values provide faster responses. Content is truncated at word boundaries with query-relevant excerpts.
### Query Optimization Tips
For best results with content truncation, use **specific terms** rather than generic phrases:
- ✅ **Good**: "Button onClick on_click callback event" - specific class and method names
- ✅ **Good**: "Tabulator pagination page_size local remote" - feature-specific terms
- ❌ **Avoid**: "how to add pagination to a table" - too generic, common words
**Why This Matters**: The search uses context-aware truncation that centers content on your query keywords. Specific terms help the system extract the most relevant sections of documentation.
**Best Practices**:
- Use class/function names: "RangeSlider add_filter" > "filtering with widgets"
- Use unique identifiers: "background_gradient text_gradient" > "styling colors"
- Avoid common terms: "pandas", "import", "data" appear everywhere
- Use multiple specific terms to help locate the right section
### What's Indexed
By default, the complete HoloViz ecosystem documentation is indexed and searchable, including:
- [Panel](https://panel.holoviz.org), [HvPlot](https://hvplot.holoviz.org), [Datashader](https://datashader.holoviz.org), [HoloViews](https://holoviews.org), [GeoViews](https://geoviews.org)
- [Param](https://param.holoviz.org), [Colorcet](https://colorcet.holoviz.org), and core HoloViz guides
The system is extensible and can be configured to include additional projects like Altair, Bokeh, Pandas, Plotly, Polars or even your own custom documentation.
### Why Use This Tool?
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.
For LLMs this tool provides a structured way to access and retrieve relevant documentation, when using the HoloViz ecosystem.
### Learn More
For more information about this project, including setup instructions and advanced configuration options, visit: [HoloViz MCP](https://marcskovmadsen.github.io/holoviz-mcp/).
""" # noqa: E501
@pn.cache
def _get_indexer() -> DocumentationIndexer:
"""Get or create the global DocumentationIndexer instance."""
return DocumentationIndexer()
class SearchConfiguration(param.Parameterized):
"""
Configuration for the search application.
Parameters correspond to the arguments of the search_documentation function.
"""
query = param.String(default="What is HoloViz?", doc="Search text for semantic similarity search across the documentation")
project = param.Selector(
default=ALL,
objects=[ALL, "panel", "hvplot", "datashader", "holoviews", "geoviews", "param", "colorcet", "holoviz"],
doc="Filter results to a specific project. Select 'all' for all projects.",
)
max_results = param.Integer(default=5, bounds=(1, 50), doc="Maximum number of search results to return")
content = param.Selector(
default="truncated",
objects=["truncated", "chunk", "full", "none"],
doc='Controls what content is returned. "truncated": full doc smart-truncated around query keywords (default). '
'"chunk": only best-matching chunk. "full": complete document, no truncation. "none": metadata only (fastest).',
)
max_content_chars = param.Integer(
default=10000,
bounds=(100, 50000),
doc="Maximum characters of content per result. Use smaller values for faster responses. Set to None for full content (may cause token limit errors).",
)
search = param.Event(doc="Event to trigger search when parameters change")
results = param.List(item_type=Document, doc="Search results as a list of Documents", precedence=-1)
def __init__(self, **params):
"""Initialize the SearchConfiguration with default values."""
super().__init__(**params)
if pn.state.location:
pn.state.location.sync(self, parameters=["query", "project", "content", "max_results", "max_content_chars"])
if self.query:
self.param.trigger("search")
@param.depends("search", watch=True)
async def _update_results(self):
indexer = _get_indexer()
project = self.project if self.project != ALL else None
content = self.content if self.content != "none" else False
self.results = await indexer.search(self.query, project=project, content=content, max_results=self.max_results, max_content_chars=self.max_content_chars)
async def _update_projects(self):
self.config.param.project.objects = [ALL] + await _get_indexer().list_projects() # Ensure indexer is initialized
class DocumentsMenuList(pn.viewable.Viewer):
"""
A Menu for selecting a Document.
This menu allows users to select a Document from a list of Documents.
"""
value = param.ClassSelector(
default=None,
class_=Document,
allow_None=True,
doc="""
Last clicked Document.""",
)
documents = param.List(item_type=Document, doc="List of Documents to display in the menu", allow_refs=True)
def __panel__(self):
"""Create the Panel layout."""
menu = pmui.MenuList(items=self._items)
pn.bind(self._update_value, menu.param.active, watch=True)
return menu
@param.depends("documents", watch=True)
def _reset_value(self):
"""Reset the value when the documents change."""
if self.documents:
self.value = self.documents[0]
else:
self.value = None
def _update_value(self, event):
if event and self.documents:
index = event[0]
self.value = self.documents[index]
else:
self.value = None
@staticmethod
def _to_secondary(document: Document):
"""Convert a Document to a secondary text for the menu item."""
return f"""{document.description}
Relevance Score: {document.relevance_score or "N/A":0.2f}
"""
@param.depends("documents")
def _items(self):
return [
{"label": f"{index + 1}. {document.project}: {document.title}", "icon": None, "secondary": self._to_secondary(document)}
for index, document in enumerate(self.documents)
]
class DocumentView(pn.viewable.Viewer):
"""
A Panel Material UI view for displaying a single Document.
This view renders the content of a Document in a tabbed interface.
"""
document = param.ClassSelector(class_=Document, doc="Document to display", allow_refs=True)
def __panel__(self):
"""Create the Panel layout."""
return pmui.Tabs(
("Human Readable View", pn.pane.HTML(self._url_view, sizing_mode="stretch_both", stylesheets=[URL_CSS])),
# Hack Column Scroll
(
"Markdown View",
pn.Column(pn.pane.Markdown(self._source_view, sizing_mode="stretch_width", stylesheets=[URL_CSS]), sizing_mode="stretch_both", scroll=True),
),
("Raw Response", pn.Column(pn.pane.JSON(self._json_view, sizing_mode="stretch_both"), scroll=True)),
dynamic=True,
)
@param.depends("document")
def _js_copy_url_to_clipboard(self):
return f"navigator.clipboard.writeText('{self._url()}');"
@param.depends("document")
def _url(self):
"""Get the URL of the document."""
if not self.document:
return ""
url = self.document.url
return f"[{url}]({url})"
@param.depends("document")
def _source_url(self):
"""Get the source URL of the document."""
if not self.document:
return ""
return f"[{self.document.source_url}]({self.document.source_url})"
@param.depends("document")
def _json_view(self):
"""Create a JSON view for the document."""
if not self.document:
return None
return self.document.model_dump_json()
@param.depends("document")
def _source_view(self):
"""Create a source view for the document."""
if not self.document:
return "No document selected."
if not self.document.content:
return "No content available for this document."
if self.document.source_path.endswith(".rst"):
language = "restructuredtext"
else:
language = "markdown"
return f"""
<a class="url" href="{self.document.source_url}" target="_blank">{self.document.source_url}</a>
`````{language}
{self.document.content}
`````
"""
@param.depends("document")
def _url_view(self):
"""Create a URL view for the document."""
if not self.document:
return "No document selected."
if not self.document.url:
return "No URL available for this document."
return f"""\
<a id="url" href="{self.document.url}" target="_blank">{self.document.url}</a>
<iframe id="iframe" src="{self.document.url}"></iframe>
"""
class SearchApp(pn.viewable.Viewer):
"""
A Panel Material UI app for searching HoloViz MCP documentation.
Features:
- Parameter-driven reactivity
- Modern, responsive UI using Panel Material UI
- Integration with HoloViz MCP search tool
"""
title = param.String(default="HoloViz MCP - search Tool Demo", doc="Title of the search app")
config = param.ClassSelector(class_=SearchConfiguration, doc="Configuration for the search app")
def __init__(self, **params):
"""Initialize the SearchApp with default configuration."""
params["config"] = params.get("config", SearchConfiguration())
super().__init__(**params)
async def _config(self):
await _update_projects(self)
with pn.config.set(sizing_mode="stretch_width"):
return pn.Param(
self.config,
name="Search",
widgets={
"query": {"type": pmui.TextAreaInput, "rows": 3, "placeholder": "Enter search query ..."},
"search": {"type": pmui.Button, "label": "Search", "button_type": "primary"},
"max_content_chars": {"type": pn.widgets.IntInput, "step": 1000, "start": 100, "end": 50000, "placeholder": "Characters per result"},
},
)
def __panel__(self):
"""Create the Panel layout for the search app."""
with pn.config.set(sizing_mode="stretch_width"):
menu = DocumentsMenuList(documents=self.config.param.results)
about_button = pmui.IconButton(
label="About", icon="info", description="Click to learn about the Search Tool.", sizing_mode="fixed", color="light", margin=(10, 0)
)
about = pmui.Dialog(ABOUT, close_on_click=True, width=0)
about_button.js_on_click(args={"about": about}, code="about.data.open = true")
github_button = pmui.IconButton(
label="Github",
icon="star",
description="Give HoloViz-MCP a star on GitHub",
sizing_mode="fixed",
color="light",
margin=(10, 0),
href="https://github.com/MarcSkovMadsen/holoviz-mcp",
target="_blank",
)
return pmui.Page(
title=self.title,
site_url="./",
sidebar=[self._config, menu],
sidebar_width=400,
header=[pn.Row(pn.Spacer(), about_button, github_button, align="end")],
main=[pmui.Container(about, DocumentView(document=menu.param.value), width_option="xl", sizing_mode="stretch_both")],
)
if pn.state.served:
pn.extension("codeeditor")
SearchApp().servable()