# v0.4.29 (2026-05-10)
## Features - Add Cline & Kilo Code tool cards - Tailscale TUN mode for stable Funnel TLS - Sort APIKEY providers by usage, collapse to top 20 ## Improvements - Local Material Symbols font (no Google Fonts) - Docker base: Bun → Node 22-alpine - MITM reads aliases from JSON cache (no native sqlite) - Stream stall timeout (2 min) in open-sse ## Fixes - Fal.ai key test: use stable models endpoint
This commit is contained in:
1 parent
52c38cf94c
commit
7ad538bcf2
41 files changed
+1171
-391
No files matched your search
@@ -94,10 +94,13 @@ function getConnectionErrorTag(connection) {
|
||||
return "ERR";
|
||||
}
|
||||
|
||||
const APIKEY_INITIAL_VISIBLE = 20;
|
||||
|
||||
export default function ProvidersPage() {
|
||||
const [connections, setConnections] = useState([]);
|
||||
const [providerNodes, setProviderNodes] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showAllApikey, setShowAllApikey] = useState(false);
|
||||
const [showAddCompatibleModal, setShowAddCompatibleModal] = useState(false);
|
||||
const [showAddAnthropicCompatibleModal, setShowAddAnthropicCompatibleModal] =
|
||||
useState(false);
|
||||
@@ -117,6 +120,13 @@ export default function ProvidersPage() {
|
||||
!searchQuery.trim() ||
|
||||
name.toLowerCase().includes(searchQuery.trim().toLowerCase());
|
||||
|
||||
const sortByConnections = (entries, authType) =>
|
||||
[...entries].sort(
|
||||
(a, b) =>
|
||||
getProviderStats(b[0], authType).total -
|
||||
getProviderStats(a[0], authType).total,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
@@ -259,10 +269,19 @@ export default function ProvidersPage() {
|
||||
const freeTierEntries = Object.entries(FREE_TIER_PROVIDERS).filter(
|
||||
([, info]) => matchSearch(info.name),
|
||||
);
|
||||
const apikeyEntries = Object.entries(APIKEY_PROVIDERS).filter(
|
||||
([, info]) =>
|
||||
(info.serviceKinds ?? ["llm"]).includes("llm") && matchSearch(info.name),
|
||||
const apikeyEntries = sortByConnections(
|
||||
Object.entries(APIKEY_PROVIDERS).filter(
|
||||
([, info]) =>
|
||||
(info.serviceKinds ?? ["llm"]).includes("llm") && matchSearch(info.name),
|
||||
),
|
||||
"apikey",
|
||||
);
|
||||
const isApikeySearching = !!searchQuery.trim();
|
||||
const visibleApikeyEntries =
|
||||
isApikeySearching || showAllApikey
|
||||
? apikeyEntries
|
||||
: apikeyEntries.slice(0, APIKEY_INITIAL_VISIBLE);
|
||||
const hiddenApikeyCount = apikeyEntries.length - APIKEY_INITIAL_VISIBLE;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -466,7 +485,7 @@ export default function ProvidersPage() {
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{apikeyEntries.map(([key, info]) => (
|
||||
{visibleApikeyEntries.map(([key, info]) => (
|
||||
<ApiKeyProviderCard
|
||||
key={key}
|
||||
providerId={key}
|
||||
@@ -477,6 +496,15 @@ export default function ProvidersPage() {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{!isApikeySearching && !showAllApikey && hiddenApikeyCount > 0 && (
|
||||
<button
|
||||
onClick={() => setShowAllApikey(true)}
|
||||
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-primary/40 px-3 py-2.5 text-sm font-medium text-primary transition-colors hover:border-primary hover:bg-primary/5"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[16px]">expand_more</span>
|
||||
Show all {apikeyEntries.length} providers
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user