Add README documentation for artdag-common library

- 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>
This commit is contained in:
giles
2026-01-11 09:58:43 +00:00
parent fd97812e3d
commit 866e6e308f

293
README.md Normal file
View File

@@ -0,0 +1,293 @@
# 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