目录

Timepress

English · 日本語 · 繁體中文 · 简体中文 · Español

One story. Many eras.

Timepress turns a short event you write into a news article, then shows how a newspaper or magazine from another era and culture would have reported it. Choose the 1890s London press, a 1930s Shanghai daily, a 1980s Chinese national paper or a 1990s tech magazine. Both the writing and the page design change with the edition.

Every page is an openly fictional creative reinterpretation, never an archival article. Your event is the only source of facts.


Contents


How it works

  1. Write an event in one to three sentences, in English or Chinese, real or imagined.
  2. Choose an edition from the dropdown.
  3. Publish. The backend asks a language model to retell the event in that edition’s editorial voice. The frontend sets the result in that edition’s page design.
  4. Compare. Switch editions to read the same event as another era would have written it. Results are cached for the session, so switching back is instant.

With no model configured, the site runs in a clearly labelled demo mode, so it can be shown without any API key.

Features

  • Four editions, each with its own editorial voice and page design, two Western and two Chinese.
  • Cross-language input: write in English or Chinese for any edition. The output is written in the edition’s language: English, Traditional Chinese or Simplified Chinese.
  • Interface in five languages: English, 日本語, 繁體中文, 简体中文 and Español, chosen from the top-right corner.
  • Two model API formats, each with official or third-party endpoints: OpenAI-compatible and Anthropic Messages API.
  • Settings in the browser (owner only): add providers, save keys to the OS keychain, pick a model and reasoning level, and run a connection test. This follows the same flow as ResearchDeck.
  • Honest by design: visible “creative reinterpretation” labels, sample content marked as such, and a prompt that forbids invented names, quotes, dates or figures.
  • Seven sample stories, each hand-written for all four editions (28 articles), so demo mode still reads well.
  • Copy article and Print / Save as PDF.
  • Accessible and responsive: keyboard-operable controls, live status announcements, correct lang attributes, prefers-reduced-motion support, and a single-column layout on phones.
  • No images: every page is typeset with HTML, CSS and inline SVG only.

Editions

Edition Fictional publication Output language Look and voice
London · 1890s Broadsheet The London Chronicle British English Ivory paper, double rules, three justified columns and a drop cap. Measured, formal and quietly curious.
Shanghai · 1930s Daily 海上新報 Traditional Chinese Warm newsprint, a vertical calligraphic masthead and authentic right-to-left vertical text on wide screens (with a toggle). Brisk prose mixing classical and vernacular Chinese.
China · 1980s National Daily 时代通讯 Simplified Chinese A red masthead and rules, a section box and column rules. Concise, formal, institutional reporting.
Technology · 1990s Magazine NEXT BYTE American English Bold sans-serif type, neon panels, an oversized “01” and a stats sidebar. Punchy and jargon-aware.

Every masthead is an original, fictional name. Real papers served only as research inspiration.

Tech stack

Layer Technology
Frontend React 19, TypeScript, Vite; plain CSS with custom properties; no UI framework
Backend Python 3.12, FastAPI, Pydantic, Uvicorn
Model access httpx for OpenAI-compatible endpoints; the official anthropic SDK for the Anthropic Messages API
Secrets OS keychain via keyring (Settings dialog), or backend/.env
Tooling pnpm (JavaScript), uv (Python), oxlint, pytest
Storage None: no database, accounts or analytics
Browser (React) ──/api──▶ FastAPI ──▶ OpenAI-compatible API  or  Anthropic Messages API
      ▲                     │
      └── typeset page ◀────┘  validated JSON {headline, deck, section, paragraphs}

In development, Vite proxies /api to the backend, so API keys never reach the browser.

Getting started

Prerequisites

  • Node.js 22.12+ and pnpm
  • uv (it installs Python 3.12 for you)
node --version && pnpm --version && uv --version

1. Install

git clone <your-repo-url> timepress
cd timepress

cd backend && uv sync && cd ..
cd frontend && pnpm install && cd ..

2. Configure (optional)

cp backend/.env.example backend/.env

Leave it as it is to run in demo mode. You can connect a model later in the browser’s Settings dialog, or with .env variables (see Connecting a language model).

3. Run

Use two terminals, both starting from the project root:

# Terminal 1: API on http://localhost:8000
cd backend
uv run uvicorn app.main:app --reload --port 8000
# Terminal 2: website on http://localhost:5173
cd frontend
pnpm dev

Open http://localhost:5173. The badge in the header shows Demo mode, or the live provider, for example External AI · Anthropic · claude-opus-5-5.

4. Build for production (frontend)

cd frontend && pnpm build   # outputs static files to frontend/dist

Serve frontend/dist from any static host, and route /api to the FastAPI app, for example with uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 behind a reverse proxy.

On a public server, set TIMEPRESS_SETTINGS=off and configure the model through .env, or set TIMEPRESS_ADMIN_TOKEN and make sure your proxy sends X-Forwarded-For. See Who can open Settings.

Connecting a language model

There are two ways to connect a model. Both support the same providers.

  1. Settings dialog (recommended): click the gear icon at the top right of the site, on the computer that runs the backend.
  2. backend/.env: environment variables, useful for servers and automation.

A default provider that passed its connection test in Settings takes precedence. Otherwise .env is used, and without either, demo mode.

Settings in the browser

  1. Click the gear (Settings) → AI providers.
  2. Choose OpenAI or Anthropic, or click Add provider for a third-party or local service. A custom provider needs a name, an API format (OpenAI-compatible or Anthropic-compatible) and a base URL; plain http:// is allowed only for localhost.
  3. Paste the API key and click Save key. It is stored in your OS keychain (the macOS Keychain on a Mac), and Settings shows only its last four characters. Built-in providers can also use OPENAI_API_KEY / ANTHROPIC_API_KEY from the environment.
  4. Choose a model: type an ID, or click Load models to pick from the provider’s list.
  5. Choose a reasoning level: Provider default sends nothing, otherwise exactly the level you pick.
  6. Click Run connection test. It sends one short fixed message (never a visitor’s event). On success, the provider shows Ready.
  7. The first provider to pass its test becomes the default automatically, and it is marked In use. Use Set as default to switch to another provider. The header badge shows which one is writing, for example External AI · Anthropic · claude-opus-5-5.

Changing the model, reasoning level, key or endpoint requires a new test. There are no silent substitutions: Timepress sends exactly what you chose. The Anthropic refusal fallback is an explicit checkbox, off by default. Providers on your own computer are labelled Local AI, and everything else External AI.

Who can open Settings

  • By default (TIMEPRESS_SETTINGS=local), only direct requests from the same computer may open Settings: the request must come from a loopback address, carry no proxy forwarding headers, and have a local Host (and a local Origin if one is present). Public visitors never see the gear.
  • TIMEPRESS_ADMIN_TOKEN=<secret> also allows access from other machines. The dialog asks for the token, which is kept only for the browser session.
  • TIMEPRESS_SETTINGS=off disables the dialog and its API completely, which is recommended for public deployments.

Configuration through .env

Timepress supports both common model API formats. Each works with the official endpoint or a third-party one:

LLM_API_FORMAT Official Third-party / self-hosted
openai (default): /v1/chat/completions OpenAI: set the key and LLM_MODEL OpenRouter, DeepSeek, Qwen, Moonshot, Gemini’s OpenAI endpoint, vLLM, Ollama… Also set LLM_BASE_URL.
anthropic: /v1/messages, through the official SDK Anthropic: the key is enough; the model defaults to claude-opus-5-5 Any gateway that exposes the Messages API. Also set LLM_BASE_URL, plus LLM_AUTH_SCHEME=bearer if it expects Authorization: Bearer.

Put the settings in backend/.env. Example setups:

# Official OpenAI
LLM_API_FORMAT=openai
LLM_API_KEY=sk-...
LLM_MODEL=your-model-name

# Third-party OpenAI-compatible provider
LLM_API_FORMAT=openai
LLM_BASE_URL=https://provider.example.com/v1
LLM_API_KEY=...
LLM_MODEL=provider-model-name

# Official Anthropic
LLM_API_FORMAT=anthropic
LLM_API_KEY=sk-ant-...

# Third-party Anthropic-compatible gateway
LLM_API_FORMAT=anthropic
LLM_BASE_URL=https://gateway.example.com
LLM_API_KEY=...
LLM_MODEL=claude-sonnet-5-5
LLM_AUTH_SCHEME=bearer

Restart the backend after editing .env, because auto-reload only watches code. Then confirm the mode:

curl http://localhost:8000/api/health   # "mode": "generated" means a model is connected

All settings

Variable Default Meaning
LLM_API_FORMAT openai openai or anthropic
LLM_BASE_URL official endpoint Set only for a third-party or self-hosted server. A trailing /v1 is fine for Anthropic.
LLM_API_KEY none API key; optional for a keyless self-hosted OpenAI-compatible server
LLM_MODEL claude-opus-5-5 for Anthropic Required for openai
DEMO_MODE false true serves labelled sample content even when a model is configured
LLM_TIMEOUT_SECONDS 60 Upstream request timeout
LLM_JSON_MODE on for official Anthropic, otherwise off Structured JSON output (response_format / output_config.format)
LLM_EFFORT medium for official Anthropic, otherwise not sent Reasoning effort: Anthropic output_config.effort (low to max) or OpenAI reasoning_effort (minimal to high); none disables it
LLM_FALLBACKS on for official Anthropic, otherwise off Anthropic server-side refusal fallback
LLM_AUTH_SCHEME x-api-key Anthropic only: x-api-key or bearer
TIMEPRESS_SETTINGS local local (Settings for direct local requests) or off
TIMEPRESS_ADMIN_TOKEN none Optional token that unlocks Settings from other machines

Features that third-party gateways often reject are on by default only for the official Anthropic API; enable them for a gateway once you know it supports them. Choose a capable model, because the Chinese editions need fluent Traditional and Simplified Chinese in a period register.

Security: keys live only in the OS keychain or in backend/.env, which git ignores. Neither is ever returned to the browser, and the API shows only the last four characters. Upstream error details (with the key redacted) appear in Settings and in the server log; public visitors see only a short translated message.

Using the site

  • Sample stories: click a chip under the text box (for example “Century-old lighthouse”), then Publish in this era.
  • Keyboard: ⌘/Ctrl + Enter publishes, and the edition dropdown is a native <select>.
  • Switching editions reuses the last published event. Editing the text alone never triggers a model call.
  • Shanghai edition: on wide screens, toggle between 直排 (vertical) and 橫排 (horizontal) typesetting.
  • Interface language: the globe dropdown at the top right. Your choice is remembered in the browser.
  • Settings: the gear at the top right (site owner only). See Settings in the browser.

Demo mode

Without credentials, the API serves clearly labelled fixtures:

  • The seven sample events return hand-written articles for every edition, marked Sample content.
  • Any other event is quoted word for word inside the edition’s template, with a note that full expansion needs a configured model. Nothing is invented.

API reference

Method and path Description
GET /api/health {status, mode: "generated" | "fixture", editions, provider, settings}
POST /api/generate Body {source_text, edition_id}. source_text must be 10–2,000 characters. edition_id is one of london-1890, shanghai-1930, china-1980 or technology-1995.

A successful response:

{
  "edition_id": "china-1980",
  "headline": "…",
  "deck": "…",
  "section": "…",
  "paragraphs": ["…", "…"],
  "mode": "generated",
  "disclaimer": "Creative historical reinterpretation; not an archival article.",
  "notice": null,
  "notice_code": null
}

Errors return {detail, code}. detail is in English. code is stable and translatable: invalid_source_text, invalid_edition, provider_timeout (HTTP 504), provider_unreachable, provider_auth, provider_busy, provider_refused, provider_not_found, provider_bad_request, provider_error or bad_model_output (HTTP 502).

The owner-only Settings API lives under /api/settings. It has endpoints to list providers, add, update or remove them, save or remove a key, list models, run a connection test, and set the default; backend/app/settings_api.py documents each one. GET /api/health also reports the live provider (name, model, local) and whether this browser may open Settings.

Model output is parsed and validated with Pydantic. Malformed output gets one bounded repair attempt. The frontend renders every field as plain text, never as HTML.

Testing

cd backend && uv run pytest              # 99 tests
cd frontend && pnpm build && pnpm lint   # type-check, build, lint

The backend tests cover:

  • the sample articles for every story and edition, and input validation;
  • both API formats, with official and gateway request shapes checked against a mocked HTTP layer;
  • the JSON repair attempt, refusals and timeouts;
  • the Settings API: keychain storage, masking, readiness, the default provider and access control;
  • keys never appearing in responses or logs.

Project structure

timepress/
├── AGENTS.md                     # Contract for coding agents working on this repo
├── README*.md                    # This file, in five languages
├── docs/DECISIONS.md             # Design and implementation decisions
├── backend/
│   ├── .env.example              # Configuration template (copy to .env)
│   ├── app/
│   │   ├── main.py               # FastAPI routes, error mapping
│   │   ├── schemas.py            # Pydantic request/response contracts
│   │   ├── generation.py         # Shared prompt rules, JSON parsing, repair attempt
│   │   ├── editions.py           # Edition allowlist and per-edition editorial direction
│   │   ├── providers.py          # Configuration + OpenAI-compatible adapter
│   │   ├── anthropic_provider.py # Anthropic Messages API adapter (official SDK)
│   │   ├── provider_factory.py   # Picks and caches the adapter for a configuration
│   │   ├── settings_store.py     # Provider records + OS keychain storage + readiness
│   │   ├── settings_api.py       # Owner-only /api/settings routes and access rules
│   │   └── fixtures.py           # Sample stories and demo templates
│   └── tests/
└── frontend/
    └── src/
        ├── App.tsx               # Page state, session cache, actions
        ├── components/           # Composer, edition selector, frame, language switcher, Settings dialog
        ├── editions/             # One component per edition + catalog.ts
        ├── i18n/                 # Interface translations (en, ja, zh-Hant, zh-Hans, es)
        ├── lib/                  # API clients (articles, settings), types, sample events, date helpers
        └── styles/               # base.css (app), editions.css (four page designs)

Content principles

  • The visitor’s event is the only source of facts. The model must not invent people, quotes, dates, places, numbers or sources. When a detail is missing, it says so rather than filling the gap.
  • Modern subjects stay modern. A smartphone is still a smartphone in 1890.
  • Period flavour is created through conventions such as vocabulary, rhythm and layout, not caricature, stereotypes or political editorialising.
  • Visitor text is treated as untrusted data. Embedded instructions are ignored.
  • Every page carries a visible creative reinterpretation label, and sample content is always marked.

Troubleshooting

Symptom Fix
Address already in use Another server is on that port. Run lsof -nP -iTCP:8000 -sTCP:LISTEN, then kill <PID>.
cd: no such file or directory: frontend Run each server in its own terminal, starting from the project root.
Header shows Press server offline The backend isn’t running on port 8000.
Still in demo mode after adding a key In Settings, the provider must be Ready and Default. For .env, restart the backend and check LLM_API_FORMAT, plus LLM_MODEL for openai.
No gear icon Open the site on the computer running the backend (http://localhost:5173), or set TIMEPRESS_ADMIN_TOKEN. Check that TIMEPRESS_SETTINGS isn’t off.
“No OS keychain” in Settings The server has no keychain backend (common on headless Linux). Put the key in backend/.env instead.
macOS asks to allow keychain access Choose Always Allow for the Python process that runs the backend.
HTTP 502 with provider_error from a gateway It may not support a feature. Set LLM_JSON_MODE, LLM_EFFORT=none or LLM_FALLBACKS=false; the server log shows the upstream reason.

Limitations and roadmap

Current limitations

  • The frontend needs the backend running, even in demo mode, because the fixtures live on the server.
  • Sample events are listed in both frontend/src/lib/examples.ts and backend/app/fixtures.py, and must be kept in sync by hand.
  • Long generated Shanghai articles scroll horizontally inside their vertical frame.

Possible next steps

  • Side-by-side comparison of two editions.
  • More researched editions, such as Japanese and other East Asian press traditions.
  • Shareable snapshots and image export generated from the DOM.
  • Optional notes explaining why a phrase or layout is typical of its era.

Contributing

  1. Read AGENTS.md (the project contract) and docs/DECISIONS.md.
  2. Use pnpm for the frontend and uv for the backend. Don’t switch package managers.
  3. Run the checks in Testing before opening a pull request.
  4. New interface text needs all five locales in frontend/src/i18n/locales/. The Messages type makes a missing key a compile error.
  5. Never commit backend/.env or any API key.

License

No license has been chosen yet. Until one is added, all rights are reserved by the authors.

邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号