feat: notebook per-cell execution via open-terminal REST endpoints

- Add notebook API functions (createNotebookSession, executeNotebookCell, stopNotebookSession)
- Create CellEditor component with CodeMirror for cell editing
- Rewrite NotebookView with session-based execution, Run All, Restart, Stop
- Kernel status indicator with tooltips
- Wire baseUrl/apiKey through FilePreview and FileNav
This commit is contained in:
Timothy Jaeryang Baek
2026-03-05 16:08:11 -06:00
parent 8cd2157564
commit 4b3ed3e802
5 changed files with 523 additions and 33 deletions
+78
View File
@@ -258,3 +258,81 @@ export const getListeningPorts = async (
export const getPortProxyUrl = (baseUrl: string, port: number, path: string = ''): string => {
return `${baseUrl.replace(/\/$/, '')}/proxy/${port}/${path}`;
};
// ---------------------------------------------------------------------------
// Notebook execution
// ---------------------------------------------------------------------------
export const createNotebookSession = async (
baseUrl: string,
apiKey: string,
path: string
): Promise<{ id: string; kernel: string; status: string } | { error: string }> => {
const url = `${baseUrl.replace(/\/$/, '')}/notebooks`;
const res = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ path })
})
.then(async (res) => {
if (!res.ok) {
const body = await res.json().catch(() => ({}));
return { error: body?.detail ?? `HTTP ${res.status}` };
}
return res.json();
})
.catch((err) => {
console.error('open-terminal createNotebookSession error:', err);
return { error: 'Connection failed' };
});
return res;
};
export const executeNotebookCell = async (
baseUrl: string,
apiKey: string,
sessionId: string,
cellIndex: number,
source?: string
): Promise<{ status: string; execution_count?: number; outputs: any[] } | { error: string }> => {
const url = `${baseUrl.replace(/\/$/, '')}/notebooks/${sessionId}/execute`;
const body: Record<string, any> = { cell_index: cellIndex };
if (source !== undefined) body.source = source;
const res = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(async (res) => {
if (!res.ok) {
const body = await res.json().catch(() => ({}));
return { error: body?.detail ?? `HTTP ${res.status}` };
}
return res.json();
})
.catch((err) => {
console.error('open-terminal executeNotebookCell error:', err);
return { error: 'Connection failed' };
});
return res;
};
export const stopNotebookSession = async (
baseUrl: string,
apiKey: string,
sessionId: string
): Promise<boolean> => {
const url = `${baseUrl.replace(/\/$/, '')}/notebooks/${sessionId}`;
const res = await fetch(url, {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` }
}).catch(() => null);
return res?.ok ?? false;
};