Improve Prepare mode with page filtering, collapsible config, and bbox overlay

Filter chunks by current PDF page for consistency with Verify mode.
Add collapsible chunking config section and paginated chunk list.
Show BboxOverlay in Prepare mode so users see element bounding boxes.
Fix scroll containment on prepare panel.
This commit is contained in:
Pier-Jean Malandrino 2026-04-02 13:41:59 +02:00
parent 102a11765a
commit ace37429bd
2 changed files with 106 additions and 31 deletions

View file

@ -1,10 +1,24 @@
<template>
<div class="chunk-panel">
<!-- Chunking config -->
<!-- Chunking config collapsible -->
<div class="chunk-config">
<div class="config-section">
<label class="config-label">{{ t('chunking.settings') }}</label>
<button class="config-toggle" @click="configOpen = !configOpen">
<svg
class="config-chevron"
:class="{ open: configOpen }"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"
/>
</svg>
<span class="config-label">{{ t('chunking.settings') }}</span>
</button>
<div v-if="configOpen" class="config-body">
<div class="config-row">
<label class="config-label-sm">{{ t('chunking.chunkerType') }}</label>
<select class="config-select" v-model="options.chunker_type">
@ -40,31 +54,31 @@
<span class="toggle-text">{{ t('chunking.repeatTableHeader') }}</span>
</label>
</div>
</div>
<button
class="chunk-btn primary"
:disabled="!canRechunk || analysisStore.rechunking"
@click="doRechunk"
>
<div v-if="analysisStore.rechunking" class="spinner-sm" />
{{ analysisStore.rechunking ? t('chunking.chunking') : t('chunking.run') }}
</button>
<button
class="chunk-btn primary"
:disabled="!canRechunk || analysisStore.rechunking"
@click="doRechunk"
>
<div v-if="analysisStore.rechunking" class="spinner-sm" />
{{ analysisStore.rechunking ? t('chunking.chunking') : t('chunking.run') }}
</button>
</div>
</div>
<!-- Chunks list -->
<div class="chunk-results" v-if="analysisStore.currentChunks.length">
<div class="chunk-results" v-if="pageChunks.length">
<div class="chunk-summary">
{{ analysisStore.currentChunks.length }} {{ t('chunking.chunks') }}
{{ pagination.totalItems.value }} {{ t('chunking.chunks') }}
</div>
<div class="chunk-list">
<div
class="chunk-card"
v-for="(chunk, idx) in analysisStore.currentChunks"
:key="idx"
v-for="(chunk, localIdx) in pagination.paginatedItems.value"
:key="globalIndex(localIdx)"
>
<div class="chunk-header">
<span class="chunk-index">#{{ idx + 1 }}</span>
<span class="chunk-index">#{{ globalIndex(localIdx) + 1 }}</span>
<span class="chunk-tokens" v-if="chunk.tokenCount">
{{ chunk.tokenCount }} tokens
</span>
@ -81,20 +95,43 @@
</div>
<div class="chunk-empty" v-else-if="!analysisStore.rechunking">
<p>{{ t('chunking.noChunks') }}</p>
<p>
{{
analysisStore.currentChunks.length
? t('chunking.noChunksOnPage')
: t('chunking.noChunks')
}}
</p>
</div>
<!-- Pagination -->
<PaginationBar
:page="pagination.page.value"
:page-count="pagination.pageCount.value"
:page-size="pagination.pageSize.value"
@update:page="pagination.goTo($event)"
@update:page-size="pagination.setPageSize($event)"
/>
</div>
</template>
<script setup lang="ts">
import { reactive, computed } from 'vue'
import { ref, reactive, computed } from 'vue'
import { useAnalysisStore } from '../../analysis/store'
import { useI18n } from '../../../shared/i18n'
import { usePagination } from '../../../shared/composables/usePagination'
import { PaginationBar } from '../../../shared/ui'
import type { ChunkingOptions } from '../../../shared/types'
const props = defineProps<{
currentPage: number
}>()
const analysisStore = useAnalysisStore()
const { t } = useI18n()
const configOpen = ref(true)
const options = reactive<Required<ChunkingOptions>>({
chunker_type: 'hybrid',
max_tokens: 512,
@ -107,6 +144,15 @@ const canRechunk = computed(() => {
return analysis?.status === 'COMPLETED' && analysis.hasDocumentJson
})
const pageChunks = computed(() =>
analysisStore.currentChunks.filter((c) => c.sourcePage === props.currentPage),
)
const pagination = usePagination(pageChunks, { pageSize: 20 })
function globalIndex(localIdx: number): number {
return (pagination.page.value - 1) * pagination.pageSize.value + localIdx
}
async function doRechunk() {
if (!analysisStore.currentAnalysis) return
await analysisStore.rechunk(analysisStore.currentAnalysis.id, { ...options })
@ -122,17 +168,35 @@ async function doRechunk() {
}
.chunk-config {
padding: 16px;
border-bottom: 1px solid var(--border);
display: flex;
flex-direction: column;
gap: 12px;
flex-shrink: 0;
}
.config-section {
.config-toggle {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
gap: 6px;
width: 100%;
padding: 10px 16px;
background: none;
border: none;
cursor: pointer;
color: var(--text-secondary);
}
.config-toggle:hover {
color: var(--text);
}
.config-chevron {
width: 14px;
height: 14px;
transition: transform 0.2s;
transform: rotate(0deg);
}
.config-chevron.open {
transform: rotate(90deg);
}
.config-label {
@ -140,7 +204,13 @@ async function doRechunk() {
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-secondary);
}
.config-body {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 16px 16px;
}
.config-label-sm {
@ -225,6 +295,7 @@ async function doRechunk() {
align-items: center;
justify-content: center;
gap: 6px;
margin-top: 4px;
}
.chunk-btn.primary {
@ -241,6 +312,7 @@ async function doRechunk() {
flex: 1;
overflow-y: auto;
padding: 12px;
min-height: 0;
}
.chunk-summary {
@ -331,6 +403,8 @@ async function doRechunk() {
}
@keyframes spin {
to { transform: rotate(360deg); }
to {
transform: rotate(360deg);
}
}
</style>

View file

@ -136,7 +136,7 @@
@load="onPdfImageLoad"
/>
<BboxOverlay
v-if="visualMode && hasAnalysisResults"
v-if="(visualMode || mode === 'preparer') && hasAnalysisResults"
ref="bboxOverlayRef"
:image-el="pdfImageRef"
:page-data="currentPageData"
@ -292,7 +292,7 @@
<!-- PREPARER MODE (feature-flipped) -->
<div v-if="mode === 'preparer' && chunkingEnabled" class="prepare-panel">
<ChunkPanel />
<ChunkPanel :current-page="currentPage" />
</div>
</div>
</div>
@ -1136,7 +1136,8 @@ onBeforeUnmount(() => {
}
/* Verify panel */
.verify-panel {
.verify-panel,
.prepare-panel {
height: 100%;
overflow: hidden;
display: flex;