131 lines
7.7 KiB
Vue
131 lines
7.7 KiB
Vue
<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>
|