UNPKG

starboard-jupyter

Version:

Jupyter-backed cells for Starboard Notebook

241 lines (216 loc) 9.18 kB
import { Kernel, KernelManager, KernelMessage } from "@jupyterlab/services"; import { JupyterPluginSettings } from "../types"; import * as P from "@jupyterlab/services/lib/serverconnection"; import { IKernelConnection } from "@jupyterlab/services/lib/kernel/kernel"; import { OutputArea } from "@jupyterlab/outputarea"; import { customElement } from "lit/decorators/custom-element"; const lit = window.runtime.exports.libraries.lit; const html = lit.html; @customElement("starboard-jupyter-manager") export class StarboardJupyterManager extends lit.LitElement { private settings: JupyterPluginSettings; private manager: KernelManager; private isReady = false; private runningKernels: Kernel.IModel[] = []; private currentKernel?: IKernelConnection; private connectionError: Error | undefined; constructor(jupyterSettings: JupyterPluginSettings) { super(); this.settings = jupyterSettings; this.manager = new KernelManager({ standby: "when-hidden", serverSettings: P.ServerConnection.makeSettings(jupyterSettings.serverSettings), }); this.manager.connectionFailure.connect((km, err) => { console.warn("Jupyter Connection Failure", err); this.connectionError = err; this.performUpdate(); }, this); this.manager.ready.then( () => { console.log("Jupyter manager is now ready"); this.isReady = true; this.performUpdate(); }, (err) => { console.warn("Jupyter manager failed to ready", err); this.isReady = false; this.connectionError = err; this.performUpdate(); } ); this.manager.runningChanged.connect((km, running) => { this.runningKernels = running; this.connectionError = undefined; this.performUpdate(); }, this); } private setupKernelConnection() { this.currentKernel!.statusChanged.connect((kc, status) => { if (status === "dead" && this.currentKernel) { this.currentKernel.dispose(); this.currentKernel = undefined; } this.performUpdate(); }); this.currentKernel!.connectionStatusChanged.connect((kc, status) => { this.performUpdate(); }); this.manager.refreshRunning().catch((e) => console.error("Failed to refresh running kernels:", e)); } createRenderRoot() { return this; } async startKernel(name?: string, shutdownCurrentKernel?: boolean) { if (shutdownCurrentKernel && this.currentKernel && !this.currentKernel.isDisposed) { console.error("Already connected to a kernel, shutting down existing kernel"); await this.currentKernel.shutdown(); this.currentKernel.dispose(); } this.currentKernel = await this.manager.startNew({ name: name }); this.setupKernelConnection(); this.performUpdate(); } async connectToKernel(id: string) { if (this.currentKernel && !this.currentKernel.isDisposed) { this.currentKernel.dispose(); this.currentKernel = undefined; } this.currentKernel = this.manager.connectTo({ model: { name: "", id } }); this.setupKernelConnection(); this.performUpdate(); } async shutdownKernel(id: string) { this.manager.shutdown(id); } async interruptKernel() { if (this.currentKernel) { this.currentKernel.interrupt(); } } async disconnectFromKernel() { if (this.currentKernel) { this.currentKernel.dispose(); this.currentKernel = undefined; this.performUpdate(); } } /** * Takes an object with a `code` field. * There are more parameters which you probably won't need. */ async runCode(content: KernelMessage.IExecuteRequestMsg["content"], output: OutputArea) { if (!this.currentKernel) { await this.startKernel(); } output.future = this.currentKernel!.requestExecute(content); } disconnectedCallback() { super.disconnectedCallback(); (async () => { if (this.currentKernel) { await this.manager.shutdown(this.currentKernel.id); } this.manager.dispose(); })(); } render() { return html` <section class="starboard-jupyter-interface py-2 px-3 my-2"> <details> <summary class="d-flex justify-content-between flex-wrap"> <div class="d-flex align-items-center flex-wrap"> ${this.settings.headerText ? html`<h2 class="h5 mb-0 me-2">${this.settings.headerText}</h2>` : undefined} ${this.connectionError ? html`<div class="badge bg-danger" style="width: max-content">Connection Error</div>` : this.isReady ? html`<div class="badge bg-success small" style="width: max-content">✅ OK</div>` : html`<div class="badge bg-light text-dark" style="width: max-content">Connecting to Jupyter..</div>`} </div> <div> ${this.currentKernel ? html` <span class="badge ${this.currentKernel.connectionStatus === "connected" ? "bg-success" : "bg-warning text-dark"}" > ${this.currentKernel.connectionStatus} </span> <span title="Kernel Status" class="badge bg-dark"> ${this.currentKernel.status} </span> <button @click=${() => this.interruptKernel()} title="Interrupt Kernel" class="btn btn-outline-secondary btn-sm btn-rounded py-0" > Interrupt </button>` : html`<span class="badge bg-light text-dark">Not connected to a kernel</span>`} </div> </summary> ${this.isReady ? html` ${ // this.connectionError ? // html`<button @click=${() => this.attemptReconnect()} class="ms-3 mt-2 btn btn-sm btn-outline-primary">Force Retry Connection</button>` : html`<button @click=${() => this.startKernel()} class="mt-2 btn btn-sm btn-outline-primary"> Start new Kernel </button>` } <ul class="list-group m-3"> ${this.runningKernels.map((v) => { if (this.currentKernel && this.currentKernel.id === v.id) { return html`<li class="list-group-item bg-light text-dark list-group-item-action d-flex justify-content-between align-items-center" > <span>🔗 <b>${v.name}</b> <code>${v.id}</code></span> <div class="d-flex align-items-center"> <button @click=${() => this.disconnectFromKernel()} class="btn btn-sm btn-outline-secondary me-2 text-dark bg-white" > Disconnect </button> <!-- <button @click=${() => this.shutdownKernel( v.id )} class="btn btn-sm btn-outline-secondary me-2 text-dark">Shut Down</button> --> <span title="Last Activity: ${(v as any).last_activity}" class="badge bg-primary rounded-pill" > ${this.currentKernel.status} </span> </div> </li>`; } else { return html`<div class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"> <span><b>${v.name}</b> <code>${v.id}</code></span> <div class="d-flex align-items-center"> <button @click=${() => this.connectToKernel(v.id)} class="btn btn-sm btn-outline-primary me-2">Connect</button> <button @click=${() => this.shutdownKernel(v.id)} class="btn btn-sm btn-outline-primary me-2">Shut Down</button> <span title="Last Activity: ${ (v as any).last_activity }" class="badge bg-primary rounded-pill"> ${(v as any).execution_state} </span> </div> </div> </div>`; } })} </ul>` : undefined} ${this.connectionError ? html` <div class="alert alert-danger mt-2"> <b>Connection Error</b> <p>${this.connectionError}</p> <br /> <p class="small">Check the Network tab in your browser's developer console for more details.</p> </div>` : undefined} </details> </section> `; } }