@@ -7,6 +7,14 @@ <h2>Compatibility Models</h2>
77 This allows external services to use familiar model names while routing to your custom models.
88 </ p >
99
10+ <!-- OpenAI API Coverage Panel -->
11+ < div class ="coverage-panel ">
12+ < h3 style ="margin: 0 0 1rem 0; font-size: 1.1rem; "> OpenAI API Coverage</ h3 >
13+ < div id ="coverage-badges " class ="coverage-badges ">
14+ Loading coverage...
15+ </ div >
16+ </ div >
17+
1018 <!-- Actions -->
1119 < div class ="compat-actions ">
1220 < button class ="btn-primary " onclick ="openAddCompatModal() "> Add Compat Model</ button >
@@ -77,6 +85,68 @@ <h3 id="compat-modal-title">Add Compat Model</h3>
7785</ div >
7886
7987< style >
88+ /* Coverage Panel */
89+ .coverage-panel {
90+ background : # f9f9f9 ;
91+ border : 1px solid # e0e0e0 ;
92+ border-radius : 8px ;
93+ padding : 1.5rem ;
94+ margin-bottom : 1.5rem ;
95+ }
96+
97+ .coverage-badges {
98+ display : flex;
99+ flex-wrap : wrap;
100+ gap : 0.75rem ;
101+ }
102+
103+ .coverage-badge {
104+ display : flex;
105+ align-items : center;
106+ padding : 0.75rem 1rem ;
107+ border-radius : 20px ;
108+ font-size : 0.9rem ;
109+ font-weight : 500 ;
110+ transition : all 0.2s ;
111+ cursor : help;
112+ min-width : 140px ;
113+ }
114+
115+ .coverage-badge : hover {
116+ transform : translateY (-2px );
117+ box-shadow : 0 4px 8px rgba (0 , 0 , 0 , 0.15 );
118+ }
119+
120+ .coverage-badge .covered {
121+ background : # e8f5e9 ;
122+ border : 2px solid # 4caf50 ;
123+ color : # 2e7d32 ;
124+ }
125+
126+ .coverage-badge .partial {
127+ background : # fff3e0 ;
128+ border : 2px solid # ff9800 ;
129+ color : # e65100 ;
130+ }
131+
132+ .coverage-badge .missing {
133+ background : # ffebee ;
134+ border : 2px solid # f44336 ;
135+ color : # c62828 ;
136+ }
137+
138+ .coverage-icon {
139+ font-size : 1.2rem ;
140+ margin-right : 0.5rem ;
141+ }
142+
143+ .coverage-count {
144+ margin-left : auto;
145+ padding-left : 0.75rem ;
146+ font-weight : 600 ;
147+ font-size : 0.85rem ;
148+ }
149+
80150.compat-actions {
81151 margin-bottom : 1.5rem ;
82152}
@@ -250,15 +320,113 @@ <h3 id="compat-modal-title">Add Compat Model</h3>
250320let allProviders = [ ] ;
251321let providerModels = { } ;
252322
323+ // OpenAI API Categories with representative model names
324+ const OPENAI_CATEGORIES = {
325+ 'Chat' : {
326+ icon : '💬' ,
327+ models : [ 'gpt-4' , 'gpt-4-turbo' , 'gpt-4o' , 'gpt-4o-mini' , 'gpt-3.5-turbo' , 'gpt-3.5-turbo-16k' , 'gpt-4-32k' ] ,
328+ description : 'Chat completion models'
329+ } ,
330+ 'Vision' : {
331+ icon : '👁️' ,
332+ models : [ 'gpt-4-vision-preview' , 'gpt-4-turbo-vision' , 'gpt-4o-vision' ] ,
333+ description : 'Vision-enabled models'
334+ } ,
335+ 'Code' : {
336+ icon : '💻' ,
337+ models : [ 'code-davinci-002' , 'gpt-4-code' ] ,
338+ description : 'Code generation models'
339+ } ,
340+ 'Reasoning' : {
341+ icon : '🧠' ,
342+ models : [ 'o1' , 'o1-mini' , 'o1-preview' ] ,
343+ description : 'Advanced reasoning models'
344+ } ,
345+ 'Embeddings' : {
346+ icon : '🔢' ,
347+ models : [ 'text-embedding-3-small' , 'text-embedding-3-large' , 'text-embedding-ada-002' ] ,
348+ description : 'Text embedding models'
349+ } ,
350+ 'Speech-to-Text' : {
351+ icon : '🎤' ,
352+ models : [ 'whisper-1' ] ,
353+ description : 'Audio transcription'
354+ } ,
355+ 'Text-to-Speech' : {
356+ icon : '🔊' ,
357+ models : [ 'tts-1' , 'tts-1-hd' ] ,
358+ description : 'Voice generation'
359+ } ,
360+ 'Images' : {
361+ icon : '🎨' ,
362+ models : [ 'dall-e-2' , 'dall-e-3' ] ,
363+ description : 'Image generation'
364+ }
365+ } ;
366+
253367// Load compat models on page load
254368async function loadCompatModels ( ) {
255369 try {
256370 const response = await fetch ( '/api/compat/models' ) ;
257371 compatModels = await response . json ( ) ;
258372 renderCompatModels ( ) ;
373+ await renderCoverage ( ) ;
259374 } catch ( error ) {
260375 document . getElementById ( 'compat-list' ) . innerHTML = `<p style="color: red;">Error loading compat models: ${ error . message } </p>` ;
376+ document . getElementById ( 'coverage-badges' ) . innerHTML = `<p style="color: red;">Error loading coverage</p>` ;
377+ }
378+ }
379+
380+ // Render OpenAI API coverage badges
381+ async function renderCoverage ( ) {
382+ // Get model names from both compat models AND database models
383+ const compatModelNames = new Set ( compatModels . map ( m => m . model_name . toLowerCase ( ) ) ) ;
384+
385+ // Also fetch database models to include in coverage
386+ let dbModelNames = new Set ( ) ;
387+ try {
388+ const providers = await fetch ( '/api/providers' ) . then ( r => r . json ( ) ) ;
389+ for ( const provider of providers ) {
390+ if ( provider . type !== 'compat' ) {
391+ const data = await fetch ( `/api/providers/${ provider . id } /models?include_orphaned=false` ) . then ( r => r . json ( ) ) ;
392+ const models = Array . isArray ( data ) ? data : ( data . models || [ ] ) ;
393+ models . forEach ( m => dbModelNames . add ( m . model_id . toLowerCase ( ) ) ) ;
394+ }
395+ }
396+ } catch ( error ) {
397+ console . error ( 'Failed to load database models for coverage:' , error ) ;
261398 }
399+
400+ // Combine both sets
401+ const allModelNames = new Set ( [ ...compatModelNames , ...dbModelNames ] ) ;
402+
403+ const badges = Object . entries ( OPENAI_CATEGORIES ) . map ( ( [ category , info ] ) => {
404+ const coveredModels = info . models . filter ( m => allModelNames . has ( m . toLowerCase ( ) ) ) ;
405+ const coverageCount = coveredModels . length ;
406+ const totalCount = info . models . length ;
407+
408+ let statusClass = 'missing' ;
409+ let statusIcon = '❌' ;
410+ if ( coverageCount === totalCount ) {
411+ statusClass = 'covered' ;
412+ statusIcon = '✓' ;
413+ } else if ( coverageCount > 0 ) {
414+ statusClass = 'partial' ;
415+ statusIcon = '⚠️' ;
416+ }
417+
418+ const title = `${ info . description } \nCovered: ${ coveredModels . join ( ', ' ) || 'None' } \nMissing: ${ info . models . filter ( m => ! allModelNames . has ( m . toLowerCase ( ) ) ) . join ( ', ' ) || 'None' } ` ;
419+
420+ return `
421+ <div class="coverage-badge ${ statusClass } " title="${ title } ">
422+ <span class="coverage-icon">${ info . icon } </span>
423+ <span>${ category } </span>
424+ <span class="coverage-count">${ statusIcon } ${ coverageCount } /${ totalCount } </span>
425+ </div>
426+ ` ;
427+ } ) . join ( '' ) ;
428+
429+ document . getElementById ( 'coverage-badges' ) . innerHTML = badges ;
262430}
263431
264432// Load all providers for dropdown
0 commit comments