feat: add Chunk-Browser tab to RAG page
- New 'Chunk-Browser' tab for sequential chunk browsing - Qdrant scroll API proxy (scroll + collection-count actions) - Pagination with prev/next through all chunks in a collection - Text search filter with highlighting - Click to expand chunk and see all metadata - 'In Chunks suchen' button now navigates to Chunk-Browser with correct collection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,72 @@ export async function GET(request: NextRequest) {
|
||||
url += `/traceability?chunk_id=${encodeURIComponent(chunkId || '')}®ulation=${encodeURIComponent(regulation || '')}`
|
||||
break
|
||||
}
|
||||
case 'scroll': {
|
||||
const collection = searchParams.get('collection') || 'bp_compliance_gesetze'
|
||||
const limit = parseInt(searchParams.get('limit') || '20', 10)
|
||||
const offsetParam = searchParams.get('offset')
|
||||
const filterKey = searchParams.get('filter_key')
|
||||
const filterValue = searchParams.get('filter_value')
|
||||
const textSearch = searchParams.get('text_search')
|
||||
|
||||
const scrollBody: Record<string, unknown> = {
|
||||
limit: Math.min(limit, 100),
|
||||
with_payload: true,
|
||||
with_vector: false,
|
||||
}
|
||||
if (offsetParam) {
|
||||
scrollBody.offset = offsetParam
|
||||
}
|
||||
if (filterKey && filterValue) {
|
||||
scrollBody.filter = {
|
||||
must: [{ key: filterKey, match: { value: filterValue } }],
|
||||
}
|
||||
}
|
||||
|
||||
const scrollRes = await fetch(`${QDRANT_URL}/collections/${encodeURIComponent(collection)}/points/scroll`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(scrollBody),
|
||||
cache: 'no-store',
|
||||
})
|
||||
if (!scrollRes.ok) {
|
||||
return NextResponse.json({ error: 'Qdrant scroll failed' }, { status: scrollRes.status })
|
||||
}
|
||||
const scrollData = await scrollRes.json()
|
||||
const points = (scrollData.result?.points || []).map((p: { id: string; payload?: Record<string, unknown> }) => ({
|
||||
id: p.id,
|
||||
...p.payload,
|
||||
}))
|
||||
|
||||
// Client-side text search filter
|
||||
let filtered = points
|
||||
if (textSearch && textSearch.trim()) {
|
||||
const term = textSearch.toLowerCase()
|
||||
filtered = points.filter((p: Record<string, unknown>) => {
|
||||
const text = String(p.text || p.content || p.chunk_text || '')
|
||||
return text.toLowerCase().includes(term)
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
chunks: filtered,
|
||||
next_offset: scrollData.result?.next_page_offset || null,
|
||||
total_in_page: points.length,
|
||||
})
|
||||
}
|
||||
case 'collection-count': {
|
||||
const col = searchParams.get('collection') || 'bp_compliance_gesetze'
|
||||
const countRes = await fetch(`${QDRANT_URL}/collections/${encodeURIComponent(col)}`, {
|
||||
cache: 'no-store',
|
||||
})
|
||||
if (!countRes.ok) {
|
||||
return NextResponse.json({ count: 0 })
|
||||
}
|
||||
const countData = await countRes.json()
|
||||
return NextResponse.json({
|
||||
count: countData.result?.points_count || 0,
|
||||
})
|
||||
}
|
||||
default:
|
||||
return NextResponse.json({ error: 'Unknown action' }, { status: 400 })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user