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

@@ -200,7 +200,12 @@ async function send() {
>
<h2 class="chapter-title">{{ ch.title }}</h2>
<div class="sections">
<article v-for="s in ch.sections" :key="s.num" class="section-card">
<article
v-for="s in ch.sections"
:key="s.num"
class="section-card"
:style="isOnePager && s.key ? { gridArea: s.key } : null"
>
<h3>{{ s.title }}</h3>
<div class="section-body markdown" v-html="renderMarkdown(s.md)"></div>
</article>
@@ -326,16 +331,43 @@ async function send() {
}
}
/* OnePager: dichtes Karten-Grid */
/* OnePager: festes 3×3-Raster über volle Breite und Höhe */
.guide-content.onepager {
max-width: none;
height: 100%;
padding: 0.9rem 1rem;
display: flex;
flex-direction: column;
}
.guide-content.onepager .guide-head,
.guide-content.onepager .chapter-title {
display: none; /* Thema steht in der Info-Karte — Platz fürs Raster */
}
.guide-content.onepager .chapter {
flex: 1;
min-height: 0;
margin-bottom: 0;
}
.guide-content.onepager .sections {
height: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-template-areas:
"info beispiel voraussetzungen"
"eigenschaften beispiel modern"
"eigenschaften zusammenhaenge veraltet";
gap: 0.6rem;
}
.guide-content.onepager .section-card {
margin-bottom: 0;
padding: 0.7rem 0.9rem;
min-height: 0;
overflow-y: auto;
h3 {
font-size: 0.88rem;
@@ -347,6 +379,23 @@ async function send() {
}
}
/* Mobil: eine Spalte in Quellreihenfolge (info → … → veraltet) */
@media (max-width: 900px) {
.guide-content.onepager {
height: auto;
}
.guide-content.onepager .sections {
height: auto;
display: flex;
flex-direction: column;
}
.guide-content.onepager .section-card {
overflow-y: visible;
}
}
.ch-toggle {
display: block;
width: 100%;
@@ -451,9 +500,23 @@ async function send() {
padding: 2px 6px;
}
/* Lesbarkeit: ~17px Fließtext, Zeilenhöhe 1.6, Textspalte max. ~70 Zeichen —
Code-Blöcke dürfen die volle Kartenbreite nutzen */
.section-body {
font-size: 0.92rem;
line-height: 1.55;
font-size: 1.0625rem;
line-height: 1.6;
}
.section-card .markdown :deep(p),
.section-card .markdown :deep(ul),
.section-card .markdown :deep(ol) {
max-width: 70ch;
}
.onepager .section-card .markdown :deep(p),
.onepager .section-card .markdown :deep(ul),
.onepager .section-card .markdown :deep(ol) {
max-width: none; /* OnePager-Zellen sind selbst schmal genug */
}
/* --- Chat --- */

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;