This commit is contained in:
team3
2026-06-30 00:36:41 +02:00
parent c794fcaccf
commit 3669fd8d0a
6 changed files with 52 additions and 26 deletions

View File

@@ -224,12 +224,12 @@ async function handleResetFromStep(step) {
await loadBlocks()
}
async function handleBlocksClick({ instructions, abPhase = null, abStep = null }) {
async function handleBlocksClick({ instructions, abPhase = null, abStep = null, toStep = null }) {
if (!selectedTopic.value) return
uiError.value = null
try {
// Source is already fixed here; abPhase/abStep control the re-run scope.
await apiCreateBausteine(selectedTopic.value, instructions, provider.value, undefined, undefined, abPhase, abStep)
// Source is already fixed here; abPhase/abStep set the start, toStep an optional end limit.
await apiCreateBausteine(selectedTopic.value, instructions, provider.value, undefined, undefined, abPhase, abStep, toStep)
} catch (e) {
uiError.value = e.message
return
@@ -422,7 +422,7 @@ onMounted(async () => {
:ready="blocks.ready"
:partial="blocks.partial"
@close="mainView = 'detail'"
@restartFrom="(i) => handleBlocksClick({ instructions: '', abStep: i })"
@restartFrom="(r) => handleBlocksClick({ instructions: '', abStep: r.from, toStep: r.to })"
@resetFrom="handleResetFromStep"
@restartAll="() => handleBlocksClick({ abPhase: blocks.ready ? 1 : null })"
@removeAll="handleResetBlocks"

View File

@@ -47,11 +47,11 @@ export async function fetchBlocksStatus(topic) {
return res.json()
}
export async function createBlocks(topic, instructions = '', provider = 'claude', sourceType = 'thema', sourceOrt = '', abPhase = null, abStep = null) {
export async function createBlocks(topic, instructions = '', provider = 'claude', sourceType = 'thema', sourceOrt = '', abPhase = null, abStep = null, toStep = null) {
const res = await fetch(`${BASE}/blocks`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic, instructions, provider, source_type: sourceType, source_location: sourceOrt, ab_phase: abPhase, ab_step: abStep }),
body: JSON.stringify({ topic, instructions, provider, source_type: sourceType, source_location: sourceOrt, ab_phase: abPhase, ab_step: abStep, to_step: toStep }),
})
return jsonOrThrow(res)
}

View File

@@ -23,22 +23,32 @@ const phaseGroups = computed(() => {
return out
})
const selected = ref(null) // marked start point (step index)
const startSel = ref(null) // marked start point (step index) — only ≤ current stand
const endSel = ref(null) // optional end point (step index, > start) — generation stops there
const confirm = ref(null) // which destructive action currently shows "Sure?"
const selectedLabel = computed(() => props.steps[selected.value]?.label || '')
const startLabel = computed(() => props.steps[startSel.value]?.label || '')
const endLabel = computed(() => props.steps[endSel.value]?.label || '')
// Start is only valid up to the current stand: done/active steps, never a pending one (never ran).
function startDisabled(idx) { return startSel.value === null && props.steps[idx]?.state === 'pending' }
function inRange(idx) { return startSel.value !== null && endSel.value !== null && idx > startSel.value && idx < endSel.value }
function stepClick(idx) {
if (props.generating) return
selected.value = selected.value === idx ? null : idx
if (props.generating || startDisabled(idx)) return
confirm.value = null
if (startSel.value === null) { startSel.value = idx; endSel.value = null } // 1st click → start
else if (idx === startSel.value) { startSel.value = null; endSel.value = null } // re-click start → clear
else if (idx > startSel.value) { endSel.value = endSel.value === idx ? null : idx } // later step → toggle end
else if (props.steps[idx]?.state !== 'pending') { startSel.value = idx; endSel.value = null } // earlier → new start
}
function clearSel() { startSel.value = null; endSel.value = null; confirm.value = null }
// 2-click confirmation for destructive actions: first click "arms", second runs it.
function arm(action, fn) {
if (confirm.value === action) { confirm.value = null; fn() }
else confirm.value = action
}
function regenerateFromHere() { const i = selected.value; selected.value = null; confirm.value = null; emit('restartFrom', i) }
function deleteFromHere() { const i = selected.value; selected.value = null; confirm.value = null; emit('resetFrom', i) }
function regenerateFromHere() { const from = startSel.value, to = endSel.value; clearSel(); emit('restartFrom', { from, to }) }
function deleteFromHere() { const from = startSel.value; clearSel(); emit('resetFrom', from) }
const items = ref([])
const loading = ref(true)
@@ -119,19 +129,19 @@ const subTotal = computed(() => items.value.reduce((n, b) => n + (b.subblocks?.l
v-for="s in g.steps"
:key="s.idx"
class="bk-step"
:class="[s.state, { sel: selected === s.idx }]"
:disabled="generating"
:title="`Choose start point «${s.label}»`"
:class="[s.state, { sel: startSel === s.idx, end: endSel === s.idx, 'in-range': inRange(s.idx) }]"
:disabled="generating || startDisabled(s.idx)"
:title="startDisabled(s.idx) ? `«${s.label}» — not reached yet` : (startSel !== null && s.idx > startSel ? `End at «${s.label}»` : `Start at «${s.label}»`)"
@click="stepClick(s.idx)"
>{{ s.label }}</button>
</div>
</div>
</div>
<div v-if="selected !== null && !generating" class="bk-step-actions">
<span class="bk-step-actions-label">From «{{ selectedLabel }}»:</span>
<div v-if="startSel !== null && !generating" class="bk-step-actions">
<span class="bk-step-actions-label">From «{{ startLabel }}»<span v-if="endSel !== null"> to «{{ endLabel }}»</span>:</span>
<button class="bk-act play" @click="regenerateFromHere"> regenerate</button>
<button class="bk-act danger" :class="{ armed: confirm === 'reset' }" @click="arm('reset', deleteFromHere)">{{ confirm === 'reset' ? 'Sure?' : ' delete all' }}</button>
<button class="bk-act ghost" @click="selected = null; confirm = null">Cancel</button>
<button class="bk-act ghost" @click="clearSel">Cancel</button>
</div>
</section>
@@ -251,7 +261,10 @@ const subTotal = computed(() => items.value.reduce((n, b) => n + (b.subblocks?.l
.bk-step.done { border-color: var(--success-border); color: var(--success); }
.bk-step.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); font-weight: 600; }
.bk-step.pending { color: var(--text-faint); }
.bk-step.sel { border-color: var(--accent); color: var(--on-accent); background: var(--accent); font-weight: 700; box-shadow: 0 0 0 2px var(--accent-soft); }
.bk-step.sel,
.bk-step.end { border-color: var(--accent); color: var(--on-accent); background: var(--accent); font-weight: 700; box-shadow: 0 0 0 2px var(--accent-soft); }
.bk-step.in-range { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.bk-step:disabled:not(.done):not(.active) { opacity: 0.45; }
/* Header: progress left, global buttons right */
.bk-steps-top { display: flex; align-items: center; gap: 1rem; min-height: 1.9rem; margin-bottom: 0.7rem; }