This commit is contained in:
Team3
2026-06-06 02:26:42 +02:00
parent a8fbf83059
commit 18bb18bf4a
30 changed files with 1184 additions and 4880 deletions

View File

@@ -1,12 +1,17 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { fetchGuides, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, fetchProjects, deleteProject as apiDeleteProject, fetchProviders } from './api.js'
import { fetchGuides, fetchTopics, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, fetchBausteineStatus, fetchActiveBausteine, createBausteine as apiCreateBausteine, deleteBausteine as apiDeleteBausteine, fetchProjects, deleteProject as apiDeleteProject, fetchProviders } from './api.js'
import TopicSidebar from './components/TopicSidebar.vue'
import TopicDetail from './components/TopicDetail.vue'
const guides = ref([])
const projects = ref([])
const manualTopics = ref([])
const manualTopics = ref(JSON.parse(localStorage.getItem('manualTopics') || '[]'))
const backendTopics = ref([])
function persistManualTopics() {
localStorage.setItem('manualTopics', JSON.stringify(manualTopics.value))
}
const selectedTopic = ref(null)
const previewGuide = ref(null)
const sidebarPinned = ref(localStorage.getItem('sidebarPinned') !== 'false')
@@ -16,6 +21,9 @@ const darkMode = ref(
? window.matchMedia('(prefers-color-scheme: dark)').matches
: localStorage.getItem('darkMode') === 'true',
)
const EMPTY_BAUSTEINE = { ready: false, generating: false, progress: null, error: null }
const bausteine = ref({ ...EMPTY_BAUSTEINE })
const activeBausteine = ref([])
const provider = ref(localStorage.getItem('provider') || 'claude')
const providers = ref([])
@@ -75,9 +83,9 @@ const topics = computed(() => {
topicDates[g.topic] = g.created_at
}
}
for (const t of manualTopics.value) {
for (const t of [...backendTopics.value, ...manualTopics.value]) {
if (isProject.has(t)) continue
if (!topicDates[t]) topicDates[t] = new Date().toISOString()
if (!topicDates[t]) topicDates[t] = ''
}
return Object.keys(topicDates).sort((a, b) => topicDates[b].localeCompare(topicDates[a]))
})
@@ -109,6 +117,14 @@ const hasActiveGuides = computed(() =>
guides.value.some((g) => g.status === 'queued' || g.status === 'generating'),
)
async function loadTopics() {
try {
backendTopics.value = await fetchTopics()
} catch (e) {
console.error('Fehler beim Laden der Themen:', e)
}
}
async function loadGuides() {
try {
guides.value = await fetchGuides()
@@ -117,6 +133,20 @@ async function loadGuides() {
}
}
async function loadBausteine() {
try {
activeBausteine.value = await fetchActiveBausteine()
if (selectedTopic.value) {
bausteine.value = await fetchBausteineStatus(selectedTopic.value)
} else {
bausteine.value = { ...EMPTY_BAUSTEINE }
}
if (activeBausteine.value.length && !pollTimer) startPolling()
} catch (e) {
console.error('Fehler beim Laden der Bausteine:', e)
}
}
async function loadProjects() {
try {
projects.value = await fetchProjects()
@@ -125,7 +155,7 @@ async function loadProjects() {
}
}
const FORMAT_ORDER = ['OnePager', 'MiniGuide', 'Guide']
const FORMAT_ORDER = ['OnePager', 'MiniGuide', 'Guide', 'FullGuide']
function autoPreview() {
const map = doneByFormat.value
@@ -142,15 +172,25 @@ function selectTopic(topic) {
selectedTopic.value = topic
previewGuide.value = null
sidebarSticky.value = false
loadBausteine()
nextTick(autoPreview)
}
function createTopic(topic) {
if (!manualTopics.value.includes(topic)) {
manualTopics.value.push(topic)
persistManualTopics()
}
selectedTopic.value = topic
previewGuide.value = null
loadBausteine()
}
async function handleBausteineClick({ instructions }) {
if (!selectedTopic.value) return
await apiCreateBausteine(selectedTopic.value, instructions, provider.value)
await loadBausteine()
startPolling()
}
async function handleFormatClick({ format, instructions }) {
@@ -184,8 +224,8 @@ async function handleDeleteGuide(guideId) {
function startPolling() {
stopPolling()
pollTimer = setInterval(async () => {
await loadGuides()
if (!hasActiveGuides.value) stopPolling()
await Promise.all([loadGuides(), loadBausteine(), loadTopics()])
if (!hasActiveGuides.value && !activeBausteine.value.length) stopPolling()
}, 3000)
}
@@ -206,7 +246,10 @@ async function handleDeleteTopic(topic) {
for (const g of topicGuides) {
await deleteGuide(g.id)
}
await apiDeleteBausteine(topic)
manualTopics.value = manualTopics.value.filter((t) => t !== topic)
persistManualTopics()
await loadTopics()
if (selectedTopic.value === topic) {
selectedTopic.value = null
previewGuide.value = null
@@ -219,12 +262,13 @@ function onVisibility() {
stopPolling()
} else {
loadGuides()
if (hasActiveGuides.value) startPolling()
loadBausteine()
if (hasActiveGuides.value || activeBausteine.value.length) startPolling()
}
}
onMounted(async () => {
await Promise.all([loadGuides(), loadProjects(), loadProviders()])
await Promise.all([loadGuides(), loadTopics(), loadProjects(), loadProviders()])
if (!selectedTopic.value && topics.value.length) {
selectTopic(topics.value[0])
}
@@ -248,6 +292,8 @@ onUnmounted(() => {
:doneByFormat="doneByFormat"
:latestByFormat="latestByFormat"
:allGuides="guides"
:bausteine="bausteine"
:activeBausteine="activeBausteine"
:pinned="sidebarPinned"
:dark="darkMode"
:provider="provider"
@@ -257,6 +303,7 @@ onUnmounted(() => {
@select="selectTopic"
@create="createTopic"
@formatClick="handleFormatClick"
@bausteineClick="handleBausteineClick"
@deleteTopic="handleDeleteTopic"
@deleteProject="handleDeleteProject"
@cancelGuide="handleCancel"