This commit is contained in:
team3
2026-05-25 22:59:37 +02:00
parent e964c807d9
commit 619bac34cb
8 changed files with 339 additions and 88 deletions

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { fetchGuides, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel } from './api.js'
import { fetchGuides, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, reworkGuide as apiRework } from './api.js'
import TopicSidebar from './components/TopicSidebar.vue'
import TopicDetail from './components/TopicDetail.vue'
@@ -73,9 +73,15 @@ function createTopic(topic) {
previewGuide.value = null
}
async function handleFormatClick(format) {
async function handleFormatClick({ format, instructions }) {
if (!selectedTopic.value) return
await apiCreate(selectedTopic.value, format)
await apiCreate(selectedTopic.value, format, instructions)
await loadGuides()
startPolling()
}
async function handleRework({ guideId, instructions }) {
await apiRework(guideId, instructions)
await loadGuides()
startPolling()
}
@@ -162,6 +168,7 @@ onUnmounted(() => {
@cancelGuide="handleCancel"
@deleteGuide="handleDeleteGuide"
@preview="handlePreview"
@rework="handleRework"
/>
<TopicDetail
v-if="selectedTopic"

View File

@@ -10,11 +10,20 @@ export async function fetchGuide(id) {
return res.json()
}
export async function createGuide(topic, format) {
export async function createGuide(topic, format, instructions = '') {
const res = await fetch(`${BASE}/guides`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic, format }),
body: JSON.stringify({ topic, format, instructions }),
})
return res.json()
}
export async function reworkGuide(id, instructions) {
const res = await fetch(`${BASE}/guides/${id}/rework`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instructions }),
})
return res.json()
}

View File

@@ -8,7 +8,7 @@ const props = defineProps({
allGuides: { type: Array, default: () => [] },
})
const emit = defineEmits(['select', 'create', 'formatClick', 'deleteTopic', 'cancelGuide', 'deleteGuide', 'preview'])
const emit = defineEmits(['select', 'create', 'formatClick', 'deleteTopic', 'cancelGuide', 'deleteGuide', 'preview', 'rework'])
const formats = [
{ key: 'OnePager', label: 'OnePager' },
@@ -38,12 +38,33 @@ function handleFormatClick(format) {
}
}
function handlePlay(format) {
const guide = props.guidesByFormat[format]
if (guide?.status === 'done') {
if (!confirm('Guide überschreiben?')) return
const activeInput = ref(null)
const inputText = ref('')
function toggleInput(format) {
if (activeInput.value === format) {
activeInput.value = null
inputText.value = ''
} else {
activeInput.value = format
inputText.value = ''
}
emit('formatClick', format)
}
function handlePlay(format) {
const text = activeInput.value === format ? inputText.value.trim() : ''
emit('formatClick', { format, instructions: text })
activeInput.value = null
inputText.value = ''
}
function handleRefresh(format) {
const guide = props.guidesByFormat[format]
if (!guide) return
const text = activeInput.value === format ? inputText.value.trim() : ''
emit('rework', { guideId: guide.id, instructions: text || 'Überarbeite das Layout' })
activeInput.value = null
inputText.value = ''
}
function handleDelete(format) {
@@ -94,31 +115,44 @@ function submit() {
<div class="progress-info" v-if="activeGenerations.length">
<div v-for="(line, i) in activeGenerations" :key="i">{{ line }}</div>
</div>
<div
v-for="f in formats"
:key="f.key"
:class="['format-row', 'fmt-' + guideStatus(f.key)]"
>
<button class="format-name" @click="handleFormatClick(f.key)">
{{ f.label }}
</button>
<div class="format-actions">
<button
v-if="guideStatus(f.key) !== 'generating' && guideStatus(f.key) !== 'queued'"
class="action-btn play"
:title="guideStatus(f.key) === 'done' ? 'Neu generieren' : 'Generieren'"
@click="handlePlay(f.key)"
>
{{ guideStatus(f.key) === 'done' ? '↻' : '▶' }}
</button>
<button
v-if="guideStatus(f.key) !== 'none'"
class="action-btn delete"
:title="guideStatus(f.key) === 'generating' || guideStatus(f.key) === 'queued' ? 'Abbrechen' : 'Löschen'"
@click="handleDelete(f.key)"
>
&times;
<div v-for="f in formats" :key="f.key">
<div :class="['format-row', 'fmt-' + guideStatus(f.key)]">
<button class="format-name" @click="handleFormatClick(f.key)">
<span class="format-label">{{ f.label }}</span>
<span
v-if="guideStatus(f.key) !== 'none'"
class="format-x"
@click.stop="handleDelete(f.key)"
:title="guideStatus(f.key) === 'generating' || guideStatus(f.key) === 'queued' ? 'Abbrechen' : 'Löschen'"
>&times;</span>
</button>
<div class="format-actions">
<template v-if="guideStatus(f.key) === 'done'">
<button class="action-btn refresh" title="Überarbeiten" @click="handleRefresh(f.key)"></button>
<button
class="action-btn pencil"
:class="{ active: activeInput === f.key }"
title="Anweisungen"
@click="toggleInput(f.key)"
></button>
</template>
<template v-else-if="guideStatus(f.key) !== 'generating' && guideStatus(f.key) !== 'queued'">
<button class="action-btn play" title="Generieren" @click="handlePlay(f.key)"></button>
<button
class="action-btn pencil"
:class="{ active: activeInput === f.key }"
title="Anweisungen"
@click="toggleInput(f.key)"
></button>
</template>
</div>
</div>
<div v-if="activeInput === f.key" class="format-input">
<input
v-model="inputText"
:placeholder="guideStatus(f.key) === 'done' ? 'Was soll überarbeitet werden?' : 'Anweisungen (optional)…'"
@keyup.enter="guideStatus(f.key) === 'done' ? handleRefresh(f.key) : handlePlay(f.key)"
/>
</div>
</div>
</div>
@@ -252,6 +286,22 @@ function submit() {
border-radius: 4px;
cursor: default;
color: #999;
display: flex;
align-items: center;
justify-content: space-between;
}
.format-x {
display: none;
color: #991b1b;
font-size: 1.1rem;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.format-name:hover .format-x {
display: inline;
}
.fmt-done .format-name {
@@ -309,14 +359,40 @@ function submit() {
border-color: #34d399;
}
.action-btn.delete {
color: #991b1b;
font-size: 1.1rem;
.action-btn.refresh {
color: #059669;
}
.action-btn.delete:hover {
background: #fee2e2;
border-color: #f87171;
.action-btn.refresh:hover {
background: #d1fae5;
border-color: #34d399;
}
.action-btn.pencil {
color: #6366f1;
}
.action-btn.pencil:hover,
.action-btn.pencil.active {
background: #ede9fe;
border-color: #a5b4fc;
}
.format-input {
padding: 4px 0.75rem 8px;
}
.format-input input {
width: 100%;
padding: 4px 8px;
border: 1px solid #d8dde3;
border-radius: 4px;
font-size: 0.8rem;
outline: none;
}
.format-input input:focus {
border-color: #6366f1;
}
@keyframes pulse {