Files
TrueNews/README.md
T

142 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TrueNews
A responsive news journal generated from `pages/*.md`. Includes a front page, search, category filters, individual article pages, and a 404 page. Article HTML is generated at build time, so articles work without JavaScript.
## Run
Requires Node.js 22 or later.
```sh
npm install
npm run dev
```
Open http://localhost:3000. Changes to `pages/` and `public/` rebuild the site; refresh your browser to see them. Set `PORT` to choose a different port. Restart the server after changing files in `scripts/`.
## Write an article
Add a `.md` file directly inside `pages/`:
```md
---
title: A new perspective
description: A short introduction for the front page.
date: 2026-09-12
category: World
author: Alex Morgan
url: /world/a-new-perspective/
image: /assets/my-photo.jpg
---
Your opening paragraph.
## The bigger picture
Write with **Markdown**, including lists, links, quotes, and images.
```
All metadata is optional. `url` (or `slug`) sets the custom URL; otherwise `my-story.md` becomes `/my-story/`. URLs support letters, numbers, hyphens, underscores, and nested path segments. Duplicate, unsafe, and reserved URLs fail the build. `/assets/` and `/404/` are reserved. A title falls back to the first Markdown H1, then the filename. Articles sort by date, newest first; the first article is featured. Reading time and a fallback description are calculated automatically.
Put images in `public/` and refer to them as `/assets/filename.jpg`, or use HTTPS image URLs. Articles without an image receive a decorative illustration. Remote images and optional Google Fonts need internet access; system fonts provide a fallback. The included sample articles are illustrative content, not verified reporting. Replace or delete them before publishing.
## Build and deploy
```sh
npm test
npm run build
```
Publish `dist/` to a static web host that serves directory `index.html` files. For example, `/world/a-new-perspective/` corresponds to `dist/world/a-new-perspective/index.html`. Configure the host to use `404.html` for missing pages; do not use a single-page-app fallback. Adding or updating Markdown requires rebuilding and redeploying. `npm start` also builds and serves the site locally.
## Generate an article with local AI
Start Ollama with `llama3.2:3b` installed. Start the included local SearXNG search service once (requires Docker and Docker Compose):
```sh
npm run search:start
```
This downloads the SearXNG image on first use and runs it at http://localhost:8888, accessible only from this computer. Allow a few seconds for startup. No search API key is required. A random local secret is created in the git-ignored `search/.env`; running the setup again preserves it. The container restarts with Docker.
Then give the generator a short prompt:
```sh
npm run generate -- "Recent developments in urban renewable energy"
```
The command searches for sources, downloads up to three readable pages, sends their excerpts to **llama3.2:3b**, validates its structured response, saves a new `pages/*.md` file, and rebuilds `dist/`. It prints the article URL when finished. With `npm run dev` running, refresh the website to see it. Generation can take several minutes depending on your hardware.
For specific reporting, supply one or more source URLs:
```sh
npm run generate -- "Explain how Ollama structured outputs work" \
--source https://docs.ollama.com/capabilities/structured-outputs \
--url /technology/ollama-structured-outputs/
```
Repeat `--source` to use multiple pages. Explicit URLs skip search. `--url` is optional; the default includes a date, title slug, and random suffix to avoid overwriting articles. `--no-build` saves only the Markdown; the development server also watches for new files.
Configuration:
- `OLLAMA_HOST`: defaults to `http://127.0.0.1:11434`. The generator always uses `llama3.2:3b`, never the larger installed model.
- `SEARXNG_URL`: optional custom SearXNG address. Without a Brave key, the default is `http://127.0.0.1:8888`. JSON output must be enabled under `search.formats`; the included configuration already does this.
- `BRAVE_SEARCH_API_KEY`: optional alternative to local SearXNG. Used when a key is provided and `SEARXNG_URL` is not explicitly set.
Optional settings can be saved in `.env` (see `.env.example`); `npm run generate` loads that file automatically. Environment variables already set in the shell take precedence.
SearXNG searches external engines and returns source URLs. TrueNews downloads the pages, then sends their text to the local model. The model does not need another installation or a larger replacement. External engines can still time out or block requests; the generator reports search failures instead of inventing sources. Be specific about the topic and dates you want covered.
Search service commands:
```sh
npm run search:logs # Inspect startup or upstream search errors
npm run search:stop # Stop the local search service
npm run search:start # Start it again
```
If port 8888 is in use, change the host port in `compose.yaml` and set `SEARXNG_URL` accordingly. Source discovery uses the documented [SearXNG JSON search API](https://docs.searxng.org/dev/search_api); deployment follows its [container installation documentation](https://docs.searxng.org/admin/installation-docker).
Only successfully downloaded pages are supplied to the model. The generation schema restricts citation IDs to the supplied sources. Every paragraph must reference at least one valid source ID; the program adds the actual source links itself, plus source titles, retrieval dates, available publication dates, generation time, model, and original prompt in Markdown metadata. Articles are requested as 3–4 short paragraphs with schema-enforced character limits. If model output is malformed or contains invalid citations, the generator sends the validation error back to the same small model for one correction attempt. If output is truncated, it starts a fresh request for exactly three shorter paragraphs with a 4,096-token allowance, instead of continuing the unfinished JSON. Complete, validated JSON is accepted even if Ollama reports the token limit. It never guesses replacement citations. If validation still fails, generation stops without creating an article. Missing or unreadable sources also stop generation.
Articles appear on the site automatically after generation and are labeled AI-generated and not independently fact-checked. Valid citations do not guarantee that the model interpreted the source correctly. Read the article and original sources before treating it as verified reporting. Source dates are preserved separately from the article's generation date; sources are not guaranteed to be recent. The reader extracts up to 5,000 characters per page and cannot read paywalls or pages requiring JavaScript.
Generation is a local terminal command, not a public website endpoint. Source text is sent to your configured Ollama server; prompts are sent to the search provider only when source discovery is used. The original prompt is retained in the local Markdown metadata.
The integration uses Ollama's documented [chat API](https://docs.ollama.com/api/chat) and [structured outputs](https://docs.ollama.com/capabilities/structured-outputs).
## Browser push notifications
Run `npm start` (or `npm run dev`) to serve the notification API and background delivery worker. Visitors see a dismissible invitation; clicking **Enable notifications** opens the browser permission dialog. The footer lets them turn notifications off or enable them later. Dismissed invitations stay hidden for 30 days. Denied permissions are not requested automatically again.
No Google account, Firebase project, or search API key is needed for notifications. The site uses standard Web Push with automatically generated VAPID keys. The browser chooses its push delivery service.
Settings go in `.env` and are loaded by the server at startup:
```dotenv
# Local testing:
SITE_URL=http://localhost:3000
# After deployment, replace with your real HTTPS address:
# SITE_URL=https://news.your-domain.com
# Optional contact address used for Web Push authentication:
# VAPID_SUBJECT=mailto:you@your-domain.com
# Optional controls:
# PORT=3000
# PUSH_ENABLED=false
# PUSH_DATA_DIR=.data/push
```
Restart the server after changing `.env`. A production `SITE_URL` must match the browser's public origin exactly, including any nonstandard port. Behind a reverse proxy, forward `/api/push/*`, `/sw.js`, and the website to this Node server; serve the public site over HTTPS. The API validates subscription requests against `SITE_URL`.
Subscriptions, signing keys, delivery retries, and previously published article URLs are stored in `.data/push/`, outside the public site and ignored by Git. **Persist and back up this directory across deployments.** Losing signing keys invalidates existing subscriptions. This implementation is intended for one running Node server with persistent disk, not multiple replicas or ephemeral/serverless storage. A static-only deployment of `dist/` can display articles but cannot accept subscriptions or send notifications.
After a successful Markdown rebuild, the server queues new article URLs and normally begins delivery within five seconds. Existing articles on first startup and edits to previously seen URLs do not produce notifications. New URLs discovered after an ordinary restart are queued for existing subscribers. Temporary delivery failures retry up to five total attempts; expired subscriptions are removed. Pending notifications expire after 24 hours. Delivery is best effort and depends on the browser, OS settings, and push provider. A process crash at the moment of delivery may repeat a notification; article-specific notification tags reduce duplicates.
Desktop browsers can test on `http://localhost:3000`; other hosts require HTTPS. For Safari testing, set `VAPID_SUBJECT` to your contact `mailto:` address, because Apple's push service rejects a localhost VAPID subject. On iPhone/iPad (iOS/iPadOS 16.4+), add the HTTPS website to the Home Screen and open it there before enabling notifications. A web app manifest and notification icons are included. The service worker opens the article when its notification is clicked and does not cache article pages.
References: [browser notification permissions](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API), [Apple Web Push support](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/), [Web Push library](https://github.com/web-push-libs/web-push).
Notification verification: `npm test` covers persistence, queuing, retries, origin checks, and service-worker behavior. For browser tests, start a separate preview with `PORT=3100 SITE_URL=http://localhost:3100 PUSH_DATA_DIR=/tmp/truenews-push-tests npm start`, then run `npm run test:browser`. Set `CHROMIUM_PATH` if needed, or install Playwright Chromium with `npx playwright install chromium`. Browser tests mock permission and subscription enrollment, exercise the actual subscription API, and verify real service-worker registration; they do not send real device notifications.