Import L1 (celery) as l1/
This commit is contained in:
203
l1/app/templates/effects/detail.html
Normal file
203
l1/app/templates/effects/detail.html
Normal file
@@ -0,0 +1,203 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% set meta = effect.meta or effect %}
|
||||
|
||||
{% block title %}{{ meta.name or 'Effect' }} - Effects - Art-DAG L1{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/lisp.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/scheme.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="/effects" class="text-gray-400 hover:text-white">← Effects</a>
|
||||
<h1 class="text-2xl font-bold">{{ meta.name or 'Unnamed Effect' }}</h1>
|
||||
<span class="text-gray-500">v{{ meta.version or '1.0.0' }}</span>
|
||||
{% if meta.temporal %}
|
||||
<span class="bg-purple-900 text-purple-300 px-2 py-1 rounded text-sm">temporal</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if meta.author %}
|
||||
<p class="text-gray-500 mb-2">by {{ meta.author }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if meta.description %}
|
||||
<p class="text-gray-400 mb-6">{{ meta.description }}</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Friendly Name & CID Info -->
|
||||
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700 mb-6">
|
||||
{% if effect.friendly_name %}
|
||||
<div class="mb-4 pb-4 border-b border-gray-700">
|
||||
<span class="text-gray-500 text-sm">Friendly Name</span>
|
||||
<p class="text-blue-400 font-medium text-lg mt-1">{{ effect.friendly_name }}</p>
|
||||
<p class="text-gray-500 text-xs mt-1">Use in recipes: <code class="bg-gray-900 px-2 py-0.5 rounded">(effect {{ effect.base_name }})</code></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="text-gray-500 text-sm">Content ID (CID)</span>
|
||||
<p class="font-mono text-sm text-gray-300 mt-1" id="effect-cid">{{ effect.cid }}</p>
|
||||
</div>
|
||||
<button onclick="copyToClipboard('{{ effect.cid }}')"
|
||||
class="bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded text-sm">
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
{% if effect.uploaded_at %}
|
||||
<div class="mt-3 text-gray-500 text-sm">
|
||||
Uploaded: {{ effect.uploaded_at }}
|
||||
{% if effect.uploader %}
|
||||
by {{ effect.uploader }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Left Column: Parameters & Dependencies -->
|
||||
<div class="lg:col-span-1 space-y-6">
|
||||
<!-- Parameters -->
|
||||
{% if meta.params %}
|
||||
<div class="bg-gray-800 rounded-lg border border-gray-700">
|
||||
<div class="border-b border-gray-700 px-4 py-2">
|
||||
<span class="text-gray-400 text-sm font-medium">Parameters</span>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
{% for param in meta.params %}
|
||||
<div>
|
||||
<div class="flex items-center space-x-2 mb-1">
|
||||
<span class="font-medium text-white">{{ param.name }}</span>
|
||||
<span class="bg-blue-900 text-blue-300 px-2 py-0.5 rounded text-xs">{{ param.type }}</span>
|
||||
</div>
|
||||
{% if param.description %}
|
||||
<p class="text-gray-400 text-sm">{{ param.description }}</p>
|
||||
{% endif %}
|
||||
<div class="flex flex-wrap gap-2 mt-1 text-xs">
|
||||
{% if param.range %}
|
||||
<span class="text-gray-500">range: {{ param.range[0] }} - {{ param.range[1] }}</span>
|
||||
{% endif %}
|
||||
{% if param.default is defined %}
|
||||
<span class="text-gray-500">default: {{ param.default }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Usage in Recipe -->
|
||||
<div class="bg-gray-800 rounded-lg border border-gray-700">
|
||||
<div class="border-b border-gray-700 px-4 py-2">
|
||||
<span class="text-gray-400 text-sm font-medium">Usage in Recipe</span>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
{% if effect.base_name %}
|
||||
<pre class="text-sm text-gray-300 bg-gray-900 rounded p-3 overflow-x-auto"><code class="language-lisp">({{ effect.base_name }} ...)</code></pre>
|
||||
<p class="text-gray-500 text-xs mt-2">
|
||||
Use the friendly name to reference this effect.
|
||||
</p>
|
||||
{% else %}
|
||||
<pre class="text-sm text-gray-300 bg-gray-900 rounded p-3 overflow-x-auto"><code class="language-lisp">(effect :cid "{{ effect.cid }}")</code></pre>
|
||||
<p class="text-gray-500 text-xs mt-2">
|
||||
Reference this effect by CID in your recipe.
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Source Code -->
|
||||
<div class="lg:col-span-2">
|
||||
<div class="bg-gray-800 rounded-lg border border-gray-700">
|
||||
<div class="border-b border-gray-700 px-4 py-2 flex items-center justify-between">
|
||||
<span class="text-gray-400 text-sm font-medium">Source Code (S-expression)</span>
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/effects/{{ effect.cid }}/source"
|
||||
class="text-gray-400 hover:text-white text-sm"
|
||||
download="{{ meta.name or 'effect' }}.sexp">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<pre class="text-sm overflow-x-auto rounded bg-gray-900"><code class="language-lisp" id="source-code">Loading...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center space-x-4 mt-8">
|
||||
{% if effect.cid.startswith('Qm') or effect.cid.startswith('bafy') %}
|
||||
<a href="https://ipfs.io/ipfs/{{ effect.cid }}"
|
||||
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="/effects/{{ effect.cid }}/publish"
|
||||
hx-target="#action-result"
|
||||
class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded font-medium">
|
||||
Share to L2
|
||||
</button>
|
||||
<button onclick="deleteEffect('{{ effect.cid }}')"
|
||||
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() {
|
||||
// Load source code
|
||||
fetch('/effects/{{ effect.cid }}/source')
|
||||
.then(response => response.text())
|
||||
.then(source => {
|
||||
const codeEl = document.getElementById('source-code');
|
||||
codeEl.textContent = source;
|
||||
hljs.highlightElement(codeEl);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('source-code').textContent = 'Failed to load source code';
|
||||
});
|
||||
});
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
const btn = event.target;
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(() => { btn.textContent = originalText; }, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteEffect(cid) {
|
||||
if (!confirm('Delete this effect from local cache? IPFS copies will persist.')) return;
|
||||
|
||||
fetch('/effects/' + cid, { method: 'DELETE' })
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Delete failed');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
document.getElementById('action-result').innerHTML =
|
||||
'<span class="text-green-400">Deleted. Redirecting...</span>';
|
||||
setTimeout(() => { window.location.href = '/effects'; }, 1000);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('action-result').innerHTML =
|
||||
'<span class="text-red-400">' + error.message + '</span>';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
200
l1/app/templates/effects/list.html
Normal file
200
l1/app/templates/effects/list.html
Normal file
@@ -0,0 +1,200 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Effects - Art-DAG L1{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-bold">Effects</h1>
|
||||
<button onclick="document.getElementById('upload-modal').classList.remove('hidden')"
|
||||
class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded font-medium">
|
||||
Upload Effect
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Upload Modal -->
|
||||
<div id="upload-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-gray-800 rounded-lg p-6 w-full max-w-md border border-gray-700">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-semibold">Upload Effect</h2>
|
||||
<button onclick="document.getElementById('upload-modal').classList.add('hidden')"
|
||||
class="text-gray-400 hover:text-white">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="upload-form" enctype="multipart/form-data" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-gray-400 text-sm mb-1">Effect File (.sexp)</label>
|
||||
<input type="file" name="file" id="upload-file" required
|
||||
accept=".sexp,.lisp"
|
||||
class="w-full bg-gray-900 border border-gray-600 rounded px-3 py-2 text-white file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:bg-blue-600 file:text-white hover:file:bg-blue-700">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-gray-400 text-sm mb-1">Friendly Name (optional)</label>
|
||||
<input type="text" name="display_name" id="upload-name" placeholder="e.g., color-shift"
|
||||
class="w-full bg-gray-900 border border-gray-600 rounded px-3 py-2 text-white">
|
||||
<p class="text-gray-500 text-xs mt-1">A name to reference this effect in recipes</p>
|
||||
</div>
|
||||
|
||||
<div id="upload-result" class="hidden"></div>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button type="button" onclick="document.getElementById('upload-modal').classList.add('hidden')"
|
||||
class="px-4 py-2 rounded border border-gray-600 hover:bg-gray-700">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" id="upload-btn"
|
||||
class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded font-medium">
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-400 mb-8">
|
||||
Effects are S-expression files that define video processing operations.
|
||||
Each effect is stored in IPFS and can be referenced by name in recipes.
|
||||
</p>
|
||||
|
||||
{% if effects %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" id="effects-list">
|
||||
{% for effect in effects %}
|
||||
{% set meta = effect.meta or effect %}
|
||||
<a href="/effects/{{ effect.cid }}"
|
||||
class="effect-card bg-gray-800 border border-gray-700 rounded-lg p-4 hover:border-gray-600 transition-colors">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="font-medium text-white">{{ meta.name or 'Unnamed' }}</span>
|
||||
<span class="text-gray-500 text-sm">v{{ meta.version or '1.0.0' }}</span>
|
||||
</div>
|
||||
|
||||
{% if meta.description %}
|
||||
<p class="text-gray-400 text-sm mb-3 line-clamp-2">{{ meta.description }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex items-center justify-between text-sm mb-2">
|
||||
{% if meta.author %}
|
||||
<span class="text-gray-500">by {{ meta.author }}</span>
|
||||
{% else %}
|
||||
<span></span>
|
||||
{% endif %}
|
||||
{% if meta.temporal %}
|
||||
<span class="bg-purple-900 text-purple-300 px-2 py-0.5 rounded text-xs">temporal</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if meta.params %}
|
||||
<div class="text-gray-500 text-sm">
|
||||
{{ meta.params | length }} parameter{{ 's' if meta.params | length != 1 else '' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-3 text-xs">
|
||||
{% if effect.friendly_name %}
|
||||
<span class="text-blue-400 font-medium">{{ effect.friendly_name }}</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 font-mono truncate">{{ effect.cid[:24] }}...</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if has_more %}
|
||||
<div hx-get="/effects?offset={{ offset + limit }}&limit={{ limit }}"
|
||||
hx-trigger="revealed"
|
||||
hx-swap="afterend"
|
||||
hx-select="#effects-list > *"
|
||||
class="h-20 flex items-center justify-center text-gray-500">
|
||||
Loading more...
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<div class="bg-gray-800 border border-gray-700 rounded-lg p-12 text-center">
|
||||
<svg class="w-16 h-16 mx-auto mb-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
||||
</svg>
|
||||
<p class="text-gray-500 mb-4">No effects uploaded yet.</p>
|
||||
<p class="text-gray-600 text-sm mb-6">
|
||||
Effects are S-expression files with metadata in comment headers.
|
||||
</p>
|
||||
<button onclick="document.getElementById('upload-modal').classList.remove('hidden')"
|
||||
class="bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded font-medium">
|
||||
Upload Your First Effect
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Handle upload form
|
||||
document.getElementById('upload-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const form = e.target;
|
||||
const fileInput = document.getElementById('upload-file');
|
||||
const displayName = document.getElementById('upload-name').value;
|
||||
const resultDiv = document.getElementById('upload-result');
|
||||
const uploadBtn = document.getElementById('upload-btn');
|
||||
|
||||
const file = fileInput.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
if (displayName) {
|
||||
formData.append('display_name', displayName);
|
||||
}
|
||||
|
||||
uploadBtn.disabled = true;
|
||||
uploadBtn.textContent = 'Uploading...';
|
||||
resultDiv.classList.add('hidden');
|
||||
|
||||
try {
|
||||
const response = await fetch('/effects/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="bg-green-900 border border-green-700 rounded p-3 text-green-300">
|
||||
<p class="font-medium">Effect uploaded!</p>
|
||||
<p class="text-sm mt-1">${data.name} <span class="font-mono">${data.friendly_name}</span></p>
|
||||
</div>
|
||||
`;
|
||||
resultDiv.classList.remove('hidden');
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} else {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="bg-red-900 border border-red-700 rounded p-3 text-red-300">
|
||||
<p class="font-medium">Upload failed</p>
|
||||
<p class="text-sm mt-1">${data.detail || 'Unknown error'}</p>
|
||||
</div>
|
||||
`;
|
||||
resultDiv.classList.remove('hidden');
|
||||
uploadBtn.disabled = false;
|
||||
uploadBtn.textContent = 'Upload';
|
||||
}
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="bg-red-900 border border-red-700 rounded p-3 text-red-300">
|
||||
<p class="font-medium">Upload failed</p>
|
||||
<p class="text-sm mt-1">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
resultDiv.classList.remove('hidden');
|
||||
uploadBtn.disabled = false;
|
||||
uploadBtn.textContent = 'Upload';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user