Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 147 additions & 1 deletion packages/web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -546,4 +546,150 @@ dialog {
}
}

}
}

/* Provider Filter */
.provider-filter-button {
background-color: var(--color-background);
color: var(--color-text);
border: 1px solid var(--color-border);
display: flex;
align-items: center;
gap: 0.375rem;

&:hover {
background-color: var(--color-surface);
}

#provider-count {
color: var(--color-text-tertiary);
}
}

.provider-popover {
position: fixed;
z-index: 100;
background-color: var(--color-background);
border: 1px solid var(--color-border);
border-radius: 0.375rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05), 0 4px 8px rgba(0, 0, 0, 0.05),
0 8px 16px rgba(0, 0, 0, 0.07), 0 16px 32px rgba(0, 0, 0, 0.07);
width: 20rem;
max-height: 28rem;
overflow: hidden;
}

.provider-popover-content {
display: flex;
flex-direction: column;
max-height: 28rem;
}

.provider-search-container {
padding: 0.75rem;
border-bottom: 1px solid var(--color-border);
flex: 0 0 auto;
display: flex;
gap: 0.5rem;
align-items: center;

input {
flex: 1 1 auto;
font-size: 0.8125rem;
line-height: 1.1;
padding: 0.5rem 0.625rem;
border-radius: 0.25rem;
border: 1px solid var(--color-border);
height: 2rem;
background: none;
color: var(--color-text);

&:focus {
border-color: var(--color-brand);
outline: none;
}
}
}

.provider-reset-button {
flex: 0 0 auto;
cursor: pointer;
border: 1px solid var(--color-border);
background-color: var(--color-background);
color: var(--color-text);
font-size: 0.8125rem;
line-height: 1.1;
height: 2rem;
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
white-space: nowrap;

&:hover:not(:disabled) {
background-color: var(--color-surface);
}

&:disabled {
cursor: not-allowed;
opacity: 0.5;
color: var(--color-text-tertiary);
}
}

.provider-list {
flex: 1;
overflow-y: auto;
padding: 0.375rem;
overscroll-behavior: contain;

&::-webkit-scrollbar {
width: 8px;
}

&::-webkit-scrollbar-thumb {
background-color: var(--color-border);
border-radius: 4px;
}
}

.provider-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.625rem;
cursor: pointer;
border-radius: 0.25rem;
user-select: none;

&:hover {
background-color: var(--color-surface);
}
}

.provider-checkbox {
flex-shrink: 0;
width: 1rem;
height: 1rem;
cursor: pointer;
accent-color: var(--color-brand);
pointer-events: none;
}

.provider-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 1rem;
height: 1rem;

svg {
width: 100%;
height: 100%;
color: var(--color-text-secondary);
}
}

.provider-name {
flex: 1;
font-size: 0.875rem;
}
180 changes: 177 additions & 3 deletions packages/web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,21 +143,60 @@ document.querySelectorAll("th.sortable").forEach((header) => {
});
});

///////////////////////////////
// Handle Provider Filter
///////////////////////////////
const providerFilterButton = document.getElementById("provider-filter")!;
const providerPopover = document.getElementById("provider-popover")!;
const providerSearch = document.getElementById(
"provider-search"
)! as HTMLInputElement;
const providerResetButton = document.getElementById(
"provider-reset"
)! as HTMLButtonElement;
const providerCheckboxes = document.querySelectorAll(
".provider-checkbox"
) as NodeListOf<HTMLInputElement>;
const providerItems = document.querySelectorAll(
".provider-item"
) as NodeListOf<HTMLElement>;
const providerCountSpan = document.getElementById("provider-count")!;

const allProviderValues = Array.from(providerCheckboxes).map((cb) => cb.value);
let selectedProviders = new Set<string>(allProviderValues);

///////////////////
// Handle Search
///////////////////
function filterTable(value: string) {
const lowerCaseValues = value.toLowerCase().split(",").filter(str => str.trim() !== "");
const lowerCaseValues = value
.toLowerCase()
.split(",")
.filter((str) => str.trim() !== "");
const rows = document.querySelectorAll(
"table tbody tr"
) as NodeListOf<HTMLTableRowElement>;

rows.forEach((row) => {
const providerId = row.cells[2].textContent?.trim() || "";
const isProviderSelected = selectedProviders.has(providerId);

if (!isProviderSelected) {
row.style.display = "none";
return;
}

if (lowerCaseValues.length === 0) {
row.style.display = "";
return;
}

const cellTexts = Array.from(row.cells).map((cell) =>
cell.textContent!.toLowerCase()
);
const isVisible = lowerCaseValues.length === 0 ||
lowerCaseValues.some((lowerCaseValue) => cellTexts.some((text) => text.includes(lowerCaseValue)));
const isVisible = lowerCaseValues.some((lowerCaseValue) =>
cellTexts.some((text) => text.includes(lowerCaseValue))
);
row.style.display = isVisible ? "" : "none";
});

Expand All @@ -182,6 +221,124 @@ search.addEventListener("keydown", (e) => {
}
});

function updateProviderCount() {
const totalProviders = providerCheckboxes.length;
const selectedCount = selectedProviders.size;
providerCountSpan.textContent = `${selectedCount}/${totalProviders}`;
providerResetButton.disabled = selectedCount === totalProviders;
}

function filterByProviders() {
filterTable(search.value);
updateProviderCount();
updateQueryParams({
providers:
selectedProviders.size === providerCheckboxes.length
? null
: Array.from(selectedProviders).sort().join(","),
});
}

function togglePopover() {
const isVisible = providerPopover.style.display !== "none";
providerPopover.style.display = isVisible ? "none" : "block";

if (!isVisible) {
const buttonRect = providerFilterButton.getBoundingClientRect();
providerPopover.style.top = `${buttonRect.bottom + 4}px`;
providerPopover.style.left = `${
buttonRect.right - providerPopover.offsetWidth
}px`;
}
}

function closePopover() {
providerPopover.style.display = "none";

providerSearch.value = "";
filterProviderList("");
}

function filterProviderList(searchValue: string) {
const searchLower = searchValue.toLowerCase();
providerItems.forEach((item) => {
const providerName = item.getAttribute("data-provider-name") || "";
if (providerName.includes(searchLower)) {
item.style.display = "";
} else {
item.style.display = "none";
}
});
}

providerFilterButton.addEventListener("click", (e) => {
e.stopPropagation();
togglePopover();
});

document.addEventListener("click", (e) => {
if (
!providerPopover.contains(e.target as Node) &&
!providerFilterButton.contains(e.target as Node)
) {
closePopover();
}
});

providerSearch.addEventListener("input", () => {
filterProviderList(providerSearch.value);
});

providerResetButton.addEventListener("click", (e) => {
e.stopPropagation();

selectedProviders = new Set(allProviderValues);
providerCheckboxes.forEach((cb) => {
cb.checked = true;
});

providerSearch.value = "";
filterProviderList("");

filterByProviders();
});

providerItems.forEach((item) => {
item.addEventListener("click", (e) => {
e.preventDefault();

const checkbox = item.querySelector(
".provider-checkbox"
) as HTMLInputElement;
const providerId = checkbox.value;
const wasChecked = checkbox.checked;
const allSelected = selectedProviders.size === providerCheckboxes.length;

if (allSelected) {
selectedProviders.clear();
selectedProviders.add(providerId);
providerCheckboxes.forEach((cb) => {
cb.checked = cb.value === providerId;
});
} else if (wasChecked) {
if (selectedProviders.size === 1) {
selectedProviders = new Set(allProviderValues);
providerCheckboxes.forEach((cb) => {
cb.checked = true;
});
} else {
selectedProviders.delete(providerId);
checkbox.checked = false;
}
} else {
selectedProviders.add(providerId);
checkbox.checked = true;
}

filterByProviders();
});
});

///////////////////////////////////
// Handle Copy model ID function
///////////////////////////////////
Expand Down Expand Up @@ -217,6 +374,19 @@ search.addEventListener("keydown", (e) => {
function initializeFromURL() {
const params = getQueryParams();

(() => {
const providersParam = params.get("providers");
if (providersParam) {
const providerIds = providersParam.split(",");
selectedProviders = new Set(providerIds);

providerCheckboxes.forEach((cb) => {
cb.checked = selectedProviders.has(cb.value);
});
}
updateProviderCount();
})();

(() => {
const searchQuery = params.get("search");
if (!searchQuery) return;
Expand All @@ -234,6 +404,10 @@ function initializeFromURL() {
const direction = (params.get("order") as "asc" | "desc") || "asc";
sortTable(columnIndex, direction);
})();

if (selectedProviders.size < providerCheckboxes.length) {
filterByProviders();
}
}

document.addEventListener("DOMContentLoaded", initializeFromURL);
Expand Down
Loading