Skip to content

Commit 38d145c

Browse files
authored
Implement new Secrets UI and REST API (#1439)
* add EnvSecretsManager and secrets REST API * make ListSecrets endpoint more generic * fix UpdateSecretsRequest type * fix bug where last secret cannot be deleted * remove error log used in dev * add new <SecretsSection /> component * tweak UI appearance * make settings page scrollable, fix #1409 * show no static secrets by default * show something when no secrets are present * adjust language * fix success & error alerts on updating secrets * fix lint * fix bug when .gitignore and .env do not exist * bump to 2s poll interval in EnvSecretsManager * disable model parameters input for now, fix merge conflicts
1 parent 4b0c455 commit 38d145c

17 files changed

+1524
-288
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,6 @@ packages/**/_version.py
142142
# Ignore local chat files & local .jupyter/ dir
143143
*.chat
144144
.jupyter/
145+
146+
# Ignore secrets in '.env'
147+
.env

packages/jupyter-ai/jupyter_ai/extension.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
import time
3-
import types
43
from asyncio import get_event_loop_policy
54
from functools import partial
65
from typing import TYPE_CHECKING, Optional
@@ -19,10 +18,11 @@
1918
from traitlets import Integer, List, Type, Unicode
2019
from traitlets.config import Config
2120

21+
from .secrets.secrets_rest_api import SecretsRestAPI
22+
from .secrets.secrets_manager import EnvSecretsManager
2223
from .completions.handlers import DefaultInlineCompletionHandler
2324
from .config_manager import ConfigManager
2425
from .handlers import (
25-
ApiKeysHandler,
2626
GlobalConfigHandler,
2727
InterruptStreamingHandler,
2828
)
@@ -58,11 +58,11 @@
5858
class AiExtension(ExtensionApp):
5959
name = "jupyter_ai"
6060
handlers = [ # type:ignore[assignment]
61-
(r"api/ai/api_keys/(?P<api_key_name>\w+)/?", ApiKeysHandler),
6261
(r"api/ai/config/?", GlobalConfigHandler),
6362
(r"api/ai/chats/stop_streaming/?", InterruptStreamingHandler),
6463
(r"api/ai/completion/inline/?", DefaultInlineCompletionHandler),
6564
(r"api/ai/models/chat/?", ChatModelEndpoint),
65+
(r"api/ai/secrets/?", SecretsRestAPI),
6666
(
6767
r"api/ai/static/jupyternaut.svg()/?",
6868
StaticFileHandler,
@@ -295,20 +295,12 @@ def on_change(
295295
def initialize_settings(self):
296296
start = time.time()
297297

298-
# Read from allowlist and blocklist
299-
restrictions = {
300-
"allowed_providers": self.allowed_providers,
301-
"blocked_providers": self.blocked_providers,
302-
}
303-
self.settings["allowed_models"] = self.allowed_models
304-
self.settings["blocked_models"] = self.blocked_models
298+
# Log traitlets configuration
305299
self.log.info(f"Configured provider allowlist: {self.allowed_providers}")
306300
self.log.info(f"Configured provider blocklist: {self.blocked_providers}")
307301
self.log.info(f"Configured model allowlist: {self.allowed_models}")
308302
self.log.info(f"Configured model blocklist: {self.blocked_models}")
309-
self.settings["model_parameters"] = self.model_parameters
310303
self.log.info(f"Configured model parameters: {self.model_parameters}")
311-
312304
defaults = {
313305
"model_provider_id": self.default_language_model,
314306
"embeddings_provider_id": self.default_embeddings_model,
@@ -319,8 +311,8 @@ def initialize_settings(self):
319311
"completions_fields": self.model_parameters,
320312
}
321313

314+
# Initialize ConfigManager
322315
self.settings["jai_config_manager"] = ConfigManager(
323-
# traitlets configuration, not JAI configuration.
324316
config=self.config,
325317
log=self.log,
326318
allowed_providers=self.allowed_providers,
@@ -330,16 +322,21 @@ def initialize_settings(self):
330322
defaults=defaults,
331323
)
332324

333-
self.log.info(f"Registered {self.name} server extension")
325+
# Initialize SecretsManager
326+
self.settings["jai_secrets_manager"] = EnvSecretsManager(parent=self)
334327

328+
# Bind event loop to settings dictionary
335329
self.settings["jai_event_loop"] = self.event_loop
336330

337-
# Create empty dictionary for events communicating that
338-
# message generation/streaming got interrupted.
331+
# Bind dictionary of interrupts to settings dictionary.
332+
# Each key is a message ID, each value is an asyncio.Event.
333+
# When a message's interrupt event is set, the response is halted.
339334
self.settings["jai_message_interrupted"] = {}
340335

341-
latency_ms = round((time.time() - start) * 1000)
342-
self.log.info(f"Initialized Jupyter AI server extension in {latency_ms} ms.")
336+
# Log server extension startup time
337+
self.log.info(f"Registered {self.name} server extension")
338+
startup_time = round((time.time() - start) * 1000)
339+
self.log.info(f"Initialized Jupyter AI server extension in {startup_time} ms.")
343340

344341
async def stop_extension(self):
345342
"""
@@ -359,7 +356,10 @@ async def _stop_extension(self):
359356
Private method that defines the cleanup code to run when the server is
360357
stopping.
361358
"""
362-
# TODO: explore if cleanup is necessary
359+
secrets_manager = self.settings.get("jai_secrets_manager", None)
360+
361+
if secrets_manager:
362+
secrets_manager.stop()
363363

364364
def _init_persona_manager(
365365
self, room_id: str, ychat: YChat
@@ -428,7 +428,6 @@ def _link_jupyter_server_extension(self, server_app: ServerApp):
428428
".git", # Git version control directory
429429
".venv", # Python virtual environment directory
430430
"venv", # Python virtual environment directory
431-
".env", # Environment variable files
432431
"node_modules", # Node.js dependencies directory
433432
".pytest_cache", # PyTest cache directory
434433
".mypy_cache", # MyPy type checker cache directory

packages/jupyter-ai/jupyter_ai/handlers.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,6 @@ def post(self):
4444
) from e
4545

4646

47-
class ApiKeysHandler(BaseAPIHandler):
48-
@property
49-
def config_manager(self) -> ConfigManager: # type:ignore[override]
50-
return self.settings["jai_config_manager"]
51-
52-
@web.authenticated
53-
def delete(self, api_key_name: str):
54-
try:
55-
self.config_manager.delete_api_key(api_key_name)
56-
except Exception as e:
57-
raise HTTPError(500, str(e))
58-
59-
6047
class InterruptStreamingHandler(BaseAPIHandler):
6148
"""Interrupt a current message streaming"""
6249

0 commit comments

Comments
 (0)