update
This commit is contained in:
@@ -224,12 +224,12 @@ async function handleResetFromStep(step) {
|
||||
await loadBlocks()
|
||||
}
|
||||
|
||||
async function handleBlocksClick({ instructions, abPhase = null, abStep = null, toStep = null }) {
|
||||
async function handleBlocksClick({ instructions, abPhase = null, abStep = null, toStep = null, research = true }) {
|
||||
if (!selectedTopic.value) return
|
||||
uiError.value = null
|
||||
try {
|
||||
// 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)
|
||||
await apiCreateBausteine(selectedTopic.value, instructions, provider.value, undefined, undefined, abPhase, abStep, toStep, research)
|
||||
} catch (e) {
|
||||
uiError.value = e.message
|
||||
return
|
||||
@@ -424,7 +424,7 @@ onMounted(async () => {
|
||||
@close="mainView = 'detail'"
|
||||
@restartFrom="(r) => handleBlocksClick({ instructions: '', abStep: r.from, toStep: r.to })"
|
||||
@resetFrom="handleResetFromStep"
|
||||
@restartAll="() => handleBlocksClick({ abPhase: blocks.ready ? 1 : null })"
|
||||
@restartAll="(o) => handleBlocksClick({ research: o?.research ?? false })"
|
||||
@removeAll="handleResetBlocks"
|
||||
@cancel="handleCancelBlocks"
|
||||
/>
|
||||
|
||||
@@ -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, toStep = null) {
|
||||
export async function createBlocks(topic, instructions = '', provider = 'claude', sourceType = 'thema', sourceOrt = '', abPhase = null, abStep = null, toStep = null, research = true) {
|
||||
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, to_step: toStep }),
|
||||
body: JSON.stringify({ topic, instructions, provider, source_type: sourceType, source_location: sourceOrt, ab_phase: abPhase, ab_step: abStep, to_step: toStep, research }),
|
||||
})
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
@@ -147,6 +147,24 @@ export async function fetchBlocksOverview(topic) {
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// Live card counts per kanban column ({} when the streaming inventory is not in use).
|
||||
export async function fetchKanban(topic) {
|
||||
const res = await fetch(`${BASE}/blocks/kanban?topic=${encodeURIComponent(topic)}`)
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// Currently running agents for a topic + their runtime in seconds.
|
||||
export async function fetchAgents(topic) {
|
||||
const res = await fetch(`${BASE}/blocks/agents?topic=${encodeURIComponent(topic)}`)
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// Attach one more research agent to the running kanban flow.
|
||||
export async function addResearch(topic) {
|
||||
const res = await fetch(`${BASE}/blocks/research?topic=${encodeURIComponent(topic)}`, { method: 'POST' })
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function cancelGuide(id) {
|
||||
await fetch(`${BASE}/guides/${id}/cancel`, { method: 'POST' })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { fetchBlocksOverview } from '../api.js'
|
||||
import { ref, computed, watch, onUnmounted } from 'vue'
|
||||
import { fetchBlocksOverview, fetchKanban, fetchAgents, addResearch } from '../api.js'
|
||||
|
||||
const props = defineProps({
|
||||
topic: { type: String, required: true },
|
||||
@@ -12,6 +12,39 @@ const props = defineProps({
|
||||
})
|
||||
const emit = defineEmits(['close', 'restartFrom', 'resetFrom', 'restartAll', 'removeAll', 'cancel'])
|
||||
|
||||
// Live kanban board (streaming inventory). Ordered columns + their card counts.
|
||||
const KANBAN_COLS = [
|
||||
['merge', 'Merge'], ['chain', 'Chain'], ['chain_verify', 'Verify'], ['naming', 'Naming'],
|
||||
['naming_verify', 'Name✓'], ['chain_filter', 'Filter'], ['filter_verify', 'Filter✓'],
|
||||
['block_assemble', 'Block'], ['small_blocks', 'Small'], ['small_verify', 'Small✓'],
|
||||
['dependency', 'Dep'], ['dependency_verify', 'Dep✓'], ['main', 'Main'], ['done_block', 'Done'],
|
||||
]
|
||||
const kanban = ref({})
|
||||
const agents = ref([])
|
||||
const kanbanCols = computed(() => KANBAN_COLS.map(([k, label]) => ({ key: k, label, n: kanban.value[k] || 0 })))
|
||||
const kanbanActive = computed(() => Object.values(kanban.value).some((n) => n > 0))
|
||||
function fmtRuntime(s) {
|
||||
const m = Math.floor(s / 60), sec = Math.floor(s % 60)
|
||||
return `${m}:${String(sec).padStart(2, '0')}`
|
||||
}
|
||||
const researchBusy = ref(false)
|
||||
async function moreResearch() {
|
||||
researchBusy.value = true
|
||||
try { await addResearch(props.topic) } catch { /* ignore */ }
|
||||
setTimeout(() => { researchBusy.value = false }, 800) // brief debounce against double-clicks
|
||||
}
|
||||
let kanbanTimer = null
|
||||
async function pollKanban() {
|
||||
try { kanban.value = await fetchKanban(props.topic) } catch { /* ignore */ }
|
||||
try { agents.value = await fetchAgents(props.topic) } catch { /* ignore */ }
|
||||
}
|
||||
watch(() => [props.topic, props.generating], () => {
|
||||
clearInterval(kanbanTimer)
|
||||
pollKanban()
|
||||
if (props.generating) kanbanTimer = setInterval(pollKanban, 1000)
|
||||
}, { immediate: true })
|
||||
onUnmounted(() => clearInterval(kanbanTimer))
|
||||
|
||||
// Group sub-steps by phase, carrying the global index for the re-run.
|
||||
const phaseGroups = computed(() => {
|
||||
const out = []
|
||||
@@ -113,7 +146,8 @@ const subTotal = computed(() => items.value.reduce((n, b) => n + (b.subblocks?.l
|
||||
<div class="bk-steps-top">
|
||||
<div v-if="progress" class="bk-progress"><span class="bk-progress-dot"></span>{{ progress }}</div>
|
||||
<div v-if="!generating" class="bk-global-actions">
|
||||
<button class="bk-act play" @click="emit('restartAll')">{{ partial ? 'Continue' : ready ? 'Regenerate' : 'Generate' }}</button>
|
||||
<button class="bk-act play" @click="emit('restartAll', { research: false })" title="Process the existing queue — search no new topics">Continue</button>
|
||||
<button class="bk-act play" @click="emit('restartAll', { research: true })" title="Start one research agent and process the queue">+ Research</button>
|
||||
<button
|
||||
v-if="ready || partial"
|
||||
class="bk-act danger"
|
||||
@@ -122,9 +156,22 @@ const subTotal = computed(() => items.value.reduce((n, b) => n + (b.subblocks?.l
|
||||
>{{ confirm === 'remove' ? 'Sure?' : 'Remove' }}</button>
|
||||
</div>
|
||||
<div v-else class="bk-global-actions">
|
||||
<button class="bk-act play" :disabled="researchBusy" @click="moreResearch" title="Start one more research agent">+ Research</button>
|
||||
<button class="bk-act danger" @click="emit('cancel')">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="generating || kanbanActive" class="bk-kanban">
|
||||
<div v-for="c in kanbanCols" :key="c.key" class="bk-kcol" :class="{ 'bk-kactive': c.n > 0 }">
|
||||
<span class="bk-kcount">{{ c.n }}</span>
|
||||
<span class="bk-klabel">{{ c.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="agents.length" class="bk-agents">
|
||||
<span class="bk-agents-label">{{ agents.length }} Agenten aktiv:</span>
|
||||
<span v-for="a in agents" :key="a.label" class="bk-agent">
|
||||
{{ a.label }} <span class="bk-agent-time">{{ fmtRuntime(a.runtime) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="bk-phasen">
|
||||
<div v-for="g in phaseGroups" :key="g.phase" class="bk-phase">
|
||||
<span class="bk-phase-label">{{ g.phase }}</span>
|
||||
@@ -274,6 +321,28 @@ const subTotal = computed(() => items.value.reduce((n, b) => n + (b.subblocks?.l
|
||||
/* Header: progress left, global buttons right */
|
||||
.bk-steps-top { display: flex; align-items: center; gap: 1rem; min-height: 1.9rem; margin-bottom: 0.7rem; }
|
||||
.bk-steps-top .bk-progress { margin-bottom: 0; }
|
||||
|
||||
/* Live kanban board (streaming inventory) */
|
||||
.bk-kanban { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.7rem; }
|
||||
.bk-kcol {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 1px;
|
||||
min-width: 3.1rem; padding: 0.3rem 0.4rem;
|
||||
border: 1px solid var(--border-strong); border-radius: 6px; background: var(--panel);
|
||||
}
|
||||
.bk-kcol.bk-kactive { border-color: var(--accent); background: var(--accent-soft); }
|
||||
.bk-kcount { font-size: 0.95rem; font-weight: 700; color: var(--text); }
|
||||
.bk-kactive .bk-kcount { color: var(--accent); }
|
||||
.bk-klabel { font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-faint); }
|
||||
|
||||
/* Running agents + live runtime */
|
||||
.bk-agents { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem 0.5rem; margin-bottom: 0.7rem; font-size: 0.78rem; }
|
||||
.bk-agents-label { color: var(--text-muted); font-weight: 600; }
|
||||
.bk-agent {
|
||||
display: inline-flex; align-items: center; gap: 0.35rem;
|
||||
padding: 0.12rem 0.5rem; border: 1px solid var(--accent); border-radius: 10px;
|
||||
background: var(--accent-soft); color: var(--text);
|
||||
}
|
||||
.bk-agent-time { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--accent); }
|
||||
.bk-global-actions { margin-left: auto; display: flex; gap: 0.4rem; }
|
||||
|
||||
/* Action bar for the selected start point */
|
||||
|
||||
Reference in New Issue
Block a user