From 41ceae1e6cda51a2c0d00bad3bde3adc0bd3646a Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 11 Jan 2026 01:03:11 +0000 Subject: [PATCH] Fix plan visualization to show completed status and artifact links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Check cache_manager.has_content(cache_id) to determine if step is cached - Show green border for cached nodes in completed runs - Display artifact preview (video) when clicking on cached nodes - Add "View" button to access cached artifacts directly - Simplify node data structure (hasCached instead of outputs array) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- server.py | 113 +++++++++++++----------------------------------------- 1 file changed, 27 insertions(+), 86 deletions(-) diff --git a/server.py b/server.py index eea98db..5b7d626 100644 --- a/server.py +++ b/server.py @@ -1394,38 +1394,24 @@ async def run_plan_visualization(run_id: str, request: Request): nodes = [] edges = [] steps = plan_data.get("steps", []) - all_outputs = plan_data.get("outputs", []) # Pre-built outputs if available - - # Use run's execution results if available (from completed runs) - step_results = run.step_results or {} - if run.all_outputs: - all_outputs = run.all_outputs - - # Build output lookup by step_id - outputs_by_step = {} - for output in all_outputs: - step_id = output.get("step_id") - if step_id: - if step_id not in outputs_by_step: - outputs_by_step[step_id] = [] - outputs_by_step[step_id].append(output) for step in steps: node_type = step.get("node_type", "EFFECT") color = NODE_COLORS.get(node_type, NODE_COLORS["default"]) step_id = step.get("step_id", "") + cache_id = step.get("cache_id", "") - # Get execution result for this step if available - step_result = step_results.get(step_id, {}) - result_status = step_result.get("status") + # Check if this step's output exists in cache (completed) + # For completed runs, check the actual cache + has_cached = cache_manager.has_content(cache_id) if cache_id else False - # Determine status from result or plan - if result_status in ("completed", "cached", "completed_by_other"): - status = result_status - elif step.get("cached", False): + if has_cached: + status = "cached" + elif run.status == "completed": + # Run completed but this step not in cache - still mark as done status = "cached" elif run.status == "running": - status = "pending" + status = "running" else: status = "pending" @@ -1438,13 +1424,6 @@ async def run_plan_visualization(run_id: str, request: Request): else: label = step_id[:12] + "..." if len(step_id) > 12 else step_id - # Get outputs for this step - prefer result outputs, then plan outputs - step_outputs = step_result.get("outputs", []) or step.get("outputs", []) or outputs_by_step.get(step_id, []) - output_cache_ids = [o.get("cache_id") for o in step_outputs] - - # Get cache_id from result if available - cache_id = step_result.get("cache_id") or step.get("cache_id", "") - nodes.append({ "data": { "id": step_id, @@ -1456,8 +1435,7 @@ async def run_plan_visualization(run_id: str, request: Request): "status": status, "color": color, "config": step.get("config"), - "outputs": step_outputs, - "outputCacheIds": output_cache_ids, + "hasCached": has_cached, } }) @@ -1495,12 +1473,9 @@ async def run_plan_visualization(run_id: str, request: Request): # Stats summary - count from built nodes to reflect actual execution status total = len(nodes) - completed_count = sum(1 for n in nodes if n["data"]["status"] in ("completed", "completed_by_other")) cached_count = sum(1 for n in nodes if n["data"]["status"] == "cached") - pending_count = total - completed_count - cached_count - - # Plan name for display - plan_name = run.plan_name or plan_data.get("recipe", run.recipe) + running_count = sum(1 for n in nodes if n["data"]["status"] == "running") + pending_count = total - cached_count - running_count content = f''' @@ -4698,63 +4673,29 @@ def render_dag_cytoscape(nodes_json: str, edges_json: str, container_id: str = " typeEl.textContent = node.data('nodeType') || ''; // Handle artifact preview - var outputs = node.data('outputs') || []; var cacheId = node.data('cacheId'); var status = node.data('status'); - var hasPlayableArtifact = false; + var hasCached = node.data('hasCached'); - // Check for playable artifacts - if ((status === 'cached' || status === 'completed' || status === 'completed_by_other') && outputs.length > 0) {{ - // Find first video/audio output - for (var i = 0; i < outputs.length; i++) {{ - var output = outputs[i]; - var mediaType = output.media_type || output.mediaType || ''; - var outputCacheId = output.cache_id || output.cacheId || cacheId; - if (outputCacheId && (mediaType.startsWith('video/') || mediaType.startsWith('audio/'))) {{ - hasPlayableArtifact = true; - videoPlayer.src = '/cache/' + outputCacheId + '/raw'; - break; - }} - }} - }} else if ((status === 'cached' || status === 'completed') && cacheId) {{ - // Try cacheId directly for single-output nodes - hasPlayableArtifact = true; - videoPlayer.src = '/cache/' + cacheId + '/raw'; - }} - - if (hasPlayableArtifact) {{ + // Show video preview if artifact is cached + if (hasCached && cacheId) {{ previewEl.classList.remove('hidden'); + videoPlayer.src = '/cache/' + cacheId + '/raw'; }} else {{ previewEl.classList.add('hidden'); videoPlayer.src = ''; }} - // Show outputs list - if (outputs.length > 0) {{ + // Show artifact link if cached + if (hasCached && cacheId) {{ outputsList.classList.remove('hidden'); - outputsContainer.innerHTML = ''; - outputs.forEach(function(output, idx) {{ - var outCacheId = output.cache_id || output.cacheId || ''; - var outName = output.name || ('output_' + idx); - var outMediaType = output.media_type || output.mediaType || 'unknown'; - var outStatus = output.status || (outCacheId ? 'cached' : 'pending'); - var statusColor = outStatus === 'cached' || outStatus === 'completed' ? 'text-green-400' : 'text-yellow-400'; - var html = ''; - outputsContainer.innerHTML += html; - }}); + outputsContainer.innerHTML = '
' + + '
' + + '
Output Artifact
' + + '
' + cacheId.substring(0, 16) + '...
' + + '
' + + 'View' + + '
'; }} else {{ outputsList.classList.add('hidden'); }} @@ -4778,13 +4719,13 @@ def render_dag_cytoscape(nodes_json: str, edges_json: str, container_id: str = " }}); // WebSocket update function for real-time status updates - window.updateNodeStatus = function(stepId, status, cacheId, outputs) {{ + window.updateNodeStatus = function(stepId, status, cacheId, hasCached) {{ if (!window.artdagCy) return; var node = window.artdagCy.getElementById(stepId); if (node && node.length) {{ node.data('status', status); if (cacheId) node.data('cacheId', cacheId); - if (outputs) node.data('outputs', outputs); + if (hasCached !== undefined) node.data('hasCached', hasCached); }} }}; }});