134 lines
9.2 KiB
JavaScript
134 lines
9.2 KiB
JavaScript
// Kleiner statischer Mock des Backends laut API.md für den Sichttest: node mock/server.js (Port 8020, PORT überschreibt)
|
||
import http from 'node:http'
|
||
|
||
const PORT = Number(process.env.PORT || 8020)
|
||
const jetzt = Date.now()
|
||
const iso = (minVor) => new Date(jetzt - minVor * 60000).toISOString()
|
||
|
||
const topics = [
|
||
{ name: 'Godot 4', titel: 'Godot 4', status: 'laeuft', laeuft: true, stufe: 'schreiben' },
|
||
{ name: 'Symfony 7', titel: 'Symfony 7', status: 'neu', laeuft: false, stufe: '' },
|
||
]
|
||
|
||
const bericht = `## Ergebnis
|
||
|
||
Kapitel 3 geschrieben, 4 Abschnitte, 6 900 Wörter.
|
||
|
||
- Quellen: \`quellen/godot-docs/tutorials/scripting/gdscript/\`
|
||
- Offen: Abschnitt 3.4 braucht ein Beispiel zu \`await\`
|
||
|
||
| Abschnitt | Wörter |
|
||
|---|---|
|
||
| 3.1 | 1400 |
|
||
| 3.2 | 1900 |
|
||
`
|
||
|
||
const state = {
|
||
name: 'Godot 4', titel: 'Godot 4', hinweise: 'für Entwickler, ab Version 4.6',
|
||
status: 'laeuft', grund: '', stufe: 'schreiben',
|
||
deutung: { titel: 'Godot 4', untertitel: 'Architektur, Konzepte und Praxis der Engine', buchtyp: 'werkzeug',
|
||
zielgruppe: 'Entwickler mit Programmiererfahrung, neu in Godot', referenz: 'Godot 4.6, offizielle Dokumentation',
|
||
deutung: 'Ein Werkzeugbuch: erklärt die Engine entlang ihrer Konzepte (Szenenbaum, Signale, GDScript) und zeigt jeweils die Praxis.' },
|
||
stufen: [
|
||
{ id: 'recherche', titel: 'Thema deuten und Quellen beschaffen', status: 'fertig', fertig: 1, gesamt: 1, kosten_usd: 1.2, tokens: 250000, gestartet: iso(60), beendet: iso(52) },
|
||
{ id: 'gliederung', titel: 'Gliederung', status: 'fertig', fertig: 1, gesamt: 1, kosten_usd: 0.8, tokens: 120000, gestartet: iso(52), beendet: iso(45) },
|
||
{ id: 'schreiben', titel: 'Kapitel schreiben', status: 'laeuft', fertig: 3, gesamt: 18, kosten_usd: 4.1, tokens: 1100000, gestartet: iso(45), beendet: '' },
|
||
{ id: 'pruefung', titel: 'Kapitel prüfen', status: 'laeuft', fertig: 1, gesamt: 18, kosten_usd: 0.9, tokens: 300000, gestartet: iso(30), beendet: '' },
|
||
{ id: 'konsistenz', titel: 'Querverweise und Vorwort', status: 'offen', fertig: 0, gesamt: 1, kosten_usd: 0, tokens: 0, gestartet: '', beendet: '' },
|
||
{ id: 'montage', titel: 'Buch zusammensetzen', status: 'offen', fertig: 0, gesamt: 1, kosten_usd: 0, tokens: 0, gestartet: '', beendet: '' },
|
||
],
|
||
kosten_usd: 7.0, tokens: 1770000,
|
||
agenten: [
|
||
{ id: 'recherche', stufe: 'recherche', name: 'Recherche', status: 'fertig', kosten_usd: 1.2, tokens: 250000, werkzeuge: 58, gestartet: iso(60), beendet: iso(52), bericht: '## Quellen\n\n12 Quellen geladen, davon 9 brauchbar.' },
|
||
{ id: 'gliederung', stufe: 'gliederung', name: 'Gliederung', status: 'fertig', kosten_usd: 0.8, tokens: 120000, werkzeuge: 14, gestartet: iso(52), beendet: iso(45), bericht: 'Gliederung mit 18 Kapiteln in 4 Teilen.' },
|
||
{ id: 'schreiben-01', stufe: 'schreiben', name: 'Kapitel 1 schreiben', status: 'fertig', kosten_usd: 0.9, tokens: 180000, werkzeuge: 41, gestartet: iso(45), beendet: iso(38), bericht },
|
||
{ id: 'schreiben-02', stufe: 'schreiben', name: 'Kapitel 2 schreiben', status: 'fehler', kosten_usd: 0.3, tokens: 60000, werkzeuge: 9, gestartet: iso(45), beendet: iso(42), bericht: '' },
|
||
{ id: 'schreiben-03', stufe: 'schreiben', name: 'Kapitel 3 schreiben', status: 'fertig', kosten_usd: 1.1, tokens: 220000, werkzeuge: 52, gestartet: iso(38), beendet: iso(30), bericht },
|
||
{ id: 'schreiben-04', stufe: 'schreiben', name: 'Kapitel 4 schreiben', status: 'laeuft', kosten_usd: 0.4, tokens: 90000, werkzeuge: 17, gestartet: iso(8), beendet: '', bericht: '' },
|
||
{ id: 'pruefung-01', stufe: 'pruefung', name: 'Kapitel 1 prüfen', status: 'fertig', kosten_usd: 0.5, tokens: 150000, werkzeuge: 22, gestartet: iso(30), beendet: iso(20), bericht: '## Prüfung\n\nAlle Aussagen belegt. Zwei Formulierungen korrigiert.' },
|
||
{ id: 'pruefung-03', stufe: 'pruefung', name: 'Kapitel 3 prüfen', status: 'laeuft', kosten_usd: 0.4, tokens: 150000, werkzeuge: 30, gestartet: iso(12), beendet: '', bericht: '' },
|
||
],
|
||
ereignisse: Array.from({ length: 100 }, (_, i) => ({
|
||
zeit: iso(100 - i), agent: i % 3 ? 'schreiben-04' : 'pruefung-03',
|
||
art: ['tool', 'text', 'start', 'ende', 'fehler'][i % 5],
|
||
text: i % 5 === 0 ? 'Write buch/04-szenen.md' : i % 5 === 1 ? 'Ich lese jetzt die Dokumentation zum Szenenbaum und gleiche die Signale ab.' : `Ereignis ${i}`,
|
||
})),
|
||
}
|
||
|
||
const text = (n) => Array.from({ length: n }, (_, i) => `Absatz ${i + 1}: Der Szenenbaum ist die zentrale Datenstruktur von Godot. Jeder Knoten hat genau einen Elternknoten, und Signale verbinden Knoten lose miteinander.`).join('\n\n')
|
||
const code = '\n\n```gdscript\nextends Node2D\n\nsignal getroffen(schaden: int)\n\nfunc _ready() -> void:\n\tgetroffen.connect(_on_getroffen)\n\nfunc _on_getroffen(schaden: int) -> void:\n\tprint("Schaden: %d" % schaden)\n```\n\n| Knoten | Zweck |\n|---|---|\n| Node2D | 2D-Transform |\n| Control | UI |\n\n'
|
||
const kap = (ord, titel, teil, status, n) => ({
|
||
id: String(ord).padStart(2, '0'), ord, titel, teil, datei: `buch/${String(ord).padStart(2, '0')}.md`, status,
|
||
intro: status === 'fehlt' ? '' : `Worum es in Kapitel ${ord} geht: ${titel}. Dieses Kapitel führt in die Grundlagen ein.`,
|
||
abschnitte: status === 'fehlt' ? [] : Array.from({ length: n }, (_, i) => ({
|
||
id: `${String(ord).padStart(2, '0')}-${i + 1}`, nr: `${ord}.${i + 1}`, titel: `Abschnitt ${i + 1} von ${titel}`,
|
||
text: text(4) + (i === 0 ? code : ''), woerter: 1400, minuten: 8 })),
|
||
woerter: status === 'fehlt' ? 0 : n * 1400, minuten: status === 'fehlt' ? 0 : n * 8,
|
||
berichte: status === 'fehlt' ? { autor: '', pruefer: '' } : { autor: bericht, pruefer: status === 'geschrieben' ? '' : '## Prüfung\n\n- 3.1: belegt\n- 3.2: eine Zahl korrigiert' },
|
||
})
|
||
const guide = {
|
||
titel: 'Godot 4', untertitel: 'Architektur, Konzepte und Praxis der Engine', status: 'laeuft',
|
||
vorwort: 'Dieses Buch erklärt Godot 4 entlang seiner Konzepte.\n\nEs richtet sich an Entwickler, die die Engine **verstehen** wollen, nicht nur bedienen.',
|
||
kapitel: [
|
||
kap(1, 'Die Engine im Überblick', 'Teil I – Grundlagen', 'konsistent', 3),
|
||
kap(2, 'Szenen und Knoten', 'Teil I – Grundlagen', 'fehlt', 0),
|
||
kap(3, 'GDScript', 'Teil I – Grundlagen', 'geprueft', 4),
|
||
kap(4, 'Signale', 'Teil II – Praxis', 'geschrieben', 2),
|
||
],
|
||
woerter: 12600, minuten: 72,
|
||
gliederung: '# Gliederung\n\n## Teil I – Grundlagen\n\n1. Die Engine im Überblick\n2. Szenen und Knoten\n3. GDScript\n\n## Teil II – Praxis\n\n4. Signale',
|
||
quellen: '# Quellen\n\n- Godot-Dokumentation 4.6 (docs.godotengine.org)\n- Engine-Quelltext, Tag 4.6-stable\n\n## Prüfmethode\n\nJedes Kapitel wird von einem unabhängigen Agenten gegen die Quellen geprüft.',
|
||
pdf: false,
|
||
}
|
||
|
||
function json(res, code, body) {
|
||
res.writeHead(code, { 'content-type': 'application/json' })
|
||
res.end(JSON.stringify(body))
|
||
}
|
||
http.createServer((req, res) => {
|
||
const url = new URL(req.url, 'http://x')
|
||
const p = url.pathname
|
||
let m
|
||
if (req.method === 'GET' && p === '/api/topics') return json(res, 200, topics)
|
||
if (req.method === 'POST' && p === '/api/topics') {
|
||
let body = ''
|
||
req.on('data', (c) => body += c)
|
||
return req.on('end', () => {
|
||
const { name } = JSON.parse(body || '{}')
|
||
const t = { name, titel: name, status: 'neu', laeuft: false, stufe: '' }
|
||
topics.push(t); json(res, 200, t)
|
||
})
|
||
}
|
||
if ((m = p.match(/^\/api\/topics\/([^/]+)$/)) && req.method === 'DELETE') {
|
||
const i = topics.findIndex((t) => t.name === decodeURIComponent(m[1]))
|
||
if (i >= 0) topics.splice(i, 1)
|
||
return json(res, 200, { ok: true })
|
||
}
|
||
if ((m = p.match(/^\/api\/topics\/([^/]+)\/(start|pause|reset)$/)) && req.method === 'POST') {
|
||
const t = topics.find((t) => t.name === decodeURIComponent(m[1]))
|
||
if (!t) return json(res, 404, { detail: 'unbekanntes Thema' })
|
||
t.status = m[2] === 'start' ? 'laeuft' : m[2] === 'pause' ? 'pausiert' : 'neu'; t.laeuft = t.status === 'laeuft'
|
||
if (t.name === state.name) state.status = t.status
|
||
return json(res, 200, { ok: true })
|
||
}
|
||
if ((m = p.match(/^\/api\/topics\/([^/]+)\/state$/))) {
|
||
const name = decodeURIComponent(m[1])
|
||
const t = topics.find((t) => t.name === name)
|
||
if (!t) return json(res, 404, { detail: 'unbekanntes Thema' })
|
||
if (name === state.name) return json(res, 200, state)
|
||
return json(res, 200, { name, titel: t.titel, hinweise: '', status: t.status, grund: '', stufe: '', deutung: null,
|
||
stufen: state.stufen.map((s) => ({ ...s, status: 'offen', fertig: 0, kosten_usd: 0, tokens: 0, gestartet: '', beendet: '' })),
|
||
kosten_usd: 0, tokens: 0, agenten: [], ereignisse: [] })
|
||
}
|
||
if ((m = p.match(/^\/api\/topics\/([^/]+)\/guide$/))) {
|
||
const name = decodeURIComponent(m[1])
|
||
if (name === state.name) return json(res, 200, guide)
|
||
return json(res, 200, { titel: name, untertitel: '', status: 'neu', vorwort: '', kapitel: [], woerter: 0, minuten: 0, gliederung: '', quellen: '', pdf: false })
|
||
}
|
||
if ((m = p.match(/^\/api\/topics\/([^/]+)\/guide\.md$/))) {
|
||
res.writeHead(200, { 'content-type': 'text/markdown; charset=utf-8' }); return res.end('# ' + guide.titel + '\n')
|
||
}
|
||
if ((m = p.match(/^\/api\/topics\/([^/]+)\/buch\.pdf$/))) return json(res, 404, { detail: 'PDF noch nicht gebaut' })
|
||
json(res, 404, { detail: 'nicht gefunden: ' + p })
|
||
}).listen(PORT, () => console.log(`Mock-Backend auf http://localhost:${PORT}`))
|