Compare commits
3 Commits
de9fbaed4a
...
17b92c77ef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17b92c77ef | ||
|
|
64ef9396d6 | ||
|
|
255d44fbf6 |
215
server.py
215
server.py
@@ -612,7 +612,7 @@ def render_home_html(actor_id: Optional[str] = None) -> str:
|
||||
<a href="/recipes" class="px-4 py-2 bg-dark-500 hover:bg-dark-600 rounded-md text-blue-400 hover:text-blue-300 font-medium transition-colors">Recipes</a>
|
||||
<a href="/media" class="px-4 py-2 bg-dark-500 hover:bg-dark-600 rounded-md text-blue-400 hover:text-blue-300 font-medium transition-colors">Media</a>
|
||||
<a href="/storage" class="px-4 py-2 bg-dark-500 hover:bg-dark-600 rounded-md text-blue-400 hover:text-blue-300 font-medium transition-colors">Storage</a>
|
||||
<a href="/docs" class="px-4 py-2 bg-dark-500 hover:bg-dark-600 rounded-md text-blue-400 hover:text-blue-300 font-medium transition-colors">API Docs</a>
|
||||
<a href="/docs" class="px-4 py-2 bg-dark-500 hover:bg-dark-600 rounded-md text-gray-400 hover:text-gray-300 font-medium transition-colors">API</a>
|
||||
{user_section}
|
||||
</nav>
|
||||
|
||||
@@ -1437,7 +1437,7 @@ async def run_plan_node_detail(run_id: str, step_id: str, request: Request):
|
||||
return HTMLResponse(f'<p class="text-red-400">Run not found</p>', status_code=404)
|
||||
|
||||
# Load plan data (with fallback to generate from recipe)
|
||||
plan_data = await asyncio.to_thread(load_plan_for_run_with_fallback, run)
|
||||
plan_data = await load_plan_for_run_with_fallback(run)
|
||||
|
||||
if not plan_data:
|
||||
return HTMLResponse('<p class="text-gray-400">Plan not found</p>')
|
||||
@@ -1584,7 +1584,7 @@ async def run_plan_visualization(run_id: str, request: Request, node: Optional[s
|
||||
return HTMLResponse(render_page("Access Denied", content, ctx.actor_id, active_tab="runs"), status_code=403)
|
||||
|
||||
# Load plan data (with fallback to generate from recipe)
|
||||
plan_data = await asyncio.to_thread(load_plan_for_run_with_fallback, run)
|
||||
plan_data = await load_plan_for_run_with_fallback(run)
|
||||
|
||||
# Build sub-navigation tabs
|
||||
tabs_html = render_run_sub_tabs(run_id, active="plan")
|
||||
@@ -6444,187 +6444,84 @@ async def download_client():
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Documentation Routes
|
||||
# Help / Documentation Routes
|
||||
# ============================================================================
|
||||
|
||||
# Documentation paths
|
||||
DOCS_DIR = Path(__file__).parent
|
||||
COMMON_DOCS_DIR = Path(__file__).parent.parent / "common"
|
||||
|
||||
DOCS_MAP = {
|
||||
"l1": DOCS_DIR / "README.md",
|
||||
"common": COMMON_DOCS_DIR / "README.md",
|
||||
"l1": ("L1 Server (Celery)", DOCS_DIR / "README.md"),
|
||||
"common": ("Common Library", COMMON_DOCS_DIR / "README.md"),
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(content: str) -> str:
|
||||
"""Convert markdown to HTML with basic styling."""
|
||||
import re
|
||||
|
||||
# Escape HTML first
|
||||
content = content.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
# Code blocks (``` ... ```)
|
||||
def code_block_replace(match):
|
||||
lang = match.group(1) or ""
|
||||
code = match.group(2)
|
||||
return f'<pre class="bg-gray-800 p-4 rounded-lg overflow-x-auto text-sm"><code class="language-{lang}">{code}</code></pre>'
|
||||
content = re.sub(r'```(\w*)\n(.*?)```', code_block_replace, content, flags=re.DOTALL)
|
||||
|
||||
# Inline code
|
||||
content = re.sub(r'`([^`]+)`', r'<code class="bg-gray-700 px-1 rounded text-sm">\1</code>', content)
|
||||
|
||||
# Headers
|
||||
content = re.sub(r'^### (.+)$', r'<h3 class="text-lg font-semibold text-white mt-6 mb-2">\1</h3>', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^## (.+)$', r'<h2 class="text-xl font-bold text-white mt-8 mb-3 border-b border-gray-700 pb-2">\1</h2>', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'^# (.+)$', r'<h1 class="text-2xl font-bold text-white mb-4">\1</h1>', content, flags=re.MULTILINE)
|
||||
|
||||
# Bold and italic
|
||||
content = re.sub(r'\*\*([^*]+)\*\*', r'<strong class="font-semibold">\1</strong>', content)
|
||||
content = re.sub(r'\*([^*]+)\*', r'<em>\1</em>', content)
|
||||
|
||||
# Links
|
||||
content = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2" class="text-blue-400 hover:underline">\1</a>', content)
|
||||
|
||||
# Tables
|
||||
def table_replace(match):
|
||||
lines = match.group(0).strip().split('\n')
|
||||
if len(lines) < 2:
|
||||
return match.group(0)
|
||||
|
||||
header = lines[0]
|
||||
rows = lines[2:] if len(lines) > 2 else []
|
||||
|
||||
header_cells = [cell.strip() for cell in header.split('|')[1:-1]]
|
||||
header_html = ''.join(f'<th class="px-4 py-2 text-left border-b border-gray-600">{cell}</th>' for cell in header_cells)
|
||||
|
||||
rows_html = ''
|
||||
for row in rows:
|
||||
cells = [cell.strip() for cell in row.split('|')[1:-1]]
|
||||
cells_html = ''.join(f'<td class="px-4 py-2 border-b border-gray-700">{cell}</td>' for cell in cells)
|
||||
rows_html += f'<tr class="hover:bg-gray-700">{cells_html}</tr>'
|
||||
|
||||
return f'<table class="w-full text-sm mb-4"><thead><tr class="bg-gray-700">{header_html}</tr></thead><tbody>{rows_html}</tbody></table>'
|
||||
|
||||
content = re.sub(r'(\|[^\n]+\|\n)+', table_replace, content)
|
||||
|
||||
# Bullet points
|
||||
content = re.sub(r'^- (.+)$', r'<li class="ml-4 list-disc">\1</li>', content, flags=re.MULTILINE)
|
||||
content = re.sub(r'(<li[^>]*>.*</li>\n?)+', r'<ul class="mb-4">\g<0></ul>', content)
|
||||
|
||||
# Paragraphs (lines not starting with < or whitespace)
|
||||
lines = content.split('\n')
|
||||
result = []
|
||||
in_paragraph = False
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
if in_paragraph:
|
||||
result.append('</p>')
|
||||
in_paragraph = False
|
||||
result.append('')
|
||||
elif stripped.startswith('<'):
|
||||
if in_paragraph:
|
||||
result.append('</p>')
|
||||
in_paragraph = False
|
||||
result.append(line)
|
||||
else:
|
||||
if not in_paragraph:
|
||||
result.append('<p class="mb-4 text-gray-300">')
|
||||
in_paragraph = True
|
||||
result.append(line)
|
||||
if in_paragraph:
|
||||
result.append('</p>')
|
||||
content = '\n'.join(result)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
@app.get("/docs", response_class=HTMLResponse)
|
||||
async def docs_index(request: Request):
|
||||
@app.get("/help", response_class=HTMLResponse)
|
||||
async def help_index(request: Request):
|
||||
"""Documentation index page."""
|
||||
user = await get_optional_user(request)
|
||||
username = user.username if user else None
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html class="dark">
|
||||
<head>
|
||||
<title>Documentation - Art DAG L1</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>tailwind.config = {{ darkMode: 'class' }}</script>
|
||||
</head>
|
||||
<body class="bg-gray-900 text-gray-100 min-h-screen">
|
||||
<nav class="bg-gray-800 border-b border-gray-700 px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="/" class="text-xl font-bold text-white">Art DAG L1</a>
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="/runs" class="text-gray-300 hover:text-white">Runs</a>
|
||||
<a href="/recipes" class="text-gray-300 hover:text-white">Recipes</a>
|
||||
<a href="/media" class="text-gray-300 hover:text-white">Media</a>
|
||||
<a href="/docs" class="text-white font-semibold">Docs</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="max-w-4xl mx-auto p-8">
|
||||
<h1 class="text-3xl font-bold mb-8">Documentation</h1>
|
||||
# Build doc links
|
||||
doc_links = ""
|
||||
for key, (title, path) in DOCS_MAP.items():
|
||||
if path.exists():
|
||||
doc_links += f'''
|
||||
<a href="/help/{key}" class="block p-6 bg-dark-600 rounded-lg hover:bg-dark-500 transition-colors">
|
||||
<h2 class="text-xl font-semibold text-white mb-2">{title}</h2>
|
||||
<p class="text-gray-400">View documentation</p>
|
||||
</a>
|
||||
'''
|
||||
|
||||
content = f'''
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-2xl font-bold text-white mb-6">Documentation</h1>
|
||||
<div class="grid gap-4">
|
||||
<a href="/docs/l1" class="block p-6 bg-gray-800 rounded-lg hover:bg-gray-700 transition">
|
||||
<h2 class="text-xl font-semibold text-white mb-2">L1 Server (Celery)</h2>
|
||||
<p class="text-gray-400">Distributed rendering server with Celery workers, IPFS integration, and 3-phase execution.</p>
|
||||
</a>
|
||||
<a href="/docs/common" class="block p-6 bg-gray-800 rounded-lg hover:bg-gray-700 transition">
|
||||
<h2 class="text-xl font-semibold text-white mb-2">Common Library</h2>
|
||||
<p class="text-gray-400">Shared components: Jinja2 templates, middleware, content negotiation, and utilities.</p>
|
||||
</a>
|
||||
{doc_links}
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>"""
|
||||
return HTMLResponse(html)
|
||||
</div>
|
||||
'''
|
||||
return HTMLResponse(render_page("Help", content, username))
|
||||
|
||||
|
||||
@app.get("/docs/{doc_name}", response_class=HTMLResponse)
|
||||
async def docs_page(doc_name: str, request: Request):
|
||||
"""Render a markdown documentation file as HTML."""
|
||||
@app.get("/help/{doc_name}", response_class=HTMLResponse)
|
||||
async def help_page(doc_name: str, request: Request):
|
||||
"""Render a README as HTML."""
|
||||
if doc_name not in DOCS_MAP:
|
||||
raise HTTPException(404, f"Documentation '{doc_name}' not found")
|
||||
|
||||
doc_path = DOCS_MAP[doc_name]
|
||||
title, doc_path = DOCS_MAP[doc_name]
|
||||
if not doc_path.exists():
|
||||
raise HTTPException(404, f"Documentation file not found: {doc_path}")
|
||||
raise HTTPException(404, f"Documentation file not found")
|
||||
|
||||
content = doc_path.read_text()
|
||||
html_content = render_markdown(content)
|
||||
user = await get_optional_user(request)
|
||||
username = user.username if user else None
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html class="dark">
|
||||
<head>
|
||||
<title>{doc_name.upper()} - Art DAG Documentation</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>tailwind.config = {{ darkMode: 'class' }}</script>
|
||||
</head>
|
||||
<body class="bg-gray-900 text-gray-100 min-h-screen">
|
||||
<nav class="bg-gray-800 border-b border-gray-700 px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="/" class="text-xl font-bold text-white">Art DAG L1</a>
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="/runs" class="text-gray-300 hover:text-white">Runs</a>
|
||||
<a href="/recipes" class="text-gray-300 hover:text-white">Recipes</a>
|
||||
<a href="/media" class="text-gray-300 hover:text-white">Media</a>
|
||||
<a href="/docs" class="text-white font-semibold">Docs</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="max-w-4xl mx-auto p-8">
|
||||
# Read and render markdown
|
||||
import markdown
|
||||
md_content = doc_path.read_text()
|
||||
html_content = markdown.markdown(md_content, extensions=['tables', 'fenced_code'])
|
||||
|
||||
content = f'''
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="mb-4">
|
||||
<a href="/docs" class="text-blue-400 hover:underline">← Back to Documentation</a>
|
||||
<a href="/help" class="text-blue-400 hover:underline">← Back to Help</a>
|
||||
</div>
|
||||
<article class="prose prose-invert max-w-none">
|
||||
<div class="prose prose-invert max-w-none
|
||||
prose-headings:text-white prose-headings:border-b prose-headings:border-dark-500 prose-headings:pb-2
|
||||
prose-h1:text-2xl prose-h1:mt-0
|
||||
prose-h2:text-xl prose-h2:mt-6
|
||||
prose-a:text-blue-400 hover:prose-a:text-blue-300
|
||||
prose-pre:bg-dark-600 prose-pre:border prose-pre:border-dark-500
|
||||
prose-code:bg-dark-600 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-green-300
|
||||
prose-table:border-collapse
|
||||
prose-th:bg-dark-600 prose-th:border prose-th:border-dark-500 prose-th:px-4 prose-th:py-2
|
||||
prose-td:border prose-td:border-dark-500 prose-td:px-4 prose-td:py-2">
|
||||
{html_content}
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>"""
|
||||
return HTMLResponse(html)
|
||||
</div>
|
||||
</div>
|
||||
'''
|
||||
return HTMLResponse(render_page(title, content, username))
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user