Skip to content

Commit

Permalink
Save notebook on disk
Browse files Browse the repository at this point in the history
  • Loading branch information
prasunanand committed Dec 17, 2024
1 parent 58615e6 commit 5bb57c3
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 33 deletions.
13 changes: 12 additions & 1 deletion content/content_api_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,18 @@ func ContentUpdateAPIHandler(w http.ResponseWriter, req *http.Request) {
return
}

UpdateContent(body.Path, body.Type, body.Format, body.Content)
if body.Type == "notebook" {
UpdateNbContent(body.Path, body.Type, body.Format, body.Content)
}

if body.Type == "file" {
contentStr, ok := body.Content.(string)
if !ok {
http.Error(w, "Invalid content type", http.StatusBadRequest)
return
}
UpdateContent(body.Path, body.Type, body.Format, contentStr)
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
Expand Down
51 changes: 50 additions & 1 deletion content/content_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,56 @@ func GetOSPath(path string) string {
return abspath
}

// save content
func UpdateNbContent(path, ftype, format string, content interface{}) error {
var nb OutNotebook
log.Info().Msgf("Updating notebook content for path: %s", path)

// Convert content to JSON if it's a string or []byte, otherwise directly marshal it
var contentBytes []byte
var err error

switch v := content.(type) {
case string:
// If content is a string, assume it's JSON and convert it to []byte
contentBytes = []byte(v)
case []byte:
// If content is already []byte, assume it's JSON
contentBytes = v
case map[string]interface{}:
// If content is already a map, we can directly marshal it into the notebook
contentBytes, err = json.Marshal(content)
if err != nil {
return fmt.Errorf("failed to marshal map content into JSON: %w", err)
}
default:
// If the content is an unsupported type
return fmt.Errorf("content is not a valid type (expected string, []byte, or map[string]interface{}), got: %T", content)
}

// Unmarshal the JSON bytes into the Notebook struct
if err := json.Unmarshal(contentBytes, &nb); err != nil {
return fmt.Errorf("failed to unmarshal content into notebook: %w", err)
}

newNb := splitLines(nb)

// Marshal the notebook struct back into JSON (to save the updated notebook)
nbJSON, err := json.Marshal(newNb)
if err != nil {
return fmt.Errorf("failed to marshal notebook: %w", err)
}

log.Info().Msgf("nbJSON: %s", string(nbJSON))

// Write the JSON back to the file
if err := os.WriteFile(path, nbJSON, 0644); err != nil {
log.Error().Err(err).Msgf("Error updating notebook content for path: %s", path)
return fmt.Errorf("error writing notebook to path %s: %w", path, err)
}

log.Info().Msgf("Successfully updated notebook content for path: %s", path)
return nil
}

func UpdateContent(path, ftype, format, content string) error {
err := os.WriteFile(path, []byte(content), 0644)
Expand Down
81 changes: 56 additions & 25 deletions content/notebook.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,31 +111,62 @@ func _splitMimeBundle(data map[string]interface{}) map[string]interface{} {
}

// splitLines splits likely multi-line text into lists of strings.
// func splitLines(nb *Notebook) *Notebook {
// for _, cell := range nb.Cells {
// if source, ok := cell.Source.(string); ok {
// cell.Source = strings.SplitAfter(source, "\n")
// }

// for _, attachment := range cell.Attachments {
// _splitMimeBundle(attachment)
// }

// if cell.CellType == "code" {
// for _, output := range cell.Outputs {
// switch output.OutputType {
// case "execute_result", "display_data":
// _splitMimeBundle(output.Data)
// case "stream":
// if text, ok := output.Text.(string); ok {
// output.Text = strings.SplitAfter(text, "\n")
// }
// }
// }
// }
// }
// return nb
// }
func splitLines(outNb OutNotebook) Notebook {
nb := Notebook{
Cells: []Cell{},
Metadata: outNb.Metadata,
}
for _, outCell := range outNb.Cells {
// Convert string slice to []interface{}
sourceLines := strings.SplitAfter(outCell.Source, "\n")
sourceInterface := make([]interface{}, len(sourceLines))
for i, v := range sourceLines {
sourceInterface[i] = v
}

// Convert attachments map
attachments := make(map[string]map[string]interface{})
for k, v := range outCell.Attachments {
attachments[k] = map[string]interface{}{"data": v}
}

// Convert outputs
outputs := make([]Output, len(outCell.Outputs))
for i, out := range outCell.Outputs {
outputs[i] = Output{
OutputType: out.OutputType,
ExecutionCount: out.ExecutionCount,
Data: map[string]interface{}{"text": out.Data},
Text: []interface{}{out.Text},
Metadata: out.Metadata,
}
}

nb.Cells = append(nb.Cells, Cell{
Source: sourceInterface,
CellType: outCell.CellType,
ExecutionCount: outCell.ExecutionCount,
Attachments: attachments,
Outputs: outputs,
Metadata: outCell.Metadata,
})
}

// if cell.CellType == "code" {
// for _, output := range cell.Outputs {
// switch output.OutputType {
// case "execute_result", "display_data":
// _splitMimeBundle(output.Data)
// case "stream":
// if text, ok := output.Text.(string); ok {
// output.Text = strings.SplitAfter(text, "\n")
// }
// }
// }
// }
return nb

}

// stripTransient removes transient metadata from the notebook.
func stripTransient(nb *Notebook) *Notebook {
Expand Down
8 changes: 4 additions & 4 deletions content/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ type (
}

ContentUpdateRequest struct {
Path string `json:"path"`
Content string `json:"content"`
Format string `json:"format"`
Type string `json:"type"`
Path string `json:"path"`
Content interface{} `json:"content"`
Format string `json:"format"`
Type string `json:"type"`
}
)
2 changes: 2 additions & 0 deletions ui/src/ide/editor/notebook/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface ICellProps{
divRefs: React.RefObject<(HTMLDivElement | null)[]>;
execution_count: number;
codeMirrorRefs: any;
updateCellSource: any;
}

export interface CodeMirrorRef {
Expand All @@ -56,6 +57,7 @@ const Cell = React.forwardRef((props: ICellProps, ref) => {

const onChange = useCallback((value, viewUpdate) => {
setCellContents(value)
props.updateCellSource(value, props.cell.id)
}, []);

const onUpdate = useCallback((viewUpdate: ViewUpdate) => {
Expand Down
35 changes: 33 additions & 2 deletions ui/src/ide/editor/notebook/NotebookEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default function NotebookEditor(props) {
useEffect(() => {
if (props.data.load_required === true) {
FetchFileData(props.data.path);
const session = startASession(props.data.path, props.data.name, props.data.type);
// const session = startASession(props.data.path, props.data.name, props.data.type);
}
}, [props.data]);

Expand Down Expand Up @@ -224,6 +224,20 @@ export default function NotebookEditor(props) {
}
};

const updateCellSource = (value: string, cellId: string) => {

setNotebook((prevNotebook) => {
const updatedCells = prevNotebook.cells.map((cell) => {
if (cell.id === cellId) {
return { ...cell, source: value };
}
return cell;
});

return { ...prevNotebook, cells: updatedCells };
});
}

const submitCell = (source: string, cellId: string) => {
setNotebook((prevNotebook) => {
const updatedCells = prevNotebook.cells.map((cell) => {
Expand Down Expand Up @@ -341,11 +355,27 @@ export default function NotebookEditor(props) {
}
};

const handleCmdEnter = () => {
console.log('Saving notebook')

fetch(BaseApiUrl + '/api/contents', {
method: 'PUT',
body: JSON.stringify({
path: props.data.path,
content: notebook,
type: 'notebook',
format: 'json'
})
})

return true
}

return (
<div className="tab-content">
<div className={props.data.active ? 'd-block' : 'd-none'} id="profile" role="tabpanel" aria-labelledby="profile-tab">
<NbButtons
saveNotebook={() => console.log('saving notebook')}
saveNotebook={handleCmdEnter}
addCell={addCellUp}
cutCell={() => console.log('cut cell')}
copyCell={() => console.log('copy cell')}
Expand Down Expand Up @@ -391,6 +421,7 @@ export default function NotebookEditor(props) {
divRefs={divRefs}
execution_count={cell.execution_count}
codeMirrorRefs={codeMirrorRefs}
updateCellSource={updateCellSource}
/>
))}
</div>
Expand Down

0 comments on commit 5bb57c3

Please sign in to comment.