1616 * under the License.
1717 */
1818
19- import React , { useMemo , useState } from 'react' ;
19+ import React , { useEffect , useMemo , useState } from 'react' ;
2020import { useNavigate } from 'react-router-dom' ;
2121import {
2222 Avatar ,
@@ -41,8 +41,14 @@ import { useProviderTemplates } from '../../../../contexts/llmProvider/providerT
4141import { useAppShell } from '../../../../contexts/AppShellContext' ;
4242import { buildOrgPath } from '../../../../utils/projectRouting' ;
4343import ErrorAlert from '../../../../Components/common/ErrorAlert' ;
44- import { truncateProviderDisplayName } from '../../../../utils/providerTemplateDisplay' ;
44+ import {
45+ familyHandle ,
46+ truncateProviderDisplayName ,
47+ } from '../../../../utils/providerTemplateDisplay' ;
4548import type { ProviderTemplate } from '../../../../utils/types' ;
49+ import * as providerTemplateApis from '../../../../apis/providerTemplateApis' ;
50+ import { PLATFORM_API_BASE_URL } from '../../../../config.env' ;
51+ import { logger } from '../../../../utils/logger' ;
4652import AnthropicLogo from '../../../../assets/brands/Anthropic.jpg' ;
4753import AWSBedrockLogo from '../../../../assets/brands/AWSBedrock.webp' ;
4854import AzureLogo from '../../../../assets/brands/Azure.png' ;
@@ -94,13 +100,82 @@ export default function ProviderTemplatesList({
94100 useProviderTemplates ( ) ;
95101
96102 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
103+ const [ allVersions , setAllVersions ] = useState < ProviderTemplate [ ] | null > (
104+ null
105+ ) ;
106+
107+ // The shared context fetches latest-only (one is_latest row per family).
108+ // This listing must consider every version's enabled flag, so fetch all
109+ // versions here. Re-fetched whenever the context refreshes (create/delete/
110+ // toggle) so the cards stay in sync.
111+ useEffect ( ( ) => {
112+ let cancelled = false ;
113+ ( async ( ) => {
114+ try {
115+ const response = await providerTemplateApis . getProviderTemplates (
116+ PLATFORM_API_BASE_URL ,
117+ false
118+ ) ;
119+ if ( ! cancelled ) setAllVersions ( response . list ?? [ ] ) ;
120+ } catch ( fetchError ) {
121+ logger . error ( 'Failed to fetch all template versions:' , fetchError ) ;
122+ if ( ! cancelled ) setAllVersions ( null ) ; // fall back to latest-only list
123+ }
124+ } ) ( ) ;
125+ return ( ) => {
126+ cancelled = true ;
127+ } ;
128+ } , [ templatesResponse ] ) ;
129+
130+ // One card per family: the highest ENABLED version; when every version in
131+ // the family is disabled, the highest version overall (rendered with the
132+ // existing dimmed "Disabled" styling). Grouping is per (ownership, family)
133+ // because creating a version from a built-in clones into the same groupId
134+ // with managedBy 'organization' — a family-only key would make the built-in
135+ // card vanish from the Built-in section.
136+ const familyCards = useMemo ( ( ) => {
137+ const source = allVersions ?? templatesResponse . list ;
138+ const parseVersion = ( value ?: string ) : [ number , number ] => {
139+ const match = / ^ v ( \d + ) \. ( \d + ) $ / . exec ( ( value ?? '' ) . trim ( ) ) ;
140+ return match ? [ Number ( match [ 1 ] ) , Number ( match [ 2 ] ) ] : [ - 1 , - 1 ] ;
141+ } ;
142+ const isHigher = ( a : ProviderTemplate , b : ProviderTemplate ) : boolean => {
143+ const [ aMajor , aMinor ] = parseVersion ( a . version ) ;
144+ const [ bMajor , bMinor ] = parseVersion ( b . version ) ;
145+ return aMajor > bMajor || ( aMajor === bMajor && aMinor > bMinor ) ;
146+ } ;
147+ const enabledPick = new Map < string , ProviderTemplate > ( ) ;
148+ const anyPick = new Map < string , ProviderTemplate > ( ) ;
149+ for ( const template of source ) {
150+ const ownership =
151+ ( template . managedBy ?? template . provider ) === 'wso2'
152+ ? 'wso2'
153+ : 'custom' ;
154+ const key = `${ ownership } :${
155+ template . groupId ?? familyHandle ( ( template . id ?? '' ) . toLowerCase ( ) )
156+ } `;
157+ const currentAny = anyPick . get ( key ) ;
158+ if ( ! currentAny || isHigher ( template , currentAny ) ) {
159+ anyPick . set ( key , template ) ;
160+ }
161+ if ( template . enabled !== false ) {
162+ const currentEnabled = enabledPick . get ( key ) ;
163+ if ( ! currentEnabled || isHigher ( template , currentEnabled ) ) {
164+ enabledPick . set ( key , template ) ;
165+ }
166+ }
167+ }
168+ return Array . from ( anyPick . entries ( ) ) . map (
169+ ( [ key , fallback ] ) => enabledPick . get ( key ) ?? fallback
170+ ) ;
171+ } , [ allVersions , templatesResponse . list ] ) ;
97172
98173 const templates = useMemo (
99174 ( ) =>
100- templatesResponse . list . filter (
175+ familyCards . filter (
101176 ( template ) => ( template . managedBy ?? template . provider ) !== 'wso2'
102177 ) ,
103- [ templatesResponse . list ]
178+ [ familyCards ]
104179 ) ;
105180
106181 const templatesBase = buildOrgPath (
@@ -132,10 +207,10 @@ export default function ProviderTemplatesList({
132207
133208 const builtInTemplates = useMemo (
134209 ( ) =>
135- templatesResponse . list . filter (
210+ familyCards . filter (
136211 ( template ) => ( template . managedBy ?? template . provider ) === 'wso2'
137212 ) ,
138- [ templatesResponse . list ]
213+ [ familyCards ]
139214 ) ;
140215 const filteredBuiltIn = useMemo ( ( ) => {
141216 const query = searchQuery . trim ( ) . toLowerCase ( ) ;
0 commit comments