Frontend: locks vom Backend, uiError sichtbar, Pausiert-Badge, Inline-Progress, Mobile-Exklusivität, Fehler-Persistenz

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
team3
2026-06-12 08:18:24 +02:00
parent 2c426e6ac4
commit 700ba1e0e8
4 changed files with 133 additions and 39 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, fetchProjects, deleteProject as apiDeleteProject, fetchProviders, fetchStats, fetchTopicFortschritt } 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, fetchProjects, deleteProject as apiDeleteProject, fetchProviders, fetchStats, fetchTopicFortschritt, fetchGuideLocks } from './api.js'
import { usePolling } from './composables/usePolling.js'
import TopicSidebar from './components/TopicSidebar.vue'
import TopicDetail from './components/TopicDetail.vue'
@@ -26,6 +26,8 @@ const provider = ref(localStorage.getItem('provider') || 'claude')
const providers = ref([])
const stats = ref(null)
const fortschritt = ref({})
const locks = ref({}) // Sperr-Gründe pro Format (Backend = einzige Regel-Quelle)
const uiError = ref(null) // abgewiesene Aktionen (409/400) sichtbar machen
const elementsOpen = ref(false) // rechte Sidebar
const elementsView = ref(false) // Übersicht im Hauptbereich
const elementsVersion = ref(0) // Erhöhung = Übersicht neu laden
@@ -126,21 +128,27 @@ async function loadTopics() {
}
}
// Fehlermeldungen verhalten sich wie Flash-Messages: × blendet aus,
// beim Reload sind Alt-Fehler von vornherein ausgeblendet.
const dismissedErrors = ref(new Set())
let errorsInitialized = false
// Weggeklickte Fehler bleiben weggeklickt — auch über Reloads (localStorage).
// Nicht weggeklickte Fehler bleiben sichtbar, bis der Nutzer sie schließt.
const dismissedErrors = ref(new Set(JSON.parse(localStorage.getItem('dismissedErrors') || '[]')))
function persistDismissed() {
localStorage.setItem('dismissedErrors', JSON.stringify([...dismissedErrors.value]))
}
function handleDismissError(guideId) {
dismissedErrors.value = new Set([...dismissedErrors.value, guideId])
persistDismissed()
}
async function loadGuides() {
try {
guides.value = await fetchGuides()
if (!errorsInitialized) {
errorsInitialized = true
dismissedErrors.value = new Set(guides.value.filter((g) => g.status === 'error').map((g) => g.id))
// IDs prunen, deren Guide nicht mehr als Fehler existiert
const errorIds = new Set(guides.value.filter((g) => g.status === 'error').map((g) => g.id))
if ([...dismissedErrors.value].some((id) => !errorIds.has(id))) {
dismissedErrors.value = new Set([...dismissedErrors.value].filter((id) => errorIds.has(id)))
persistDismissed()
}
loadStats()
} catch (e) {
@@ -154,9 +162,11 @@ async function loadBausteine() {
if (selectedTopic.value) {
bausteine.value = await fetchBausteineStatus(selectedTopic.value)
fortschritt.value = await fetchTopicFortschritt(selectedTopic.value)
locks.value = await fetchGuideLocks(selectedTopic.value)
} else {
bausteine.value = { ...EMPTY_BAUSTEINE }
fortschritt.value = {}
locks.value = {}
}
if (activeBausteine.value.length && !polling.running()) startPolling()
} catch (e) {
@@ -224,7 +234,13 @@ async function handleResetBausteine() {
async function handleBausteineClick({ instructions }) {
if (!selectedTopic.value) return
await apiCreateBausteine(selectedTopic.value, instructions, provider.value)
uiError.value = null
try {
await apiCreateBausteine(selectedTopic.value, instructions, provider.value)
} catch (e) {
uiError.value = e.message
return
}
await loadBausteine()
startPolling()
}
@@ -237,7 +253,13 @@ async function handleFormatClick({ format, instructions }) {
&& (g.status === 'generating' || g.status === 'queued'),
)
if (running) return
await apiCreate(selectedTopic.value, format, instructions, provider.value)
uiError.value = null
try {
await apiCreate(selectedTopic.value, format, instructions, provider.value)
} catch (e) {
uiError.value = e.message
return
}
await loadGuides()
startPolling()
}
@@ -327,6 +349,8 @@ onMounted(async () => {
:selectedTopic="selectedTopic"
:stats="stats"
:fortschritt="fortschritt"
:locks="locks"
:uiError="uiError"
:doneByFormat="doneByFormat"
:latestByFormat="latestByFormat"
:allGuides="guides"
@@ -350,6 +374,7 @@ onMounted(async () => {
@cancelGuide="handleCancel"
@deleteGuide="handleDeleteGuide"
@dismissError="handleDismissError"
@dismissUiError="uiError = null"
@preview="handlePreview"
@openElements="handleOpenElements"
@togglePin="toggleSidebarPin"