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:
parent
102a11765a
commit
ace37429bd
2 changed files with 106 additions and 31 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue