File size: 9,956 Bytes
561e6f0
5908601
561e6f0
 
 
 
 
 
b3d2294
34f4083
56a5607
6b6afea
561e6f0
2b3982d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
---
title: Research Article Template Editor
emoji: ✏️
colorFrom: purple
colorTo: blue
sdk: docker
app_port: 8080
pinned: false
hf_oauth: true
hf_oauth_scopes:
  - manage-repos
  - inference-api
---

# Research Article Template Editor

A collaborative, real-time editor for web-native scientific articles. It lets multiple authors co-write a paper with rich text, math, citations, figures and interactive D3 embeds, then publishes the result as a static HTML page (or a PDF) aligned with the [research-article-template](https://github.com/huggingface/research-article-template).

## What it gives you

- **Real-time collaboration** over WebSocket (Y.js + Hocuspocus), with visible cursors and per-user selection colors
- **Rich article authoring**: headings, lists, tables, code blocks with syntax highlighting, LaTeX math (KaTeX), footnotes, sidenotes, block quotes, callouts
- **Research-specific blocks**: citations + bibliography (BibTeX), figures with captions, stacks / wide / full-width layouts, glossary terms, Mermaid/Wardley/architecture diagrams
- **Interactive D3 embeds** authored inline: each embed is a self-contained HTML file the editor can generate and iterate on via an **AI-assisted "embed studio"**
- **Comments & discussion** anchored on any selection
- **Slash menu** (`/`) and drag/drop block handles, in the spirit of Notion
- **Click-to-edit frontmatter**: title, subtitle, authors, affiliations, links, banner color
- **Publishing pipeline**: one-click export to a standalone static HTML bundle, plus PDF generation (Puppeteer) and an `llms.txt` Markdown twin for LLM agents/crawlers (served at `/llms.txt`, advertised in `/robots.txt`)
- **Persistence**:
  - Local mode: documents stored on disk under `DATA_DIR`
  - HF mode: documents pushed/pulled from a Hugging Face dataset via OAuth
- **Dark mode**, responsive layout (TOC drawer on mobile), live table of contents with scroll-spy
- **AI chat side-panel** that can edit the article via structured tool calls (agent loop over the current TipTap doc)

## Stack

| Layer | Tech |
|---|---|
| Editor | React 18, TypeScript, TipTap v3, ProseMirror |
| Collaboration | Y.js, Hocuspocus (WebSocket), y-tiptap |
| Backend | Node.js, Express, Vite (dev proxy), Hocuspocus server |
| Publishing | Custom TipTap-JSON → HTML renderer, Puppeteer for PDF |
| AI | Vercel AI SDK v6 (`ai`, `@ai-sdk/react`) → Hugging Face Inference Providers (OpenAI-compatible router) |
| Styling | Plain CSS with custom properties, no framework |
| Storage | Local FS or Hugging Face datasets (via `@huggingface/hub`) |
| Container | Single-image Docker build, runs on port 8080 |

Around **3.6k LOC backend** and **9.5k LOC frontend** (TypeScript/TSX, excluding generated code).

## Repo layout

```
collab-editor/
├── backend/              # Express + Hocuspocus server, publisher, AI agent routes
│   └── src/
│       ├── server.ts             # Entry point
│       ├── create-app.ts         # App factory (routes, middleware, Hocuspocus)
│       ├── publisher/            # TipTap-JSON → HTML + PDF
│       ├── agent/                # LLM agent (tool calls over the doc)
│       ├── shared/               # Component defs shared with the frontend
│       └── hf-storage.ts         # HF dataset sync
├── frontend/             # Vite + React + TipTap editor
│   └── src/
│       ├── App.tsx               # Top-level shell
│       ├── editor/               # TipTap editor + extensions + components
│       ├── components/           # Shared UI pieces (TOC, Chat, Dialog, ...)
│       ├── hooks/                # React hooks (agent chat, selection, ...)
│       ├── styles/               # CSS layers (see docs/ARCHITECTURE.md)
│       └── utils/
├── docs/
│   ├── ARCHITECTURE.md           # Deep dive on layers, data flow, CSS
│   ├── SPECIFICATION.md          # Feature spec and contracts
│   ├── TESTS.md                  # Testing strategy
│   └── embed-studio.md           # How the AI-authored embeds pipeline works
└── Dockerfile            # Production multi-stage build
```

See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for a diagram and the full tour.

## Getting started

### Prerequisites

- Node.js 20+
- A Hugging Face token with the `Make calls to Inference Providers` permission for the AI features (embed studio, chat agent). Generate one at https://huggingface.co/settings/tokens. On a HF Space the logged-in user's OAuth token is used instead - no manual setup needed.
- A Hugging Face OAuth app (client id/secret) if you want login + HF dataset persistence

### Local development

Backend and frontend run as two separate processes in dev (Vite proxies `/api`, `/collab`, `/uploads`, `/published`, `/oauth`, `/auth` to the backend).

```bash
# terminal 1 — backend (Express + Hocuspocus on :8080)
cd backend
cp .env.example .env          # set HF_TOKEN, optional OAUTH_* and HF_DATASET_ID
npm install
npm run dev

# terminal 2 — frontend (Vite on :5678)
cd frontend
npm install
npm run dev
```

Then open http://localhost:5678. Open a second tab or browser to see collaboration in action.

### Production (Docker / HF Spaces)

The `Dockerfile` builds both frontend and backend into a single image listening on port 8080. This is the image used by the Hugging Face Space.

```bash
docker build -t collab-editor .
docker run -p 8080:8080 --env-file backend/.env collab-editor
```

Then open http://localhost:8080.

### Run your own copy on a Hugging Face Space

Want your own editor? One step:

1. **Duplicate the Space.** On https://huggingface.co/spaces/tfrere/research-article-template-editor, click `⋯ → Duplicate this Space`. Pick your namespace and visibility. HF copies the Dockerfile, the OAuth wiring and rebuilds the image automatically.

That's it. No API key to wire up. The AI features (chat agent + embed studio) call **Hugging Face Inference Providers** at `https://router.huggingface.co/v1` using the OAuth token of whoever is currently logged in. As long as your duplicated Space requests the `inference-api` scope (already declared in the README frontmatter as `hf_oauth_scopes`), every editor gets AI for free under their own Inference Providers quota.

Optional public variable: `HF_INFERENCE_MODEL` (e.g. `meta-llama/Llama-3.3-70B-Instruct`) to override the default model id. The full list of supported chat-completion models lives at https://huggingface.co/models?inference_provider=all&other=conversational.

## Scripts

### Backend (`cd backend`)

| Command | What it does |
|---|---|
| `npm run dev` | Start Express + Hocuspocus in watch mode |
| `npm run build` | Compile TypeScript to `dist/` |
| `npm start` | Run the compiled server |
| `npm run test` | Unit + integration tests (Vitest) |
| `npm run test:e2e` | End-to-end tests (Playwright) |

### Frontend (`cd frontend`)

| Command | What it does |
|---|---|
| `npm run dev` | Start Vite dev server on :5678 |
| `npm run build` | Production bundle to `dist/` |
| `npm run preview` | Preview the built bundle |
| `npm run test` | Unit tests (Vitest) |
| `npm run typecheck` | `tsc --noEmit` on the whole frontend |

## Environment variables

Copy `backend/.env.example` to `backend/.env` and fill the relevant values. Key ones:

| Variable | Purpose |
|---|---|
| `OAUTH_CLIENT_ID` / `OAUTH_CLIENT_SECRET` | HF OAuth app for user login (required to edit when running on a Space) |
| `OAUTH_SCOPES` | OAuth scopes (default `openid profile`). Add `manage-repos` for dataset persistence and `inference-api` to power the AI features with the user's token |
| `HF_TOKEN` | Server-side Hugging Face token. Used as a fallback when no user OAuth token is present (e.g. local dev). Needs the `Make calls to Inference Providers` permission to enable the chat agent + embed studio |
| `HF_INFERENCE_MODEL` | Override the default chat-completion model id (defaults to `openai/gpt-oss-120b`). Any tool-calling-capable model exposed by HF Inference Providers works |
| `HF_DATASET_ID` | Target HF dataset repo for document persistence (when not running on a Space) |
| `SPACE_ID` / `SPACE_HOST` | Auto-set by HF Spaces; drive dataset id + secure cookies in production |
| `DATA_DIR` | Where documents, uploads and published bundles are stored on disk (default: `./data`) |
| `PUBLISH_BASE_URL` | Absolute base URL used when publishing (defaults to `http://127.0.0.1:${PORT}`) |
| `ENABLE_PDF` | Set to `false` to disable Playwright-based PDF export |
| `PORT` | Server port (default 8080) |

## Testing

- **Backend unit tests**: Vitest covers the publisher (HTML renderer, frontmatter, bibliography), storage, auth utilities.
- **Backend E2E**: Playwright drives the full editor against a real backend.
- **Frontend unit tests**: Vitest covers chat persistence and a handful of utilities.
- **Type checking**: `npm run typecheck` in both workspaces.

See [`docs/TESTS.md`](docs/TESTS.md) for the current strategy and gaps.

## Known technical debt

These are tracked explicitly so new contributors don't trip on them:

- **`useEmbedChat` still lacks dedicated unit tests**; the rest of the stores (frontmatter, comments, embeds) and the agent undo batching primitive are now covered.
- **Bundle size warning**: the frontend bundle is over the 500 kB Vite warning threshold. Code-splitting the Mermaid / KaTeX / D3 stacks via dynamic imports would help.
- **`addToolOutput` typing**: the ai-sdk v6 `ChatAddToolOutputFunction` is a generic over the tool name union. We currently cast to a plain signature at the two call sites because we don't export a typed tool registry yet.
- **`backend/src/publisher/html-renderer.ts` is ~1000 LOC**: a per-node-type registry would make it more maintainable.

## License

Follow the upstream [research-article-template](https://github.com/huggingface/research-article-template) license.