Files
buchwerk/frontend/src/components/Lauf.vue
T

131 lines
7.7 KiB
Vue
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.
<script setup>
import { computed, ref, watch } from 'vue'
import { render } from '../markdown.js'
const props = defineProps({ state: Object })
const emit = defineEmits(['start', 'pause', 'reset', 'neue-version'])
const laeuft = computed(() => props.state?.status === 'laeuft')
const fertig = computed(() => props.state?.status === 'fertig')
const begonnen = computed(() => ['pausiert', 'fehler', 'fertig'].includes(props.state?.status)
|| (props.state?.stufen || []).some((s) => s.status !== 'offen'))
function usd(n) { return (n || 0).toFixed(2) + ' $' }
function tokens(n) { return n >= 1e6 ? (n / 1e6).toFixed(2) + ' M' : n >= 1e3 ? Math.round(n / 1e3) + ' k' : String(n || 0) }
function dauer(s) {
if (!s.gestartet) return ''
const a = new Date(s.gestartet)
const b = s.beendet ? new Date(s.beendet) : new Date()
const sek = Math.max(0, Math.round((b - a) / 1000))
return sek < 90 ? `${sek} s` : `${Math.round(sek / 60)} min`
}
function uhr(t) { return t ? new Date(t).toLocaleTimeString('de-DE') : '' }
const statusText = { offen: 'wartet', laeuft: 'läuft', fertig: 'fertig', pausiert: 'pausiert', fehler: 'Fehler', abgebrochen: 'abgebrochen' }
const statusFarbe = { laeuft: 'blau', fertig: 'gruen', fehler: 'rot', abgebrochen: 'gelb', pausiert: 'gelb' }
// Laufende Agenten zuerst, danach die übrigen nach Stufe (in Stufenreihenfolge)
const gruppen = computed(() => {
const ag = props.state?.agenten || []
const out = [{ titel: 'Läuft gerade', agenten: ag.filter((a) => a.status === 'laeuft') }]
const rest = ag.filter((a) => a.status !== 'laeuft')
const stufen = (props.state?.stufen || []).map((s) => s.id)
for (const id of stufen) out.push({ titel: props.state.stufen.find((s) => s.id === id).titel, agenten: rest.filter((a) => a.stufe === id) })
out.push({ titel: 'Sonstige', agenten: rest.filter((a) => !stufen.includes(a.stufe)) })
return out.filter((g) => g.agenten.length)
})
const ereignisse = computed(() => (props.state?.ereignisse || []).slice(-100).reverse())
// Stopp nach der Gliederung: wählbar, solange die Gliederung noch aussteht; wird mit dem Start gespeichert
const stopp = ref(false)
watch(() => props.state?.stopp_gliederung, (s) => { stopp.value = !!s }, { immediate: true })
const vorGliederung = computed(() => (props.state?.stufen || []).find((s) => s.id === 'gliederung')?.status !== 'fertig')
function reset() { if (confirm('Lauf zurücksetzen? Alles außer dem Thema wird gelöscht.')) emit('reset') }
// Neue Version: Formular mit den Hinweisen dieser Version vorbelegt; Thema und Spezialisierung bleiben.
const neuOffen = ref(false)
const neuHinweise = ref('')
const neuStopp = ref(false)
function neuOeffnen() { neuHinweise.value = props.state.hinweise || ''; neuStopp.value = !!props.state.stopp_gliederung; neuOffen.value = true }
function neuStarten() { neuOffen.value = false; emit('neue-version', { hinweise: neuHinweise.value.trim(), stopp_gliederung: neuStopp.value }) }
</script>
<template>
<div class="lauf">
<div class="aktionen">
<button v-if="!laeuft && !fertig" @click="emit('start', stopp)">{{ begonnen ? 'Fortsetzen' : 'Buch erzeugen' }}</button>
<label v-if="!laeuft && !fertig && vorGliederung" class="haken" title="Günstiger Testlauf: Recherche und Gliederung, dann Pause"><input type="checkbox" v-model="stopp" /> Nach der Gliederung anhalten</label>
<button v-if="laeuft" class="sekundaer" @click="emit('pause')">Pause</button>
<button v-if="!laeuft && begonnen && !fertig" class="sekundaer" @click="reset">Zurücksetzen</button>
<button v-if="!laeuft && begonnen" class="sekundaer" title="Gleiches Thema, kompletter neuer Lauf; Hinweise änderbar" @click="neuOeffnen">Neue Version</button>
<span class="spacer"></span>
<span class="badge" :class="statusFarbe[state.status]">{{ statusText[state.status] || state.status }}<template v-if="state.stufe && laeuft"> · {{ state.stufe }}</template></span>
<span class="badge">{{ usd(state.kosten_usd) }}</span>
<span class="badge">{{ tokens(state.tokens) }} Tokens</span>
</div>
<div v-if="state.grund" class="grund">{{ state.grund }}</div>
<div v-if="neuOffen" class="neu-version">
<div class="titel">Neue Version von „{{ state.name }}“</div>
<input v-model="neuHinweise" placeholder="Hinweise (optional), z. B. für Entwickler, ab Version 4.6" @keyup.enter="neuStarten" />
<div class="knoepfe"><button @click="neuStarten">Buch erzeugen</button><label class="haken" title="Günstiger Testlauf: Recherche und Gliederung, dann Pause"><input type="checkbox" v-model="neuStopp" /> Nach der Gliederung anhalten</label><span class="spacer"></span><button class="sekundaer" @click="neuOffen = false">Abbrechen</button></div>
</div>
<div class="eingaben">
<b>Thema</b><span>{{ state.thema }}</span>
<b>Spezialisierung</b><span>{{ state.spezialisierung || '–' }}</span>
<b>Hinweise</b><span>{{ state.hinweise || '–' }}</span>
<template v-if="laeuft && state.stopp_gliederung && vorGliederung"><b>Lauf</b><span>hält nach der Gliederung an</span></template>
</div>
<div v-if="state.deutung" class="deutung">
<div class="titel">{{ state.deutung.titel }}</div>
<div v-if="state.deutung.untertitel" class="sub">{{ state.deutung.untertitel }}</div>
<p v-if="state.deutung.deutung">{{ state.deutung.deutung }}</p>
<div class="fakten">
<span v-if="state.deutung.buchtyp"><b>Buchtyp</b> {{ state.deutung.buchtyp === 'werkzeug' ? 'Werkzeug' : state.deutung.buchtyp === 'wissen' ? 'Wissen' : state.deutung.buchtyp }}</span>
<span v-if="state.deutung.zielgruppe"><b>Zielgruppe</b> {{ state.deutung.zielgruppe }}</span>
<span v-if="state.deutung.referenz"><b>Referenz</b> {{ state.deutung.referenz }}</span>
</div>
</div>
<div class="stufen">
<div v-for="s in state.stufen" :key="s.id" class="stufe" :class="s.status">
<div class="kreis">{{ s.status === 'fertig' ? '✓' : '' }}</div>
<div>
<div class="name">{{ s.titel }}</div>
<div class="sub">{{ statusText[s.status] || s.status }}<template v-if="s.gesamt"> · {{ s.fertig }} / {{ s.gesamt }}</template></div>
<div v-if="s.gesamt" class="balken"><i :style="{ width: Math.min(100, 100 * s.fertig / s.gesamt) + '%' }"></i></div>
</div>
<div class="sub rechts">{{ s.tokens ? usd(s.kosten_usd) + ' · ' + tokens(s.tokens) + ' Tokens' : '' }}</div>
<div class="sub rechts">{{ dauer(s) }}</div>
</div>
</div>
<div v-if="gruppen.length" class="agenten">
<template v-for="g in gruppen" :key="g.titel">
<h3>{{ g.titel }}</h3>
<details v-for="a in g.agenten" :key="a.id" class="agent" :class="a.status">
<summary>
<span class="name">{{ a.name }}</span>
<span class="badge" :class="statusFarbe[a.status]">{{ statusText[a.status] || a.status }}</span>
<span class="sub">{{ usd(a.kosten_usd) }}</span>
<span class="sub">{{ tokens(a.tokens) }} Tokens</span>
<span class="sub">{{ a.werkzeuge || 0 }} Werkzeuge</span>
<span class="sub">{{ dauer(a) }}</span>
</summary>
<div v-if="a.bericht" class="bericht md" v-html="render(a.bericht)"></div>
<div v-else class="bericht sub">Noch kein Abschlussbericht.</div>
</details>
</template>
</div>
<details v-if="ereignisse.length" class="ereignisse">
<summary>Ereignisse ({{ ereignisse.length }})</summary>
<div v-for="(e, i) in ereignisse" :key="i" class="ereignis" :class="e.art">
<span class="zeit">{{ uhr(e.zeit) }}</span>
<span class="wer">{{ e.agent }}</span>
<span class="text">{{ e.text }}</span>
</div>
</details>
</div>
</template>