mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-23 20:05:12 +00:00
fix: make FuzzySearch generic to support typed items
- Add SearchableItem base interface for minimum required fields - Make FuzzySearch class generic with type parameter - Update all page scripts to use typed FuzzySearch instances - Fix type casting in calculateScore method
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generated": "2026-01-28T22:44:20.356Z",
|
"generated": "2026-01-28T22:56:09.687Z",
|
||||||
"counts": {
|
"counts": {
|
||||||
"agents": 140,
|
"agents": 140,
|
||||||
"prompts": 134,
|
"prompts": 134,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ interface AgentsData {
|
|||||||
|
|
||||||
const resourceType = 'agent';
|
const resourceType = 'agent';
|
||||||
let allItems: Agent[] = [];
|
let allItems: Agent[] = [];
|
||||||
let search = new FuzzySearch();
|
let search = new FuzzySearch<Agent>();
|
||||||
let modelSelect: Choices;
|
let modelSelect: Choices;
|
||||||
let toolSelect: Choices;
|
let toolSelect: Choices;
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
* Collections page functionality
|
* Collections page functionality
|
||||||
*/
|
*/
|
||||||
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
||||||
import { FuzzySearch, type SearchItem } from '../search';
|
import { FuzzySearch } from '../search';
|
||||||
import { fetchData, debounce, escapeHtml, getGitHubUrl } from '../utils';
|
import { fetchData, debounce, escapeHtml, getGitHubUrl } from '../utils';
|
||||||
import { setupModal, openFileModal } from '../modal';
|
import { setupModal, openFileModal } from '../modal';
|
||||||
|
|
||||||
interface Collection {
|
interface Collection {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
path: string;
|
path: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
@@ -25,7 +26,7 @@ interface CollectionsData {
|
|||||||
|
|
||||||
const resourceType = 'collection';
|
const resourceType = 'collection';
|
||||||
let allItems: Collection[] = [];
|
let allItems: Collection[] = [];
|
||||||
let search = new FuzzySearch();
|
let search = new FuzzySearch<Collection>();
|
||||||
let tagSelect: Choices;
|
let tagSelect: Choices;
|
||||||
let currentFilters = {
|
let currentFilters = {
|
||||||
tags: [] as string[],
|
tags: [] as string[],
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export async function initHomepage(): Promise<void> {
|
|||||||
// Load search index
|
// Load search index
|
||||||
const searchIndex = await fetchData<SearchItem[]>('search-index.json');
|
const searchIndex = await fetchData<SearchItem[]>('search-index.json');
|
||||||
if (searchIndex) {
|
if (searchIndex) {
|
||||||
const search = new FuzzySearch();
|
const search = new FuzzySearch<SearchItem>();
|
||||||
search.setItems(searchIndex);
|
search.setItems(searchIndex);
|
||||||
|
|
||||||
const searchInput = document.getElementById('global-search') as HTMLInputElement;
|
const searchInput = document.getElementById('global-search') as HTMLInputElement;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface InstructionsData {
|
|||||||
|
|
||||||
const resourceType = 'instruction';
|
const resourceType = 'instruction';
|
||||||
let allItems: Instruction[] = [];
|
let allItems: Instruction[] = [];
|
||||||
let search = new FuzzySearch();
|
let search = new FuzzySearch<Instruction>();
|
||||||
let extensionSelect: Choices;
|
let extensionSelect: Choices;
|
||||||
let currentFilters = { extensions: [] as string[] };
|
let currentFilters = { extensions: [] as string[] };
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ interface PromptsData {
|
|||||||
|
|
||||||
const resourceType = 'prompt';
|
const resourceType = 'prompt';
|
||||||
let allItems: Prompt[] = [];
|
let allItems: Prompt[] = [];
|
||||||
let search = new FuzzySearch();
|
let search = new FuzzySearch<Prompt>();
|
||||||
let toolSelect: Choices;
|
let toolSelect: Choices;
|
||||||
let currentFilters = { tools: [] as string[] };
|
let currentFilters = { tools: [] as string[] };
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ interface SkillsData {
|
|||||||
|
|
||||||
const resourceType = 'skill';
|
const resourceType = 'skill';
|
||||||
let allItems: Skill[] = [];
|
let allItems: Skill[] = [];
|
||||||
let search = new FuzzySearch();
|
let search = new FuzzySearch<Skill>();
|
||||||
let categorySelect: Choices;
|
let categorySelect: Choices;
|
||||||
let currentFilters = {
|
let currentFilters = {
|
||||||
categories: [] as string[],
|
categories: [] as string[],
|
||||||
|
|||||||
@@ -14,30 +14,36 @@ export interface SearchItem {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SearchableItem {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SearchOptions {
|
export interface SearchOptions {
|
||||||
fields?: string[];
|
fields?: string[];
|
||||||
limit?: number;
|
limit?: number;
|
||||||
minScore?: number;
|
minScore?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FuzzySearch {
|
export class FuzzySearch<T extends SearchableItem = SearchItem> {
|
||||||
private items: SearchItem[] = [];
|
private items: T[] = [];
|
||||||
|
|
||||||
constructor(items: SearchItem[] = []) {
|
constructor(items: T[] = []) {
|
||||||
this.items = items;
|
this.items = items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the items to search
|
* Update the items to search
|
||||||
*/
|
*/
|
||||||
setItems(items: SearchItem[]): void {
|
setItems(items: T[]): void {
|
||||||
this.items = items;
|
this.items = items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search items with fuzzy matching
|
* Search items with fuzzy matching
|
||||||
*/
|
*/
|
||||||
search(query: string, options: SearchOptions = {}): SearchItem[] {
|
search(query: string, options: SearchOptions = {}): T[] {
|
||||||
const {
|
const {
|
||||||
fields = ['title', 'description', 'searchText'],
|
fields = ['title', 'description', 'searchText'],
|
||||||
limit = 50,
|
limit = 50,
|
||||||
@@ -50,7 +56,7 @@ export class FuzzySearch {
|
|||||||
|
|
||||||
const normalizedQuery = query.toLowerCase().trim();
|
const normalizedQuery = query.toLowerCase().trim();
|
||||||
const queryWords = normalizedQuery.split(/\s+/);
|
const queryWords = normalizedQuery.split(/\s+/);
|
||||||
const results: Array<{ item: SearchItem; score: number }> = [];
|
const results: Array<{ item: T; score: number }> = [];
|
||||||
|
|
||||||
for (const item of this.items) {
|
for (const item of this.items) {
|
||||||
const score = this.calculateScore(item, queryWords, fields);
|
const score = this.calculateScore(item, queryWords, fields);
|
||||||
@@ -68,14 +74,14 @@ export class FuzzySearch {
|
|||||||
/**
|
/**
|
||||||
* Calculate match score for an item
|
* Calculate match score for an item
|
||||||
*/
|
*/
|
||||||
private calculateScore(item: SearchItem, queryWords: string[], fields: string[]): number {
|
private calculateScore(item: T, queryWords: string[], fields: string[]): number {
|
||||||
let totalScore = 0;
|
let totalScore = 0;
|
||||||
|
|
||||||
for (const word of queryWords) {
|
for (const word of queryWords) {
|
||||||
let wordScore = 0;
|
let wordScore = 0;
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
const value = item[field];
|
const value = (item as Record<string, unknown>)[field];
|
||||||
if (!value) continue;
|
if (!value) continue;
|
||||||
|
|
||||||
const normalizedValue = String(value).toLowerCase();
|
const normalizedValue = String(value).toLowerCase();
|
||||||
@@ -108,7 +114,7 @@ export class FuzzySearch {
|
|||||||
// Bonus for matching all words
|
// Bonus for matching all words
|
||||||
const matchesAllWords = queryWords.every(word =>
|
const matchesAllWords = queryWords.every(word =>
|
||||||
fields.some(field => {
|
fields.some(field => {
|
||||||
const value = item[field];
|
const value = (item as Record<string, unknown>)[field];
|
||||||
return value && String(value).toLowerCase().includes(word);
|
return value && String(value).toLowerCase().includes(word);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -140,13 +146,13 @@ export class FuzzySearch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global search instance
|
// Global search instance (uses SearchItem for the global search index)
|
||||||
export const globalSearch = new FuzzySearch();
|
export const globalSearch = new FuzzySearch<SearchItem>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize global search with search index
|
* Initialize global search with search index
|
||||||
*/
|
*/
|
||||||
export async function initGlobalSearch(): Promise<FuzzySearch> {
|
export async function initGlobalSearch(): Promise<FuzzySearch<SearchItem>> {
|
||||||
const searchIndex = await fetchData<SearchItem[]>('search-index.json');
|
const searchIndex = await fetchData<SearchItem[]>('search-index.json');
|
||||||
if (searchIndex) {
|
if (searchIndex) {
|
||||||
globalSearch.setItems(searchIndex);
|
globalSearch.setItems(searchIndex);
|
||||||
|
|||||||
Reference in New Issue
Block a user