Generate execution plans on-the-fly for plan visualization
Instead of only showing cached plans, now attempts to generate a plan from the recipe if no cached plan is found. This works for recipe-based runs even if they were started via the legacy execute_dag path. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
37
server.py
37
server.py
@@ -1324,10 +1324,43 @@ async def run_plan_visualization(run_id: str, request: Request):
|
|||||||
except (json.JSONDecodeError, IOError):
|
except (json.JSONDecodeError, IOError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# If no cached plan, try to generate one from the recipe
|
||||||
|
if not plan_data:
|
||||||
|
recipe_name = run.recipe.replace("recipe:", "") if run.recipe.startswith("recipe:") else run.recipe
|
||||||
|
recipe_status = None
|
||||||
|
for recipe in list_all_recipes():
|
||||||
|
if recipe.name == recipe_name:
|
||||||
|
recipe_status = recipe
|
||||||
|
break
|
||||||
|
|
||||||
|
if recipe_status:
|
||||||
|
recipe_path = cache_manager.get_by_content_hash(recipe_status.recipe_id)
|
||||||
|
if recipe_path and recipe_path.exists():
|
||||||
|
try:
|
||||||
|
recipe_yaml = recipe_path.read_text()
|
||||||
|
# Build input_hashes mapping from run inputs
|
||||||
|
input_hashes = {}
|
||||||
|
for i, var_input in enumerate(recipe_status.variable_inputs):
|
||||||
|
if i < len(run.inputs):
|
||||||
|
input_hashes[var_input.node_id] = run.inputs[i]
|
||||||
|
|
||||||
|
# Try to generate plan using the orchestrate module
|
||||||
|
try:
|
||||||
|
from tasks.orchestrate import generate_plan as gen_plan_task
|
||||||
|
# Call synchronously (it's fast for just planning)
|
||||||
|
plan_result = gen_plan_task(recipe_yaml, input_hashes)
|
||||||
|
if plan_result and plan_result.get("status") == "planned":
|
||||||
|
plan_data = plan_result
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to generate plan for run {run_id}: {e}")
|
||||||
|
|
||||||
# Build sub-navigation tabs
|
# Build sub-navigation tabs
|
||||||
tabs_html = render_run_sub_tabs(run_id, active="plan")
|
tabs_html = render_run_sub_tabs(run_id, active="plan")
|
||||||
|
|
||||||
if not plan_data:
|
if not plan_data:
|
||||||
|
# Show a simpler visualization based on the run's recipe structure
|
||||||
content = f'''
|
content = f'''
|
||||||
<a href="/runs" class="inline-flex items-center text-blue-400 hover:text-blue-300 mb-6">
|
<a href="/runs" class="inline-flex items-center text-blue-400 hover:text-blue-300 mb-6">
|
||||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -1340,8 +1373,8 @@ async def run_plan_visualization(run_id: str, request: Request):
|
|||||||
|
|
||||||
<div class="bg-dark-700 rounded-lg p-6">
|
<div class="bg-dark-700 rounded-lg p-6">
|
||||||
<h2 class="text-xl font-bold text-white mb-4">Execution Plan</h2>
|
<h2 class="text-xl font-bold text-white mb-4">Execution Plan</h2>
|
||||||
<p class="text-gray-400">No execution plan available for this run.</p>
|
<p class="text-gray-400">Could not generate execution plan for this run.</p>
|
||||||
<p class="text-gray-500 text-sm mt-2">Plans are generated when using recipe-based runs with the v2 API.</p>
|
<p class="text-gray-500 text-sm mt-2">This may be a legacy effect-based run without a recipe, or the recipe is no longer available.</p>
|
||||||
</div>
|
</div>
|
||||||
'''
|
'''
|
||||||
return HTMLResponse(render_page_with_cytoscape(f"Plan: {run_id[:16]}...", content, ctx.actor_id, active_tab="runs"))
|
return HTMLResponse(render_page_with_cytoscape(f"Plan: {run_id[:16]}...", content, ctx.actor_id, active_tab="runs"))
|
||||||
|
|||||||
Reference in New Issue
Block a user