- Document Jinja2 templating system with examples - Document authentication middleware (UserContext, cookie/header parsing) - Document content negotiation utilities - List all reusable template components - Document custom Jinja2 filters 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
294 lines
6.6 KiB
Markdown
294 lines
6.6 KiB
Markdown
# artdag-common
|
|
|
|
Shared components for Art-DAG L1 (celery) and L2 (activity-pub) servers.
|
|
|
|
## Features
|
|
|
|
- **Jinja2 Templating**: Unified template environment with shared base templates
|
|
- **Reusable Components**: Cards, tables, pagination, DAG visualization, media preview
|
|
- **Authentication Middleware**: Cookie and JWT token parsing
|
|
- **Content Negotiation**: HTML/JSON/ActivityPub format detection
|
|
- **Utility Functions**: Hash truncation, file size formatting, status colors
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pip install -e /path/to/artdag-common
|
|
|
|
# Or add to requirements.txt
|
|
-e file:../common
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```python
|
|
from fastapi import FastAPI, Request
|
|
from artdag_common import create_jinja_env, render
|
|
|
|
app = FastAPI()
|
|
|
|
# Initialize templates with app-specific directory
|
|
templates = create_jinja_env("app/templates")
|
|
|
|
@app.get("/")
|
|
async def home(request: Request):
|
|
return render(templates, "home.html", request, title="Home")
|
|
```
|
|
|
|
## Package Structure
|
|
|
|
```
|
|
artdag_common/
|
|
├── __init__.py # Package exports
|
|
├── constants.py # CDN URLs, colors, configs
|
|
├── rendering.py # Jinja2 environment and helpers
|
|
├── middleware/
|
|
│ ├── auth.py # Authentication utilities
|
|
│ └── content_negotiation.py # Accept header parsing
|
|
├── models/
|
|
│ ├── requests.py # Shared request models
|
|
│ └── responses.py # Shared response models
|
|
├── utils/
|
|
│ ├── formatting.py # Text/date formatting
|
|
│ ├── media.py # Media type detection
|
|
│ └── pagination.py # Pagination helpers
|
|
└── templates/
|
|
├── base.html # Base layout template
|
|
└── components/
|
|
├── badge.html # Status/type badges
|
|
├── card.html # Info cards
|
|
├── dag.html # Cytoscape DAG visualization
|
|
├── media_preview.html # Video/image/audio preview
|
|
├── pagination.html # HTMX pagination
|
|
└── table.html # Styled tables
|
|
```
|
|
|
|
## Jinja2 Templates
|
|
|
|
### Base Template
|
|
|
|
The `base.html` template provides:
|
|
- Dark theme with Tailwind CSS
|
|
- HTMX integration
|
|
- Navigation slot
|
|
- Content block
|
|
- Optional Cytoscape.js block
|
|
|
|
```html
|
|
{% extends "base.html" %}
|
|
|
|
{% block title %}My Page{% endblock %}
|
|
|
|
{% block content %}
|
|
<h1>Hello World</h1>
|
|
{% endblock %}
|
|
```
|
|
|
|
### Reusable Components
|
|
|
|
#### Card
|
|
|
|
```html
|
|
{% include "components/card.html" %}
|
|
```
|
|
|
|
```html
|
|
<!-- Usage in your template -->
|
|
<div class="...card styles...">
|
|
{% block card_title %}Title{% endblock %}
|
|
{% block card_content %}Content{% endblock %}
|
|
</div>
|
|
```
|
|
|
|
#### Badge
|
|
|
|
Status and type badges with appropriate colors:
|
|
|
|
```html
|
|
{% from "components/badge.html" import status_badge, type_badge %}
|
|
|
|
{{ status_badge("completed") }} <!-- Green -->
|
|
{{ status_badge("failed") }} <!-- Red -->
|
|
{{ type_badge("video") }}
|
|
```
|
|
|
|
#### DAG Visualization
|
|
|
|
Interactive Cytoscape.js graph:
|
|
|
|
```html
|
|
{% include "components/dag.html" %}
|
|
```
|
|
|
|
Requires passing `nodes` and `edges` data to template context.
|
|
|
|
#### Media Preview
|
|
|
|
Responsive media preview with format detection:
|
|
|
|
```html
|
|
{% include "components/media_preview.html" %}
|
|
```
|
|
|
|
Supports video, audio, and image formats.
|
|
|
|
#### Pagination
|
|
|
|
HTMX-powered infinite scroll pagination:
|
|
|
|
```html
|
|
{% include "components/pagination.html" %}
|
|
```
|
|
|
|
## Template Rendering
|
|
|
|
### Full Page Render
|
|
|
|
```python
|
|
from artdag_common import render
|
|
|
|
@app.get("/runs/{run_id}")
|
|
async def run_detail(run_id: str, request: Request):
|
|
run = get_run(run_id)
|
|
return render(templates, "runs/detail.html", request, run=run)
|
|
```
|
|
|
|
### Fragment Render (HTMX)
|
|
|
|
```python
|
|
from artdag_common import render_fragment
|
|
|
|
@app.get("/runs/{run_id}/status")
|
|
async def run_status_fragment(run_id: str):
|
|
run = get_run(run_id)
|
|
html = render_fragment(templates, "components/status.html", status=run.status)
|
|
return HTMLResponse(html)
|
|
```
|
|
|
|
## Authentication Middleware
|
|
|
|
### UserContext
|
|
|
|
```python
|
|
from artdag_common.middleware.auth import UserContext, get_user_from_cookie
|
|
|
|
@app.get("/profile")
|
|
async def profile(request: Request):
|
|
user = get_user_from_cookie(request)
|
|
if not user:
|
|
return RedirectResponse("/login")
|
|
return {"username": user.username, "actor_id": user.actor_id}
|
|
```
|
|
|
|
### Token Parsing
|
|
|
|
```python
|
|
from artdag_common.middleware.auth import get_user_from_header, decode_jwt_claims
|
|
|
|
@app.get("/api/me")
|
|
async def api_me(request: Request):
|
|
user = get_user_from_header(request)
|
|
if not user:
|
|
raise HTTPException(401, "Not authenticated")
|
|
return {"user": user.username}
|
|
```
|
|
|
|
## Content Negotiation
|
|
|
|
Detect what response format the client wants:
|
|
|
|
```python
|
|
from artdag_common.middleware.content_negotiation import wants_html, wants_json, wants_activity_json
|
|
|
|
@app.get("/users/{username}")
|
|
async def user_profile(username: str, request: Request):
|
|
user = get_user(username)
|
|
|
|
if wants_activity_json(request):
|
|
return ActivityPubActor(user)
|
|
elif wants_json(request):
|
|
return user.dict()
|
|
else:
|
|
return render(templates, "users/profile.html", request, user=user)
|
|
```
|
|
|
|
## Constants
|
|
|
|
### CDN URLs
|
|
|
|
```python
|
|
from artdag_common import TAILWIND_CDN, HTMX_CDN, CYTOSCAPE_CDN
|
|
|
|
# Available in templates as globals:
|
|
# {{ TAILWIND_CDN }}
|
|
# {{ HTMX_CDN }}
|
|
# {{ CYTOSCAPE_CDN }}
|
|
```
|
|
|
|
### Node Colors
|
|
|
|
```python
|
|
from artdag_common import NODE_COLORS
|
|
|
|
# {
|
|
# "SOURCE": "#3b82f6", # Blue
|
|
# "EFFECT": "#22c55e", # Green
|
|
# "OUTPUT": "#a855f7", # Purple
|
|
# "ANALYSIS": "#f59e0b", # Amber
|
|
# "_LIST": "#6366f1", # Indigo
|
|
# "default": "#6b7280", # Gray
|
|
# }
|
|
```
|
|
|
|
### Status Colors
|
|
|
|
```python
|
|
STATUS_COLORS = {
|
|
"completed": "bg-green-600",
|
|
"cached": "bg-blue-600",
|
|
"running": "bg-yellow-600",
|
|
"pending": "bg-gray-600",
|
|
"failed": "bg-red-600",
|
|
}
|
|
```
|
|
|
|
## Custom Jinja2 Filters
|
|
|
|
The following filters are available in all templates:
|
|
|
|
| Filter | Usage | Description |
|
|
|--------|-------|-------------|
|
|
| `truncate_hash` | `{{ hash\|truncate_hash }}` | Shorten hash to 16 chars with ellipsis |
|
|
| `format_size` | `{{ bytes\|format_size }}` | Format bytes as KB/MB/GB |
|
|
| `status_color` | `{{ status\|status_color }}` | Get Tailwind class for status |
|
|
|
|
Example:
|
|
|
|
```html
|
|
<span class="{{ run.status|status_color }}">
|
|
{{ run.status }}
|
|
</span>
|
|
|
|
<code>{{ content_hash|truncate_hash }}</code>
|
|
|
|
<span>{{ file_size|format_size }}</span>
|
|
```
|
|
|
|
## Development
|
|
|
|
```bash
|
|
cd /root/art-dag/common
|
|
|
|
# Install in development mode
|
|
pip install -e .
|
|
|
|
# Run tests
|
|
pytest
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
- `fastapi>=0.100.0` - Web framework
|
|
- `jinja2>=3.1.0` - Templating engine
|
|
- `pydantic>=2.0.0` - Data validation
|