Skip to content

Fix interactive workflow execution and display matplotlib figures inline - #76

Merged
IzBrain67 merged 3 commits into
mainfrom
feat/run-inline-figures
Jul 5, 2026
Merged

Fix interactive workflow execution and display matplotlib figures inline#76
IzBrain67 merged 3 commits into
mainfrom
feat/run-inline-figures

Conversation

@IzBrain67

Copy link
Copy Markdown
Collaborator

Summary

Interactive workflow execution (the Run button) failed or silently lost output in several ways. This PR makes it work end-to-end and renders matplotlib figures inline in the execution log.

Fixes

  • Every run failed with a TypeError: the backend container has websockets 16 while the code used the pre-14 extra_headers API, so the kernel WebSocket connection always failed. Migrated to websockets.asyncio.client.connect with additional_headers, which works on both the locked 13.1 and newer versions.
  • Inline matplotlib figures: display_data messages (what plt.show() emits) were dropped by the backend. image/png payloads are now forwarded as a new image SSE event and rendered as images in the execution log modal.
  • Runs with large figures aborted: raised the WebSocket frame limit from 8 MB to 128 MB.
  • Output was silently lost: execution now completes only after both the shell execute_reply and the iopub status: idle have arrived. The channels are independent, so execute_reply could beat large iopub messages still in flight, and everything behind them was discarded.
  • SSE parser fix: the event type was lost when a multi-MB data: line spanned multiple read chunks (guaranteed to happen for images).
  • Successful runs reported as errors: the generated script ends with sys.exit(main()), which raises SystemExit inside the Jupyter kernel. The run path now applies the same main() replacement the notebook export already uses.

Changed files

  • Backend: jupyter_execution_service.py (WS connection, message handling, completion condition), views.py (sys.exit replacement)
  • Frontend: workflowRunApi.ts (parser), projectSelector.tsx (image event), logViewModal.tsx (inline image rendering)

Testing

  • Backend verified against a live kernel via docker exec: a small figure arrives in order (stdout → image → stdout → done ok); a 9.4 MB PNG (previously aborted by the 8 MB limit) streams successfully; a real project run finishes done(ok) with no SystemExit.
  • tsc --noEmit passes; isort clean (black flags only pre-existing lines left untouched).
  • Manual frontend test: figures render inline in the modal; text-only runs unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L77mAJQZVU9rC2BecmBqbn

IzBrain67 and others added 3 commits July 4, 2026 01:39
websockets >= 14 renamed extra_headers to additional_headers and made the
new asyncio implementation the default, so workflow execution failed with
'create_connection() got an unexpected keyword argument extra_headers'.
Import connect from websockets.asyncio.client (available since 13.0) so
the code works with both the locked 13.1 and newer versions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L77mAJQZVU9rC2BecmBqbn
The interactive run dropped all image output: display_data messages had
no handler, figures over the 8 MB WebSocket frame limit aborted the run,
and the stream stopped at execute_reply, discarding iopub messages still
in flight. Forward image/png payloads as a new 'image' SSE event, raise
max_size to 128 MB, and finish only after both execute_reply and the
iopub idle status have arrived.

On the frontend, keep the SSE event type across read() chunks (a
multi-MB data: line spans many chunks) and render image events as
inline images in the execution log modal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L77mAJQZVU9rC2BecmBqbn
The generated script ends with sys.exit(main()), which raises
SystemExit inside the Jupyter kernel and marks successful runs as
errors. Apply the same replacement the notebook export already uses
before sending the code to the kernel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L77mAJQZVU9rC2BecmBqbn

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR repairs end-to-end interactive workflow execution (Run button) by fixing the Jupyter kernel WebSocket connection, ensuring execution completion waits for both shell and iopub idle signals, and extending the SSE/GUI pipeline to render inline matplotlib figures in the execution log.

Changes:

  • Update kernel WS connection behavior (headers API + frame size) and improve completion logic to avoid dropping late iopub output.
  • Forward Jupyter display_data / execute_result image/png payloads as a new image SSE event.
  • Improve frontend SSE parsing across read chunks and render image events inline in the log modal.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
gui/workflow_frontend/src/views/home/components/projectSelector.tsx Handles new image SSE event by appending an image log entry.
gui/workflow_frontend/src/views/home/components/logViewModal.tsx Extends log entry types and renders image entries as inline images.
gui/workflow_frontend/src/api/workflowRunApi.ts Fixes SSE parsing to retain event: type across chunk boundaries for large data: payloads.
gui/workflow_backend/django-project/app/workflow/views.py Replaces sys.exit(main()) to avoid SystemExit within the Jupyter kernel execution path.
gui/workflow_backend/django-project/app/workflow/jupyter_execution_service.py Updates WS connect API usage, raises frame limits, forwards inline images, and waits for both execute_reply and iopub idle.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 7 to 9
import httpx
import websockets
from websockets.asyncio.client import connect

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Verified empirically against both versions — the current import is correct, and the suggested change would reintroduce the exact bug this PR fixes:

  • websockets 13.1 (poetry.lock): websockets.asyncio.client.connect exists (the new asyncio implementation shipped in 13.0) and accepts additional_headers. The legacy websockets.client.connect only accepts extra_headers there, so from websockets.client import connect with additional_headers= would raise the same create_connection() got an unexpected keyword argument TypeError.
  • websockets 16.0 (what the backend container actually runs): websockets.client.connect is deprecated (emits a DeprecationWarning), while websockets.asyncio.client.connect is the supported path.
# websockets==13.1
websockets.asyncio.client.connect exists
new API additional_headers: True
legacy websockets.client.connect additional_headers: False
legacy websockets.client.connect extra_headers: True

The change was also exercised end-to-end against a live kernel in the backend container. Keeping the import as-is.

@IzBrain67
IzBrain67 merged commit e19a1f5 into main Jul 5, 2026
1 check passed
@IzBrain67
IzBrain67 deleted the feat/run-inline-figures branch July 5, 2026 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants