From c3788724300c8de13d339682de1050d66f396865 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 20:24:34 +0000 Subject: [PATCH] Show display() outputs in notebook editor after flow execution When running a flow with python_script nodes, display outputs (matplotlib plots, plotly charts, images, HTML, text) from flowfile.display() calls were captured by the kernel but discarded by the flow execution engine. Now they are stored on the node and exposed via a new API endpoint so the notebook editor can show them automatically. Backend: - Store display_outputs on NodeResults after kernel execution - Add GET /node/display_outputs endpoint to retrieve them Frontend: - Add NodeApi.getDisplayOutputs() method - Fetch display outputs when PythonScript panel loads and after flow run completes, attaching them to the last notebook cell https://claude.ai/code/session_01LxQSB6d2gGPRCG9wGcmRnH --- .../flowfile_core/flowfile/flow_graph.py | 9 ++++ .../flowfile/flow_node/models.py | 3 ++ flowfile_core/flowfile_core/routes/routes.py | 15 ++++++ .../src/renderer/app/api/node.api.ts | 16 +++++- .../elements/pythonScript/PythonScript.vue | 52 ++++++++++++++++++- 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/flowfile_core/flowfile_core/flowfile/flow_graph.py b/flowfile_core/flowfile_core/flowfile/flow_graph.py index 641b28cad..b7a4229dd 100644 --- a/flowfile_core/flowfile_core/flowfile/flow_graph.py +++ b/flowfile_core/flowfile_core/flowfile/flow_graph.py @@ -1218,6 +1218,15 @@ def _func(*flowfile_tables: FlowDataEngine) -> FlowDataEngine: for line in result.stderr.strip().splitlines(): node_logger.warning(f"[stderr] {line}") + # Store display outputs on the node so the frontend can retrieve them + if result.display_outputs: + node = self.get_node(node_id) + if node is not None: + node.results.display_outputs = [ + {"mime_type": d.mime_type, "data": d.data, "title": d.title} + for d in result.display_outputs + ] + if not result.success: raise RuntimeError(f"Kernel execution failed: {result.error}") diff --git a/flowfile_core/flowfile_core/flowfile/flow_node/models.py b/flowfile_core/flowfile_core/flowfile/flow_node/models.py index cf02b6391..316ea943b 100644 --- a/flowfile_core/flowfile_core/flowfile/flow_node/models.py +++ b/flowfile_core/flowfile_core/flowfile/flow_node/models.py @@ -256,6 +256,7 @@ class NodeResults: errors: str | None = None warnings: str | None = None analysis_data_generator: Callable[[], pa.Table] | None = None + display_outputs: list[dict] | None = None def __init__(self): self._resulting_data = None @@ -265,6 +266,7 @@ def __init__(self): self.warnings = None self.example_data_generator = None self.analysis_data_generator = None + self.display_outputs = None def get_example_data(self) -> pa.Table | None: """ @@ -294,3 +296,4 @@ def reset(self): """Resets all result attributes to their default, empty state.""" self._resulting_data = None self.run_time = -1 + self.display_outputs = None diff --git a/flowfile_core/flowfile_core/routes/routes.py b/flowfile_core/flowfile_core/routes/routes.py index 289ab54de..2337fc425 100644 --- a/flowfile_core/flowfile_core/routes/routes.py +++ b/flowfile_core/flowfile_core/routes/routes.py @@ -868,6 +868,21 @@ def get_node(flow_id: int, node_id: int, get_data: bool = False): return v +@router.get("/node/display_outputs", tags=["editor"]) +def get_node_display_outputs(flow_id: int, node_id: int): + """Retrieves display outputs (images, HTML, text) from the last flow execution of a python_script node.""" + flow = flow_file_handler.get_flow(flow_id) + if not flow: + raise HTTPException(status_code=404, detail="Flow not found") + node = flow.get_node(node_id) + if node is None: + raise HTTPException(status_code=404, detail="Node not found") + display_outputs = node.results.display_outputs + if display_outputs is None: + return [] + return display_outputs + + @router.post("/node/description/", tags=["editor"]) def update_description_node(flow_id: int, node_id: int, description: str = Body(...)): """Updates the description text for a specific node.""" diff --git a/flowfile_frontend/src/renderer/app/api/node.api.ts b/flowfile_frontend/src/renderer/app/api/node.api.ts index 232a2744a..ff894551c 100644 --- a/flowfile_frontend/src/renderer/app/api/node.api.ts +++ b/flowfile_frontend/src/renderer/app/api/node.api.ts @@ -1,6 +1,6 @@ // Node API Service - Handles all node-related HTTP requests import axios from "../services/axios.config"; -import type { NodeData, TableExample, NodeDescriptionResponse } from "../types"; +import type { NodeData, TableExample, NodeDescriptionResponse, DisplayOutput } from "../types"; export class NodeApi { /** @@ -116,6 +116,20 @@ export class NodeApi { return response.data; } + /** + * Get display outputs from the last flow execution of a python_script node + */ + static async getDisplayOutputs(flowId: number, nodeId: number): Promise { + try { + const response = await axios.get("/node/display_outputs", { + params: { flow_id: flowId, node_id: nodeId }, + }); + return response.data; + } catch { + return []; + } + } + /** * Update user-defined node settings */ diff --git a/flowfile_frontend/src/renderer/app/components/nodes/node-types/elements/pythonScript/PythonScript.vue b/flowfile_frontend/src/renderer/app/components/nodes/node-types/elements/pythonScript/PythonScript.vue index ebfd070da..bd0d99a63 100644 --- a/flowfile_frontend/src/renderer/app/components/nodes/node-types/elements/pythonScript/PythonScript.vue +++ b/flowfile_frontend/src/renderer/app/components/nodes/node-types/elements/pythonScript/PythonScript.vue @@ -176,16 +176,18 @@