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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user