mirror of
https://github.com/github/awesome-copilot.git
synced 2026-10-12 23:04:14 +00:00
Optimize catalog filter membership checks (#4448)
* Optimize catalog filter membership checks Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * perf: cache catalog filter bucket lookups Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * perf: reduce repeated category scans in search Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot App
parent
804187ef0d
commit
0052323536
@@ -152,13 +152,14 @@ export function AgentsCatalog({
|
|||||||
|
|
||||||
const sortedAgents = useMemo(() => {
|
const sortedAgents = useMemo(() => {
|
||||||
const copy = agents.filter((agent) => {
|
const copy = agents.filter((agent) => {
|
||||||
const models = modelsOf(agent);
|
const modelSet = new Set(modelsOf(agent));
|
||||||
|
const toolSet = new Set(agent.tools ?? []);
|
||||||
const modelOk =
|
const modelOk =
|
||||||
filters.model.length === 0 ||
|
filters.model.length === 0 ||
|
||||||
filters.model.some((model) => models.includes(model));
|
filters.model.some((model) => modelSet.has(model));
|
||||||
const toolsOk =
|
const toolsOk =
|
||||||
filters.tools.length === 0 ||
|
filters.tools.length === 0 ||
|
||||||
filters.tools.every((tool) => (agent.tools ?? []).includes(tool));
|
filters.tools.every((tool) => toolSet.has(tool));
|
||||||
const updatedOk =
|
const updatedOk =
|
||||||
filters.updated.length === 0 ||
|
filters.updated.length === 0 ||
|
||||||
filters.updated.includes(updatedBucketOf(daysSince(agent.lastUpdated)));
|
filters.updated.includes(updatedBucketOf(daysSince(agent.lastUpdated)));
|
||||||
|
|||||||
@@ -152,11 +152,12 @@ export function PluginsCatalog({
|
|||||||
|
|
||||||
const sortedPlugins = useMemo(() => {
|
const sortedPlugins = useMemo(() => {
|
||||||
const copy = plugins.filter((plugin) => {
|
const copy = plugins.filter((plugin) => {
|
||||||
|
const tagSet = new Set(plugin.tags ?? []);
|
||||||
const sourceOk =
|
const sourceOk =
|
||||||
filters.source.length === 0 || filters.source.includes(sourceOf(plugin));
|
filters.source.length === 0 || filters.source.includes(sourceOf(plugin));
|
||||||
const categoryOk =
|
const categoryOk =
|
||||||
filters.category.length === 0 ||
|
filters.category.length === 0 ||
|
||||||
filters.category.some((tag) => (plugin.tags ?? []).includes(tag));
|
filters.category.some((tag) => tagSet.has(tag));
|
||||||
const size = sizeLabelOf(plugin);
|
const size = sizeLabelOf(plugin);
|
||||||
const sizeOk =
|
const sizeOk =
|
||||||
filters.size.length === 0 ||
|
filters.size.length === 0 ||
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ export function TopNavSearch({
|
|||||||
const groups = useMemo<ResultGroup[]>(() => {
|
const groups = useMemo<ResultGroup[]>(() => {
|
||||||
const query = trimmed.toLowerCase();
|
const query = trimmed.toLowerCase();
|
||||||
if (query.length === 0) return [];
|
if (query.length === 0) return [];
|
||||||
|
|
||||||
const staticMatches = normalizedIndex
|
const staticMatches = normalizedIndex
|
||||||
.filter(
|
.filter(
|
||||||
({ title, description, category }) =>
|
({ title, description, category }) =>
|
||||||
@@ -148,25 +149,33 @@ export function TopNavSearch({
|
|||||||
category.includes(query),
|
category.includes(query),
|
||||||
)
|
)
|
||||||
.map(({ item }) => item);
|
.map(({ item }) => item);
|
||||||
|
|
||||||
// Static hits win on ties: they carry curated titles and descriptions.
|
// Static hits win on ties: they carry curated titles and descriptions.
|
||||||
const seen = new Set(staticMatches.map((item) => hrefKey(item.href)));
|
const seen = new Set(staticMatches.map((item) => hrefKey(item.href)));
|
||||||
const matches = [...staticMatches];
|
const matches: SearchItem[] = [...staticMatches];
|
||||||
for (const hit of pagefindHits) {
|
for (const hit of pagefindHits) {
|
||||||
const key = hrefKey(hit.href);
|
const key = hrefKey(hit.href);
|
||||||
if (seen.has(key)) continue;
|
if (seen.has(key)) continue;
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
matches.push(hit);
|
matches.push(hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const matchesByCategory = new Map<SearchCategory, SearchItem[]>();
|
||||||
|
for (const item of matches) {
|
||||||
|
const bucket = matchesByCategory.get(item.category) ?? [];
|
||||||
|
bucket.push(item);
|
||||||
|
matchesByCategory.set(item.category, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
let remaining = MAX_RESULTS;
|
let remaining = MAX_RESULTS;
|
||||||
const grouped: ResultGroup[] = [];
|
const grouped: ResultGroup[] = [];
|
||||||
for (const category of CATEGORY_ORDER) {
|
for (const category of CATEGORY_ORDER) {
|
||||||
if (remaining <= 0) break;
|
if (remaining <= 0) break;
|
||||||
const items = matches
|
const items = matchesByCategory.get(category) ?? [];
|
||||||
.filter((item) => item.category === category)
|
const slice = items.slice(0, Math.min(MAX_PER_GROUP, remaining));
|
||||||
.slice(0, Math.min(MAX_PER_GROUP, remaining));
|
if (slice.length === 0) continue;
|
||||||
if (items.length === 0) continue;
|
remaining -= slice.length;
|
||||||
remaining -= items.length;
|
grouped.push({ category, items: slice });
|
||||||
grouped.push({ category, items });
|
|
||||||
}
|
}
|
||||||
return grouped;
|
return grouped;
|
||||||
}, [trimmed, normalizedIndex, pagefindHits]);
|
}, [trimmed, normalizedIndex, pagefindHits]);
|
||||||
|
|||||||
@@ -15,15 +15,34 @@ export const updatedBuckets: { label: string; max: number }[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/** Whole days between `lastUpdated` and now. Unparsable dates sort as oldest. */
|
/** Whole days between `lastUpdated` and now. Unparsable dates sort as oldest. */
|
||||||
|
const daysSinceCache = new Map<string, number>();
|
||||||
|
|
||||||
export function daysSince(lastUpdated: string | undefined): number {
|
export function daysSince(lastUpdated: string | undefined): number {
|
||||||
if (!lastUpdated) return Number.POSITIVE_INFINITY;
|
if (!lastUpdated) return Number.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
const dayStamp = Math.floor(Date.now() / 86_400_000);
|
||||||
|
const cacheKey = `${lastUpdated}:${dayStamp}`;
|
||||||
|
const cached = daysSinceCache.get(cacheKey);
|
||||||
|
if (cached !== undefined) return cached;
|
||||||
|
|
||||||
const then = Date.parse(lastUpdated);
|
const then = Date.parse(lastUpdated);
|
||||||
if (Number.isNaN(then)) return Number.POSITIVE_INFINITY;
|
const days = Number.isNaN(then)
|
||||||
return Math.max(0, Math.floor((Date.now() - then) / 86_400_000));
|
? Number.POSITIVE_INFINITY
|
||||||
|
: Math.max(0, Math.floor((Date.now() - then) / 86_400_000));
|
||||||
|
|
||||||
|
daysSinceCache.set(cacheKey, days);
|
||||||
|
return days;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedBucketCache = new Map<number, string>();
|
||||||
|
|
||||||
export function updatedBucketOf(days: number): string {
|
export function updatedBucketOf(days: number): string {
|
||||||
return updatedBuckets.find((bucket) => days <= bucket.max)?.label ?? "Older";
|
const cached = updatedBucketCache.get(days);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
const bucket = updatedBuckets.find((option) => days <= option.max)?.label ?? "Older";
|
||||||
|
updatedBucketCache.set(days, bucket);
|
||||||
|
return bucket;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fileBuckets: { label: string; min: number; max: number }[] = [
|
export const fileBuckets: { label: string; min: number; max: number }[] = [
|
||||||
@@ -33,11 +52,18 @@ export const fileBuckets: { label: string; min: number; max: number }[] = [
|
|||||||
{ label: "11+ files", min: 11, max: Number.POSITIVE_INFINITY },
|
{ label: "11+ files", min: 11, max: Number.POSITIVE_INFINITY },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const fileBucketCache = new Map<number, string>();
|
||||||
|
|
||||||
export function fileBucketOf(files: number): string {
|
export function fileBucketOf(files: number): string {
|
||||||
return (
|
const cached = fileBucketCache.get(files);
|
||||||
fileBuckets.find((bucket) => files >= bucket.min && files <= bucket.max)
|
if (cached) return cached;
|
||||||
?.label ?? "1–2 files"
|
|
||||||
);
|
const bucket =
|
||||||
|
fileBuckets.find((option) => files >= option.min && files <= option.max)?.label ??
|
||||||
|
"1–2 files";
|
||||||
|
|
||||||
|
fileBucketCache.set(files, bucket);
|
||||||
|
return bucket;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user