findArchive($archiveUid); } catch (Throwable $exception) { return $this->jsonResponse([ 'code' => 500, 'message' => 'Archive lookup failed.', 'errors' => ['archive' => $exception->getMessage()], ], 500); } if ($archive === null) { return $this->jsonResponse([ 'code' => 404, 'message' => 'Archive not found.', 'errors' => ['archive_uid' => $archiveUid], ], 404); } $archive['chunk_count'] = is_array($archive['chunks'] ?? null) ? count($archive['chunks']) : 0; return $this->jsonResponse([ 'code' => 0, 'message' => 'Archive loaded.', 'data' => $archive, ], 200); } public function chunk(string $chunkUid): Response { try { $chunk = (new ArchiveRepository())->findChunk($chunkUid); } catch (Throwable $exception) { return $this->jsonResponse([ 'code' => 500, 'message' => 'Chunk lookup failed.', 'errors' => ['chunk' => $exception->getMessage()], ], 500); } if ($chunk === null) { return $this->jsonResponse([ 'code' => 404, 'message' => 'Chunk not found.', 'errors' => ['chunk_uid' => $chunkUid], ], 404); } return $this->jsonResponse([ 'code' => 0, 'message' => 'Chunk loaded.', 'data' => $chunk, ], 200); } public function archiveChunks(string $archiveUid): Response { try { $repository = new ArchiveRepository(); $archive = $repository->findArchive($archiveUid); $chunks = $archive === null ? [] : $repository->findArchiveChunks($archiveUid); } catch (Throwable $exception) { return $this->jsonResponse([ 'code' => 500, 'message' => 'Archive chunks lookup failed.', 'errors' => ['archive_chunks' => $exception->getMessage()], ], 500); } if ($archive === null) { return $this->jsonResponse([ 'code' => 404, 'message' => 'Archive not found.', 'errors' => ['archive_uid' => $archiveUid], ], 404); } return $this->jsonResponse([ 'code' => 0, 'message' => 'Archive chunks loaded.', 'data' => [ 'archive_uid' => $archive['archive_uid'], 'title' => $archive['title'], 'summary' => $archive['summary'], 'source' => $archive['source'], 'author' => $archive['author'], 'year' => $archive['year'], 'series' => $archive['series'], 'tags' => $archive['tags'], 'chunk_count' => count($chunks), 'chunks' => $chunks, ], ], 200); } public function evidence(string $chunkUid): Response { try { $chunk = (new ArchiveRepository())->findChunk($chunkUid); } catch (Throwable $exception) { return $this->jsonResponse([ 'code' => 500, 'message' => 'Evidence lookup failed.', 'errors' => ['evidence' => $exception->getMessage()], ], 500); } if ($chunk === null) { return $this->jsonResponse([ 'code' => 404, 'message' => 'Evidence not found.', 'errors' => ['chunk_uid' => $chunkUid], ], 404); } $archive = $chunk['archive']; $pages = $chunk['pages']; $pageLabel = $this->pageLabel($pages); return $this->jsonResponse([ 'code' => 0, 'message' => 'Evidence loaded.', 'data' => [ 'chunk_uid' => $chunk['chunk_uid'], 'archive_uid' => $chunk['archive_uid'], 'title' => $archive['title'] ?? null, 'source' => $archive['source'] ?? null, 'author' => $archive['author'] ?? null, 'year' => $archive['year'] ?? null, 'series' => $archive['series'] ?? null, 'tags' => $archive['tags'] ?? [], 'page_start' => $chunk['page_start'], 'page_end' => $chunk['page_end'], 'pages' => $pages, 'page_label' => $pageLabel, 'citation' => $this->citation($archive, $pageLabel), 'quote' => $chunk['text'], 'chunk' => [ 'chunk_index' => $chunk['chunk_index'], 'length' => $chunk['length'], 'embedding_model' => $chunk['embedding_model'], 'embedding_status' => $chunk['embedding_status'], 'search_index_status' => $chunk['search_index_status'], ], ], ], 200); } public function archiveEvidence(string $archiveUid): Response { try { $repository = new ArchiveRepository(); $archive = $repository->findArchive($archiveUid); $chunks = $archive === null ? [] : $repository->findArchiveChunks($archiveUid); } catch (Throwable $exception) { return $this->jsonResponse([ 'code' => 500, 'message' => 'Archive evidence lookup failed.', 'errors' => ['archive_evidence' => $exception->getMessage()], ], 500); } if ($archive === null) { return $this->jsonResponse([ 'code' => 404, 'message' => 'Archive not found.', 'errors' => ['archive_uid' => $archiveUid], ], 404); } $evidence = array_map(function (array $chunk): array { $archive = $chunk['archive']; $pages = $chunk['pages']; $pageLabel = $this->pageLabel($pages); return [ 'chunk_uid' => $chunk['chunk_uid'], 'chunk_index' => $chunk['chunk_index'], 'page_start' => $chunk['page_start'], 'page_end' => $chunk['page_end'], 'pages' => $pages, 'page_label' => $pageLabel, 'citation' => $this->citation($archive, $pageLabel), 'quote' => $chunk['text'], 'length' => $chunk['length'], 'embedding_model' => $chunk['embedding_model'], 'embedding_status' => $chunk['embedding_status'], 'search_index_status' => $chunk['search_index_status'], ]; }, $chunks); return $this->jsonResponse([ 'code' => 0, 'message' => 'Archive evidence loaded.', 'data' => [ 'archive_uid' => $archive['archive_uid'], 'title' => $archive['title'], 'summary' => $archive['summary'], 'source' => $archive['source'], 'author' => $archive['author'], 'year' => $archive['year'], 'series' => $archive['series'], 'tags' => $archive['tags'], 'chunk_count' => count($evidence), 'evidence' => $evidence, ], ], 200); } private function citation(array $archive, string $pageLabel): string { $parts = array_values(array_filter([ $archive['title'] ?? null, $archive['author'] ?? null, isset($archive['year']) ? (string) $archive['year'] : null, $pageLabel === '' ? null : $pageLabel, $archive['source'] ?? null, ], static fn ($value): bool => $value !== null && trim((string) $value) !== '')); return implode(' | ', $parts); } private function pageLabel(array $pages): string { if ($pages === []) { return ''; } if (count($pages) === 1) { return 'p. ' . (string) $pages[0]; } return 'pp. ' . (string) $pages[0] . '-' . (string) $pages[count($pages) - 1]; } private function jsonResponse(array $data, int $status): Response { return response( json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), $status, ['Content-Type' => 'application/json'] ); } }