docs: rewrite README and AGENTS.md, add admin sync button
This commit is contained in:
parent
94f353d773
commit
9acdbaa453
4 changed files with 177 additions and 11 deletions
41
AGENTS.md
41
AGENTS.md
|
|
@ -1,5 +1,44 @@
|
|||
# Agents
|
||||
|
||||
Kirche Einfach — AI-assisted church administration. FastAPI backend (`backend/`, Python 3.12, SQLModel, SQLite) + React 19/Vite frontend (`frontend/`). Flow definitions live as JSON files in `backend/flows/` (source of truth); the DB is derived by the sync engine.
|
||||
|
||||
## Commands
|
||||
|
||||
Backend (run from `backend/`):
|
||||
- `uv sync` — install dependencies
|
||||
- `uv run pytest` — full test suite (throwaway SQLite file, no external DB needed)
|
||||
- `uv run alembic upgrade head` — migrate the dev database (`backend/data/`, gitignored)
|
||||
- `uv run python scripts/sync_flows.py --sync` — reconcile `backend/flows/` into DB + LFX
|
||||
- `uv run uvicorn app.main:app --port 8000` — run the API (paths resolve from repo root, CWD-independent)
|
||||
|
||||
Frontend (run from `frontend/`): `npm install`, `npm run dev`, `npm run build` (typecheck + build).
|
||||
|
||||
## Definition of done
|
||||
|
||||
- New behavior has tests; `uv run pytest` is green before reporting completion.
|
||||
- `npm run build` is green when frontend code changed.
|
||||
- Schema changes ship as a new Alembic revision — never edit an applied migration.
|
||||
|
||||
## Architecture rules
|
||||
|
||||
- `backend/flows/*.json` is the source of truth (one Langflow export per file). Never write flow JSON anywhere else.
|
||||
- Only `backend/app/flows/sync.py` reconciles files into the DB. CLI, `POST /api/flows/sync`, and the embedded watcher must call it — never duplicate its logic.
|
||||
- All DB access goes through `backend/app/flows/store.py`. Keep API handlers thin.
|
||||
- Talk to LFX only via `LfxClient`, to Langflow only via `LangflowClient`.
|
||||
- Flow JSON must stay a valid Langflow export (`store` requires `data.nodes`/`data.edges`).
|
||||
|
||||
```python
|
||||
from app.flows.sync import sync_from_directory
|
||||
report = await sync_from_directory(session, lfx, settings.flows_dir)
|
||||
```
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Never commit secrets. `.env` is gitignored; only `.env.example` is tracked.
|
||||
- Never edit `backend/data/*` (runtime SQLite files) or `__pycache__`.
|
||||
- Never add a second file-watcher or a second sync path.
|
||||
- Deleting `backend/flows/<name>.json` deletes the flow including its runs from the DB (by design).
|
||||
|
||||
## Git
|
||||
|
||||
When committing: `git add -A && git commit -m "<message>"`. When pushing: `git push` (branch is tracking `origin/main`).
|
||||
When committing: `git add -A && git commit -m "<message>"` (conventional commits: `feat:`, `fix:`, `chore:`, `docs:`). When pushing: `git push` (branch is tracking `origin/main`).
|
||||
|
|
|
|||
104
README.md
104
README.md
|
|
@ -1,17 +1,103 @@
|
|||
# Kirche Einfach
|
||||
|
||||
KI-gestützte Verwaltungsplattform für Kirche.
|
||||
AI-assisted administration platform for church organizations.
|
||||
|
||||
## Stack
|
||||
Staff describe a request in plain words. The platform guides them through the
|
||||
process, checks the rules automatically, and asks a human only when something
|
||||
needs judgment. Every step is documented and traceable.
|
||||
|
||||
- **Backend:** Python 3.12, FastAPI, Django, uv
|
||||
- **Frontend:** React 19, TypeScript, Vite
|
||||
- **Dev:** Devcontainer, opencode
|
||||
## Features
|
||||
|
||||
- **Visual flow authoring** — design decision flows in Langflow, then bring
|
||||
them into the platform.
|
||||
- **Flows as files** — each flow lives as one JSON file under
|
||||
`backend/flows/`. The directory is the source of truth: committed to git,
|
||||
reviewable, diffable.
|
||||
- **Automatic sync** — the API watches `backend/flows/` and reconciles every
|
||||
change into the database and the runtime: new files become flows, edits
|
||||
become new versions, deleted files remove the flow (including its runs).
|
||||
Same on every startup; also triggerable via `POST /api/flows/sync` or
|
||||
`uv run python scripts/sync_flows.py --sync`.
|
||||
- **Versioned runtime** — every change creates an immutable version; versions
|
||||
deploy to the lightweight LFX runtime and can run synchronously or streamed.
|
||||
- **Approvals** — a flow can pause and ask a human for a decision. Approving
|
||||
can automatically start a follow-up ("continuation") flow.
|
||||
- **Web UI** — three tabs: *Vorgänge* (flows and runs), *Freigaben*
|
||||
(approvals), *Administration* (import, sync, deploy).
|
||||
|
||||
## Infrastructure
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Repo["Git repo — source of truth"]
|
||||
FLOWS["backend/flows/*.json<br/>one Langflow export per flow"]
|
||||
end
|
||||
subgraph Stack["Compose stack (dev + prod)"]
|
||||
UI["frontend — React UI (3000)"]
|
||||
API["app — FastAPI (8000)<br/>+ embedded flow-watcher"]
|
||||
DB[("app.db — SQLite file")]
|
||||
LF["langflow — authoring UI (7860)<br/>langflow.db — SQLite file"]
|
||||
LFX["lfx — flow runtime (6004)"]
|
||||
WT["watchtower — nightly image updates"]
|
||||
end
|
||||
UI --> API
|
||||
FLOWS -->|watch + sync| API
|
||||
API -->|definitions · versions · runs · approvals| DB
|
||||
API -->|pull / export| LF
|
||||
API -->|upload · run · unregister| LFX
|
||||
LFX -->|approval callbacks| API
|
||||
WT -.->|updates :latest images| LF
|
||||
```
|
||||
|
||||
| Service | Purpose | URL |
|
||||
| -------------- | ------------------------------------ | -------------------------- |
|
||||
| React frontend | Web UI (Vite) | http://localhost:3000 |
|
||||
| FastAPI backend| API + flow orchestration | http://localhost:8000 |
|
||||
| API docs | Swagger UI | http://localhost:8000/api/docs |
|
||||
| Langflow | Visual flow authoring | http://localhost:7860 |
|
||||
| LFX | Flow runtime (internal, no direct UI)| localhost:6004 |
|
||||
| SQLite files | `backend/data/kirche.db` (app), Langflow keeps its own file | — |
|
||||
|
||||
Why SQLite: a flow run needs only a handful of single-row statements, and
|
||||
Langflow itself runs on SQLite by default — so the whole stack works without
|
||||
a database server. Postgres can return later by changing `DATABASE_URL`
|
||||
(migrations stay portable).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Open in VS Code → "Reopen in Container".
|
||||
1. Copy `.env.example` to `.env` and fill in the secrets.
|
||||
2. Open the repo in VS Code → "Reopen in Container". Setup installs
|
||||
dependencies, migrates the database, and syncs `backend/flows/`.
|
||||
3. Run the backend from `backend/`: `uv run uvicorn app.main:app --port 8000`.
|
||||
Run the frontend from `frontend/`: `npm run dev`.
|
||||
|
||||
Backend: `http://localhost:8000`
|
||||
Frontend: `http://localhost:3000`
|
||||
API Docs: `http://localhost:8000/api/docs`
|
||||
Production (self-hosted, internal use — no TLS/reverse proxy included):
|
||||
|
||||
```sh
|
||||
docker compose -f compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Backend (from `backend/`):
|
||||
|
||||
- `uv run pytest` — full test suite (throwaway SQLite file, no external DB).
|
||||
- `uv run alembic upgrade head` — migrate the dev database.
|
||||
- `uv run python scripts/sync_flows.py --sync` — reconcile `backend/flows/`.
|
||||
- `uv run python scripts/sync_flows.py --pull` — export all Langflow flows
|
||||
into `backend/flows/` first.
|
||||
- `uv run python scripts/deploy_flow.py --langflow-id <uuid>` — import one
|
||||
Langflow flow via the API.
|
||||
- `uv run python scripts/make_flow_fixtures.py` — regenerate the bundled
|
||||
flows (writes test fixtures and `backend/flows/`).
|
||||
- `uv run python scripts/smoke_e2e.py` — manual end-to-end smoke.
|
||||
|
||||
Frontend (from `frontend/`): `npm install`, `npm run dev`, `npm run build`.
|
||||
|
||||
Flow workflow: design in Langflow → export into `backend/flows/<name>.json`
|
||||
(`--pull`, or UI download) → commit → the watcher deploys it automatically.
|
||||
Deleting a file removes the flow including its runs (git keeps the JSON).
|
||||
|
||||
Container updates: registry images (`langflowai/langflow:latest`)
|
||||
auto-update nightly via Watchtower (`nickfedor/watchtower`). Locally built
|
||||
images (`app`, `lfx`, `frontend`) update with `docker compose build --pull`.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export interface FlowDefinition {
|
|||
name: string;
|
||||
description: string;
|
||||
status: string;
|
||||
source_path: string | null;
|
||||
continuation_flow_id: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
|
@ -60,6 +61,14 @@ export interface Decision {
|
|||
continuation_run: Run | null;
|
||||
}
|
||||
|
||||
export interface SyncReport {
|
||||
added: string[];
|
||||
updated: string[];
|
||||
deleted: string[];
|
||||
skipped: string[];
|
||||
errors: Record<string, string>;
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
constructor(status: number, message: string) {
|
||||
|
|
@ -135,4 +144,8 @@ export const api = {
|
|||
),
|
||||
listLangflow: () =>
|
||||
req<{ id: string; name: string }[]>("/api/flows/langflow/available"),
|
||||
syncFlows: () =>
|
||||
req<SyncReport>("/api/flows/sync", {
|
||||
method: "POST",
|
||||
}),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -64,6 +64,26 @@ export default function AdminTab() {
|
|||
}
|
||||
}
|
||||
|
||||
async function onSync() {
|
||||
setError("");
|
||||
setInfo("");
|
||||
try {
|
||||
const r = await api.syncFlows();
|
||||
const parts = [
|
||||
`+${r.added.length}`,
|
||||
`~${r.updated.length}`,
|
||||
`-${r.deleted.length}`,
|
||||
].join(" ");
|
||||
const errs = Object.entries(r.errors)
|
||||
.map(([f, e]) => `${f}: ${e}`)
|
||||
.join("; ");
|
||||
setInfo(`Sync ${parts}${errs ? ` — Fehler: ${errs}` : ""}`);
|
||||
await load();
|
||||
} catch (e) {
|
||||
setError(e instanceof ApiError ? `${e.status}: ${e.message}` : String(e));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Administration</h2>
|
||||
|
|
@ -93,11 +113,19 @@ export default function AdminTab() {
|
|||
</button>
|
||||
</div>
|
||||
<h3>Flows + Versionen</h3>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<button onClick={() => void onSync()}>
|
||||
Aus backend/flows synchronisieren
|
||||
</button>
|
||||
</div>
|
||||
{flows.length === 0 && <p>Keine Flows vorhanden.</p>}
|
||||
<ul>
|
||||
{flows.map((f) => (
|
||||
<li key={f.id} style={{ marginBottom: 8 }}>
|
||||
<strong>{f.name}</strong>
|
||||
<strong>{f.name}</strong>{" "}
|
||||
<span style={{ fontFamily: "monospace", fontSize: "0.85em" }}>
|
||||
{f.source_path ? `(${f.source_path})` : "(API)"}
|
||||
</span>
|
||||
<ul>
|
||||
{(versions[f.id] ?? []).map((v) => (
|
||||
<li key={v.id}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue