tfrere's picture
tfrere HF Staff
fix(auth): cookie lifetime now matches the JWT exp, not a stale 8h fallback
5908601
|
Raw
History Blame Contribute Delete
9.96 kB
metadata
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.

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 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).

# 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.

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 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 license.