192 lines
7.4 KiB
HTML
192 lines
7.4 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ recipe.name }} - Recipe - Art-DAG L1{% endblock %}
|
|
|
|
{% block head %}
|
|
{{ super() }}
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.23.0/cytoscape.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/cytoscape-dagre@2.5.0/cytoscape-dagre.min.js"></script>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-6xl mx-auto">
|
|
<!-- Header -->
|
|
<div class="flex items-center space-x-4 mb-6">
|
|
<a href="/recipes" class="text-gray-400 hover:text-white">← Recipes</a>
|
|
<h1 class="text-2xl font-bold">{{ recipe.name or 'Unnamed Recipe' }}</h1>
|
|
{% if recipe.version %}
|
|
<span class="text-gray-500">v{{ recipe.version }}</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if recipe.description %}
|
|
<p class="text-gray-400 mb-4">{{ recipe.description }}</p>
|
|
{% endif %}
|
|
|
|
<!-- Metadata -->
|
|
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700 mb-6">
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<span class="text-gray-500">Recipe ID</span>
|
|
<p class="text-gray-300 font-mono text-xs truncate" title="{{ recipe.recipe_id }}">{{ recipe.recipe_id[:16] }}...</p>
|
|
</div>
|
|
{% if recipe.ipfs_cid %}
|
|
<div>
|
|
<span class="text-gray-500">IPFS CID</span>
|
|
<p class="text-gray-300 font-mono text-xs truncate" title="{{ recipe.ipfs_cid }}">{{ recipe.ipfs_cid[:16] }}...</p>
|
|
</div>
|
|
{% endif %}
|
|
<div>
|
|
<span class="text-gray-500">Steps</span>
|
|
<p class="text-gray-300">{{ recipe.step_count or recipe.steps|length }}</p>
|
|
</div>
|
|
{% if recipe.author %}
|
|
<div>
|
|
<span class="text-gray-500">Author</span>
|
|
<p class="text-gray-300">{{ recipe.author }}</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if recipe.type == 'streaming' %}
|
|
<!-- Streaming Recipe Info -->
|
|
<div class="bg-gray-800 rounded-lg border border-gray-700 mb-6 p-4">
|
|
<div class="flex items-center space-x-2 mb-2">
|
|
<span class="bg-purple-900 text-purple-300 px-2 py-1 rounded text-sm">Streaming Recipe</span>
|
|
</div>
|
|
<p class="text-gray-400 text-sm">
|
|
This recipe uses frame-by-frame streaming rendering. The pipeline is defined as an S-expression that generates frames dynamically.
|
|
</p>
|
|
</div>
|
|
{% else %}
|
|
<!-- DAG Visualization -->
|
|
<div class="bg-gray-800 rounded-lg border border-gray-700 mb-6">
|
|
<div class="border-b border-gray-700 px-4 py-2 flex items-center justify-between">
|
|
<span class="text-gray-400 text-sm">Pipeline DAG</span>
|
|
<span class="text-gray-500 text-sm">{{ recipe.steps | length }} steps</span>
|
|
</div>
|
|
<div id="dag-container" class="h-80"></div>
|
|
</div>
|
|
|
|
<!-- Steps -->
|
|
<h2 class="text-lg font-semibold mb-4">Steps</h2>
|
|
<div class="space-y-3 mb-8">
|
|
{% for step in recipe.steps %}
|
|
{% set colors = {
|
|
'effect': 'blue',
|
|
'analyze': 'purple',
|
|
'transform': 'green',
|
|
'combine': 'orange',
|
|
'output': 'cyan'
|
|
} %}
|
|
{% set color = colors.get(step.type, 'gray') %}
|
|
|
|
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<div class="flex items-center space-x-3">
|
|
<span class="w-8 h-8 rounded bg-{{ color }}-900 text-{{ color }}-300 flex items-center justify-center font-mono text-sm">
|
|
{{ loop.index }}
|
|
</span>
|
|
<span class="font-medium">{{ step.name }}</span>
|
|
<span class="bg-{{ color }}-900 text-{{ color }}-300 px-2 py-0.5 rounded text-xs">
|
|
{{ step.type }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{% if step.inputs %}
|
|
<div class="text-sm text-gray-400 mb-1">
|
|
Inputs: {{ step.inputs | join(', ') }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if step.params %}
|
|
<div class="mt-2 bg-gray-900 rounded p-2">
|
|
<code class="text-xs text-gray-400">{{ step.params | tojson }}</code>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Source Code -->
|
|
<h2 class="text-lg font-semibold mb-4">Recipe (S-expression)</h2>
|
|
<div class="bg-gray-900 rounded-lg p-4 border border-gray-700">
|
|
{% if recipe.sexp %}
|
|
<pre class="text-sm font-mono text-gray-300 overflow-x-auto whitespace-pre-wrap">{{ recipe.sexp }}</pre>
|
|
{% else %}
|
|
<p class="text-gray-500">No source available</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex items-center space-x-4 mt-8">
|
|
<button hx-post="/runs/rerun/{{ recipe.recipe_id }}"
|
|
hx-target="#action-result"
|
|
hx-swap="innerHTML"
|
|
class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded font-medium">
|
|
Run Recipe
|
|
</button>
|
|
{% if recipe.ipfs_cid %}
|
|
<a href="https://ipfs.io/ipfs/{{ recipe.ipfs_cid }}"
|
|
target="_blank"
|
|
class="bg-cyan-600 hover:bg-cyan-700 px-4 py-2 rounded font-medium">
|
|
View on IPFS
|
|
</a>
|
|
{% elif recipe.recipe_id.startswith('Qm') or recipe.recipe_id.startswith('bafy') %}
|
|
<a href="https://ipfs.io/ipfs/{{ recipe.recipe_id }}"
|
|
target="_blank"
|
|
class="bg-cyan-600 hover:bg-cyan-700 px-4 py-2 rounded font-medium">
|
|
View on IPFS
|
|
</a>
|
|
{% endif %}
|
|
<button hx-post="/recipes/{{ recipe.recipe_id }}/publish"
|
|
hx-target="#action-result"
|
|
class="bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded font-medium">
|
|
Share to L2
|
|
</button>
|
|
<button hx-delete="/recipes/{{ recipe.recipe_id }}/ui"
|
|
hx-target="#action-result"
|
|
hx-confirm="Delete this recipe? This cannot be undone."
|
|
class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded font-medium">
|
|
Delete
|
|
</button>
|
|
<span id="action-result"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const cy = cytoscape({
|
|
container: document.getElementById('dag-container'),
|
|
style: [
|
|
{ selector: 'node', style: {
|
|
'label': 'data(label)',
|
|
'background-color': 'data(color)',
|
|
'color': '#fff',
|
|
'text-valign': 'center',
|
|
'text-halign': 'center',
|
|
'font-size': '11px',
|
|
'width': 'label',
|
|
'height': 35,
|
|
'padding': '10px',
|
|
'shape': 'round-rectangle'
|
|
}},
|
|
{ selector: 'edge', style: {
|
|
'width': 2,
|
|
'line-color': '#4b5563',
|
|
'target-arrow-color': '#4b5563',
|
|
'target-arrow-shape': 'triangle',
|
|
'curve-style': 'bezier'
|
|
}}
|
|
],
|
|
elements: {{ dag_elements | tojson }},
|
|
layout: { name: 'dagre', rankDir: 'LR', padding: 30 }
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|