mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
feat(books): rebuild duplicate books as a ledger
Sort groups by reclaimable space, pre-pick a keeper with a rule that states its reasoning, and expand any row into a field-by-field comparison that shows only what differs. Persist "not duplicates" so a judgement survives a rescan, and review every copy before deleting. Reading progress no longer renders 0%.
This commit is contained in:
@@ -6,6 +6,12 @@ export type BookDuplicateMatchReason = (typeof BOOK_DUPLICATE_MATCH_REASONS)[num
|
||||
export const BOOK_DUPLICATE_SCAN_STATUSES = ["queued", "running", "completed", "failed"] as const;
|
||||
export type BookDuplicateScanStatus = (typeof BOOK_DUPLICATE_SCAN_STATUSES)[number];
|
||||
|
||||
export const BOOK_DUPLICATE_GROUP_SORTS = ["reclaimable", "copies", "confidence", "title"] as const;
|
||||
export type BookDuplicateGroupSort = (typeof BOOK_DUPLICATE_GROUP_SORTS)[number];
|
||||
|
||||
export const BOOK_DUPLICATE_SORT_ORDERS = ["asc", "desc"] as const;
|
||||
export type BookDuplicateSortOrder = (typeof BOOK_DUPLICATE_SORT_ORDERS)[number];
|
||||
|
||||
export type CreateBookDuplicateScanRequest = {
|
||||
libraryId?: number;
|
||||
similarityPercent: number;
|
||||
@@ -21,6 +27,13 @@ export type BookDuplicateScan = {
|
||||
totalBooks: number | null;
|
||||
progressPercent: number | null;
|
||||
totalGroups: number | null;
|
||||
/** Copies beyond the first in every group, set when the scan completes. */
|
||||
totalExtraCopies: number | null;
|
||||
/**
|
||||
* Bytes freed by keeping the largest copy in every group, set when the scan completes. A floor:
|
||||
* keeping a smaller copy frees more.
|
||||
*/
|
||||
totalReclaimableBytes: number | null;
|
||||
errorCode: string | null;
|
||||
createdAt: string;
|
||||
completedAt: string | null;
|
||||
@@ -65,10 +78,25 @@ export type BookDuplicateGroup = {
|
||||
id: number;
|
||||
reasons: BookDuplicateMatchReason[];
|
||||
maxTitleSimilarity: number | null;
|
||||
/** Bytes freed by keeping the largest copy in this group. */
|
||||
reclaimableBytes: number;
|
||||
books: BookDuplicateCandidate[];
|
||||
pairs: BookDuplicatePair[];
|
||||
};
|
||||
|
||||
export type BookDuplicateDismissal = {
|
||||
bookIdA: number;
|
||||
bookIdB: number;
|
||||
titleA: string | null;
|
||||
titleB: string | null;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type CreateBookDuplicateDismissalRequest = {
|
||||
scanId: number;
|
||||
groupId: number;
|
||||
};
|
||||
|
||||
export type BookDuplicateGroupsResponse = {
|
||||
groups: BookDuplicateGroup[];
|
||||
total: number;
|
||||
|
||||
Reference in New Issue
Block a user