Skip to content

Commit ffffb15

Browse files
authored
Autocomplete UI for slash commands (#810)
* add REST API for fetching list of available slash commands * implement slash command autocomplete UI * pre-commit * shorten /export help message * add border and more right margin on icons in popup * prefer mdash over hyphen * include space after selecting an option
1 parent 633df43 commit ffffb15

File tree

7 files changed

+300
-38
lines changed

7 files changed

+300
-38
lines changed

packages/jupyter-ai/jupyter_ai/chat_handlers/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Literal,
1212
Optional,
1313
Type,
14+
Union,
1415
)
1516
from uuid import uuid4
1617

@@ -27,7 +28,7 @@
2728

2829
# Chat handler type, with specific attributes for each
2930
class HandlerRoutingType(BaseModel):
30-
routing_method: ClassVar[str] = Literal["slash_command"]
31+
routing_method: ClassVar[Union[Literal["slash_command"]]] = ...
3132
"""The routing method that sends commands to this handler."""
3233

3334

packages/jupyter-ai/jupyter_ai/chat_handlers/export.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
class ExportChatHandler(BaseChatHandler):
1212
id = "export"
13-
name = "Export chat messages"
14-
help = "Export the chat messages in markdown format with timestamps"
13+
name = "Export chat history"
14+
help = "Export chat history to a Markdown file"
1515
routing_type = SlashCommandRoutingType(slash_id="export")
1616

1717
uses_llm = False

packages/jupyter-ai/jupyter_ai/extension.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
GlobalConfigHandler,
3131
ModelProviderHandler,
3232
RootChatHandler,
33+
SlashCommandsInfoHandler,
3334
)
3435

3536
JUPYTERNAUT_AVATAR_ROUTE = JupyternautPersona.avatar_route
@@ -45,6 +46,7 @@ class AiExtension(ExtensionApp):
4546
(r"api/ai/config/?", GlobalConfigHandler),
4647
(r"api/ai/chats/?", RootChatHandler),
4748
(r"api/ai/chats/history?", ChatHistoryHandler),
49+
(r"api/ai/chats/slash_commands?", SlashCommandsInfoHandler),
4850
(r"api/ai/providers?", ModelProviderHandler),
4951
(r"api/ai/providers/embeddings?", EmbeddingsModelProviderHandler),
5052
(r"api/ai/completion/inline/?", DefaultInlineCompletionHandler),

packages/jupyter-ai/jupyter_ai/handlers.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
from typing import TYPE_CHECKING, Dict, List, Optional
88

99
import tornado
10-
from jupyter_ai.chat_handlers import BaseChatHandler
10+
from jupyter_ai.chat_handlers import BaseChatHandler, SlashCommandRoutingType
1111
from jupyter_ai.config_manager import ConfigManager, KeyEmptyError, WriteConflictError
1212
from jupyter_server.base.handlers import APIHandler as BaseAPIHandler
1313
from jupyter_server.base.handlers import JupyterHandler
1414
from langchain.pydantic_v1 import ValidationError
1515
from tornado import web, websocket
1616
from tornado.web import HTTPError
1717

18-
from .completions.models import InlineCompletionRequest
1918
from .models import (
2019
AgentChatMessage,
2120
ChatClient,
@@ -27,6 +26,8 @@
2726
HumanChatMessage,
2827
ListProvidersEntry,
2928
ListProvidersResponse,
29+
ListSlashCommandsEntry,
30+
ListSlashCommandsResponse,
3031
Message,
3132
UpdateConfigRequest,
3233
)
@@ -405,3 +406,52 @@ def delete(self, api_key_name: str):
405406
self.config_manager.delete_api_key(api_key_name)
406407
except Exception as e:
407408
raise HTTPError(500, str(e))
409+
410+
411+
class SlashCommandsInfoHandler(BaseAPIHandler):
412+
"""List slash commands that are currently available to the user."""
413+
414+
@property
415+
def config_manager(self) -> ConfigManager:
416+
return self.settings["jai_config_manager"]
417+
418+
@property
419+
def chat_handlers(self) -> Dict[str, "BaseChatHandler"]:
420+
return self.settings["jai_chat_handlers"]
421+
422+
@web.authenticated
423+
def get(self):
424+
response = ListSlashCommandsResponse()
425+
426+
# if no selected LLM, return an empty response
427+
if not self.config_manager.lm_provider:
428+
self.finish(response.json())
429+
return
430+
431+
for id, chat_handler in self.chat_handlers.items():
432+
# filter out any chat handler that is not a slash command
433+
if (
434+
id == "default"
435+
or chat_handler.routing_type.routing_method != "slash_command"
436+
):
437+
continue
438+
439+
# hint the type of this attribute
440+
routing_type: SlashCommandRoutingType = chat_handler.routing_type
441+
442+
# filter out any chat handler that is unsupported by the current LLM
443+
if (
444+
"/" + routing_type.slash_id
445+
in self.config_manager.lm_provider.unsupported_slash_commands
446+
):
447+
continue
448+
449+
response.slash_commands.append(
450+
ListSlashCommandsEntry(
451+
slash_id=routing_type.slash_id, description=chat_handler.help
452+
)
453+
)
454+
455+
# sort slash commands by slash id and deliver the response
456+
response.slash_commands.sort(key=lambda sc: sc.slash_id)
457+
self.finish(response.json())

packages/jupyter-ai/jupyter_ai/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,12 @@ class GlobalConfig(BaseModel):
162162
api_keys: Dict[str, str]
163163
completions_model_provider_id: Optional[str]
164164
completions_fields: Dict[str, Dict[str, Any]]
165+
166+
167+
class ListSlashCommandsEntry(BaseModel):
168+
slash_id: str
169+
description: str
170+
171+
172+
class ListSlashCommandsResponse(BaseModel):
173+
slash_commands: List[ListSlashCommandsEntry] = []

0 commit comments

Comments
 (0)