update
This commit is contained in:
@@ -189,6 +189,12 @@ async function handleBausteineClick({ instructions }) {
|
||||
|
||||
async function handleFormatClick({ format, instructions }) {
|
||||
if (!selectedTopic.value) return
|
||||
// Kein Duplikat-Start: läuft für Thema+Format schon eine Generierung, ignorieren
|
||||
const running = guides.value.some(
|
||||
(g) => g.topic === selectedTopic.value && g.format === format
|
||||
&& (g.status === 'generating' || g.status === 'queued'),
|
||||
)
|
||||
if (running) return
|
||||
await apiCreate(selectedTopic.value, format, instructions, provider.value)
|
||||
await loadGuides()
|
||||
startPolling()
|
||||
|
||||
@@ -49,14 +49,28 @@ const activeGenerations = computed(() => {
|
||||
return [...bausteinLines, ...guideLines]
|
||||
})
|
||||
|
||||
// Inline-Bestätigung statt confirm(): erster Klick scharfschalten („Sicher?"),
|
||||
// zweiter Klick führt aus. Browser-Dialoge können unterdrückt sein (Firefox).
|
||||
const pendingConfirm = ref(null)
|
||||
let confirmTimer = null
|
||||
|
||||
function armOrRun(key, action) {
|
||||
clearTimeout(confirmTimer)
|
||||
if (pendingConfirm.value === key) {
|
||||
pendingConfirm.value = null
|
||||
action()
|
||||
} else {
|
||||
pendingConfirm.value = key
|
||||
confirmTimer = setTimeout(() => { pendingConfirm.value = null }, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCancelBausteine() {
|
||||
if (!confirm('Aktuellen Schritt abbrechen? Bisheriger Fortschritt bleibt erhalten.')) return
|
||||
emit('cancelBausteine')
|
||||
armOrRun('bausteine', () => emit('cancelBausteine'))
|
||||
}
|
||||
|
||||
function confirmResetBausteine() {
|
||||
if (!confirm('Gespeicherten Bausteine-Fortschritt löschen?')) return
|
||||
emit('resetBausteine')
|
||||
armOrRun('bausteine', () => emit('resetBausteine'))
|
||||
}
|
||||
|
||||
function handleBausteinePlay() {
|
||||
@@ -68,10 +82,12 @@ function handleBausteinePlay() {
|
||||
}
|
||||
|
||||
function guideStatus(format) {
|
||||
if (props.doneByFormat[format]) return 'done'
|
||||
// Laufende Generierung hat Vorrang — sonst maskiert ein älterer fertiger
|
||||
// Guide den Lauf und ▶ würde Duplikate starten.
|
||||
const latest = props.latestByFormat[format]
|
||||
if (!latest) return 'none'
|
||||
if (latest.status === 'error') return 'none'
|
||||
if (latest && (latest.status === 'generating' || latest.status === 'queued')) return latest.status
|
||||
if (props.doneByFormat[format]) return 'done'
|
||||
if (!latest || latest.status === 'error') return 'none'
|
||||
return latest.status
|
||||
}
|
||||
|
||||
@@ -116,15 +132,19 @@ function dismissError(format) {
|
||||
}
|
||||
|
||||
function handleDelete(format) {
|
||||
const guide = props.latestByFormat[format]
|
||||
if (!guide) return
|
||||
if (guide.status === 'generating' || guide.status === 'queued') {
|
||||
if (!confirm('Generierung abbrechen?')) return
|
||||
emit('cancelGuide', guide.id)
|
||||
} else {
|
||||
if (!confirm('Guide löschen?')) return
|
||||
emit('deleteGuide', guide.id)
|
||||
}
|
||||
if (!props.latestByFormat[format]) return
|
||||
armOrRun('fmt-' + format, () => {
|
||||
// Alle laufenden Generierungen des Formats abbrechen (deckt auch Duplikate ab)
|
||||
const running = props.allGuides.filter(
|
||||
(g) => g.topic === props.selectedTopic && g.format === format
|
||||
&& (g.status === 'generating' || g.status === 'queued'),
|
||||
)
|
||||
if (running.length) {
|
||||
for (const g of running) emit('cancelGuide', g.id)
|
||||
} else {
|
||||
emit('deleteGuide', props.latestByFormat[format].id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const newTopic = ref('')
|
||||
@@ -137,13 +157,11 @@ function submit() {
|
||||
}
|
||||
|
||||
function confirmDeleteTopic(topic) {
|
||||
if (!confirm(`Thema "${topic}" und alle zugehörigen Guides löschen?`)) return
|
||||
emit('deleteTopic', topic)
|
||||
armOrRun('topic-' + topic, () => emit('deleteTopic', topic))
|
||||
}
|
||||
|
||||
function confirmDeleteProject(name) {
|
||||
if (!confirm(`Projekt "${name}" entfernen?\n\nAchtung: Der Quellordner ./projects/${name} wird gelöscht.`)) return
|
||||
emit('deleteProject', name)
|
||||
armOrRun('project-' + name, () => emit('deleteProject', name))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -196,15 +214,17 @@ function confirmDeleteProject(name) {
|
||||
<span
|
||||
v-if="bausteineState === 'generating'"
|
||||
class="format-x"
|
||||
:class="{ armed: pendingConfirm === 'bausteine' }"
|
||||
title="Aktuellen Schritt abbrechen (Fortschritt bleibt)"
|
||||
@click.stop="confirmCancelBausteine"
|
||||
>×</span>
|
||||
>{{ pendingConfirm === 'bausteine' ? 'Sicher?' : '×' }}</span>
|
||||
<span
|
||||
v-else-if="bausteine.partial"
|
||||
class="format-x"
|
||||
:class="{ armed: pendingConfirm === 'bausteine' }"
|
||||
title="Fortschritt löschen"
|
||||
@click.stop="confirmResetBausteine"
|
||||
>×</span>
|
||||
>{{ pendingConfirm === 'bausteine' ? 'Sicher?' : '×' }}</span>
|
||||
</div>
|
||||
<div class="format-actions">
|
||||
<template v-if="bausteineState !== 'generating'">
|
||||
@@ -239,9 +259,10 @@ function confirmDeleteProject(name) {
|
||||
<span
|
||||
v-if="guideStatus(f.key) !== 'none'"
|
||||
class="format-x"
|
||||
:class="{ armed: pendingConfirm === 'fmt-' + f.key }"
|
||||
@click.stop="handleDelete(f.key)"
|
||||
:title="guideStatus(f.key) === 'generating' || guideStatus(f.key) === 'queued' ? 'Abbrechen' : 'Löschen'"
|
||||
>×</span>
|
||||
>{{ pendingConfirm === 'fmt-' + f.key ? 'Sicher?' : '×' }}</span>
|
||||
</button>
|
||||
<div class="format-actions">
|
||||
<template v-if="guideStatus(f.key) !== 'generating' && guideStatus(f.key) !== 'queued'">
|
||||
@@ -282,7 +303,12 @@ function confirmDeleteProject(name) {
|
||||
@click="emit('select', t)"
|
||||
>
|
||||
<span>{{ t }}</span>
|
||||
<button class="delete-topic" @click.stop="confirmDeleteTopic(t)" title="Löschen">×</button>
|
||||
<button
|
||||
class="delete-topic"
|
||||
:class="{ armed: pendingConfirm === 'topic-' + t }"
|
||||
@click.stop="confirmDeleteTopic(t)"
|
||||
title="Thema und alle Guides löschen"
|
||||
>{{ pendingConfirm === 'topic-' + t ? 'Sicher?' : '×' }}</button>
|
||||
</li>
|
||||
<template v-if="projects.length">
|
||||
<li class="projects-divider">Projekte</li>
|
||||
@@ -293,7 +319,12 @@ function confirmDeleteProject(name) {
|
||||
@click="emit('select', p)"
|
||||
>
|
||||
<span>{{ p }}</span>
|
||||
<button class="delete-topic" @click.stop="confirmDeleteProject(p)" title="Projekt entfernen">×</button>
|
||||
<button
|
||||
class="delete-topic"
|
||||
:class="{ armed: pendingConfirm === 'project-' + p }"
|
||||
@click.stop="confirmDeleteProject(p)"
|
||||
title="Projekt entfernen (löscht ./projects-Ordner)"
|
||||
>{{ pendingConfirm === 'project-' + p ? 'Sicher?' : '×' }}</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
@@ -578,6 +609,17 @@ function confirmDeleteProject(name) {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.format-x.armed,
|
||||
.delete-topic.armed {
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
background: var(--danger);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.fmt-done .format-name {
|
||||
color: var(--success);
|
||||
font-weight: 600;
|
||||
|
||||
Reference in New Issue
Block a user