feat: review project pseudonyms by category
This commit is contained in:
66
src/lib/project-review.ts
Normal file
66
src/lib/project-review.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
const SEPARATOR = "\n\n\n\n";
|
||||
export const MAX_BATCH_CODEPOINTS = 24000;
|
||||
|
||||
export type BatchItem = { key: string; text: string };
|
||||
export type BatchSegment = BatchItem & { start: number; end: number };
|
||||
export type TextBatch = { text: string; segments: BatchSegment[] };
|
||||
|
||||
/** Keep file boundaries while reducing many small text/path API calls to a few batches. */
|
||||
export function makeTextBatches(items: BatchItem[], limit = MAX_BATCH_CODEPOINTS): TextBatch[] {
|
||||
const batches: TextBatch[] = [];
|
||||
let segments: BatchSegment[] = [];
|
||||
let text = "";
|
||||
let length = 0;
|
||||
const separatorLength = [...SEPARATOR].length;
|
||||
const flush = () => {
|
||||
if (segments.length) batches.push({ text, segments });
|
||||
segments = [];
|
||||
text = "";
|
||||
length = 0;
|
||||
};
|
||||
for (const item of items) {
|
||||
if (!item.text) continue;
|
||||
const itemLength = [...item.text].length;
|
||||
if (segments.length && length + separatorLength + itemLength > limit) flush();
|
||||
if (segments.length) { text += SEPARATOR; length += separatorLength; }
|
||||
segments.push({ ...item, start: length, end: length + itemLength });
|
||||
text += item.text;
|
||||
length += itemLength;
|
||||
}
|
||||
flush();
|
||||
return batches;
|
||||
}
|
||||
|
||||
export function spansBySegment<T extends { start: number; end: number }>(
|
||||
batch: TextBatch, spans: T[],
|
||||
): Map<string, T[]> {
|
||||
const result = new Map<string, T[]>(batch.segments.map((segment) => [segment.key, []]));
|
||||
let segmentIndex = 0;
|
||||
for (const span of [...spans].sort((a, b) => a.start - b.start)) {
|
||||
while (segmentIndex < batch.segments.length && span.start >= batch.segments[segmentIndex].end) segmentIndex++;
|
||||
const segment = batch.segments[segmentIndex];
|
||||
if (!segment || span.start < segment.start || span.end > segment.end) continue;
|
||||
result.get(segment.key)!.push({ ...span, start: span.start - segment.start, end: span.end - segment.start });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export type GroupableHit = { id: string; type: string; original: string; placeholder: string };
|
||||
export type ReviewGroup<T extends GroupableHit> = {
|
||||
key: string; type: string; original: string; placeholder: string; hits: T[];
|
||||
};
|
||||
|
||||
export function groupReviewHits<T extends GroupableHit>(hits: T[]): ReviewGroup<T>[] {
|
||||
const groups = new Map<string, ReviewGroup<T>>();
|
||||
for (const hit of hits) {
|
||||
const key = JSON.stringify([hit.type, hit.original, hit.placeholder]);
|
||||
let group = groups.get(key);
|
||||
if (!group) {
|
||||
group = { key, type: hit.type, original: hit.original, placeholder: hit.placeholder,
|
||||
hits: [] };
|
||||
groups.set(key, group);
|
||||
}
|
||||
group.hits.push(hit);
|
||||
}
|
||||
return [...groups.values()].sort((a, b) => a.type.localeCompare(b.type) || a.original.localeCompare(b.original));
|
||||
}
|
||||
Reference in New Issue
Block a user