This commit is contained in:
team3
2026-06-22 15:23:39 +02:00
parent 0951337a29
commit 36b7aabdb0
5 changed files with 69 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { fetchGuides, fetchTopics, createTopic as apiCreateTopic, deleteTopic as apiDeleteTopic, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, fetchBausteineStatus, fetchActiveBausteine, createBausteine as apiCreateBausteine, cancelBausteine as apiCancelBausteine, deleteBausteine as apiDeleteBausteine, fetchProviders, fetchStats, fetchTopicFortschritt, fetchGuideLocks, fetchFolders, updateQuelle as apiUpdateQuelle } from './api.js'
import { fetchGuides, fetchTopics, createTopic as apiCreateTopic, deleteTopic as apiDeleteTopic, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, fetchBausteineStatus, fetchActiveBausteine, createBausteine as apiCreateBausteine, cancelBausteine as apiCancelBausteine, deleteBausteine as apiDeleteBausteine, fetchProviders, fetchStats, fetchTopicFortschritt, fetchGuideLocks, fetchGuideSteps, fetchFolders, updateQuelle as apiUpdateQuelle } from './api.js'
import { usePolling } from './composables/usePolling.js'
import TopicSidebar from './components/TopicSidebar.vue'
import TopicDetail from './components/TopicDetail.vue'
@@ -30,6 +30,7 @@ const ansichtModus = ref('kompakt') // kompakt | erklärend — je
const stats = ref(null)
const fortschritt = ref({})
const locks = ref({}) // Sperr-Gründe pro Format (Backend = einzige Regel-Quelle)
const guideStepsDone = ref({}) // höchster fertiger Schritt-Index je Format (artefakt-basiert)
const uiError = ref(null) // abgewiesene Aktionen (409/400) sichtbar machen
const elementsOpen = ref(false) // rechte Sidebar
const elementsView = ref(false) // Übersicht im Hauptbereich
@@ -170,10 +171,12 @@ async function loadBausteine() {
bausteine.value = await fetchBausteineStatus(selectedTopic.value)
fortschritt.value = await fetchTopicFortschritt(selectedTopic.value)
locks.value = await fetchGuideLocks(selectedTopic.value)
guideStepsDone.value = await fetchGuideSteps(selectedTopic.value)
} else {
bausteine.value = { ...EMPTY_BAUSTEINE }
fortschritt.value = {}
locks.value = {}
guideStepsDone.value = {}
}
if (activeBausteine.value.length && !polling.running()) startPolling()
} catch (e) {
@@ -380,6 +383,7 @@ onMounted(async () => {
:stats="stats"
:fortschritt="fortschritt"
:locks="locks"
:guideStepsDone="guideStepsDone"
:uiError="uiError"
:doneByFormat="doneByFormat"
:latestByFormat="latestByFormat"

View File

@@ -18,6 +18,11 @@ export async function fetchGuides() {
return res.json()
}
export async function fetchGuideSteps(topic) {
const res = await fetch(`${BASE}/guides/steps?topic=${encodeURIComponent(topic)}`)
return res.json()
}
export async function fetchGuideLocks(topic) {
const res = await fetch(`${BASE}/guides/locks?topic=${encodeURIComponent(topic)}`)
return res.json()

View File

@@ -9,6 +9,7 @@ const props = defineProps({
stats: { type: Object, default: null },
fortschritt: { type: Object, default: () => ({}) },
locks: { type: Object, default: () => ({}) },
guideStepsDone: { type: Object, default: () => ({}) }, // höchster fertiger Schritt je Format (-1 = keiner)
uiError: { type: String, default: null },
doneByFormat: { type: Object, default: () => ({}) },
latestByFormat: { type: Object, default: () => ({}) },
@@ -121,35 +122,26 @@ function guideStatus(format) {
// Schritt-Kugeln der Guide-Pipeline
const GUIDE_STEPS = ['Auswahl', 'Gliederung', 'Inhalte', 'Inhalts-Check', 'Schreiben', 'Lese-Prüfung']
// Kugeln werden wie bei den Bausteinen immer angezeigt:
// fertig = alle grün, laufend = live, abgebrochen = Teilfortschritt, sonst grau
// Kugeln aus dem artefakt-basierten „fertig"-Marker (wie Bausteine, nicht aus dem DB-Zähler):
// fertig = done. Läuft gerade → der nächste Schritt (fertig+1) ist aktiv.
function guideSteps(format) {
const labels = GUIDE_STEPS
const fertig = props.guideStepsDone[format] ?? -1
const st = guideStatus(format)
if (st === 'generating' || st === 'queued') {
// Clamp: alte DB-Läufe können step-Werte oberhalb der neuen Listen haben
const step = Math.min(props.latestByFormat[format]?.step ?? -1, labels.length - 1)
return labels.map((label, i) => ({
label,
state: i < step ? 'done' : i === step ? 'active' : 'pending',
}))
}
if (props.doneByFormat[format]) {
return labels.map((label) => ({ label, state: 'done' }))
}
if (abgebrochen(format)) {
const step = Math.min(props.latestByFormat[format]?.step ?? 0, labels.length)
return labels.map((label, i) => ({ label, state: i < step ? 'done' : 'pending' }))
}
return labels.map((label) => ({ label, state: 'pending' }))
const aktiv = st === 'generating' || st === 'queued' ? fertig + 1 : -1
return labels.map((label, i) => ({
label,
state: i <= fertig ? 'done' : i === aktiv ? 'active' : 'pending',
}))
}
// Re-Run ab Guide-Schritt (1-basierte Kugel je Format). null = voll/Resume.
const gewaehlterStep = reactive({})
// Kugeln klickbar, sobald der Guide (teil-)gebaut ist und gerade nicht generiert wird.
// Kugeln klickbar, sobald Artefakte existieren (Marker ≥ 0 oder fertig) und nicht generiert wird.
function guideWaehlbar(format) {
const st = guideStatus(format)
return (st === 'done' || abgebrochen(format)) && st !== 'generating' && st !== 'queued'
if (st === 'generating' || st === 'queued') return false
return (props.guideStepsDone[format] ?? -1) >= 0 || st === 'done'
}
function guideStepKlick(format, n) {
if (!guideWaehlbar(format)) return