Skip to content

Conversation

@nakul-py
Copy link
Contributor

First try to getting model from widget than fallback to documentManager for both files and notebook cells.

file_widget.mp4
cell_widget.mp4

cc @jtpio @brichet

@brichet brichet added the enhancement New feature or request label Nov 26, 2025
@brichet
Copy link
Collaborator

brichet commented Nov 26, 2025

Thanks @nakul-py for working in this.

After a quick test, it works well on my side with cell but with a notebook I have something like

**File: test.ipynb**```json[object Object]```

Can you use the types of objects instead of any ? That would probably help preventing unexpected errors, and also simplify review.

@nakul-py
Copy link
Contributor Author

@brichet I have used all the needed object types but still i get Untitled1.ipynb, but the content attached seems to be a placeholder [object Object]. from llm. Now how can i tackle this out. I share the updated code below. :) Need your help on this!

Here is the code

 private async _readFileAttachment(
    attachment: IAttachment
  ): Promise<string | null> {
    // Handle both 'file' and 'notebook' types since both have a 'value' path
    if (attachment.type !== 'file' && attachment.type !== 'notebook') {
      return null;
    }

    try {
      // Try reading from an open widget first
      const widget = this.input.documentManager?.findWidget(attachment.value);

      if (widget && widget.context && widget.context.model) {
        const model = widget.context.model as DocumentRegistry.IModel;

        const sharedText = (model as any).sharedModel as
          | ISharedText
          | undefined;
        if (sharedText && typeof sharedText.getSource === 'function') {
          return sharedText.getSource();
        }

        const sharedNb = (model as any).sharedModel as
          | ISharedNotebook
          | undefined;
        if (sharedNb && typeof sharedNb.getCell === 'function') {
          const cells: nbformat.ICell[] = [];
          for (let i = 0; i < sharedNb.cells.length; i++) {
            const cell = sharedNb.getCell(i);
            const cellJSON = cell.toJSON();

            cells.push({
              ...cellJSON,
              outputs: [],
              execution_count: null
            });
          }

          const nb: nbformat.INotebookContent = {
            cells,
            metadata: sharedNb.getMetadata(),
            nbformat: 4,
            nbformat_minor: 5
          };

          return JSON.stringify(nb);
        }
      }

      // If not open, load from disk
      const diskModel = await this.input.documentManager?.services.contents.get(
        attachment.value
      );

      if (!diskModel?.content) {
        return null;
      }

      if (diskModel.type === 'file') {
        // Regular file content
        return diskModel.content;
      }

      if (diskModel.type === 'notebook') {
        const cleaned = {
          ...diskModel,
          cells: diskModel.content.cells.map((cell: any) => ({
            ...cell,
            outputs: [],
            execution_count: null
          }))
        };

        return JSON.stringify(cleaned);
      }
      return null;
    } catch (error) {
      console.warn(`Failed to read file ${attachment.value}:`, error);
      return null;
    }
  }

@nakul-py
Copy link
Contributor Author

nakul-py commented Nov 29, 2025

Hey @brichet!

In this commit 5521434, I fixed the Untitled1.ipynb, is [object Object]. error by ensuring that sharedModel.getSource() always returns a string. If it returns an object, I now convert it into JSON using JSON.stringify(). This prevents the LLM from receiving [object Object].

return null;
}
// Try reading from live notebook if open
const widget = this.input.documentManager?.findWidget(attachment.value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, the widget can only be a IDocumentWidget<Notebook, INotebookModel> | undefined.
Casting it would avoid typing the following sharedModel and cells as any.
Did you see use case where the widget is of another type ?

: JSON.stringify(source, null, 2);
}

if ((model as any).sharedModel?.getCells) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we reach this comparison ?
As in the comment above, the widget should only be a IDocumentWidget<Notebook, INotebookModel> | undefined, so the model should be INotebookModel, with sharedModel.getSource always defined (and the previous comparison always return the source).

@nakul-py nakul-py requested a review from brichet December 1, 2025 15:00
Copy link
Collaborator

@brichet brichet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nakul-py for updating the PR 👍

I have some nitpick comments

return null;
}
) as IDocumentWidget<Notebook, INotebookModel> | undefined;
let cellData: nbformat.ICell[] | null = null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let cellData: nbformat.ICell[] | null = null;
let cellData: nbformat.ICell[];

if (!model || model.type !== 'notebook') {
return null;
}
cellData = model.content.cells;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cellData = model.content.cells;
cellData = model.content.cells ?? [];

Related to above comment

const cell = model.content.cells.find(
(c: any) => c.id === cellInfo.id
);
const cell = cellData!.find(c => c.id === cellInfo.id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const cell = cellData!.find(c => c.id === cellInfo.id);
const cell = cellData.find(c => c.id === cellInfo.id);

Related to above comments

let cellData: nbformat.ICell[] | null = null;
let kernelLang = 'text';

const ymodel = widget?.context?.model?.sharedModel as YNotebook;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const ymodel = widget?.context?.model?.sharedModel as YNotebook;
const ymodel = widget?.context.model.sharedModel as YNotebook;

Comment on lines 647 to 648
nb.metadata?.language_info?.name ||
nb.metadata?.kernelspec?.language ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nb.metadata?.language_info?.name ||
nb.metadata?.kernelspec?.language ||
nb.metadata.language_info?.name ||
nb.metadata.kernelspec?.language ||

: JSON.stringify(source, null, 2);
}

if (typeof ymodel.toJSON === 'function') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reach this comparison ?
The YNotebook always has a getSource() function defined, AFAIK https://github.com/jupyter-server/jupyter_ydoc/blob/fdbf658e072467f49048ddef513a5e569207f25b/javascript/src/ynotebook.ts#L408

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are correct Nicolas!. This comparison is unreachable as you say earlier YNotebook always implements getSource(), and getSource() internally calls toJSON().

@nakul-py nakul-py requested a review from brichet December 2, 2025 12:49
@nakul-py
Copy link
Contributor Author

nakul-py commented Dec 2, 2025

@brichet Now everything looks fine. 👍

Copy link
Collaborator

@brichet brichet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nakul-py, looks great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Get the model from the widget, instead of the documentManager

2 participants