Skip to content

Commit efee467

Browse files
author
Krish Dholakia
authored
Merge pull request #5 from fastrepl/litellm_improve_discoverability_by_endpoint
feat: search enhancements
2 parents ad1d26c + b8c369c commit efee467

1 file changed

Lines changed: 96 additions & 30 deletions

File tree

src/Providers.svelte

Lines changed: 96 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,17 @@
1212
};
1313
};
1414
15+
type EndpointMetadata = {
16+
display_name: string;
17+
leftnav_label: string;
18+
url: string;
19+
docs_label: string;
20+
bridges_to_chat_completion?: boolean;
21+
};
22+
1523
let loading = true;
1624
let providers: ProviderEndpoint[] = [];
25+
let endpointsMetadata: { [key: string]: EndpointMetadata } = {};
1726
let searchQuery = "";
1827
let selectedEndpoint = "";
1928
let allEndpoints: string[] = [];
@@ -28,6 +37,11 @@
2837
const response = await fetch(PROVIDERS_URL);
2938
const data = await response.json();
3039
40+
// Extract endpoints metadata
41+
if (data.endpoints) {
42+
endpointsMetadata = data.endpoints;
43+
}
44+
3145
// Transform the data into our format
3246
if (data.providers) {
3347
providers = Object.entries(data.providers).map(([provider, info]: [string, any]) => ({
@@ -83,61 +97,74 @@
8397
}, 1000);
8498
8599
function formatEndpointName(endpoint: string): string {
86-
// Convert snake_case to /path format
100+
// Use leftnav_label from metadata if available, otherwise convert snake_case to /path format
101+
if (endpointsMetadata[endpoint]?.leftnav_label) {
102+
return endpointsMetadata[endpoint].leftnav_label;
103+
}
87104
const formatted = endpoint.replace(/_/g, "/");
88105
return `/${formatted}`;
89106
}
90107
91108
function formatEndpointTitle(endpoint: string): string {
92-
// Convert snake_case to Title Case
109+
// Use display_name from metadata if available, otherwise convert snake_case to Title Case
110+
if (endpointsMetadata[endpoint]?.display_name) {
111+
return endpointsMetadata[endpoint].display_name;
112+
}
93113
return endpoint
94114
.split('_')
95115
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
96116
.join(' ');
97117
}
98118
99-
// Filter endpoints based on search
119+
function getEndpointUrl(endpoint: string): string {
120+
return endpointsMetadata[endpoint]?.url || DOCS_URL;
121+
}
122+
123+
// Filter endpoints based on search query - show only endpoints supported by matching providers
100124
$: {
101125
if (searchQuery.trim() === "") {
102126
filteredEndpoints = allEndpoints;
103127
} else {
104128
const query = searchQuery.toLowerCase();
105129
106-
// Filter endpoints by name
107-
const matchingEndpoints = allEndpoints.filter((endpoint) =>
108-
endpoint.toLowerCase().includes(query) ||
109-
formatEndpointName(endpoint).toLowerCase().includes(query) ||
110-
formatEndpointTitle(endpoint).toLowerCase().includes(query)
130+
// Find providers that match the search query
131+
const matchingProviders = providers.filter(p =>
132+
p.provider.toLowerCase().includes(query) ||
133+
p.display_name.toLowerCase().includes(query)
111134
);
112135
113-
// Also include endpoints if any provider name matches
114-
const endpointsWithMatchingProviders = new Set<string>();
115-
providers.forEach(p => {
116-
const providerMatches =
117-
p.provider.toLowerCase().includes(query) ||
118-
p.display_name.toLowerCase().includes(query);
119-
120-
if (providerMatches) {
121-
Object.keys(p.endpoints).forEach(endpoint => {
122-
if (p.endpoints[endpoint] === true) {
123-
endpointsWithMatchingProviders.add(endpoint);
124-
}
125-
});
126-
}
136+
// Collect all endpoints supported by matching providers
137+
const supportedEndpoints = new Set<string>();
138+
matchingProviders.forEach(p => {
139+
Object.keys(p.endpoints).forEach(endpoint => {
140+
if (p.endpoints[endpoint] === true) {
141+
supportedEndpoints.add(endpoint);
142+
}
143+
});
127144
});
128145
129-
// Combine both sets of endpoints
130-
const combinedEndpoints = new Set([...matchingEndpoints, ...endpointsWithMatchingProviders]);
131-
filteredEndpoints = allEndpoints.filter(e => combinedEndpoints.has(e));
146+
// Filter to only show endpoints that are supported by matching providers
147+
filteredEndpoints = allEndpoints.filter(e => supportedEndpoints.has(e));
132148
}
133149
}
134150
135151
// Filter providers that support the selected endpoint AND match search query
136152
$: {
137153
if (selectedEndpoint) {
138-
let providersList = providers.filter(p => p.endpoints[selectedEndpoint] === true);
154+
// Check if this endpoint bridges to chat_completions
155+
const bridgesToChatCompletion = endpointsMetadata[selectedEndpoint]?.bridges_to_chat_completion || false;
156+
157+
let providersList = providers.filter(p => {
158+
// Include if provider directly supports this endpoint
159+
const directSupport = p.endpoints[selectedEndpoint] === true;
160+
161+
// If endpoint bridges to chat_completions, also include providers that support chat_completions
162+
const bridgedSupport = bridgesToChatCompletion && p.endpoints['chat_completions'] === true;
163+
164+
return directSupport || bridgedSupport;
165+
});
139166
140-
// Further filter by search query if present
167+
// Filter by search query - only providers
141168
if (searchQuery.trim() !== "") {
142169
const query = searchQuery.toLowerCase();
143170
providersList = providersList.filter(p =>
@@ -155,7 +182,13 @@
155182
156183
// Count how many providers support each endpoint
157184
function getProviderCount(endpoint: string): number {
158-
return providers.filter(p => p.endpoints[endpoint] === true).length;
185+
const bridgesToChatCompletion = endpointsMetadata[endpoint]?.bridges_to_chat_completion || false;
186+
187+
return providers.filter(p => {
188+
const directSupport = p.endpoints[endpoint] === true;
189+
const bridgedSupport = bridgesToChatCompletion && p.endpoints['chat_completions'] === true;
190+
return directSupport || bridgedSupport;
191+
}).length;
159192
}
160193
161194
</script>
@@ -179,7 +212,7 @@
179212
bind:value={searchQuery}
180213
type="text"
181214
autocomplete="off"
182-
placeholder="Search endpoints or providers..."
215+
placeholder="Search providers..."
183216
class="search-input"
184217
/>
185218
</div>
@@ -215,7 +248,16 @@
215248
<section class="content">
216249
{#if selectedEndpoint}
217250
<div class="content-header">
218-
<h2 class="endpoint-title">{formatEndpointTitle(selectedEndpoint)}</h2>
251+
<h2 class="endpoint-title">
252+
{formatEndpointTitle(selectedEndpoint)}
253+
<a href={getEndpointUrl(selectedEndpoint)} target="_blank" rel="noopener noreferrer" class="endpoint-docs-link" title="View documentation">
254+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
255+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
256+
<polyline points="15 3 21 3 21 9"></polyline>
257+
<line x1="10" y1="14" x2="21" y2="3"></line>
258+
</svg>
259+
</a>
260+
</h2>
219261
<p class="endpoint-subtitle">
220262
{filteredProviders.length} {filteredProviders.length === 1 ? 'provider' : 'providers'} support {formatEndpointName(selectedEndpoint)}
221263
</p>
@@ -492,6 +534,30 @@
492534
font-weight: 700;
493535
color: var(--text-color);
494536
margin: 0 0 0.5rem 0;
537+
display: flex;
538+
align-items: center;
539+
gap: 0.75rem;
540+
}
541+
542+
.endpoint-docs-link {
543+
display: inline-flex;
544+
align-items: center;
545+
justify-content: center;
546+
color: var(--link-color);
547+
text-decoration: none;
548+
transition: all 0.2s ease;
549+
opacity: 0.7;
550+
}
551+
552+
.endpoint-docs-link:hover {
553+
opacity: 1;
554+
color: var(--link-hover);
555+
transform: translateY(-1px);
556+
}
557+
558+
.endpoint-docs-link svg {
559+
width: 24px;
560+
height: 24px;
495561
}
496562
497563
.endpoint-subtitle {

0 commit comments

Comments
 (0)