feat(corpus): category filter, passage expand, drill enhancements, URL hash state
- Search: category filter pills scope results to a legal domain
- Search: full chunk text returned; click to expand inline beyond 600-char excerpt
- Drill panel: total count label ("Showing X of Y"), sort dropdown, title filter (300ms debounce)
- URL hash: preserves query/mode/lang/category/drill state for bookmarking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,15 @@ try {
|
||||
|
||||
$category = isset($_GET['category']) && $_GET['category'] !== '' ? trim((string)$_GET['category']) : null;
|
||||
$sourceName = isset($_GET['source_name']) && $_GET['source_name'] !== '' ? trim((string)$_GET['source_name']) : null;
|
||||
$titleFilter = isset($_GET['title']) && $_GET['title'] !== '' ? trim((string)$_GET['title']) : null;
|
||||
$offset = max(0, (int)($_GET['offset'] ?? 0));
|
||||
$limit = max(1, min(50, (int)($_GET['limit'] ?? 20)));
|
||||
$orderBy = match($_GET['sort'] ?? 'newest') {
|
||||
'oldest' => 'd.updated_at ASC',
|
||||
'alpha' => 'd.title ASC',
|
||||
'chunks' => 'chunk_count DESC',
|
||||
default => 'd.updated_at DESC',
|
||||
};
|
||||
|
||||
// Build WHERE clause
|
||||
$where = ["d.corpus_id = 1", "d.status = 'ready'"];
|
||||
@@ -24,6 +31,11 @@ try {
|
||||
$params[] = $category;
|
||||
}
|
||||
|
||||
if ($titleFilter !== null) {
|
||||
$where[] = 'd.title LIKE ?';
|
||||
$params[] = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $titleFilter) . '%';
|
||||
}
|
||||
|
||||
if ($sourceName !== null) {
|
||||
// Filter by source via a JOIN to corpus_sources on category match
|
||||
// or by matching the scraper's URL pattern in source_url
|
||||
@@ -61,7 +73,7 @@ try {
|
||||
LEFT JOIN chunks c ON c.document_id = d.id
|
||||
WHERE $whereStr
|
||||
GROUP BY d.id
|
||||
ORDER BY d.updated_at DESC
|
||||
ORDER BY $orderBy
|
||||
LIMIT $limit OFFSET $offset"
|
||||
);
|
||||
$dataStmt->execute($params);
|
||||
@@ -82,6 +94,8 @@ try {
|
||||
'filter' => [
|
||||
'category' => $category,
|
||||
'source_name' => $sourceName,
|
||||
'title' => $titleFilter,
|
||||
'sort' => $_GET['sort'] ?? 'newest',
|
||||
],
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
|
||||
@@ -29,6 +29,7 @@ try {
|
||||
'category' => $h['category'] ?? '',
|
||||
'section' => $h['section'] ?? null,
|
||||
'excerpt' => $h['excerpt'] ?? ($h['chunk_text'] ?? ''),
|
||||
'full_text' => $h['full_text'] ?? $h['chunk_text'] ?? $h['excerpt'] ?? '',
|
||||
'score' => $h['score'] ?? null,
|
||||
'document_id' => $h['document_id'] ?? null,
|
||||
'chunk_id' => $h['chunk_id'] ?? null,
|
||||
@@ -93,6 +94,7 @@ try {
|
||||
'category' => $r['category'] ?? '',
|
||||
'section' => $r['section'] ?? null,
|
||||
'excerpt' => mb_substr((string)($r['excerpt'] ?? ''), 0, 600, 'UTF-8'),
|
||||
'full_text' => (string)($r['excerpt'] ?? ''),
|
||||
'score' => isset($r['score']) ? round((float)$r['score'], 4) : null,
|
||||
'document_id' => (int)$r['document_id'],
|
||||
'chunk_id' => isset($r['chunk_id']) ? (int)$r['chunk_id'] : null,
|
||||
@@ -148,6 +150,7 @@ try {
|
||||
'category' => $p['category'] ?? '',
|
||||
'section' => $p['section_title'] ?? null,
|
||||
'excerpt' => mb_substr((string)($p['content'] ?? ''), 0, 600, 'UTF-8'),
|
||||
'full_text' => (string)($p['content'] ?? ''),
|
||||
'score' => round((float)($pt['score'] ?? 0), 4),
|
||||
'document_id' => isset($p['document_id']) ? (int)$p['document_id'] : null,
|
||||
'chunk_id' => $pt['id'] ?? null,
|
||||
@@ -234,6 +237,7 @@ try {
|
||||
'category' => $d['category'] ?? '',
|
||||
'section' => $d['section_title'] ?? null,
|
||||
'excerpt' => mb_substr((string)($d['content'] ?? ''), 0, 600, 'UTF-8'),
|
||||
'full_text' => (string)($d['content'] ?? ''),
|
||||
'score' => round((float)($d['@search.rerankerScore'] ?? $d['@search.score'] ?? 0), 4),
|
||||
'document_id' => null,
|
||||
'chunk_id' => $d['chunk_id'] ?? $d['id'] ?? null,
|
||||
|
||||
Reference in New Issue
Block a user