This commit is contained in:
team3
2026-09-30 01:16:32 +02:00
parent d10be4ad65
commit 26da589d90
3 changed files with 38 additions and 19 deletions

View File

@@ -11,7 +11,7 @@ EFFORT = "high"
PARALLEL = 5 # gleichzeitige Kapitel-Agenten
MAX_TURNS = 800
KONSISTENZ_GRUPPE = 5 # Kapitel je Querverweis-Agent
LESE_WPM = 180
LESE_WPM = 120
STUFEN = [
("recherche", "Thema deuten und Quellen beschaffen"),

View File

@@ -30,7 +30,7 @@ class ThemaNeu(BaseModel):
class Position(BaseModel):
abschnitt: str | None = None
lesemodus: int | None = None
lesemodus: str | int | None = None # neu: key des Bildschirms, alt: Index
def _pruefen(name: str) -> dict:

View File

@@ -8,25 +8,41 @@ const emit = defineEmits(['schliessen'])
const buehne = ref(null)
const pos = ref(0)
// Bildschirme: Vorwort, je Kapitel ein Intro, je Abschnitt ein Bildschirm, Ende.
// Ein Abschnitt wird an seinen Zwischenüberschriften (###) in Seiten geteilt; Codeblöcke bleiben ganz.
function teile(text) {
const out = []
let akt = [], zaun = false
for (const zeile of (text || '').split('\n')) {
if (/^(```|~~~)/.test(zeile)) zaun = !zaun
if (!zaun && /^### /.test(zeile) && akt.some((z) => z.trim())) { out.push(akt.join('\n')); akt = [] }
akt.push(zeile)
}
if (akt.some((z) => z.trim())) out.push(akt.join('\n'))
return out.length ? out : ['']
}
// Bildschirme: Vorwort, je Kapitel ein Intro, je Seite eines Abschnitts ein Bildschirm, Ende.
// key ist die gespeicherte Leseposition und bleibt stabil, wenn Kapitel dazukommen.
const screens = computed(() => {
const out = []
const kaps = props.daten?.kapitel || []
if (props.daten?.vorwort) out.push({ typ: 'vorwort' })
if (props.daten?.vorwort) out.push({ typ: 'vorwort', key: 'vorwort' })
kaps.forEach((kap, ki) => {
out.push({ typ: 'kapitel', kap, nr: ki + 1, von: kaps.length })
for (const a of kap.abschnitte) out.push({ typ: 'abschnitt', a, kap })
out.push({ typ: 'kapitel', kap, nr: ki + 1, von: kaps.length, key: 'k' + (ki + 1) })
for (const a of kap.abschnitte) {
const t = teile(a.text)
t.forEach((text, i) => out.push({ typ: 'abschnitt', a, kap, text, i, n: t.length, key: a.id + ':' + i }))
}
})
out.push({ typ: 'ende' })
out.push({ typ: 'ende', key: 'ende' })
return out
})
const screen = computed(() => screens.value[pos.value] || { typ: 'ende' })
const gesamt = computed(() => screens.value.filter((s) => s.typ === 'abschnitt').length)
const gelesen = computed(() => screens.value.slice(0, pos.value + 1).filter((s) => s.typ === 'abschnitt').length)
const cache = new Map()
function html(a) {
const key = a.id + ':' + (a.text || '').length
if (!cache.has(key)) cache.set(key, render(a.text))
function html(s) {
const key = s.key + ':' + s.text.length
if (!cache.has(key)) cache.set(key, render(s.text))
return cache.get(key)
}
@@ -36,7 +52,8 @@ let merkTimer = null
watch(pos, (p) => {
buehne.value?.scrollTo({ top: 0 })
clearTimeout(merkTimer)
merkTimer = setTimeout(() => api.positionSetzen(props.topic, { lesemodus: p }).catch(() => {}), 1500)
const key = screens.value[p]?.key
if (key) merkTimer = setTimeout(() => api.positionSetzen(props.topic, { lesemodus: key }).catch(() => {}), 1500)
})
function taste(e) {
@@ -65,8 +82,10 @@ function wischEnde(e) {
}
onMounted(async () => {
window.addEventListener('keydown', taste)
const merk = Number((await api.position(props.topic).catch(() => ({}))).lesemodus || 0)
pos.value = Math.min(merk, screens.value.length - 1)
const merk = (await api.position(props.topic).catch(() => ({}))).lesemodus
// Alte Positionen waren ein Index, neue sind ein key.
const i = typeof merk === 'string' ? screens.value.findIndex((s) => s.key === merk) : Number(merk || 0)
pos.value = Math.min(Math.max(i, 0), screens.value.length - 1)
})
onUnmounted(() => window.removeEventListener('keydown', taste))
</script>
@@ -74,9 +93,9 @@ onUnmounted(() => window.removeEventListener('keydown', taste))
<template>
<div class="lesemodus" @touchstart.passive="wischStart" @touchend.passive="wischEnde">
<header class="lm-kopf">
<span>{{ gelesen }} / {{ gesamt }} Abschnitte</span>
<span>{{ gelesen }} / {{ gesamt }} Seiten</span>
<div class="balken"><i :style="{ width: (gesamt ? 100 * gelesen / gesamt : 0) + '%' }"></i></div>
<span v-if="screen.typ === 'abschnitt'">{{ screen.kap.titel }}</span>
<span v-if="screen.typ === 'abschnitt'">{{ screen.a.nr }} · {{ screen.i + 1 }} / {{ screen.n }}</span>
<button title="Schließen (Esc)" @click="emit('schliessen')">✕</button>
</header>
<main ref="buehne" class="lm-buehne">
@@ -95,13 +114,13 @@ onUnmounted(() => window.removeEventListener('keydown', taste))
<div v-else class="stats">{{ screen.kap.abschnitte.length }} Abschnitte · {{ zeit(screen.kap.minuten) }}</div>
</section>
<section v-else-if="screen.typ === 'abschnitt'" class="lm-screen">
<div class="lm-label">{{ screen.kap.titel }}</div>
<h2>{{ screen.a.nr }} {{ screen.a.titel }}</h2>
<div v-html="html(screen.a)"></div>
<div class="lm-label">{{ screen.kap.titel }}<template v-if="screen.i > 0"> · {{ screen.a.nr }} {{ screen.a.titel }}</template></div>
<h2 v-if="screen.i === 0">{{ screen.a.nr }} {{ screen.a.titel }}</h2>
<div v-html="html(screen)"></div>
</section>
<section v-else class="lm-screen ende">
<h1>Fertig gelesen</h1>
<p class="intro">Alle {{ gesamt }} Abschnitte von „{{ daten.titel }}“.</p>
<p class="intro">Alle {{ gesamt }} Seiten von „{{ daten.titel }}“.</p>
<button class="sekundaer" style="margin-top: 16px" @click="pos = 0">Von vorn</button>
</section>
</main>