This commit is contained in:
Team3
2026-06-07 09:00:20 +02:00
parent fce82fbd16
commit 8d6d1bf089
22 changed files with 876 additions and 274 deletions

View File

@@ -46,8 +46,6 @@ const formats = [
{ key: 'FullGuide', label: 'FullGuide' },
]
const BAUSTEINE_KEY = '__bausteine__'
const bausteineState = computed(() => {
if (props.bausteine.generating) return 'generating'
return props.bausteine.ready ? 'done' : 'none'
@@ -89,10 +87,7 @@ function confirmResetBausteine() {
function handleBausteinePlay() {
if (bausteineState.value === 'generating') return
const text = activeInput.value === BAUSTEINE_KEY ? inputText.value.trim() : ''
emit('bausteineClick', { instructions: text })
activeInput.value = null
inputText.value = ''
emit('bausteineClick', { instructions: '' })
}
function guideStatus(format) {
@@ -105,6 +100,21 @@ function guideStatus(format) {
return latest.status
}
// Schritt-Kugeln der Guide-Pipelines
const GUIDE_STEPS = ['Auswahl', 'Auswahl-Prüfung', 'Gliederung', 'Gliederungs-Prüfung', 'Schreiben', 'Lese-Prüfung']
const ONEPAGER_STEPS = ['Recherche', 'Recherche-Prüfung', 'Bauen', 'Prüfung']
function guideSteps(format) {
const st = guideStatus(format)
if (st !== 'generating' && st !== 'queued') return []
const labels = format === 'OnePager' ? ONEPAGER_STEPS : GUIDE_STEPS
const step = props.latestByFormat[format]?.step ?? -1
return labels.map((label, i) => ({
label,
state: i < step ? 'done' : i === step ? 'active' : 'pending',
}))
}
function errorMsg(format) {
const latest = props.latestByFormat[format]
if (latest?.status === 'error') return latest.error_msg || 'Fehler bei der Generierung'
@@ -118,24 +128,8 @@ function handleFormatClick(format) {
}
}
const activeInput = ref(null)
const inputText = ref('')
function toggleInput(format) {
if (activeInput.value === format) {
activeInput.value = null
inputText.value = ''
} else {
activeInput.value = format
inputText.value = ''
}
}
function handlePlay(format) {
const text = activeInput.value === format ? inputText.value.trim() : ''
emit('formatClick', { format, instructions: text })
activeInput.value = null
inputText.value = ''
emit('formatClick', { format, instructions: '' })
}
function dismissError(format) {
@@ -219,7 +213,7 @@ function confirmDeleteProject(name) {
<div class="progress-info" v-if="activeGenerations.length">
<div v-for="(line, i) in activeGenerations" :key="i">{{ line }}</div>
</div>
<div class="format-row bausteine-row">
<div class="format-row bausteine-row ord-bausteine">
<div class="format-name bausteine-name">
<span class="format-label">Bausteine</span>
<span class="step-dots">
@@ -253,29 +247,26 @@ function confirmDeleteProject(name) {
:title="bausteine.partial ? 'Fortsetzen' : bausteine.ready ? 'Bausteine neu erstellen' : 'Bausteine erstellen'"
@click="handleBausteinePlay"
></button>
<button
class="action-btn pencil"
:class="{ active: activeInput === BAUSTEINE_KEY }"
title="Anweisungen"
@click="toggleInput(BAUSTEINE_KEY)"
></button>
</template>
</div>
</div>
<div v-if="bausteine.error" class="format-error">
<div v-if="bausteine.error" class="format-error ord-bausteine">
<span class="format-error-text">{{ bausteine.error }}</span>
</div>
<div v-if="activeInput === BAUSTEINE_KEY" class="format-input">
<input
v-model="inputText"
placeholder="Anweisungen (optional)…"
@keyup.enter="handleBausteinePlay"
/>
</div>
<div v-for="f in formats" :key="f.key">
<!-- OnePager (unabhängig von Bausteinen) steht per CSS-order vor der Bausteine-Zeile -->
<div v-for="f in formats" :key="f.key" :style="{ order: f.key === 'OnePager' ? 1 : 3 }">
<div :class="['format-row', 'fmt-' + guideStatus(f.key)]">
<button class="format-name" @click="handleFormatClick(f.key)">
<span class="format-label">{{ f.label }}</span>
<span class="step-dots" v-if="guideSteps(f.key).length">
<span
v-for="s in guideSteps(f.key)"
:key="s.label"
class="step-dot"
:class="s.state"
:title="s.state === 'active' ? (latestByFormat[f.key]?.progress || s.label) : s.label"
></span>
</span>
<span
v-if="guideStatus(f.key) !== 'none'"
class="format-x"
@@ -292,12 +283,6 @@ function confirmDeleteProject(name) {
:disabled="f.key !== 'OnePager' && !bausteine.ready"
@click="handlePlay(f.key)"
></button>
<button
class="action-btn pencil"
:class="{ active: activeInput === f.key }"
title="Anweisungen"
@click="toggleInput(f.key)"
></button>
</template>
</div>
</div>
@@ -305,13 +290,6 @@ function confirmDeleteProject(name) {
<span class="format-error-text">{{ errorMsg(f.key) }}</span>
<button class="format-error-x" title="Fehler entfernen" @click="dismissError(f.key)">&times;</button>
</div>
<div v-if="activeInput === f.key" class="format-input">
<input
v-model="inputText"
placeholder="Anweisungen (optional)…"
@keyup.enter="handlePlay(f.key)"
/>
</div>
</div>
</div>
@@ -564,10 +542,6 @@ function confirmDeleteProject(name) {
}
/* Format section */
.format-row.bausteine-row {
margin-bottom: 0.5rem;
}
.bausteine-name {
display: flex;
align-items: center;
@@ -613,6 +587,13 @@ function confirmDeleteProject(name) {
max-height: 60vh;
overflow-y: auto;
padding: 0.5rem 0;
/* flex + order: OnePager (order 1) vor Bausteine (order 2) vor den restlichen Formaten (order 3) */
display: flex;
flex-direction: column;
}
.ord-bausteine {
order: 2;
}
.progress-info {
@@ -648,6 +629,7 @@ function confirmDeleteProject(name) {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.format-x {
@@ -754,36 +736,6 @@ function confirmDeleteProject(name) {
border-color: var(--success-border);
}
.action-btn.pencil {
color: var(--accent);
}
.action-btn.pencil:hover,
.action-btn.pencil.active {
background: var(--accent-soft);
border-color: var(--accent-border);
}
.format-input {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 0.75rem 8px;
}
.format-input input {
flex: 1;
padding: 4px 8px;
border: 1px solid var(--border-strong);
border-radius: 4px;
font-size: 0.8rem;
outline: none;
}
.format-input input:focus {
border-color: var(--accent);
}
.action-btn:disabled {
opacity: 0.35;
cursor: not-allowed;