Skip to content

Commit 9a3a028

Browse files
authored
Merge pull request #2137 from mito-ds/create-notebook
Auto create new notebook
2 parents 2890ef1 + 26c1e5d commit 9a3a028

File tree

6 files changed

+309
-10
lines changed

6 files changed

+309
-10
lines changed

mito-ai/src/Extensions/AiChat/AiChatPlugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const AiChatPlugin: JupyterFrontEndPlugin<WidgetTracker> = {
6868
rendermime,
6969
contextManager,
7070
streamlitPreviewManager,
71+
documentManager,
7172
);
7273
return chatWidget;
7374
};

mito-ai/src/Extensions/AiChat/ChatTaskpane.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import React, { useEffect, useRef } from 'react';
1111
import { JupyterFrontEnd } from '@jupyterlab/application';
1212
import { INotebookTracker } from '@jupyterlab/notebook';
1313
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
14+
import { IDocumentManager } from '@jupyterlab/docmanager';
1415
import { addIcon, historyIcon, deleteIcon, settingsIcon } from '@jupyterlab/ui-components';
1516
import { ReadonlyPartialJSONObject, UUID } from '@lumino/coreutils';
1617

@@ -49,6 +50,7 @@ import {
4950
import { getActiveCellOutput } from '../../utils/cellOutput';
5051
import { OperatingSystem } from '../../utils/user';
5152
import { IStreamlitPreviewManager } from '../AppPreview/StreamlitPreviewPlugin';
53+
import { ensureNotebookExists } from './utils';
5254
import { waitForNotebookReady } from '../../utils/waitForNotebookReady';
5355
import { getBase64EncodedCellOutputInNotebook } from './utils';
5456
import { logEvent } from '../../restAPI/RestAPI';
@@ -122,6 +124,7 @@ interface IChatTaskpaneProps {
122124
app: JupyterFrontEnd
123125
operatingSystem: OperatingSystem
124126
websocketClient: CompletionWebsocketClient
127+
documentManager: IDocumentManager
125128
}
126129

127130
// Re-export types from hooks for backward compatibility
@@ -142,6 +145,7 @@ const ChatTaskpane: React.FC<IChatTaskpaneProps> = ({
142145
app,
143146
operatingSystem,
144147
websocketClient,
148+
documentManager,
145149
}) => {
146150

147151
// User signup state
@@ -374,6 +378,7 @@ const ChatTaskpane: React.FC<IChatTaskpaneProps> = ({
374378
// Step 3: No post processing step needed for explaining code.
375379
}
376380

381+
377382
const sendAgentExecutionMessage = async (
378383
input: string,
379384
messageIndex?: number,
@@ -384,12 +389,6 @@ const ChatTaskpane: React.FC<IChatTaskpaneProps> = ({
384389
// Step 0: reset the state for a new message
385390
resetForNewMessage()
386391

387-
const agentTargetNotebookPanel = agentTargetNotebookPanelRef.current
388-
389-
if (agentTargetNotebookPanel === null) {
390-
return
391-
}
392-
393392
// Step 1: Add the user's message to the chat history
394393
const newChatHistoryManager = getDuplicateChatHistoryManager()
395394

@@ -400,15 +399,15 @@ const ChatTaskpane: React.FC<IChatTaskpaneProps> = ({
400399

401400
const agentExecutionMetadata = newChatHistoryManager.addAgentExecutionMessage(
402401
activeThreadIdRef.current,
403-
agentTargetNotebookPanel,
402+
agentTargetNotebookPanelRef.current,
404403
input,
405404
additionalContext
406405
)
407406
if (messageIndex !== undefined) {
408407
agentExecutionMetadata.index = messageIndex
409408
}
410409

411-
agentExecutionMetadata.base64EncodedActiveCellOutput = await getBase64EncodedCellOutputInNotebook(agentTargetNotebookPanel, sendCellIDOutput)
410+
agentExecutionMetadata.base64EncodedActiveCellOutput = await getBase64EncodedCellOutputInNotebook(agentTargetNotebookPanelRef.current, sendCellIDOutput)
412411

413412
setChatHistoryManager(newChatHistoryManager)
414413
setLoadingStatus('thinking');
@@ -430,6 +429,9 @@ const ChatTaskpane: React.FC<IChatTaskpaneProps> = ({
430429
// Step 0: reset the state for a new message
431430
resetForNewMessage()
432431

432+
// Ensure a notebook exists before proceeding
433+
await ensureNotebookExists(notebookTracker, documentManager);
434+
433435
// Enable follow mode when user sends a new message
434436
setAutoScrollFollowMode(true);
435437

@@ -702,6 +704,7 @@ const ChatTaskpane: React.FC<IChatTaskpaneProps> = ({
702704
app,
703705
streamlitPreviewManager,
704706
websocketClient,
707+
documentManager,
705708
chatHistoryManagerRef,
706709
activeThreadIdRef,
707710
activeRequestControllerRef,

mito-ai/src/Extensions/AiChat/ChatWidget.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import chatIconSvg from '../../../src/icons/ChatIcon.svg';
1313
import { IContextManager } from '../ContextManager/ContextManagerPlugin';
1414
import { IStreamlitPreviewManager } from '../AppPreview/StreamlitPreviewPlugin';
1515
import { JupyterFrontEnd } from '@jupyterlab/application';
16+
import { IDocumentManager } from '@jupyterlab/docmanager';
1617
import { getOperatingSystem, OperatingSystem } from '../../utils/user';
1718
import type { IChatWidget as IChatWidget } from './token';
1819
import { Signal, type ISignal } from '@lumino/signaling';
@@ -40,6 +41,7 @@ export class ChatWidget extends ReactWidget implements IChatWidget {
4041
contextManager: IContextManager;
4142
streamlitPreviewManager: IStreamlitPreviewManager;
4243
operatingSystem: OperatingSystem;
44+
documentManager: IDocumentManager;
4345
}
4446
) {
4547
super();
@@ -93,6 +95,7 @@ export class ChatWidget extends ReactWidget implements IChatWidget {
9395
streamlitPreviewManager={this.options.streamlitPreviewManager}
9496
operatingSystem={this.options.operatingSystem}
9597
websocketClient={this.websocketClient}
98+
documentManager={this.options.documentManager}
9699
/>
97100
);
98101
}
@@ -118,6 +121,7 @@ export function buildChatWidget(
118121
renderMimeRegistry: IRenderMimeRegistry,
119122
contextManager: IContextManager,
120123
streamlitPreviewManager: IStreamlitPreviewManager,
124+
documentManager: IDocumentManager,
121125
): ChatWidget {
122126
// Get the operating system here so we don't have to do it each time the chat changes.
123127
// The operating system won't change, duh.
@@ -129,7 +133,8 @@ export function buildChatWidget(
129133
renderMimeRegistry,
130134
contextManager,
131135
streamlitPreviewManager: streamlitPreviewManager,
132-
operatingSystem
136+
operatingSystem,
137+
documentManager
133138
});
134139
chatWidget.id = 'mito_ai';
135140
return chatWidget;

mito-ai/src/Extensions/AiChat/hooks/useAgentExecution.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { useRef, useState } from 'react';
77
import { JupyterFrontEnd } from '@jupyterlab/application';
88
import { INotebookTracker } from '@jupyterlab/notebook';
9+
import { IDocumentManager } from '@jupyterlab/docmanager';
910
import { UUID } from '@lumino/coreutils';
1011
import { IStreamlitPreviewManager } from '../../AppPreview/StreamlitPreviewPlugin';
1112
import { CompletionWebsocketClient } from '../../../websockets/completions/CompletionsWebsocketClient';
@@ -18,6 +19,7 @@ import { getCodeBlockFromMessage } from '../../../utils/strings';
1819
import { getAIOptimizedCellsInNotebookPanel, setActiveCellByIDInNotebookPanel } from '../../../utils/notebook';
1920
import { AgentReviewStatus } from '../ChatTaskpane';
2021
import { LoadingStatus } from './useChatState';
22+
import { ensureNotebookExists } from '../utils';
2123

2224
export type AgentExecutionStatus = 'working' | 'stopping' | 'idle';
2325

@@ -28,6 +30,7 @@ interface UseAgentExecutionProps {
2830
app: JupyterFrontEnd;
2931
streamlitPreviewManager: IStreamlitPreviewManager;
3032
websocketClient: CompletionWebsocketClient;
33+
documentManager: IDocumentManager;
3134
chatHistoryManagerRef: React.MutableRefObject<ChatHistoryManager>;
3235
activeThreadIdRef: React.MutableRefObject<string>;
3336
activeRequestControllerRef: React.MutableRefObject<AbortController | null>;
@@ -63,6 +66,7 @@ export const useAgentExecution = ({
6366
app,
6467
streamlitPreviewManager,
6568
websocketClient,
69+
documentManager,
6670
chatHistoryManagerRef,
6771
activeThreadIdRef,
6872
activeRequestControllerRef,
@@ -133,7 +137,10 @@ export const useAgentExecution = ({
133137
messageIndex?: number,
134138
additionalContext?: Array<{ type: string, value: string }>
135139
): Promise<void> => {
136-
agentTargetNotebookPanelRef.current = notebookTracker.currentWidget;
140+
141+
// Ensure a notebook exists before proceeding with agent execution
142+
const agentTargetNotebookPanel = await ensureNotebookExists(notebookTracker, documentManager);
143+
agentTargetNotebookPanelRef.current = agentTargetNotebookPanel;
137144

138145
agentReview.acceptAllAICode();
139146
agentReview.setNotebookSnapshotPreAgentExecution(getAIOptimizedCellsInNotebookPanel(agentTargetNotebookPanelRef.current));

mito-ai/src/Extensions/AiChat/utils.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
*/
55

66
import { INotebookTracker, NotebookPanel } from "@jupyterlab/notebook"
7+
import { IDocumentManager } from "@jupyterlab/docmanager"
78
import { getCellIndexByIDInNotebookPanel } from "../../utils/notebook"
89
import { getCellOutputByIDInNotebook } from "../../utils/cellOutput"
910
import { logEvent } from "../../restAPI/RestAPI"
11+
import { waitForNotebookReady } from "../../utils/waitForNotebookReady"
12+
import { setNotebookID } from "../../utils/notebookMetadata"
1013

1114
export const getBase64EncodedCellOutput = async (notebookTracker: INotebookTracker, cellID: string | undefined): Promise<string | undefined> => {
1215
const notebookPanel = notebookTracker.currentWidget
@@ -33,3 +36,46 @@ export const getBase64EncodedCellOutputInNotebook = async (notebookPanel: Notebo
3336

3437
return undefined
3538
}
39+
40+
/*
41+
Ensure a notebook exists. If no notebook is open, create a new one.
42+
Returns the notebook panel.
43+
*/
44+
export const ensureNotebookExists = async (
45+
notebookTracker: INotebookTracker,
46+
documentManager: IDocumentManager
47+
): Promise<NotebookPanel> => {
48+
// Check if a notebook already exists
49+
let notebookPanel = notebookTracker.currentWidget;
50+
51+
if (notebookPanel !== null) {
52+
return notebookPanel;
53+
}
54+
55+
// No notebook exists, create a new one
56+
try {
57+
// Create a new notebook model (Contents.IModel has a path property)
58+
const model = await documentManager.newUntitled({ type: 'notebook' });
59+
60+
// Open the notebook using the path from the model
61+
await documentManager.open(model.path);
62+
63+
// Wait for the notebook to appear in the tracker and be ready
64+
await waitForNotebookReady(notebookTracker);
65+
66+
// Get the notebook panel from the tracker
67+
notebookPanel = notebookTracker.currentWidget;
68+
69+
if (notebookPanel === null) {
70+
throw new Error('Failed to get notebook panel after creation');
71+
}
72+
73+
// Set the notebook ID if it doesn't exist
74+
setNotebookID(notebookPanel);
75+
76+
return notebookPanel;
77+
} catch (error) {
78+
console.error('Error creating new notebook:', error);
79+
throw error;
80+
}
81+
};

0 commit comments

Comments
 (0)