mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 10:25:13 +00:00
- Add VS Code and VS Code Insiders install buttons to agent, prompt, and instruction list items - Use smaller button style for list view - Include VS Code icon for visual identification
182 lines
6.8 KiB
TypeScript
182 lines
6.8 KiB
TypeScript
/**
|
|
* Agents page functionality
|
|
*/
|
|
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
|
import { FuzzySearch } from '../search';
|
|
import { fetchData, debounce, escapeHtml, getGitHubUrl, getVSCodeInstallUrl } from '../utils';
|
|
import { setupModal, openFileModal } from '../modal';
|
|
|
|
interface Agent {
|
|
title: string;
|
|
description?: string;
|
|
path: string;
|
|
model?: string;
|
|
tools?: string[];
|
|
hasHandoffs?: boolean;
|
|
}
|
|
|
|
interface AgentsData {
|
|
items: Agent[];
|
|
filters: {
|
|
models: string[];
|
|
tools: string[];
|
|
};
|
|
}
|
|
|
|
const resourceType = 'agent';
|
|
let allItems: Agent[] = [];
|
|
let search = new FuzzySearch();
|
|
let modelSelect: Choices;
|
|
let toolSelect: Choices;
|
|
|
|
let currentFilters = {
|
|
models: [] as string[],
|
|
tools: [] as string[],
|
|
hasHandoffs: false,
|
|
};
|
|
|
|
function applyFiltersAndRender(): void {
|
|
const searchInput = document.getElementById('search-input') as HTMLInputElement;
|
|
const countEl = document.getElementById('results-count');
|
|
const query = searchInput?.value || '';
|
|
|
|
let results = query ? search.search(query) : [...allItems];
|
|
|
|
if (currentFilters.models.length > 0) {
|
|
results = results.filter(item => {
|
|
if (currentFilters.models.includes('(none)') && !item.model) {
|
|
return true;
|
|
}
|
|
return item.model && currentFilters.models.includes(item.model);
|
|
});
|
|
}
|
|
|
|
if (currentFilters.tools.length > 0) {
|
|
results = results.filter(item =>
|
|
item.tools?.some(tool => currentFilters.tools.includes(tool))
|
|
);
|
|
}
|
|
|
|
if (currentFilters.hasHandoffs) {
|
|
results = results.filter(item => item.hasHandoffs);
|
|
}
|
|
|
|
renderItems(results, query);
|
|
|
|
const activeFilters: string[] = [];
|
|
if (currentFilters.models.length > 0) activeFilters.push(`models: ${currentFilters.models.length}`);
|
|
if (currentFilters.tools.length > 0) activeFilters.push(`tools: ${currentFilters.tools.length}`);
|
|
if (currentFilters.hasHandoffs) activeFilters.push('has handoffs');
|
|
|
|
let countText = `${results.length} of ${allItems.length} agents`;
|
|
if (activeFilters.length > 0) {
|
|
countText += ` (filtered by ${activeFilters.join(', ')})`;
|
|
}
|
|
if (countEl) countEl.textContent = countText;
|
|
}
|
|
|
|
function renderItems(items: Agent[], query = ''): void {
|
|
const list = document.getElementById('resource-list');
|
|
if (!list) return;
|
|
|
|
if (items.length === 0) {
|
|
list.innerHTML = `
|
|
<div class="empty-state">
|
|
<h3>No agents found</h3>
|
|
<p>Try a different search term or adjust filters</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = items.map(item => `
|
|
<div class="resource-item" data-path="${escapeHtml(item.path)}">
|
|
<div class="resource-info">
|
|
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
|
|
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
|
|
<div class="resource-meta">
|
|
${item.model ? `<span class="resource-tag tag-model">${escapeHtml(item.model)}</span>` : ''}
|
|
${item.tools?.slice(0, 3).map(t => `<span class="resource-tag">${escapeHtml(t)}</span>`).join('') || ''}
|
|
${item.tools && item.tools.length > 3 ? `<span class="resource-tag">+${item.tools.length - 3} more</span>` : ''}
|
|
${item.hasHandoffs ? `<span class="resource-tag tag-handoffs">handoffs</span>` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="resource-actions">
|
|
<a href="${getVSCodeInstallUrl(resourceType, item.path, false)}" class="btn btn-primary btn-small" target="_blank" onclick="event.stopPropagation()" title="Install to VS Code">
|
|
<svg viewBox="0 0 100 100" width="14" height="14" fill="currentColor"><path d="M95.436 26.986L75.282 15.768a6.04 6.04 0 0 0-6.895.876L28.78 51.927 11.912 39.151a4.03 4.03 0 0 0-5.154.387l-5.36 4.878a4.03 4.03 0 0 0-.003 5.947l14.646 13.396-14.646 13.396a4.03 4.03 0 0 0 .003 5.947l5.36 4.878a4.03 4.03 0 0 0 5.154.387L28.78 74.59l39.607 35.283a6.04 6.04 0 0 0 6.895.876l20.154-11.218a6.04 6.04 0 0 0 3.127-5.288V32.274a6.04 6.04 0 0 0-3.127-5.288zM75.015 73.428L46.339 51.927l28.676-21.5z" transform="scale(0.16)"/></svg>
|
|
VS Code
|
|
</a>
|
|
<a href="${getVSCodeInstallUrl(resourceType, item.path, true)}" class="btn btn-secondary btn-small" target="_blank" onclick="event.stopPropagation()" title="Install to VS Code Insiders">
|
|
Insiders
|
|
</a>
|
|
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary btn-small" target="_blank" onclick="event.stopPropagation()">
|
|
GitHub
|
|
</a>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Add click handlers
|
|
list.querySelectorAll('.resource-item').forEach(el => {
|
|
el.addEventListener('click', () => {
|
|
const path = (el as HTMLElement).dataset.path;
|
|
if (path) openFileModal(path, resourceType);
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function initAgentsPage(): Promise<void> {
|
|
const list = document.getElementById('resource-list');
|
|
const searchInput = document.getElementById('search-input') as HTMLInputElement;
|
|
const handoffsCheckbox = document.getElementById('filter-handoffs') as HTMLInputElement;
|
|
const clearFiltersBtn = document.getElementById('clear-filters');
|
|
|
|
const data = await fetchData<AgentsData>('agents.json');
|
|
if (!data || !data.items) {
|
|
if (list) list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
|
|
return;
|
|
}
|
|
|
|
allItems = data.items;
|
|
search.setItems(allItems);
|
|
|
|
// Initialize Choices.js for model filter
|
|
modelSelect = createChoices('#filter-model', { placeholderValue: 'All Models' });
|
|
modelSelect.setChoices(data.filters.models.map(m => ({ value: m, label: m })), 'value', 'label', true);
|
|
document.getElementById('filter-model')?.addEventListener('change', () => {
|
|
currentFilters.models = getChoicesValues(modelSelect);
|
|
applyFiltersAndRender();
|
|
});
|
|
|
|
// Initialize Choices.js for tool filter
|
|
toolSelect = createChoices('#filter-tool', { placeholderValue: 'All Tools' });
|
|
toolSelect.setChoices(data.filters.tools.map(t => ({ value: t, label: t })), 'value', 'label', true);
|
|
document.getElementById('filter-tool')?.addEventListener('change', () => {
|
|
currentFilters.tools = getChoicesValues(toolSelect);
|
|
applyFiltersAndRender();
|
|
});
|
|
|
|
applyFiltersAndRender();
|
|
|
|
searchInput?.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
|
|
|
|
handoffsCheckbox?.addEventListener('change', () => {
|
|
currentFilters.hasHandoffs = handoffsCheckbox.checked;
|
|
applyFiltersAndRender();
|
|
});
|
|
|
|
clearFiltersBtn?.addEventListener('click', () => {
|
|
currentFilters = { models: [], tools: [], hasHandoffs: false };
|
|
modelSelect.removeActiveItems();
|
|
toolSelect.removeActiveItems();
|
|
if (handoffsCheckbox) handoffsCheckbox.checked = false;
|
|
if (searchInput) searchInput.value = '';
|
|
applyFiltersAndRender();
|
|
});
|
|
|
|
setupModal();
|
|
}
|
|
|
|
// Auto-initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', initAgentsPage);
|