From 639d867f658e5071954fa0e7ef167a40d6afcc4d Mon Sep 17 00:00:00 2001 From: Tharindu Dharmarathna Date: Mon, 23 Jun 2025 16:45:45 +0530 Subject: [PATCH 1/9] add change according to model family support --- .../main/webapp/site/public/locales/en.json | 5 +- .../src/main/webapp/source/dev/keys.json | 2 +- .../components/AiVendors/AddEditAiVendor.jsx | 174 +++++++++++++----- .../app/components/AiVendors/ModelEntry.jsx | 173 +++++++++++++++++ .../admin/src/main/webapp/webpack.config.js | 2 +- .../main/webapp/site/public/locales/en.json | 24 ++- .../Policies/CustomPolicies/ModelCard.tsx | 19 +- .../Policies/CustomPolicies/ModelFailover.tsx | 2 +- 8 files changed, 334 insertions(+), 67 deletions(-) create mode 100644 portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelEntry.jsx diff --git a/portals/admin/src/main/webapp/site/public/locales/en.json b/portals/admin/src/main/webapp/site/public/locales/en.json index 531226347fd..75eed3c3c46 100644 --- a/portals/admin/src/main/webapp/site/public/locales/en.json +++ b/portals/admin/src/main/webapp/site/public/locales/en.json @@ -153,7 +153,7 @@ "AdminPages.Gateways.table.header.description": "Description", "AdminPages.Gateways.table.header.displayName": "Name", "AdminPages.Gateways.table.header.gatewayType": "Gateway Type", - "AdminPages.Gateways.table.header.permission": "Visibility Permission", + "AdminPages.Gateways.table.header.permission": "Visibility", "AdminPages.Gateways.table.header.type": "Type", "AdminPages.Gateways.table.header.vhosts": "Virtual Host(s)", "AdminPages.Governance.Policy.Delete.form.delete.confirmation.message": "Are you sure you want to delete this Policy?", @@ -223,7 +223,6 @@ "AdminPages.Organizations.List.title.organizations": "Organizations", "AdminPages.Organizations.table.header.organization.description": "Description", "AdminPages.Organizations.table.header.organization.name": "Organization Name", - "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.displayName.empty": "Display Name is Empty", "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.vhost.duplicate": "VHosts are duplicated", "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.vhost.empty": "VHost is empty", "AiVendor.add.success.msg": "- AI/LLM Vendor added successfully.", @@ -245,6 +244,8 @@ "AiVendors.AddEditAiVendor.form.description.help": "Description of the AI/LLM Vendor.", "AiVendors.AddEditAiVendor.form.displayName.help": "API Version of the AI/LLM Vendor.", "AiVendors.AddEditAiVendor.form.has.errors": "One or more fields contain errors.", + "AiVendors.AddEditAiVendor.form.modelFamilySupport.help": "Model family support", + "AiVendors.AddEditAiVendor.form.modelVendorEntries.vendorName.empty": "Model Vendor name cannot be empty for all entries.", "AiVendors.AddEditAiVendor.form.name": "Name", "AiVendors.AddEditAiVendor.form.name.help": "Name of the AI/LLM Vendor.", "AiVendors.AddEditAiVendor.form.update.btn": "Update", diff --git a/portals/admin/src/main/webapp/source/dev/keys.json b/portals/admin/src/main/webapp/source/dev/keys.json index 7c325678552..f2a6b85f2e3 100644 --- a/portals/admin/src/main/webapp/source/dev/keys.json +++ b/portals/admin/src/main/webapp/source/dev/keys.json @@ -1 +1 @@ -{"clientId":"Xd75fiONwC3Ac44zARH1eDbC51Aa","clientName":"admin_webpack_dev","callBackURL":"https://localhost:8083/admin/services/auth/callback/login","clientSecret":"0lXy45lQW9ypFQ99xHAaLqez0gYa","isSaasApplication":true,"appOwner":"admin","jsonString":"{\"grant_types\":\"refresh_token authorization_code \"}","jsonAppAttribute":"{}","applicationUUID":null,"tokenType":null} \ No newline at end of file +{"clientId":"z9YvEMbi6peHfG2_hgw__A0Rsy8a","clientName":"admin_webpack_dev","callBackURL":"https://localhost:8083/admin/services/auth/callback/login","clientSecret":"EfNlzsJ7jBAoyg0CP8yq62_EMnEa","isSaasApplication":true,"appOwner":"admin","jsonString":"{\"grant_types\":\"authorization_code refresh_token\"}","jsonAppAttribute":"{}","applicationUUID":null,"tokenType":"DEFAULT"} \ No newline at end of file diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx index 8c837dd04ce..b7616c2eb2d 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx @@ -7,7 +7,7 @@ * in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an @@ -19,11 +19,14 @@ */ import React, { useReducer, useState, useEffect } from 'react'; +import { v4 as uuidv4 } from 'uuid'; // Import the v4 UUID generator import { FormattedMessage, useIntl } from 'react-intl'; import { Link as RouterLink } from 'react-router-dom'; import { MenuItem, Typography, + FormControlLabel, + Checkbox, } from '@mui/material'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -38,6 +41,8 @@ import TextField from '@mui/material/TextField'; import { MuiChipsInput } from 'mui-chips-input'; import ContentBase from 'AppComponents/AdminPages/Addons/ContentBase'; import AIAPIDefinition from './AIAPIDefinition'; +// Import the new ModelEntry component +import ModelEntry from './ModelEntry'; // Assuming ModelEntry.jsx is in the same directory const StyledSpan = styled('span')(({ theme }) => ({ color: theme.palette.error.dark })); @@ -56,8 +61,9 @@ const StyledContentBase = styled(ContentBase)({ /** * Reducer - * @param {JSON} state The second number. - * @returns {Promise} + * @param {JSON} state The current state. + * @param {Object} newValue The action object containing field and value. + * @returns {Promise} The new state. */ function reducer(state, newValue) { const { field, value } = newValue; @@ -66,7 +72,9 @@ function reducer(state, newValue) { case 'apiVersion': case 'description': case 'modelList': + case 'multipleVendorSupport': case 'apiDefinition': + case 'models': // New case for handling model vendor entries return { ...state, [field]: value }; case 'requestModel': case 'responseModel': @@ -109,7 +117,7 @@ function reducer(state, newValue) { export default function AddEditAiVendor(props) { const intl = useIntl(); const [saving, setSaving] = useState(false); - const { match: { params: { id } }, history } = props; + const { match: { params: { id: vendorId } }, history } = props; // <-- Rename id to vendorId const inputSources = ['payload', 'header', 'queryParams']; const [authSource, setAuthSource] = useState('authHeader'); const authSources = ['unsecured', 'authHeader', 'authQueryParameter']; @@ -162,13 +170,15 @@ export default function AddEditAiVendor(props) { authQueryParameter: '', authHeader: '', }, + multipleVendorSupport: false, apiDefinition: '', modelList: [], + models: [], // Initialize new state for ModelEntry }); const [state, dispatch] = useReducer(reducer, initialState); - const pageTitle = id ? `${intl.formatMessage({ + const pageTitle = vendorId ? `${intl.formatMessage({ id: 'AiVendors.AddEditAiVendor.title.edit', defaultMessage: 'AI/LLM Vendor - Edit ', })} ${state.name}` : intl.formatMessage({ @@ -178,18 +188,29 @@ export default function AddEditAiVendor(props) { useEffect(() => { const fetchData = async () => { - if (id) { - const aiVendorResult = await new API().aiVendorGet(id); + if (vendorId) { // <-- Use vendorId instead of id + const aiVendorResult = await new API().aiVendorGet(vendorId); const aiVendorBody = aiVendorResult.body; - if (aiVendorBody) { + let models = []; + let modelList = []; + if (aiVendorBody.models) { + models = JSON.parse(aiVendorBody.models); + modelList = models.find((item) => item.vendor === aiVendorBody.name); + modelList = modelList ? modelList.values : []; + models = models.map((model) => ({ + ...model, id: uuidv4(), + })); + } const newState = { name: aiVendorBody.name || '', apiVersion: aiVendorBody.apiVersion || '', description: aiVendorBody.description || '', configurations: JSON.parse(aiVendorBody.configurations), apiDefinition: aiVendorBody.apiDefinition || '', - modelList: aiVendorBody.modelList || [], + modelList, + models, + multipleVendorSupport: aiVendorBody.multipleVendorSupport || false, }; if (newState.configurations.authQueryParameter) { setAuthSource('authQueryParameter'); @@ -207,7 +228,7 @@ export default function AddEditAiVendor(props) { }; fetchData(); - }, [id]); + }, [vendorId]); const camelCaseToTitleCase = (camelCaseStr) => { return camelCaseStr @@ -280,10 +301,21 @@ export default function AddEditAiVendor(props) { || hasErrors('inputSource', meta.inputSource, validatingActive); }); + // Basic validation for modelVendorEntries: ensure vendor name is not empty + const modelVendorEntriesErrors = state.models.some((entry) => entry.vendor.trim() === ''); + if (modelVendorEntriesErrors && validatingActive) { + Alert.error(intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.form.modelVendorEntries.vendorName.empty', + defaultMessage: 'Model Vendor name cannot be empty for all entries.', + })); + return true; // Indicate error + } + return hasErrors('name', state.name, validatingActive) || hasErrors('apiVersion', state.apiVersion, validatingActive) || hasErrors('connectorType', state.configurations.connectorType, validatingActive) - || metadataErrors.some((error) => error); + || metadataErrors.some((error) => error) + || modelVendorEntriesErrors; // Include modelVendorEntries validation }; const formSaveCallback = async () => { @@ -309,14 +341,22 @@ export default function AddEditAiVendor(props) { [authSource]: state.configurations[authSource], }; } + let models; + if (state.multipleVendorSupport) { + models = state.models.map(({ id, ...rest }) => rest); + } else { + models = [{ vendor: state.name, values: state.modelList }]; + } const newState = { ...state, configurations: updatedConfigurations, - modelList: JSON.stringify(state.modelList), + // modelList: JSON.stringify(state.modelList), + // Stringify modelVendorEntries before sending to API + models: JSON.stringify(models), }; - if (id) { - await new API().updateAiVendor(id, { ...newState, apiDefinition: file }); + if (vendorId) { // <-- Use vendorId instead of id + await new API().updateAiVendor(vendorId, { ...newState, apiDefinition: file }); Alert.success(`${state.name} ${intl.formatMessage({ id: 'AiVendor.edit.success', defaultMessage: ' - AI/LLM Vendor edited successfully.', @@ -411,7 +451,7 @@ export default function AddEditAiVendor(props) { fullWidth variant='outlined' value={state.name} - disabled={!!id} + disabled={!!vendorId} onChange={(e) => dispatch({ field: 'name', value: e.target.value, @@ -432,7 +472,7 @@ export default function AddEditAiVendor(props) { fullWidth variant='outlined' value={state.apiVersion} - disabled={!!id} + disabled={!!vendorId} onChange={(e) => dispatch({ field: 'apiVersion', value: e.target.value, @@ -482,6 +522,30 @@ export default function AddEditAiVendor(props) { defaultMessage: 'Description of the AI/LLM Vendor.', })} /> + dispatch({ + field: 'multipleVendorSupport', + value: e.target.checked, + })} + name='enableModelFamilySupport' + color='primary' + disabled={!!vendorId} + /> + )} + label={( + + )} + labelPlacement='end' + /> <> @@ -785,7 +849,7 @@ export default function AddEditAiVendor(props) { fullWidth variant='outlined' value={state.configurations.connectorType} - disabled={!!id} + disabled={!!vendorId} onChange={(e) => dispatch({ field: 'connectorType', value: e.target.value, @@ -832,36 +896,49 @@ export default function AddEditAiVendor(props) { - - { - state.modelList.push(model); - }} - onDeleteChip={(model) => { - const filteredModelList = state.modelList.filter( - (modelItem) => modelItem !== model, - ); - dispatch({ field: 'modelList', value: filteredModelList }); - }} - placeholder={intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.modelList.placeholder', - defaultMessage: 'Type Model name and press Enter', - })} - data-testid='ai-vendor-llm-model-list' - helperText={( -
- {intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.modelList.help', - defaultMessage: 'Type available models and ' - + 'press enter/return to add them.', - })} -
- )} - /> -
+ {state.multipleVendorSupport ? ( + + {/* TODO ADD THE MODEL VENDOR MANAGER HERE */} + dispatch({ + field: 'models', + value: newEntries, + })} + /> + + ) : ( + + { + state.modelList.push(model); + }} + onDeleteChip={(model) => { + const filteredModelList = state.modelList.filter( + (modelItem) => modelItem !== model, + ); + dispatch({ field: 'modelList', value: filteredModelList }); + }} + placeholder={intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.modelList.placeholder', + defaultMessage: 'Type Model name and press Enter', + })} + data-testid='ai-vendor-llm-model-list' + helperText={( +
+ {intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.modelList.help', + defaultMessage: 'Type available models and ' + + 'press enter/return to add them.', + })} +
+ )} + /> +
+ )}
@@ -879,7 +956,7 @@ export default function AddEditAiVendor(props) { > {saving ? () : ( <> - {id ? ( + {vendorId ? ( - ); } diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelEntry.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelEntry.jsx new file mode 100644 index 00000000000..d7fc220891f --- /dev/null +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelEntry.jsx @@ -0,0 +1,173 @@ +import React, { useState, useEffect } from 'react'; +import { + TextField, + Chip, + Box, + IconButton, + Paper, +} from '@mui/material'; +import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; +import CloseIcon from '@mui/icons-material/Close'; + +/** + * ModelEntry Component for managing AI model vendors and their associated models. + * This component is designed to be controlled by a parent component, passing its + * data up via the `onEntriesChange` prop. + * + * @param {Object} props - The component props. + * @param {Array} props.entries - The array of model vendor entries. + * Each entry object should have an `id`, `vendor` (string), and `models` (array of strings). + * @param {Function} props.onEntriesChange - Callback function to update the parent's state + * when the entries array changes. It receives the new array of entries as an argument. + */ +function ModelEntry({ entries, onEntriesChange }) { + // currentModelInput holds the temporary text input for adding new chips for each entry. + // It's an object where keys are entry IDs and values are the current input string. + const [currentModelInput, setCurrentModelInput] = useState({}); + + // Add this useEffect to ensure at least one entry exists + useEffect(() => { + if (entries.length === 0) { + onEntriesChange([{ id: Date.now(), vendor: '', values: [] }]); + } + // Only run on mount or when entries changes + }, [entries, onEntriesChange]); + + // handleVendorChange: Updates the vendor name for a specific entry. + const handleVendorChange = (id, event) => { + const newEntries = entries.map((entry) => { + return entry.id === id ? { ...entry, vendor: event.target.value } : entry; + }); + onEntriesChange(newEntries); + }; + + // handleModelInputChange: Updates the temporary input string for adding a model chip + // for a specific entry. + const handleModelInputChange = (id, event) => { + setCurrentModelInput((prevInput) => ({ + ...prevInput, + [id]: event.target.value, + })); + }; + + // handleAddModelChip: Adds a new model chip to the values array of a specific entry + // when the Enter key is pressed. It checks if the model already exists to avoid duplicates + const handleAddModelChip = (id, event) => { + if (event.key === 'Enter' && currentModelInput[id] && currentModelInput[id].trim() !== '') { + const newModel = currentModelInput[id].trim(); + + const newEntries = entries.map((entry) => { + if (entry.id === id) { + // Check if the new model already exists in the values array + if (!entry.values.includes(newModel)) { + return { ...entry, values: [...entry.values, newModel] }; + } + return entry; + } + return entry; + }); + + onEntriesChange(newEntries); + setCurrentModelInput((prevInput) => ({ ...prevInput, [id]: '' })); + } + }; + + // handleDeleteModelChip: Removes a specific model chip from an entry. + const handleDeleteModelChip = (entryId, modelToDelete) => { + const newEntries = entries.map((entry) => { + return entry.id === entryId + ? { ...entry, values: entry.values.filter((model) => model !== modelToDelete) } + : entry; + }); + onEntriesChange(newEntries); + }; + + // handleAddEntry: Adds a new, empty model vendor entry. + const handleAddEntry = () => { + const newEntry = { id: Date.now(), vendor: '', values: [] }; // Use Date.now() for unique ID + onEntriesChange([...entries, newEntry]); + }; + + // handleRemoveEntry: Removes a specific model vendor entry. + const handleRemoveEntry = (idToRemove) => { + const newEntries = entries.filter((entry) => entry.id !== idToRemove); + onEntriesChange(newEntries); + }; + + return ( + + {/* Render existing entries */} + {entries.map((entry) => ( + + handleVendorChange(entry.id, e)} + sx={{ flexShrink: 0 }} + /> + + + {entry.values.map((model) => ( + handleDeleteModelChip(entry.id, model)} + sx={{ mr: 0.5, mb: 0.5 }} + /> + ))} + handleModelInputChange(entry.id, e)} + onKeyDown={(e) => handleAddModelChip(entry.id, e)} + InputProps={{ + disableUnderline: true, + }} + sx={{ + flexGrow: 1, + minWidth: '150px', + }} + /> + + + + + + {/* Only show remove button if there's more than one entry */} + {entries.length > 1 && ( + handleRemoveEntry(entry.id)} color='error'> + + + )} + + ))} + + ); +} + +export default ModelEntry; diff --git a/portals/admin/src/main/webapp/webpack.config.js b/portals/admin/src/main/webapp/webpack.config.js index 8578ce9c295..bd33bd7a646 100644 --- a/portals/admin/src/main/webapp/webpack.config.js +++ b/portals/admin/src/main/webapp/webpack.config.js @@ -48,7 +48,7 @@ module.exports = function (env, args) { hot: true, devMiddleware: { index: false, - writeToDisk: false, + writeToDisk: true, publicPath: '/site/public/dist/', }, client: { diff --git a/portals/publisher/src/main/webapp/site/public/locales/en.json b/portals/publisher/src/main/webapp/site/public/locales/en.json index 68676911fc1..722069a7981 100644 --- a/portals/publisher/src/main/webapp/site/public/locales/en.json +++ b/portals/publisher/src/main/webapp/site/public/locales/en.json @@ -881,6 +881,20 @@ "Apis.Details.Endpoints.GeneralConfiguration.CustomBackend.sandbox.backend": "Sandbox", "Apis.Details.Endpoints.GeneralConfiguration.EditableParameterRow.Parameter.Name": "Parameter Name", "Apis.Details.Endpoints.GeneralConfiguration.EditableParameterRow.Parameter.Value": "Parameter Value", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .connection.request.timeout.duration": "Connection Request Timeout Duration (ms)", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .connection.timeout.duration": "Connection Timeout Duration (ms)", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .proxyHost.input": "Proxy Hostname", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .proxyPassword.input": "Proxy Password", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .proxyPort.input": "Proxy Port", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .proxyProtocol.input": "Proxy Protocol", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .proxyUsername.input": "Proxy Username", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .socket.timeout.duration": "Socket Timeout Duration (ms)", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .token.endpoint.connection.configurations.endpoint.specific": "Endpoint Specific", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .token.endpoint.connection.configurations.global": "Global", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .token.endpoint.connection.configurations.none": "None", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .token.endpoint.proxy.configurations.endpoint.specific": "Endpoint Specific", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .token.endpoint.proxy.configurations.global": "Global", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity\n .token.endpoint.proxy.configurations.none": "None", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.add.new.parameter": "Add New Parameter", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.add.new.parameter.\n info": "You can add any additional payload parameters required for the endpoint below", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.basic": "Basic Auth", @@ -888,8 +902,6 @@ "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.clientId.message": "Enter Client ID", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.clientSecret.input": "Client Secret", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.clientSecret.message": "Enter Client Secret", - "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.connection.\n request.timeout.duration": "Connection Request Timeout Duration (ms)", - "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.connection.\n timeout.duration": "Connection Timeout Duration (ms)", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.digest.auth": "Digest Auth", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.grant.type.input": "Grant Type", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.input.parameter.name": "Parameter Name", @@ -908,13 +920,9 @@ "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.oauth.grant.type.password": "Resource Owner Password", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.password.input": "Password", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.password.message": "Enter Password", - "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.proxyHost.input": "Proxy Hostname", - "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.proxyPassword.input": "Proxy Password", - "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.proxyPort.input": "Proxy Port", - "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.proxyProtocol.input": "Proxy Protocol", - "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.proxyUsername.input": "Proxy Username", - "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.socket.\n timeout.duration": "Socket Timeout Duration (ms)", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.token.endpoint\n .connection.configurations.proxy.configurations": "Proxy Configurations", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.token.endpoint.\n connection.configurations": "Token Endpoint Connection Configurations", + "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.token.endpoint.\n connection.configurations.timeout.configurations": "Timeout Configurations", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.token.url.input": "Token URL", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.tokenUrl.message": "Enter Token URL", "Apis.Details.Endpoints.GeneralConfiguration.EndpointSecurity.user.name.input": "Username", diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx index 8d3ece3c1cd..447f9bf1aee 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx @@ -25,13 +25,13 @@ import InputLabel from '@mui/material/InputLabel'; import MenuItem from '@mui/material/MenuItem'; import FormControl from '@mui/material/FormControl'; import Select from '@mui/material/Select'; -import { Paper, IconButton } from '@mui/material'; +import { Paper, IconButton, Box, Typography } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import { Endpoint, ModelData } from './Types'; interface ModelCardProps { modelData: ModelData; - modelList: string[]; + modelList: any[]; endpointList: Endpoint[]; isWeightApplicable: boolean; onUpdate: (updatedModel: ModelData) => void; @@ -81,9 +81,18 @@ const ModelCard: FC = ({ name='model' onChange={(e: any) => handleChange(e)} > - {modelList.map((model) => ( - {model} - ))} + {modelList.map(({ vendor, values }) => + values.map((model: string) => ( + + + {model} + + Vendor: {vendor} + + + + )) + )} diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx index 2dc59c91cc3..556708043fa 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx @@ -98,7 +98,7 @@ const ModelFailover: FC = ({ requestTimeout: undefined, suspendDuration: undefined, }); - const [modelList, setModelList] = useState([]); + const [modelList, setModelList] = useState([]); const [productionEndpoints, setProductionEndpoints] = useState([]); const [sandboxEndpoints, setSandboxEndpoints] = useState([]); const [loading, setLoading] = useState(false); From e098230de69fdd3bd28cc8b5f6c9c9dad9f686c1 Mon Sep 17 00:00:00 2001 From: Ashera Silva Date: Fri, 27 Jun 2025 02:10:23 +0530 Subject: [PATCH 2/9] Improve model family support UI --- .../components/AiVendors/AddEditAiVendor.jsx | 200 +++++++++--------- .../app/components/AiVendors/ModelEntry.jsx | 173 --------------- .../app/components/AiVendors/ModelFamily.jsx | 197 +++++++++++++++++ 3 files changed, 299 insertions(+), 271 deletions(-) delete mode 100644 portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelEntry.jsx create mode 100644 portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx index b7616c2eb2d..96f91ae5267 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx @@ -41,8 +41,7 @@ import TextField from '@mui/material/TextField'; import { MuiChipsInput } from 'mui-chips-input'; import ContentBase from 'AppComponents/AdminPages/Addons/ContentBase'; import AIAPIDefinition from './AIAPIDefinition'; -// Import the new ModelEntry component -import ModelEntry from './ModelEntry'; // Assuming ModelEntry.jsx is in the same directory +import ModelFamily from './ModelFamily'; const StyledSpan = styled('span')(({ theme }) => ({ color: theme.palette.error.dark })); @@ -522,31 +521,110 @@ export default function AddEditAiVendor(props) { defaultMessage: 'Description of the AI/LLM Vendor.', })} /> - dispatch({ - field: 'multipleVendorSupport', - value: e.target.checked, + + + + + + + + {/* TODO: Use radio button options for "no grouping", "group by model family" */} + + + + + + + + + + + + dispatch({ + field: 'multipleVendorSupport', + value: e.target.checked, + })} + name='enableModelFamilySupport' + color='primary' + disabled={!!vendorId} + /> + )} + label={( + + )} + labelPlacement='end' + /> + + + {state.multipleVendorSupport ? ( + dispatch({ + field: 'models', + value: newModels, })} - name='enableModelFamilySupport' - color='primary' - disabled={!!vendorId} - /> - )} - label={( - + ) : ( + + { + state.modelList.push(model); + }} + onDeleteChip={(model) => { + const filteredModelList = state.modelList.filter( + (modelItem) => modelItem !== model, + ); + dispatch({ field: 'modelList', value: filteredModelList }); + }} + placeholder={intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.modelList.placeholder', + defaultMessage: 'Type Model name and press Enter', + })} + data-testid='ai-vendor-llm-model-list' + helperText={( +
+ {intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.modelList.help', + defaultMessage: 'Type available models and ' + + 'press enter/return to add them.', + })} +
+ )} + /> +
)} - labelPlacement='end' - /> - +
+
<> @@ -871,80 +949,6 @@ export default function AddEditAiVendor(props) { - - - - - - - - - - {state.multipleVendorSupport ? ( - - {/* TODO ADD THE MODEL VENDOR MANAGER HERE */} - dispatch({ - field: 'models', - value: newEntries, - })} - /> - - ) : ( - - { - state.modelList.push(model); - }} - onDeleteChip={(model) => { - const filteredModelList = state.modelList.filter( - (modelItem) => modelItem !== model, - ); - dispatch({ field: 'modelList', value: filteredModelList }); - }} - placeholder={intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.modelList.placeholder', - defaultMessage: 'Type Model name and press Enter', - })} - data-testid='ai-vendor-llm-model-list' - helperText={( -
- {intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.modelList.help', - defaultMessage: 'Type available models and ' - + 'press enter/return to add them.', - })} -
- )} - /> -
- )} -
- - - - - + + + + {models.map((model, index) => ( + + + handleVendorChange(model.id, e)} + /> + + + { + const updatedModels = models.map((m) => { + if (m.id === model.id) { + return { + ...m, + values: [...m.values, modelName], + }; + } + return m; + }); + onModelsChange(updatedModels); + }} + onDeleteChip={(modelName) => { + const updatedModels = models.map((m) => { + if (m.id === model.id) { + return { + ...m, + values: m.values.filter( + (v) => v !== modelName, + ), + }; + } + return m; + }); + onModelsChange(updatedModels); + }} + placeholder={intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.vendor.models.placeholder', + defaultMessage: 'Type Model name and press Enter', + })} + data-testid={`ai-vendor-llm-models-${index}`} + helperText={( +
+ {intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.vendor.models.help', + defaultMessage: 'Type available models and press enter/' + + 'return to add them.', + })} +
+ )} + /> +
+ + + )} + placement='right-end' + interactive + > + + + + + +
+ ))} +
+
+
+ + ); +}; + +ModelFamily.propTypes = { + models: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string, + values: PropTypes.arrayOf(PropTypes.string), + })).isRequired, + onModelsChange: PropTypes.func.isRequired, +}; + +export default ModelFamily; From ee5487c2853c46ad57de70e13df9c1ecd5c18f83 Mon Sep 17 00:00:00 2001 From: Ashera Silva Date: Mon, 30 Jun 2025 01:38:54 +0530 Subject: [PATCH 3/9] Add admin changes for model family support --- .../components/AiVendors/AddEditAiVendor.jsx | 133 +++++------- .../components/AiVendors/ListAiVendors.jsx | 67 ++++-- .../app/components/AiVendors/ModelFamily.jsx | 204 +++++++++--------- .../Policies/CustomPolicies/Types.d.ts | 5 + 4 files changed, 207 insertions(+), 202 deletions(-) diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx index 96f91ae5267..16779713635 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx @@ -21,12 +21,10 @@ import React, { useReducer, useState, useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; // Import the v4 UUID generator import { FormattedMessage, useIntl } from 'react-intl'; -import { Link as RouterLink } from 'react-router-dom'; +import { Link as RouterLink, useLocation } from 'react-router-dom'; import { MenuItem, Typography, - FormControlLabel, - Checkbox, } from '@mui/material'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -116,12 +114,15 @@ function reducer(state, newValue) { export default function AddEditAiVendor(props) { const intl = useIntl(); const [saving, setSaving] = useState(false); - const { match: { params: { id: vendorId } }, history } = props; // <-- Rename id to vendorId + const { match: { params: { id: vendorId } }, history } = props; const inputSources = ['payload', 'header', 'queryParams']; const [authSource, setAuthSource] = useState('authHeader'); const authSources = ['unsecured', 'authHeader', 'authQueryParameter']; const [validating, setValidating] = useState(false); const [file, setFile] = useState(null); + const location = useLocation(); + const isSingleProvider = location.state?.isSingleProvider ?? true; + const [initialState] = useState({ name: '', apiVersion: '', @@ -229,6 +230,16 @@ export default function AddEditAiVendor(props) { fetchData(); }, [vendorId]); + /** + * Effect to update the state when isSingleProvider changes. + */ + useEffect(() => { + dispatch({ + field: 'multipleVendorSupport', + value: !isSingleProvider, + }); + }, [isSingleProvider]); + const camelCaseToTitleCase = (camelCaseStr) => { return camelCaseStr .replace(/([A-Z])/g, ' $1') @@ -528,103 +539,65 @@ export default function AddEditAiVendor(props) { - {/* TODO: Use radio button options for "no grouping", "group by model family" */} - - - dispatch({ - field: 'multipleVendorSupport', - value: e.target.checked, - })} - name='enableModelFamilySupport' - color='primary' - disabled={!!vendorId} - /> - )} - label={( - - )} - labelPlacement='end' - /> - - - {state.multipleVendorSupport ? ( - dispatch({ - field: 'models', - value: newModels, + {isSingleProvider ? ( + + + { + const updatedList = [...state.modelList, model]; + dispatch({ field: 'modelList', value: updatedList }); + }} + onDeleteChip={(model) => { + const filteredModelList = state.modelList.filter( + (modelItem) => modelItem !== model, + ); + dispatch({ field: 'modelList', value: filteredModelList }); + }} + placeholder={intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.modelList.placeholder', + defaultMessage: 'Type Model name and press Enter', })} + data-testid='ai-vendor-llm-model-list' /> - ) : ( - - { - state.modelList.push(model); - }} - onDeleteChip={(model) => { - const filteredModelList = state.modelList.filter( - (modelItem) => modelItem !== model, - ); - dispatch({ field: 'modelList', value: filteredModelList }); - }} - placeholder={intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.modelList.placeholder', - defaultMessage: 'Type Model name and press Enter', - })} - data-testid='ai-vendor-llm-model-list' - helperText={( -
- {intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.modelList.help', - defaultMessage: 'Type available models and ' - + 'press enter/return to add them.', - })} -
- )} - /> -
- )} -
-
+
+ + ) : ( + dispatch({ + field: 'models', + value: newModels, + })} + /> + )}
<> diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ListAiVendors.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ListAiVendors.jsx index 40f194ff169..923d6aca196 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ListAiVendors.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ListAiVendors.jsx @@ -26,7 +26,7 @@ import DeleteAiVendor from 'AppComponents/AiVendors/DeleteAiVendor'; import { Link as RouterLink } from 'react-router-dom'; import Alert from '@mui/material/Alert'; import Button from '@mui/material/Button'; -import { styled } from '@mui/material'; +import { Menu, MenuItem, styled } from '@mui/material'; import ContentBase from 'AppComponents/AdminPages/Addons/ContentBase'; import Toolbar from '@mui/material/Toolbar'; import Grid from '@mui/material/Grid'; @@ -41,6 +41,7 @@ import InlineProgress from 'AppComponents/AdminPages/Addons/InlineProgress'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import CardActions from '@mui/material/CardActions'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; const styles = { searchBar: { @@ -75,6 +76,19 @@ export default function ListAiVendors() { const [aiVendorsList, setAiVendorsList] = useState(null); const [searchText, setSearchText] = useState(''); const [error, setError] = useState(null); + const [anchorEl, setAnchorEl] = useState(null); + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleMenuItemClick = () => { + setAnchorEl(null); + }; const fetchData = async () => { // Fetch data from backend when an apiCall is provided @@ -93,7 +107,6 @@ export default function ListAiVendors() { })); } } catch (e) { - // throw error; setError(e.message); } setSearchText(''); @@ -101,18 +114,44 @@ export default function ListAiVendors() { const addCreateButton = () => { return ( - + <> + + + handleMenuItemClick(false)} + component={RouterLink} + to={{ pathname: '/settings/ai-vendors/create', state: { isSingleProvider: true } }} + data-testid='add-single-provider-vendor-menu-item' + > + Add Single Provider Vendor + + handleMenuItemClick(true)} + component={RouterLink} + to={{ pathname: '/settings/ai-vendors/create', state: { isSingleProvider: false } }} + data-testid='add-multi-provider-vendor-menu-item' + > + Add Multi-Provider Vendor + + + ); }; diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx index 23840ef8e88..02d00980d32 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx @@ -23,7 +23,7 @@ import { useIntl, FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import { v4 as uuidv4 } from 'uuid'; import { - Box, Grid, Button, Table, TableBody, TableRow, TableCell, TextField, + Box, Grid, Button, TextField, IconButton, Tooltip, } from '@mui/material'; import AddCircle from '@mui/icons-material/AddCircle'; @@ -37,7 +37,7 @@ const ModelFamily = ({ models, onModelsChange }) => { * Handles adding a new model family. * Creates a new model object with a unique ID and empty vendor and values. */ - const handleAddModelFamily = () => { + const handleAddProvider = () => { const newModel = { id: uuidv4(), vendor: '', @@ -50,23 +50,23 @@ const ModelFamily = ({ models, onModelsChange }) => { * Handles deleting a model family. * @param {*} event - The event object from the click event. */ - const handleDeleteModelFamily = (event) => { + const handleProviderDelete = (event) => { const index = event.currentTarget.id; const updatedModels = models.filter((_, i) => i !== parseInt(index, 10)); onModelsChange(updatedModels); }; /** - * Handles changes to the vendor name of a model family. + * Handles changes to the provider name of a model family. * @param {*} id - The ID of the model family. * @param {*} event - The event object from the change event. */ - const handleVendorChange = (id, event) => { + const handleProviderChange = (id, event) => { const updatedModels = models.map((model) => { if (model.id === id) { return { ...model, - name: event.target.value, + vendor: event.target.value, }; } return model; @@ -76,110 +76,98 @@ const ModelFamily = ({ models, onModelsChange }) => { return ( - - - - - - - {models.map((model, index) => ( - - - + + + + + {models.map((model, index) => ( + + + handleProviderChange(model.id, e)} + /> + + + { + const updatedModels = models.map((m) => { + if (m.id === model.id) { + return { + ...m, + values: [...m.values, modelName], + }; + } + return m; + }); + onModelsChange(updatedModels); + }} + onDeleteChip={(modelName) => { + const updatedModels = models.map((m) => { + if (m.id === model.id) { + return { + ...m, + values: m.values.filter( + (v) => v !== modelName, + ), + }; + } + return m; + }); + onModelsChange(updatedModels); + }} + placeholder={intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.provider.models.placeholder', + defaultMessage: 'Type Model name and press Enter', + })} + data-testid={`ai-vendor-llm-models-${index}`} + /> + + + + )} + placement='right-end' + interactive + > + handleVendorChange(model.id, e)} - /> - - - { - const updatedModels = models.map((m) => { - if (m.id === model.id) { - return { - ...m, - values: [...m.values, modelName], - }; - } - return m; - }); - onModelsChange(updatedModels); - }} - onDeleteChip={(modelName) => { - const updatedModels = models.map((m) => { - if (m.id === model.id) { - return { - ...m, - values: m.values.filter( - (v) => v !== modelName, - ), - }; - } - return m; - }); - onModelsChange(updatedModels); - }} - placeholder={intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.vendor.models.placeholder', - defaultMessage: 'Type Model name and press Enter', - })} - data-testid={`ai-vendor-llm-models-${index}`} - helperText={( -
- {intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.vendor.models.help', - defaultMessage: 'Type available models and press enter/' - + 'return to add them.', - })} -
- )} - /> -
- - - )} - placement='right-end' - interactive + onClick={handleProviderDelete} + size='large' > - - - - - -
- ))} -
-
+ + + +
+
+ ))} + ); diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts index db0a0ef999e..9c735e3a375 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts @@ -30,3 +30,8 @@ export type ModelData = { endpointName: string; weight?: number; } + +export type ModelVendor = { + vendor: string; + values: string[]; +} From 5d2f9acdf8e127b25b97ea0200e4f7f258efa94c Mon Sep 17 00:00:00 2001 From: Ashera Silva Date: Mon, 30 Jun 2025 11:09:15 +0530 Subject: [PATCH 4/9] Add support for model family within AI policies --- .../components/AiVendors/AddEditAiVendor.jsx | 63 ++++++----- .../app/components/AiVendors/ModelFamily.jsx | 15 ++- .../Policies/CustomPolicies/ModelCard.tsx | 105 ++++++++++++------ .../Policies/CustomPolicies/ModelFailover.tsx | 17 ++- .../CustomPolicies/ModelRoundRobin.tsx | 13 ++- .../ModelWeightedRoundRobin.tsx | 13 ++- .../Policies/CustomPolicies/Types.d.ts | 1 + 7 files changed, 150 insertions(+), 77 deletions(-) diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx index 16779713635..32dba5c59e6 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx @@ -108,8 +108,9 @@ function reducer(state, newValue) { } /** - * Render a list - * @returns {JSX} Header AppBar components. + * AddEditAiVendor component + * @param {*} props props passed from parents. + * @returns {JSX} AddEditAiVendor component. */ export default function AddEditAiVendor(props) { const intl = useIntl(); @@ -121,7 +122,6 @@ export default function AddEditAiVendor(props) { const [validating, setValidating] = useState(false); const [file, setFile] = useState(null); const location = useLocation(); - const isSingleProvider = location.state?.isSingleProvider ?? true; const [initialState] = useState({ name: '', @@ -173,7 +173,7 @@ export default function AddEditAiVendor(props) { multipleVendorSupport: false, apiDefinition: '', modelList: [], - models: [], // Initialize new state for ModelEntry + models: [], }); const [state, dispatch] = useReducer(reducer, initialState); @@ -188,7 +188,7 @@ export default function AddEditAiVendor(props) { useEffect(() => { const fetchData = async () => { - if (vendorId) { // <-- Use vendorId instead of id + if (vendorId) { const aiVendorResult = await new API().aiVendorGet(vendorId); const aiVendorBody = aiVendorResult.body; if (aiVendorBody) { @@ -234,11 +234,13 @@ export default function AddEditAiVendor(props) { * Effect to update the state when isSingleProvider changes. */ useEffect(() => { - dispatch({ - field: 'multipleVendorSupport', - value: !isSingleProvider, - }); - }, [isSingleProvider]); + if (location.state?.isSingleProvider !== undefined) { + dispatch({ + field: 'multipleVendorSupport', + value: !location.state.isSingleProvider, + }); + } + }, []); const camelCaseToTitleCase = (camelCaseStr) => { return camelCaseStr @@ -299,6 +301,14 @@ export default function AddEditAiVendor(props) { }); } break; + case 'providerName': + if (fieldValue.trim() === '') { + error = intl.formatMessage({ + id: 'AiVendors.AddEditAiVendor.is.empty.error.providerName', + defaultMessage: 'Provider name is required.', + }); + } + break; default: break; } @@ -311,21 +321,14 @@ export default function AddEditAiVendor(props) { || hasErrors('inputSource', meta.inputSource, validatingActive); }); - // Basic validation for modelVendorEntries: ensure vendor name is not empty - const modelVendorEntriesErrors = state.models.some((entry) => entry.vendor.trim() === ''); - if (modelVendorEntriesErrors && validatingActive) { - Alert.error(intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.form.modelVendorEntries.vendorName.empty', - defaultMessage: 'Model Vendor name cannot be empty for all entries.', - })); - return true; // Indicate error - } + // Check for errors in model provider entries + const modelProviderEntriesErrors = state.models.some((entry) => entry.vendor.trim() === ''); return hasErrors('name', state.name, validatingActive) || hasErrors('apiVersion', state.apiVersion, validatingActive) || hasErrors('connectorType', state.configurations.connectorType, validatingActive) || metadataErrors.some((error) => error) - || modelVendorEntriesErrors; // Include modelVendorEntries validation + || modelProviderEntriesErrors; }; const formSaveCallback = async () => { @@ -564,7 +567,17 @@ export default function AddEditAiVendor(props) { - {isSingleProvider ? ( + {state.multipleVendorSupport ? ( + dispatch({ + field: 'models', + value: newModels, + })} + hasErrors={hasErrors} + validating={validating} + /> + ) : ( - ) : ( - dispatch({ - field: 'models', - value: newModels, - })} - /> )} <> diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx index 02d00980d32..5f63b54e963 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx @@ -30,7 +30,12 @@ import AddCircle from '@mui/icons-material/AddCircle'; import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import { MuiChipsInput } from 'mui-chips-input'; -const ModelFamily = ({ models, onModelsChange }) => { +const ModelFamily = ({ + models, + onModelsChange, + hasErrors, + validating, +}) => { const intl = useIntl(); /** @@ -96,6 +101,7 @@ const ModelFamily = ({ models, onModelsChange }) => { { })} margin='dense' variant='outlined' - value={model.name} + value={model.vendor} onChange={(e) => handleProviderChange(model.id, e)} + error={hasErrors('providerName', model.vendor, validating)} /> @@ -149,7 +156,7 @@ const ModelFamily = ({ models, onModelsChange }) => { )} @@ -180,6 +187,8 @@ ModelFamily.propTypes = { values: PropTypes.arrayOf(PropTypes.string), })).isRequired, onModelsChange: PropTypes.func.isRequired, + hasErrors: PropTypes.func.isRequired, + validating: PropTypes.bool.isRequired, }; export default ModelFamily; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx index 447f9bf1aee..44418f3c888 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx @@ -16,7 +16,7 @@ * under the License. */ -import React, { FC } from 'react'; +import React, { FC, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import Grid from '@mui/material/Grid'; @@ -27,11 +27,11 @@ import FormControl from '@mui/material/FormControl'; import Select from '@mui/material/Select'; import { Paper, IconButton, Box, Typography } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; -import { Endpoint, ModelData } from './Types'; +import { Endpoint, ModelData, ModelVendor } from './Types'; interface ModelCardProps { modelData: ModelData; - modelList: any[]; + modelList: ModelVendor[]; endpointList: Endpoint[]; isWeightApplicable: boolean; onUpdate: (updatedModel: ModelData) => void; @@ -46,7 +46,7 @@ const ModelCard: FC = ({ onUpdate, onDelete, }) => { - const { model, endpointId, weight } = modelData; + const { vendor, model, endpointId, weight } = modelData; const handleChange = (event: React.ChangeEvent) => { const { name, value } = event.target; @@ -66,35 +66,74 @@ const ModelCard: FC = ({ <> - - - - - - + {modelList.length === 1 ? ( + + {/* If there's only one vendor, we can directly show the model selection */} + + + + + + ) : ( + <> + + + + + + + + + + + + + + )} = ({ const [config, setConfig] = useState({ production: { targetModel: { + vendor: '', model: '', endpointId: '', endpointName: '', @@ -89,6 +90,7 @@ const ModelFailover: FC = ({ }, sandbox: { targetModel: { + vendor: '', model: '', endpointId: '', endpointName: '', @@ -98,7 +100,7 @@ const ModelFailover: FC = ({ requestTimeout: undefined, suspendDuration: undefined, }); - const [modelList, setModelList] = useState([]); + const [modelList, setModelList] = useState([]); const [productionEndpoints, setProductionEndpoints] = useState([]); const [sandboxEndpoints, setSandboxEndpoints] = useState([]); const [loading, setLoading] = useState(false); @@ -156,12 +158,16 @@ const ModelFailover: FC = ({ const modelListPromise = API.getLLMProviderModelList(JSON.parse(apiFromContext.subtypeConfiguration.configuration).llmProviderId); modelListPromise .then((response) => { - setModelList(response.body); + const vendors: ModelVendor[] = response.body.map((vendor: any) => ({ + vendor: vendor.vendor, + values: vendor.values + })); + setModelList(vendors); }).catch((error) => { console.error(error); }); } - + useEffect(() => { fetchModelList(); fetchEndpoints(); @@ -189,6 +195,7 @@ const ModelFailover: FC = ({ const handleAddFallbackModel = (env: 'production' | 'sandbox') => { const newModel: ModelData = { + vendor: '', model: '', endpointId: '', endpointName: '', @@ -251,6 +258,7 @@ const ModelFailover: FC = ({ ...prev, production: { targetModel: { + vendor: '', model: '', endpointId: '', endpointName: '', @@ -268,6 +276,7 @@ const ModelFailover: FC = ({ ...prev, sandbox: { targetModel: { + vendor: '', model: '', endpointId: '', endpointName: '', diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelRoundRobin.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelRoundRobin.tsx index 4ffbeb51a83..844774aadc4 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelRoundRobin.tsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelRoundRobin.tsx @@ -30,7 +30,7 @@ import { styled } from '@mui/material/styles'; import API from 'AppData/api'; import { Progress } from 'AppComponents/Shared'; import { useAPI } from 'AppComponents/Apis/Details/components/ApiContext'; -import { Endpoint, ModelData } from './Types'; +import { Endpoint, ModelData, ModelVendor } from './Types'; import ModelCard from './ModelCard'; import Alert from '@mui/material/Alert'; import { Link } from 'react-router-dom'; @@ -77,7 +77,7 @@ const ModelRoundRobin: FC = ({ sandbox: [], suspendDuration: undefined, }); - const [modelList, setModelList] = useState([]); + const [modelList, setModelList] = useState([]); const [productionEndpoints, setProductionEndpoints] = useState([]); const [sandboxEndpoints, setSandboxEndpoints] = useState([]); const [loading, setLoading] = useState(false); @@ -135,12 +135,16 @@ const ModelRoundRobin: FC = ({ const modelListPromise = API.getLLMProviderModelList(JSON.parse(apiFromContext.subtypeConfiguration.configuration).llmProviderId); modelListPromise .then((response) => { - setModelList(response.body); + const vendors: ModelVendor[] = response.body.map((vendor: any) => ({ + vendor: vendor.vendor, + values: vendor.values + })); + setModelList(vendors); }).catch((error) => { console.error(error); }); } - + useEffect(() => { fetchModelList(); fetchEndpoints(); @@ -162,6 +166,7 @@ const ModelRoundRobin: FC = ({ const handleAddModel = (env: 'production' | 'sandbox') => { const newModel: ModelData = { + vendor: '', model: '', endpointId: '', endpointName: '', diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelWeightedRoundRobin.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelWeightedRoundRobin.tsx index df1f2a3d378..f6bd0c91b22 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelWeightedRoundRobin.tsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelWeightedRoundRobin.tsx @@ -29,7 +29,7 @@ import AddCircle from '@mui/icons-material/AddCircle'; import API from 'AppData/api'; import { Progress } from 'AppComponents/Shared'; import { useAPI } from 'AppComponents/Apis/Details/components/ApiContext'; -import { Endpoint, ModelData } from './Types'; +import { Endpoint, ModelData, ModelVendor } from './Types'; import ModelCard from './ModelCard'; import { styled } from '@mui/material/styles'; import Alert from '@mui/material/Alert'; @@ -80,7 +80,7 @@ const ModelWeightedRoundRobin: FC = ({ sandbox: [], suspendDuration: undefined, }); - const [modelList, setModelList] = useState([]); + const [modelList, setModelList] = useState([]); const [productionEndpoints, setProductionEndpoints] = useState([]); const [sandboxEndpoints, setSandboxEndpoints] = useState([]); const [loading, setLoading] = useState(false); @@ -137,8 +137,12 @@ const ModelWeightedRoundRobin: FC = ({ const fetchModelList = () => { const modelListPromise = API.getLLMProviderModelList(JSON.parse(apiFromContext.subtypeConfiguration.configuration).llmProviderId); modelListPromise - .then((response) => { - setModelList(response.body); + .then((response) => { + const vendors: ModelVendor[] = response.body.map((vendor: any) => ({ + vendor: vendor.vendor, + values: vendor.values + })); + setModelList(vendors); }).catch((error) => { console.error(error); }); @@ -165,6 +169,7 @@ const ModelWeightedRoundRobin: FC = ({ const handleAddModel = (env: 'production' | 'sandbox') => { const newModel: ModelData = { + vendor: '', model: '', endpointId: '', endpointName: '', diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts index 9c735e3a375..22e776ef830 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts @@ -25,6 +25,7 @@ export type Endpoint = { } export type ModelData = { + vendor: string; model: string; endpointId: string; endpointName: string; From c38d55ff1459483907a62903bc4856eddfb0f55d Mon Sep 17 00:00:00 2001 From: Tharindu Dharmarathna Date: Tue, 8 Jul 2025 01:19:20 +0530 Subject: [PATCH 5/9] Include AWS bedrock related changes to ui --- .../main/webapp/site/public/locales/en.json | 13 +- .../main/webapp/site/public/locales/fr.json | 13 +- .../components/AiVendors/AddEditAiVendor.jsx | 225 +++++++--- .../main/webapp/site/public/locales/en.json | 18 +- .../Endpoints/AIEndpoints/AIEndpoints.jsx | 4 +- .../AIEndpoints/AddEditAIEndpoint.jsx | 392 ++++++++++++++---- .../Endpoints/AIEndpoints/EndpointCard.jsx | 88 ++-- .../Details/Endpoints/EndpointOverview.jsx | 2 +- .../Apis/Details/Endpoints/Endpoints.jsx | 14 +- 9 files changed, 593 insertions(+), 176 deletions(-) diff --git a/portals/admin/src/main/webapp/site/public/locales/en.json b/portals/admin/src/main/webapp/site/public/locales/en.json index 75eed3c3c46..7143f924636 100644 --- a/portals/admin/src/main/webapp/site/public/locales/en.json +++ b/portals/admin/src/main/webapp/site/public/locales/en.json @@ -1,5 +1,6 @@ { "Admin.Addons.Help.Base.title": "Help", + "Admin.AiVendor.form.llm.auth.aws.service.label": "AWS Service Name", "Admin.AiVendor.label.apiVersion": "API Version", "Admin.GatewayEnvironment.form.type": "Gateway Environment Type", "Admin.KeyManager.form.type": "Key Manager Type", @@ -231,6 +232,8 @@ "AiVendors.AddEditAiVendor.AiVendor.configurations.llm.auth": "LLM Provider Auth Configurations", "AiVendors.AddEditAiVendor.AiVendor.general.details.description.llm": "Configure to extract LLM related metadata", "AiVendors.AddEditAiVendor.AiVendor.general.details.description.llm.auth": "Configure to add LLM provider authorization", + "AiVendors.AddEditAiVendor.AiVendor.provider.configurations.description": "Define provider configurations of the AI/LLM Vendor", + "AiVendors.AddEditAiVendor.add.provider": "Add Provider", "AiVendors.AddEditAiVendor.apiDefinition": "API Definition", "AiVendors.AddEditAiVendor.apiDefinition.description": "Upload API Definition of the AI/LLM Vendor", "AiVendors.AddEditAiVendor.apiDefinition.upload": "Upload API Definition", @@ -244,8 +247,6 @@ "AiVendors.AddEditAiVendor.form.description.help": "Description of the AI/LLM Vendor.", "AiVendors.AddEditAiVendor.form.displayName.help": "API Version of the AI/LLM Vendor.", "AiVendors.AddEditAiVendor.form.has.errors": "One or more fields contain errors.", - "AiVendors.AddEditAiVendor.form.modelFamilySupport.help": "Model family support", - "AiVendors.AddEditAiVendor.form.modelVendorEntries.vendorName.empty": "Model Vendor name cannot be empty for all entries.", "AiVendors.AddEditAiVendor.form.name": "Name", "AiVendors.AddEditAiVendor.form.name.help": "Name of the AI/LLM Vendor.", "AiVendors.AddEditAiVendor.form.update.btn": "Update", @@ -256,10 +257,12 @@ "AiVendors.AddEditAiVendor.is.empty.error.attributeIdentifier": "Attribute identifier is required.", "AiVendors.AddEditAiVendor.is.empty.error.connectorType": "Connector type is required.", "AiVendors.AddEditAiVendor.is.empty.error.inputSource": "Input source is required.", - "AiVendors.AddEditAiVendor.modelList": "Model List", - "AiVendors.AddEditAiVendor.modelList.description": "AI/LLM Vendor supported model list", - "AiVendors.AddEditAiVendor.modelList.help": "Type available models and press enter/return to add them.", + "AiVendors.AddEditAiVendor.is.empty.error.providerName": "Provider name is required.", "AiVendors.AddEditAiVendor.modelList.placeholder": "Type Model name and press Enter", + "AiVendors.AddEditAiVendor.provider.configurations": "Provider Configurations", + "AiVendors.AddEditAiVendor.provider.delete": "Delete", + "AiVendors.AddEditAiVendor.provider.models.placeholder": "Type Model name and press Enter", + "AiVendors.AddEditAiVendor.provider.name": "Provider Name", "AiVendors.AddEditAiVendor.title.edit": "AI/LLM Vendor - Edit", "AiVendors.AddEditAiVendor.title.new": "AI/LLM Vendor - Create new", "AiVendors.AiAPIDefinition.browse.files.to.upload": "Browse File to Upload", diff --git a/portals/admin/src/main/webapp/site/public/locales/fr.json b/portals/admin/src/main/webapp/site/public/locales/fr.json index 531226347fd..8f244c79768 100644 --- a/portals/admin/src/main/webapp/site/public/locales/fr.json +++ b/portals/admin/src/main/webapp/site/public/locales/fr.json @@ -153,7 +153,7 @@ "AdminPages.Gateways.table.header.description": "Description", "AdminPages.Gateways.table.header.displayName": "Name", "AdminPages.Gateways.table.header.gatewayType": "Gateway Type", - "AdminPages.Gateways.table.header.permission": "Visibility Permission", + "AdminPages.Gateways.table.header.permission": "Visibility", "AdminPages.Gateways.table.header.type": "Type", "AdminPages.Gateways.table.header.vhosts": "Virtual Host(s)", "AdminPages.Governance.Policy.Delete.form.delete.confirmation.message": "Are you sure you want to delete this Policy?", @@ -223,7 +223,6 @@ "AdminPages.Organizations.List.title.organizations": "Organizations", "AdminPages.Organizations.table.header.organization.description": "Description", "AdminPages.Organizations.table.header.organization.name": "Organization Name", - "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.displayName.empty": "Display Name is Empty", "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.vhost.duplicate": "VHosts are duplicated", "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.vhost.empty": "VHost is empty", "AiVendor.add.success.msg": "- AI/LLM Vendor added successfully.", @@ -232,6 +231,8 @@ "AiVendors.AddEditAiVendor.AiVendor.configurations.llm.auth": "LLM Provider Auth Configurations", "AiVendors.AddEditAiVendor.AiVendor.general.details.description.llm": "Configure to extract LLM related metadata", "AiVendors.AddEditAiVendor.AiVendor.general.details.description.llm.auth": "Configure to add LLM provider authorization", + "AiVendors.AddEditAiVendor.AiVendor.provider.configurations.description": "Define provider configurations of the AI/LLM Vendor", + "AiVendors.AddEditAiVendor.add.provider": "Add Provider", "AiVendors.AddEditAiVendor.apiDefinition": "API Definition", "AiVendors.AddEditAiVendor.apiDefinition.description": "Upload API Definition of the AI/LLM Vendor", "AiVendors.AddEditAiVendor.apiDefinition.upload": "Upload API Definition", @@ -255,10 +256,12 @@ "AiVendors.AddEditAiVendor.is.empty.error.attributeIdentifier": "Attribute identifier is required.", "AiVendors.AddEditAiVendor.is.empty.error.connectorType": "Connector type is required.", "AiVendors.AddEditAiVendor.is.empty.error.inputSource": "Input source is required.", - "AiVendors.AddEditAiVendor.modelList": "Model List", - "AiVendors.AddEditAiVendor.modelList.description": "AI/LLM Vendor supported model list", - "AiVendors.AddEditAiVendor.modelList.help": "Type available models and press enter/return to add them.", + "AiVendors.AddEditAiVendor.is.empty.error.providerName": "Provider name is required.", "AiVendors.AddEditAiVendor.modelList.placeholder": "Type Model name and press Enter", + "AiVendors.AddEditAiVendor.provider.configurations": "Provider Configurations", + "AiVendors.AddEditAiVendor.provider.delete": "Delete", + "AiVendors.AddEditAiVendor.provider.models.placeholder": "Type Model name and press Enter", + "AiVendors.AddEditAiVendor.provider.name": "Provider Name", "AiVendors.AddEditAiVendor.title.edit": "AI/LLM Vendor - Edit", "AiVendors.AddEditAiVendor.title.new": "AI/LLM Vendor - Create new", "AiVendors.AiAPIDefinition.browse.files.to.upload": "Browse File to Upload", diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx index 32dba5c59e6..34e28e59c84 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx @@ -36,6 +36,9 @@ import Grid from '@mui/material/Grid'; import Select from '@mui/material/Select'; import { styled } from '@mui/material/styles'; import TextField from '@mui/material/TextField'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; import { MuiChipsInput } from 'mui-chips-input'; import ContentBase from 'AppComponents/AdminPages/Addons/ContentBase'; import AIAPIDefinition from './AIAPIDefinition'; @@ -116,9 +119,13 @@ export default function AddEditAiVendor(props) { const intl = useIntl(); const [saving, setSaving] = useState(false); const { match: { params: { id: vendorId } }, history } = props; - const inputSources = ['payload', 'header', 'queryParams']; - const [authSource, setAuthSource] = useState('authHeader'); - const authSources = ['unsecured', 'authHeader', 'authQueryParameter']; + const inputSources = ['payload', 'header', 'queryParams', 'pathParams']; + const [authConfig, setAuthenticationConfiguration] = useState({ + enabled: 'false', + type: 'none', + parameters: {}, + }); + const authSources = ['none', 'apikey', 'aws']; const [validating, setValidating] = useState(false); const [file, setFile] = useState(null); const location = useLocation(); @@ -202,6 +209,37 @@ export default function AddEditAiVendor(props) { ...model, id: uuidv4(), })); } + if (aiVendorBody.configurations) { + const config = JSON.parse(aiVendorBody.configurations); + if (config.authenticationConfiguration) { + setAuthenticationConfiguration({ + enabled: config.authenticationConfiguration.enabled, + type: config.authenticationConfiguration.type, + parameters: config.authenticationConfiguration.parameters ?? {}, + }); + } else { + const hasAuthHeader = config.authHeader && config.authHeader.trim() !== ''; + const hasAuthQueryParameter = config.authQueryParameter + && config.authQueryParameter.trim() !== ''; + if (hasAuthHeader || hasAuthQueryParameter) { + setAuthenticationConfiguration({ + enabled: 'true', + type: 'apikey', + parameters: { + headersEnabled: !!hasAuthHeader, + headerName: config.authHeader || '', + queryParameterEnabled: !!hasAuthQueryParameter, + queryParameterName: config.authQueryParameter || '', + }, + }); + } else { + setAuthenticationConfiguration({ + enabled: 'false', + type: 'none', + }); + } + } + } const newState = { name: aiVendorBody.name || '', apiVersion: aiVendorBody.apiVersion || '', @@ -212,14 +250,6 @@ export default function AddEditAiVendor(props) { models, multipleVendorSupport: aiVendorBody.multipleVendorSupport || false, }; - if (newState.configurations.authQueryParameter) { - setAuthSource('authQueryParameter'); - } else if (newState.configurations.authHeader) { - setAuthSource('authHeader'); - } else { - setAuthSource('unsecured'); - } - dispatch({ field: 'all', value: newState }); setFile(new Blob([aiVendorBody.apiDefinition || ''], { type: 'text/plain;charset=utf-8' })); @@ -348,12 +378,10 @@ export default function AddEditAiVendor(props) { metadata: state.configurations.metadata, connectorType: state.configurations.connectorType, }; - if (state.configurations[authSource]) { - updatedConfigurations = { - ...updatedConfigurations, - [authSource]: state.configurations[authSource], - }; - } + updatedConfigurations = { + ...updatedConfigurations, + authenticationConfiguration: authConfig, + }; let models; if (state.multipleVendorSupport) { models = state.models.map(({ id, ...rest }) => rest); @@ -397,19 +425,64 @@ export default function AddEditAiVendor(props) { }; const clearAuthHeader = () => { - dispatch({ - field: 'authHeader', - value: '', - }); + setAuthenticationConfiguration((prev) => ({ + ...prev, + parameters: {}, + })); }; - const clearAuthQueryParameter = () => { - dispatch({ - field: 'authQueryParameter', - value: '', - }); - }; + function setAuthType(type) { + if (type === 'none') { + authConfig.enabled = false; + authConfig.type = 'none'; + } else { + authConfig.enabled = true; + authConfig.type = type; + } + } + + // Add this helper function above your component + function getApiKeyLocation(authConfiguration) { + if (authConfiguration?.parameters?.headerEnabled) { + return 'header'; + } + if (authConfiguration?.parameters?.queryParameterEnabled) { + return 'queryParameter'; + } + return ''; + } + // Add this function inside your component, above the return statement + const handleApiKeyLocationChange = (e) => { + const { value } = e.target; + setAuthenticationConfiguration((prev) => ({ + ...prev, + parameters: { + ...prev.parameters, + headerEnabled: value === 'header', + queryParameterEnabled: value === 'queryParameter', + }, + })); + }; + /** + * Handles changes to the API key identifier input. + * Updates headerName or queryParameterName in authConfig.parameters. + */ + const handleApiKeyIdentifierChange = (e) => { + const { value } = e.target; + setAuthenticationConfiguration((prev) => ({ + ...prev, + parameters: { + ...prev.parameters, + ...(prev.parameters.headerEnabled + ? { headerName: value } + : {}), + ...(prev.parameters.queryParameterEnabled + ? { queryParameterName: value } + : {}), + }, + })); + }; return ( - - * + {metadata.required && *} )} fullWidth @@ -808,11 +880,10 @@ export default function AddEditAiVendor(props) { variant='outlined' id='Admin.AiVendor.form.llm.auth.select' name='authSource' - value={authSource} + value={authConfig.type} onChange={(e) => { + setAuthType(e.target.value); clearAuthHeader(); - clearAuthQueryParameter(); - setAuthSource(e.target.value); }} data-testid='ai-vendor-llm-auth-select' > @@ -823,33 +894,89 @@ export default function AddEditAiVendor(props) { ))} - {authSource !== 'unsecured' && ( + {(authConfig.type === 'apikey') && ( + <> + + + + } + label='Header' + /> + } + label='Query Parameter' + /> + + + + + + + )} + fullWidth + variant='outlined' + value={ + (() => { + if (authConfig?.parameters?.headerEnabled) { + return authConfig.parameters.headerName ?? ''; + } + if (authConfig?.parameters?.queryParameterEnabled) { + return authConfig.parameters.queryParameterName ?? ''; + } + return ''; + })() + } + onChange={handleApiKeyIdentifierChange} + /> + + )} + {(authConfig.type === 'aws') && ( )} fullWidth variant='outlined' - value={authSource === 'authHeader' - ? state.configurations.authHeader ?? '' - : state.configurations.authQueryParameter ?? ''} - onChange={(e) => dispatch({ - field: authSource, - value: e.target.value, - })} + value={authConfig.parameters?.awsServiceName || ''} + onChange={(e) => setAuthenticationConfiguration((prev) => ({ + ...prev, + parameters: { + ...prev.parameters, + awsServiceName: e.target.value, + }, + }))} + required /> )} diff --git a/portals/publisher/src/main/webapp/site/public/locales/en.json b/portals/publisher/src/main/webapp/site/public/locales/en.json index 722069a7981..755a4516b7c 100644 --- a/portals/publisher/src/main/webapp/site/public/locales/en.json +++ b/portals/publisher/src/main/webapp/site/public/locales/en.json @@ -759,10 +759,6 @@ "Apis.Details.Endpoints.AIEndpoints.AIEndpoints.sandbox.endpoints.label": "Sandbox Endpoints", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.add.success": "Endpoint Added Successfully", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.advanced.configurations": "Advanced Configurations", - "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.api.key.header": "Authorization Header", - "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.api.key.placeholder": "Enter API Key", - "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.api.key.query.param": "Authorization Query Param", - "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.api.key.value": "API Key", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.cancel": "Cancel", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.create.btn": "Create", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.create.new.endpoint": "Add New Endpoint", @@ -770,8 +766,11 @@ "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.edit.success": "Endpoint Updated Successfully", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.endpoint.configuration": "Endpoint configurations", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.duplicate.name": "This endpoint name already exists", + "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.accessKey": "AWS AccessKey cannot be empty", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.apiKey": "API Key cannot be empty", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.name": "Endpoint name cannot be empty", + "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.region": "AWS region cannot be empty", + "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.secretKey": "AWS SecretKey cannot be empty", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.url": "Endpoint URL cannot be empty", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.invalid.url": "Please enter a valid endpoint URL", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.loading": "Error loading endpoint", @@ -784,6 +783,16 @@ "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.select.endpoint.type": "Select Endpoint Type", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.test.endpoint": "Check endpoint status", "Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.update.btn": "Update", + "Apis.Details.Endpoints.AIEndpoints.Edit.acessKey": "AWS Access Key", + "Apis.Details.Endpoints.AIEndpoints.Edit.acessKey.placeholder": "Enter AWS Access Key", + "Apis.Details.Endpoints.AIEndpoints.Edit.api.key.header": "Authorization Header", + "Apis.Details.Endpoints.AIEndpoints.Edit.api.key.placeholder": "Enter API Key", + "Apis.Details.Endpoints.AIEndpoints.Edit.api.key.query.param": "Authorization Query Param", + "Apis.Details.Endpoints.AIEndpoints.Edit.api.key.value": "API Key", + "Apis.Details.Endpoints.AIEndpoints.Edit.aws.region": "AWS Region", + "Apis.Details.Endpoints.AIEndpoints.Edit.aws.region.placeholder": "Enter AWS Region", + "Apis.Details.Endpoints.AIEndpoints.Edit.secretKey": "AWS Secret Key", + "Apis.Details.Endpoints.AIEndpoints.Edit.secretKey.placeholder": "Enter AWS Secret Key", "Apis.Details.Endpoints.API.Definition.fetch.error": "Error occurred while fetching API definition", "Apis.Details.Endpoints.AdvancedConfig.AdvanceEndpointConfig.action": "Action", "Apis.Details.Endpoints.AdvancedConfig.AdvanceEndpointConfig.cancel.button": "Close", @@ -1338,6 +1347,7 @@ "Apis.Details.Policies.CustomPolicies.ModelRoundRobin.no.sandbox.endpoints": "No sandbox endpoints available. Please {configureLink} first.", "Apis.Details.Policies.CustomPolicies.ModelRoundRobin.select.endpoint": "Endpoint", "Apis.Details.Policies.CustomPolicies.ModelRoundRobin.select.model": "Model", + "Apis.Details.Policies.CustomPolicies.ModelRoundRobin.select.provider": "Provider", "Apis.Details.Policies.CustomPolicies.ModelWeightedRoundRobin.accordion.production": "Production", "Apis.Details.Policies.CustomPolicies.ModelWeightedRoundRobin.accordion.sandbox": "Sandbox", "Apis.Details.Policies.CustomPolicies.ModelWeightedRoundRobin.no.models": "No models available. Please configure models for the LLM provider.", diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AIEndpoints.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AIEndpoints.jsx index 17b290d7209..3b62e1d9f15 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AIEndpoints.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AIEndpoints.jsx @@ -39,6 +39,7 @@ const StyledPaper = styled(Paper)(({ theme }) => ({ const AIEndpoints = ({ apiObject, + endpointConfiguration, }) => { const [productionEndpoints, setProductionEndpoints] = useState([]); const [sandboxEndpoints, setSandboxEndpoints] = useState([]); @@ -106,7 +107,6 @@ const AIEndpoints = ({ useEffect(() => { fetchEndpoints(); }, []); - const handleDelete = (endpoint) => { // Check if endpoint is primary if (endpoint.id === apiObject.primaryProductionEndpointId || @@ -250,6 +250,7 @@ const AIEndpoints = ({ onDelete={handleDelete} onSetPrimary={handleSetAsPrimary} onRemovePrimary={handleRemovePrimary} + endpointConfiguration={endpointConfiguration} /> )) ) : ( @@ -281,6 +282,7 @@ const AIEndpoints = ({ onDelete={handleDelete} onSetPrimary={handleSetAsPrimary} onRemovePrimary={handleRemovePrimary} + endpointConfiguration={endpointConfiguration} /> )) ) : ( diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AddEditAIEndpoint.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AddEditAIEndpoint.jsx index 29c5b220dde..e8b7b19d249 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AddEditAIEndpoint.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AddEditAIEndpoint.jsx @@ -250,9 +250,11 @@ const AddEditAIEndpoint = ({ endpoint_security: {}, } }); - const [apiKeyParamConfig, setApiKeyParamConfig] = useState({ - authHeader: null, - authQueryParameter: null + const [endpointConfiguration, setEndpointConfiguration] = useState({ + authenticationConfiguration: { + "authenticationConfiguration": + { "enabled": false, "type": null, parameters: {} } + } }); const [isEndpointSaving, setEndpointSaving] = useState(false); const iff = (condition, then, otherwise) => (condition ? then : otherwise); @@ -262,6 +264,9 @@ const AddEditAIEndpoint = ({ const { updateAPI } = useContext(APIContext); const [apiKeyValue, setApiKeyValue] = useState(null); + const [accessKey, setAccessKey] = useState(null); + const [secretKey, setSecretKey] = useState(null); + const [region, setRegion] = useState(null); const [showApiKey, setShowApiKey] = useState(false); const subtypeConfig = apiObject.subtypeConfiguration && JSON.parse(apiObject.subtypeConfiguration.configuration); @@ -291,7 +296,6 @@ const AddEditAIEndpoint = ({ return isDuplicate; }; - useEffect(() => { if (endpointId) { setIsEditing(true); @@ -327,10 +331,20 @@ const AddEditAIEndpoint = ({ // Set API key value const envType = isProd ? 'production' : 'sandbox'; - const apiKeyConfig = endpointConfig.endpoint_security?.[envType]; - if (apiKeyConfig?.apiKeyValue === '') { + const securityConfig = endpointConfig.endpoint_security?.[envType]; + if (securityConfig?.apiKeyValue === '') { setApiKeyValue('********'); } + // Set AWS related values + if (securityConfig?.accessKey) { + setAccessKey(securityConfig?.accessKey); + } + if (securityConfig?.secretKey === '') { + setSecretKey('********'); + } + if (securityConfig?.region) { + setRegion(securityConfig?.region); + } } else { // Load custom endpoint data from API API.getApiEndpoint(apiObject.id, endpointId) @@ -347,10 +361,20 @@ const AddEditAIEndpoint = ({ // Set API key value const envType = body.deploymentStage === "PRODUCTION" ? 'production' : 'sandbox'; - const apiKeyConfig = body.endpointConfig.endpoint_security?.[envType]; - if (apiKeyConfig?.apiKeyValue === '') { + const securityConfig = body.endpointConfig.endpoint_security?.[envType]; + if (securityConfig?.apiKeyValue === '') { setApiKeyValue('********'); } + // Set AWS related values + if (securityConfig?.accessKey) { + setAccessKey(securityConfig?.accessKey); + } + if (securityConfig?.secretKey === '') { + setSecretKey('********'); + } + if (securityConfig?.region) { + setRegion(securityConfig?.region); + } }) .catch((error) => { console.error('Error loading endpoint:', error); @@ -419,11 +443,24 @@ const AddEditAIEndpoint = ({ } const isProduction = state.deploymentStage === CONSTS.DEPLOYMENT_STAGE.production; + let apiKeyIdentifier; + let apiKeyIdentifierType; + if (endpointConfiguration.authenticationConfiguration.enabled + && endpointConfiguration.authenticationConfiguration.type === "apikey") { + if (endpointConfiguration.authenticationConfiguration.parameters.headerEnabled) { + apiKeyIdentifier = endpointConfiguration.authenticationConfiguration.parameters.headerName; + apiKeyIdentifierType = "HEADER"; + } + if (endpointConfiguration.authenticationConfiguration.parameters.queryParameterEnabled) { + apiKeyIdentifier = endpointConfiguration.authenticationConfiguration.parameters.queryParameterName; + apiKeyIdentifierType = "QUERY_PARAMETER"; + } + } saveEndpointSecurityConfig({ ...CONSTS.DEFAULT_ENDPOINT_SECURITY, - type: 'apikey', - apiKeyIdentifier: apiKeyParamConfig.authHeader || apiKeyParamConfig.authQueryParam, - apiKeyIdentifierType: apiKeyParamConfig.authHeader ? 'HEADER' : 'QUERY_PARAMETER', + type: endpointConfiguration.authenticationConfiguration.type, + apiKeyIdentifier, + apiKeyIdentifierType, apiKeyValue: updatedApiKeyValue, enabled: true, }, isProduction ? 'production' : 'sandbox'); @@ -445,7 +482,7 @@ const AddEditAIEndpoint = ({ .then((response) => { if (response.body) { const config = response.body; - setApiKeyParamConfig(config); + setEndpointConfiguration(config); } }); } @@ -539,11 +576,55 @@ const AddEditAIEndpoint = ({ } return false; case 'apiKey': - if (!fieldValue) { - return intl.formatMessage({ - id: 'Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.apiKey', - defaultMessage: 'API Key cannot be empty', - }); + if ( + endpointConfiguration.authenticationConfiguration?.enabled === true && + endpointConfiguration.authenticationConfiguration?.type === "apikey" + ) { + if (!fieldValue) { + return intl.formatMessage({ + id: 'Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.apiKey', + defaultMessage: 'API Key cannot be empty', + }); + } + } + return false; + case 'accessKey': + if ( + endpointConfiguration.authenticationConfiguration?.enabled === true && + endpointConfiguration.authenticationConfiguration?.type === "aws" + ) { + if (!fieldValue) { + return intl.formatMessage({ + id: 'Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.accessKey', + defaultMessage: 'AWS AccessKey cannot be empty', + }); + } + } + return false; + case 'secretKey': + if ( + endpointConfiguration.authenticationConfiguration?.enabled === true && + endpointConfiguration.authenticationConfiguration?.type === "aws" + ) { + if (!fieldValue) { + return intl.formatMessage({ + id: 'Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.secretKey', + defaultMessage: 'AWS SecretKey cannot be empty', + }); + } + } + return false; + case 'region': + if ( + endpointConfiguration.authenticationConfiguration?.enabled === true && + endpointConfiguration.authenticationConfiguration?.type === "aws" + ) { + if (!fieldValue) { + return intl.formatMessage({ + id: 'Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.error.empty.region', + defaultMessage: 'AWS region cannot be empty', + }); + } } return false; default: @@ -554,7 +635,10 @@ const AddEditAIEndpoint = ({ const formHasErrors = (validateActive = false) => { if (hasErrors('name', state.name, validateActive) || hasErrors('url', endpointUrl, validateActive) || - hasErrors('apiKey', apiKeyValue, validateActive)) { + hasErrors('apiKey', apiKeyValue, validateActive) || + hasErrors('secretKey', secretKey, validateActive) || + hasErrors('region', region, validateActive) || + hasErrors('accessKey', accessKey, validateActive)) { return true; } return false; @@ -647,7 +731,40 @@ const AddEditAIEndpoint = ({ }); } }; + const handleAWSCredentials = (e, field) => { + if (field === "accessKey") { + setAccessKey(e.target.value) + } else if (field === "secretKey") { + let seretKeyValue = e.target.value; + if (seretKeyValue === '********') { + seretKeyValue = ''; + } else if (seretKeyValue === '') { + seretKeyValue = null; + } else if (seretKeyValue.includes('********')) { + seretKeyValue = seretKeyValue.replace('********', ''); + } + setSecretKey(seretKeyValue) + } else if (field === "region") { + setRegion(e.target.value) + } + } + const handleOnBlurOnAWSCredentials = () => { + const isProduction = state.deploymentStage === CONSTS.DEPLOYMENT_STAGE.production; + // Skip if apiKeyValue is null, empty, or ******** + if (!secretKey || secretKey === '********') { + return; + } + saveEndpointSecurityConfig({ + ...CONSTS.DEFAULT_ENDPOINT_SECURITY, + type: endpointConfiguration.authenticationConfiguration.type, + service: endpointConfiguration.authenticationConfiguration.parameters.awsServiceName, + accessKey, + secretKey, + region, + enabled: true, + }, isProduction ? 'production' : 'sandbox'); + } const formSave = () => { setValidating(true); if (formHasErrors(true)) { @@ -657,7 +774,7 @@ const AddEditAIEndpoint = ({ })); return false; } - + setEndpointSaving(true); let savePromise; @@ -727,6 +844,28 @@ const AddEditAIEndpoint = ({ return true; }; + // Add this before the return statement, after all hooks and state + const apiKeyParamConfig = { + authHeader: + endpointConfiguration.authenticationConfiguration?.enabled && + endpointConfiguration.authenticationConfiguration?.type === "apikey" && + endpointConfiguration.authenticationConfiguration?.parameters?.headerEnabled + ? endpointConfiguration.authenticationConfiguration.parameters.headerName + : null, + authQueryParam: + endpointConfiguration.authenticationConfiguration?.enabled && + endpointConfiguration.authenticationConfiguration?.type === "apikey" && + endpointConfiguration.authenticationConfiguration?.parameters?.queryParameterEnabled + ? endpointConfiguration.authenticationConfiguration.parameters.queryParameterName + : null, + }; + + const IS_APIKEY_AUTH_ENABLED = (config) => + config.authenticationConfiguration?.enabled === true && + config.authenticationConfiguration?.type === "apikey"; + const IS_AWS_SIGV4_AUTH_ENABLED = (config) => + config.authenticationConfiguration?.enabled === true && + config.authenticationConfiguration?.type === "aws"; return ( @@ -904,66 +1043,165 @@ const AddEditAIEndpoint = ({
{/* AI Endpoint Auth Fields */} - - + + + ) : ( + + )} + fullWidth + id='api-key-id' + value={apiKeyParamConfig.authHeader || + apiKeyParamConfig.authQueryParam} + placeholder={apiKeyParamConfig.authHeader || + apiKeyParamConfig.authQueryParam} + InputLabelProps={{ + shrink: true, + }} + helperText=' ' + required /> - ) : ( - + + + } + id='api-key-value' + value={apiKeyValue} + placeholder={intl.formatMessage({ + id: 'Apis.Details.Endpoints.AIEndpoints.Edit.api.key.placeholder', + defaultMessage: 'Enter API Key', + })} + fullWidth + onChange={handleApiKeyChange} + onBlur={handleApiKeyBlur} + error={hasErrors('apiKey', apiKeyValue, validating)} + helperText={hasErrors('apiKey', apiKeyValue, validating)} + required + type={showApiKey ? 'text' : 'password'} + InputLabelProps={{ + shrink: Boolean(apiKeyValue), + }} + InputProps={{ + endAdornment: ( + + + {showApiKey ? : } + + + ), + }} /> - )} - fullWidth - id='api-key-id' - value={apiKeyParamConfig.authHeader || apiKeyParamConfig.authQueryParam} - placeholder={apiKeyParamConfig.authHeader || apiKeyParamConfig.authQueryParam} - InputLabelProps={{ - shrink: true, - }} - helperText=' ' - required - /> - - - } - id='api-key-value' - value={apiKeyValue} - placeholder={intl.formatMessage({ - id: 'Apis.Details.Endpoints.AIEndpoints.AddEditAIEndpoint.api.key.placeholder', - defaultMessage: 'Enter API Key', - })} - fullWidth - onChange={handleApiKeyChange} - onBlur={handleApiKeyBlur} - error={hasErrors('apiKey', apiKeyValue, validating)} - helperText={hasErrors('apiKey', apiKeyValue, validating)} - required - type={showApiKey ? 'text' : 'password'} - InputLabelProps={{ - shrink: Boolean(apiKeyValue), - }} - InputProps={{ - endAdornment: ( - - - {showApiKey ? : } - - - ), - }} - /> - + + + )} + + {/* AWS SigV4 Auth Fields */} + {IS_AWS_SIGV4_AUTH_ENABLED(endpointConfiguration) && ( + <> + + + } + id='aws-access-key' + value={accessKey} + placeholder={intl.formatMessage({ + id: 'Apis.Details.Endpoints.AIEndpoints.Edit.acessKey.placeholder', + defaultMessage: 'Enter AWS Access Key', + })} + fullWidth + onChange={(e) => handleAWSCredentials(e, 'accessKey')} + onBlur={handleOnBlurOnAWSCredentials} + required + InputLabelProps={{ + shrink: true, + }} + /> + + + + } + id='aws-secret-key' + type='password' + value={secretKey} + placeholder={intl.formatMessage({ + id: 'Apis.Details.Endpoints.AIEndpoints.Edit.secretKey.placeholder', + defaultMessage: 'Enter AWS Secret Key', + })} + fullWidth + onChange={(e) => handleAWSCredentials(e, 'secretKey')} + onBlur={handleOnBlurOnAWSCredentials} + required + InputLabelProps={{ + shrink: true, + }} + /> + + + + } + id='aws-region' + value={region} + placeholder={intl.formatMessage({ + id: 'Apis.Details.Endpoints.AIEndpoints.Edit.aws.region.placeholder', + defaultMessage: 'Enter AWS Region', + })} + fullWidth + onChange={(e) => handleAWSCredentials(e, 'region')} + onBlur={handleOnBlurOnAWSCredentials} + required + InputLabelProps={{ + shrink: true, + }} + /> + + + )} + {/* Action Buttons */} diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/EndpointCard.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/EndpointCard.jsx index 677b1b1a8ef..46b27ca7663 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/EndpointCard.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/EndpointCard.jsx @@ -99,6 +99,7 @@ const EndpointCard = ({ onDelete, onSetPrimary, onRemovePrimary, + endpointConfiguration, }) => { const history = useHistory(); @@ -108,35 +109,68 @@ const EndpointCard = ({ endpoint.deploymentStage === 'PRODUCTION' ? endpoint.endpointConfig?.production_endpoints?.url : endpoint.endpointConfig?.sandbox_endpoints?.url; - const renderEndpointSecurityWarning = () => { - const endpointSecurity = - endpoint.deploymentStage === 'PRODUCTION' - ? endpoint.endpointConfig?.endpoint_security?.production - : endpoint.endpointConfig?.endpoint_security?.sandbox; + if (endpointConfiguration.authenticationConfiguration.enabled) { + const endpointSecurity = + endpoint.deploymentStage === 'PRODUCTION' + ? endpoint.endpointConfig?.endpoint_security?.production + : endpoint.endpointConfig?.endpoint_security?.sandbox; - if (!endpointSecurity) { - return ( - - } - label='API Key Required' - size='small' - variant='outlined' - className={classes.warningChip} - onClick={() => { - const urlPrefix = - apiObject.apiType === API.CONSTS.APIProduct - ? 'api-products' - : 'apis'; - history.push( - `/${urlPrefix}/${apiObject.id}/endpoints/${endpoint.id}`, - ); - }} - sx={{ my: '4px' }} - /> - - ); + // API Key warning + if ( + !endpointSecurity && + endpointConfiguration.authenticationConfiguration.type === "apikey" + ) { + return ( + + } + label='API Key Required' + size='small' + variant='outlined' + className={classes.warningChip} + onClick={() => { + const urlPrefix = + apiObject.apiType === API.CONSTS.APIProduct + ? 'api-products' + : 'apis'; + history.push( + `/${urlPrefix}/${apiObject.id}/endpoints/${endpoint.id}`, + ); + }} + sx={{ my: '4px' }} + /> + + ); + } + + // AWS SigV4 warning + if ( + !endpointSecurity && + endpointConfiguration.authenticationConfiguration.type === "aws" + ) { + return ( + + } + label='AWS Credentials Required' + size='small' + variant='outlined' + className={classes.warningChip} + onClick={() => { + const urlPrefix = + apiObject.apiType === API.CONSTS.APIProduct + ? 'api-products' + : 'apis'; + history.push( + `/${urlPrefix}/${apiObject.id}/endpoints/${endpoint.id}`, + ); + }} + sx={{ my: '4px' }} + /> + + ); + } } return null; }; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/EndpointOverview.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/EndpointOverview.jsx index 1f386104e40..a8b287fb10a 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/EndpointOverview.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/EndpointOverview.jsx @@ -190,7 +190,7 @@ function EndpointOverview(props) { setIsValidSequenceBackend, isCustomBackendSelected, setIsCustomBackendSelected, - apiKeyParamConfig, + endpointConfiguration, componentValidator, endpointSecurityTypes, } = props; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/Endpoints.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/Endpoints.jsx index e24aad7d11d..746bb4a93a5 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/Endpoints.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/Endpoints.jsx @@ -129,9 +129,8 @@ function Endpoints(props) { const [productionBackendList, setProductionBackendList] = useState([]); const [isValidSequenceBackend, setIsValidSequenceBackend] = useState(false); const [isCustomBackendSelected, setIsCustomBackendSelected] = useState(false); - const [apiKeyParamConfig, setApiKeyParamConfig] = useState({ - authHeader: null, - authQueryParameter: null + const [endpointConfiguration, setEndpointConfiguration] = useState({ + authenticationConfiguration: {"authenticationConfiguration":{"enabled":false,"type":null,parameters:{}}} }); const [componentValidator, setComponentValidator] = useState([]); const [endpointSecurityTypes, setEndpointSecurityTypes] = useState([]); @@ -142,7 +141,7 @@ function Endpoints(props) { .then((response) => { if (response.body) { const config = response.body; - setApiKeyParamConfig(config); + setEndpointConfiguration(config); } }); } @@ -577,7 +576,8 @@ function Endpoints(props) { } } else if ((!endpointConfig || !endpointConfig.endpoint_security) && apiObject.subtypeConfiguration?.subtype === 'AIAPI' - && (apiKeyParamConfig.authHeader || apiKeyParamConfig.authQueryParameter)) { + && (endpointConfiguration.authenticationConfiguration) + && (endpointConfiguration.authenticationConfiguration.enabled)) { return { isValid: false, message: intl.formatMessage({ @@ -802,7 +802,7 @@ function Endpoints(props) { onChangeAPI={apiDispatcher} endpointsDispatcher={apiDispatcher} saveAndRedirect={saveAndRedirect} - apiKeyParamConfig={apiKeyParamConfig} + endpointConfiguration={endpointConfiguration} /> ))} {(api.subtypeConfiguration?.subtype !== 'AIAPI') && ( @@ -824,7 +824,7 @@ function Endpoints(props) { setIsValidSequenceBackend={setIsValidSequenceBackend} isCustomBackendSelected={isCustomBackendSelected} setIsCustomBackendSelected={setIsCustomBackendSelected} - apiKeyParamConfig={apiKeyParamConfig} + endpointConfiguration={endpointConfiguration} componentValidator={componentValidator} endpointSecurityTypes={endpointSecurityTypes} /> From 3621e9d6f8d9f3d0751685631b1e787735fb202b Mon Sep 17 00:00:00 2001 From: Tharindu Dharmarathna Date: Fri, 25 Jul 2025 15:07:28 +0530 Subject: [PATCH 6/9] fix ui for new path --- .../components/AiVendors/AddEditAiVendor.jsx | 26 +++++++++---------- .../app/components/AiVendors/ModelFamily.jsx | 18 ++++++------- .../main/webapp/source/src/app/data/api.js | 22 ++++++++-------- .../Policies/CustomPolicies/ModelFailover.tsx | 4 +-- .../CustomPolicies/ModelRoundRobin.tsx | 4 +-- .../ModelWeightedRoundRobin.tsx | 4 +-- .../main/webapp/source/src/app/data/api.js | 18 ++++++------- 7 files changed, 48 insertions(+), 48 deletions(-) diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx index 34e28e59c84..2c939be350f 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx @@ -72,7 +72,7 @@ function reducer(state, newValue) { case 'apiVersion': case 'description': case 'modelList': - case 'multipleVendorSupport': + case 'multipleModelProviderSupport': case 'apiDefinition': case 'models': // New case for handling model vendor entries return { ...state, [field]: value }; @@ -177,7 +177,7 @@ export default function AddEditAiVendor(props) { authQueryParameter: '', authHeader: '', }, - multipleVendorSupport: false, + multipleModelProviderSupport: false, apiDefinition: '', modelList: [], models: [], @@ -201,10 +201,10 @@ export default function AddEditAiVendor(props) { if (aiVendorBody) { let models = []; let modelList = []; - if (aiVendorBody.models) { - models = JSON.parse(aiVendorBody.models); - modelList = models.find((item) => item.vendor === aiVendorBody.name); - modelList = modelList ? modelList.values : []; + if (aiVendorBody.modelProviders) { + models = JSON.parse(aiVendorBody.modelProviders); + modelList = models.find((item) => item.name === aiVendorBody.name); + modelList = modelList ? modelList.models : []; models = models.map((model) => ({ ...model, id: uuidv4(), })); @@ -248,7 +248,7 @@ export default function AddEditAiVendor(props) { apiDefinition: aiVendorBody.apiDefinition || '', modelList, models, - multipleVendorSupport: aiVendorBody.multipleVendorSupport || false, + multipleModelProviderSupport: aiVendorBody.multipleModelProviderSupport || false, }; dispatch({ field: 'all', value: newState }); @@ -266,7 +266,7 @@ export default function AddEditAiVendor(props) { useEffect(() => { if (location.state?.isSingleProvider !== undefined) { dispatch({ - field: 'multipleVendorSupport', + field: 'multipleModelProviderSupport', value: !location.state.isSingleProvider, }); } @@ -352,7 +352,7 @@ export default function AddEditAiVendor(props) { }); // Check for errors in model provider entries - const modelProviderEntriesErrors = state.models.some((entry) => entry.vendor.trim() === ''); + const modelProviderEntriesErrors = state.models.some((entry) => entry.name.trim() === ''); return hasErrors('name', state.name, validatingActive) || hasErrors('apiVersion', state.apiVersion, validatingActive) @@ -383,17 +383,17 @@ export default function AddEditAiVendor(props) { authenticationConfiguration: authConfig, }; let models; - if (state.multipleVendorSupport) { + if (state.multipleModelProviderSupport) { models = state.models.map(({ id, ...rest }) => rest); } else { - models = [{ vendor: state.name, values: state.modelList }]; + models = [{ name: state.name, models: state.modelList }]; } const newState = { ...state, configurations: updatedConfigurations, // modelList: JSON.stringify(state.modelList), // Stringify modelVendorEntries before sending to API - models: JSON.stringify(models), + modelList: JSON.stringify(models), }; if (vendorId) { // <-- Use vendorId instead of id @@ -640,7 +640,7 @@ export default function AddEditAiVendor(props) { - {state.multipleVendorSupport ? ( + {state.multipleModelProviderSupport ? ( dispatch({ diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx index 5f63b54e963..03b1f0e2866 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx @@ -40,13 +40,13 @@ const ModelFamily = ({ /** * Handles adding a new model family. - * Creates a new model object with a unique ID and empty vendor and values. + * Creates a new model object with a unique ID and empty multipleModelProviderSupport and values. */ const handleAddProvider = () => { const newModel = { id: uuidv4(), - vendor: '', - values: [], + name: '', + models: [], }; onModelsChange([...models, newModel]); }; @@ -71,7 +71,7 @@ const ModelFamily = ({ if (model.id === id) { return { ...model, - vendor: event.target.value, + name: event.target.value, }; } return model; @@ -109,22 +109,22 @@ const ModelFamily = ({ })} margin='dense' variant='outlined' - value={model.vendor} + value={model.name} onChange={(e) => handleProviderChange(model.id, e)} - error={hasErrors('providerName', model.vendor, validating)} + error={hasErrors('providerName', model.name, validating)} /> { const updatedModels = models.map((m) => { if (m.id === model.id) { return { ...m, - values: [...m.values, modelName], + models: [...m.models, modelName], }; } return m; @@ -136,7 +136,7 @@ const ModelFamily = ({ if (m.id === model.id) { return { ...m, - values: m.values.filter( + models: m.models.filter( (v) => v !== modelName, ), }; diff --git a/portals/admin/src/main/webapp/source/src/app/data/api.js b/portals/admin/src/main/webapp/source/src/app/data/api.js index af0c6ebf0cc..fe7f0363410 100644 --- a/portals/admin/src/main/webapp/source/src/app/data/api.js +++ b/portals/admin/src/main/webapp/source/src/app/data/api.js @@ -1172,7 +1172,7 @@ class API extends Resource { */ getAiVendorsList() { return this.client.then((client) => { - return client.apis['LLMProviders'].getLLMProviders( + return client.apis['AIServiceProviders'].getAIServiceProviders( this._requestMetaData(), ); }); @@ -1185,8 +1185,8 @@ class API extends Resource { */ aiVendorGet(aiVendorId) { return this.client.then((client) => { - return client.apis['LLMProvider'].getLLMProvider( - { llmProviderId: aiVendorId }, + return client.apis['AIServiceProvider'].getAIServiceProvider( + { aiServiceProviderId: aiVendorId }, this._requestMetaData(), ); }); @@ -1199,8 +1199,8 @@ class API extends Resource { */ deleteAiVendor(aiVendorId) { return this.client.then((client) => { - return client.apis['LLMProvider'].deleteLLMProvider( - { llmProviderId: aiVendorId }, + return client.apis['AIServiceProvider'].deleteAIServiceProvider( + { aiServiceProviderId: aiVendorId }, this._requestMetaData(), ); }); @@ -1214,11 +1214,11 @@ class API extends Resource { const payload = { 'Content-Type': 'multipart/form-data', }; - return client.apis['LLMProviders'].addLLMProvider( + return client.apis['AIServiceProviders'].addAIServiceProvider( payload, { requestBody: { ...aiVendorBody, - modelList: JSON.stringify(aiVendorBody.modelList) + modelProviders: JSON.stringify(aiVendorBody.modelList) }}, this._requestMetaData(), ); @@ -1231,15 +1231,15 @@ class API extends Resource { updateAiVendor(aiVendorId, aiVendorBody) { return this.client.then((client) => { const payload = { - llmProviderId: aiVendorId, + aiServiceProviderId: aiVendorId, 'Content-Type': 'multipart/form-data', }; - return client.apis['LLMProvider'].updateLLMProvider( + return client.apis['AIServiceProvider'].updateAIServiceProvider( payload, { requestBody: { ...aiVendorBody, - llmProviderId: aiVendorId, - modelList: JSON.stringify(aiVendorBody.modelList) + aiServiceProviderId: aiVendorId, + modelProviders: JSON.stringify(aiVendorBody.modelList) }}, this._requestMetaData(), ); diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx index a318403dbe7..33e3b69e79b 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx @@ -159,8 +159,8 @@ const ModelFailover: FC = ({ modelListPromise .then((response) => { const vendors: ModelVendor[] = response.body.map((vendor: any) => ({ - vendor: vendor.vendor, - values: vendor.values + vendor: vendor.name, + values: vendor.models })); setModelList(vendors); }).catch((error) => { diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelRoundRobin.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelRoundRobin.tsx index 844774aadc4..62ce9d57b3c 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelRoundRobin.tsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelRoundRobin.tsx @@ -136,8 +136,8 @@ const ModelRoundRobin: FC = ({ modelListPromise .then((response) => { const vendors: ModelVendor[] = response.body.map((vendor: any) => ({ - vendor: vendor.vendor, - values: vendor.values + vendor: vendor.name, + values: vendor.models })); setModelList(vendors); }).catch((error) => { diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelWeightedRoundRobin.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelWeightedRoundRobin.tsx index f6bd0c91b22..48d643c31c7 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelWeightedRoundRobin.tsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelWeightedRoundRobin.tsx @@ -139,8 +139,8 @@ const ModelWeightedRoundRobin: FC = ({ modelListPromise .then((response) => { const vendors: ModelVendor[] = response.body.map((vendor: any) => ({ - vendor: vendor.vendor, - values: vendor.values + vendor: vendor.name, + values: vendor.models })); setModelList(vendors); }).catch((error) => { diff --git a/portals/publisher/src/main/webapp/source/src/app/data/api.js b/portals/publisher/src/main/webapp/source/src/app/data/api.js index 853745863e7..aff19f6e4df 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/api.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/api.js @@ -3606,7 +3606,7 @@ class API extends Resource { static getLLMProviders() { const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; return restApiClient.then(client => { - return client.apis['LLMProviders'].getLLMProviders(); + return client.apis['AIServiceProviders'].getAIServiceProviders(); }); } @@ -3618,8 +3618,8 @@ class API extends Resource { static getLLMProviderById(llmProviderId) { const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; return restApiClient.then(client => { - return client.apis['LLMProvider'].getLLMProvider( - {llmProviderId}, + return client.apis['AIServiceProvider'].getAIServiceProvider( + {aiServiceProviderId:llmProviderId}, this._requestMetaData(), ); }); @@ -3633,8 +3633,8 @@ class API extends Resource { static getLLMProviderAPIDefinition(llmProviderId) { const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; return restApiClient.then(client => { - return client.apis['LLMProvider'].getLLMProviderApiDefinition( - { llmProviderId }, + return client.apis['AIServiceProvider'].getAIServiceProviderApiDefinition( + {aiServiceProviderId:llmProviderId}, this._requestMetaData(), ); }); @@ -3648,8 +3648,8 @@ class API extends Resource { static getLLMProviderEndpointConfiguration(llmProviderId) { const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; return restApiClient.then(client => { - return client.apis['LLMProvider'].getLLMProviderEndpointConfiguration( - { llmProviderId }, + return client.apis['AIServiceProvider'].getAIServiceProviderEndpointConfiguration( + {aiServiceProviderId:llmProviderId}, this._requestMetaData(), ); }); @@ -3664,8 +3664,8 @@ class API extends Resource { static getLLMProviderModelList(llmProviderId) { const restApiClient = new APIClientFactory().getAPIClient(Utils.getCurrentEnvironment(), Utils.CONST.API_CLIENT).client; return restApiClient.then(client => { - return client.apis['LLMProvider'].getLLMProviderModels( - { llmProviderId }, + return client.apis['AIServiceProvider'].getAIServiceProviderModels( + {aiServiceProviderId:llmProviderId}, this._requestMetaData(), ) }); From f71512b88ac3ec0bc1c6dbdf2c908605c6f96505 Mon Sep 17 00:00:00 2001 From: Ashera Silva Date: Wed, 30 Jul 2025 18:32:01 +0530 Subject: [PATCH 7/9] Improve multi model provider support UI --- .../main/webapp/site/public/locales/en.json | 112 ++--- .../main/webapp/site/public/locales/fr.json | 113 ++--- .../src/main/webapp/source/dev/keys.json | 2 +- .../AddEditAiServiceProvider.jsx} | 392 +++++++++++------- .../AiApiDefinition.jsx} | 4 +- .../DeleteAiServiceProvider.jsx} | 22 +- .../ListAiServiceProviders.jsx} | 103 ++--- .../AiServiceProviders/ModelProviders.jsx | 190 +++++++++ .../components/AiServiceProviders/index.jsx | 22 + .../app/components/AiVendors/ModelFamily.jsx | 194 --------- .../src/app/components/AiVendors/index.jsx | 22 - .../app/components/Base/RouteMenuMapping.jsx | 28 +- .../main/webapp/source/src/app/data/api.js | 40 +- .../admin/src/main/webapp/webpack.config.js | 2 +- 14 files changed, 644 insertions(+), 602 deletions(-) rename portals/admin/src/main/webapp/source/src/app/components/{AiVendors/AddEditAiVendor.jsx => AiServiceProviders/AddEditAiServiceProvider.jsx} (72%) rename portals/admin/src/main/webapp/source/src/app/components/{AiVendors/AIAPIDefinition.jsx => AiServiceProviders/AiApiDefinition.jsx} (97%) rename portals/admin/src/main/webapp/source/src/app/components/{AiVendors/DeleteAiVendor.jsx => AiServiceProviders/DeleteAiServiceProvider.jsx} (77%) rename portals/admin/src/main/webapp/source/src/app/components/{AiVendors/ListAiVendors.jsx => AiServiceProviders/ListAiServiceProviders.jsx} (81%) create mode 100644 portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/ModelProviders.jsx create mode 100755 portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/index.jsx delete mode 100644 portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx delete mode 100755 portals/admin/src/main/webapp/source/src/app/components/AiVendors/index.jsx diff --git a/portals/admin/src/main/webapp/site/public/locales/en.json b/portals/admin/src/main/webapp/site/public/locales/en.json index 7143f924636..ab14189bdc4 100644 --- a/portals/admin/src/main/webapp/site/public/locales/en.json +++ b/portals/admin/src/main/webapp/site/public/locales/en.json @@ -102,11 +102,7 @@ "AdminPages.Addons.ListBaseWithPagination.noDataError": "Error while retrieving data.", "AdminPages.Addons.ListBaseWithPagination.nodata.message": "No items yet", "AdminPages.Addons.ListBaseWithPagination.reload": "Reload", - "AdminPages.AiVendor.Delete.form.delete.confirmation.message": "Are you sure you want to delete this AI/LLM Vendor ?", - "AdminPages.AiVendor.Delete.form.delete.dialog.btn": "Delete", - "AdminPages.AiVendor.Delete.form.delete.dialog.title": "Delete AI/LLM Vendor ?", - "AdminPages.AiVendor.Delete.form.delete.successful": "AI/LLM Vendor deleted successfully", - "AdminPages.AiVendors.List.empty.content.Aivendors": "It is possible to register an AI/LLM Vendor.", + "AdminPages.AiVendors.List.empty.content.Aivendors": "It is possible to register an AI Service Provider.", "AdminPages.ApiCategories.AddEdit.form.add.successful": "API Category added successfully", "AdminPages.ApiCategories.AddEdit.form.description": "Description", "AdminPages.ApiCategories.AddEdit.form.description.helper.text": "Description of the API category", @@ -226,55 +222,61 @@ "AdminPages.Organizations.table.header.organization.name": "Organization Name", "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.vhost.duplicate": "VHosts are duplicated", "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.vhost.empty": "VHost is empty", - "AiVendor.add.success.msg": "- AI/LLM Vendor added successfully.", - "AiVendor.edit.success": "- AI/LLM Vendor edited successfully.", - "AiVendors.AddEditAiVendor.AiVendor.configurations.llm": "LLM Configurations", - "AiVendors.AddEditAiVendor.AiVendor.configurations.llm.auth": "LLM Provider Auth Configurations", - "AiVendors.AddEditAiVendor.AiVendor.general.details.description.llm": "Configure to extract LLM related metadata", - "AiVendors.AddEditAiVendor.AiVendor.general.details.description.llm.auth": "Configure to add LLM provider authorization", - "AiVendors.AddEditAiVendor.AiVendor.provider.configurations.description": "Define provider configurations of the AI/LLM Vendor", - "AiVendors.AddEditAiVendor.add.provider": "Add Provider", - "AiVendors.AddEditAiVendor.apiDefinition": "API Definition", - "AiVendors.AddEditAiVendor.apiDefinition.description": "Upload API Definition of the AI/LLM Vendor", - "AiVendors.AddEditAiVendor.apiDefinition.upload": "Upload API Definition", - "AiVendors.AddEditAiVendor.connectorType": "Connector Type for AI/LLM Vendor", - "AiVendors.AddEditAiVendor.connectorType.description": "Reference to the connector model for the AI/LLM vendor", - "AiVendors.AddEditAiVendor.form.add": "Add", - "AiVendors.AddEditAiVendor.form.cancel": "Cancel", - "AiVendors.AddEditAiVendor.form.connectorType": "Connector Type", - "AiVendors.AddEditAiVendor.form.connectorType.help": "Connector Type for AI/LLM Vendor", - "AiVendors.AddEditAiVendor.form.description": "Description", - "AiVendors.AddEditAiVendor.form.description.help": "Description of the AI/LLM Vendor.", - "AiVendors.AddEditAiVendor.form.displayName.help": "API Version of the AI/LLM Vendor.", - "AiVendors.AddEditAiVendor.form.has.errors": "One or more fields contain errors.", - "AiVendors.AddEditAiVendor.form.name": "Name", - "AiVendors.AddEditAiVendor.form.name.help": "Name of the AI/LLM Vendor.", - "AiVendors.AddEditAiVendor.form.update.btn": "Update", - "AiVendors.AddEditAiVendor.general.details": "General Details", - "AiVendors.AddEditAiVendor.general.details.description": "Provide name and description of the AI/LLM Vendor", - "AiVendors.AddEditAiVendor.is.empty.error": "is empty", - "AiVendors.AddEditAiVendor.is.empty.error.apiVersion": "Required field is empty.", - "AiVendors.AddEditAiVendor.is.empty.error.attributeIdentifier": "Attribute identifier is required.", - "AiVendors.AddEditAiVendor.is.empty.error.connectorType": "Connector type is required.", - "AiVendors.AddEditAiVendor.is.empty.error.inputSource": "Input source is required.", - "AiVendors.AddEditAiVendor.is.empty.error.providerName": "Provider name is required.", - "AiVendors.AddEditAiVendor.modelList.placeholder": "Type Model name and press Enter", - "AiVendors.AddEditAiVendor.provider.configurations": "Provider Configurations", - "AiVendors.AddEditAiVendor.provider.delete": "Delete", - "AiVendors.AddEditAiVendor.provider.models.placeholder": "Type Model name and press Enter", - "AiVendors.AddEditAiVendor.provider.name": "Provider Name", - "AiVendors.AddEditAiVendor.title.edit": "AI/LLM Vendor - Edit", - "AiVendors.AddEditAiVendor.title.new": "AI/LLM Vendor - Create new", + "AiServiceProviders.AddEditAiServiceProvider.AiVendor.configurations.llm": "LLM Configurations", + "AiServiceProviders.AddEditAiServiceProvider.AiVendor.configurations.llm.auth": "LLM Provider Auth Configurations", + "AiServiceProviders.AddEditAiServiceProvider.AiVendor.general.details.description.llm": "Configure to extract LLM related metadata", + "AiServiceProviders.AddEditAiServiceProvider.AiVendor.general.details.description.llm.auth": "Configure to add LLM provider authorization", + "AiServiceProviders.AddEditAiServiceProvider.apiDefinition": "API Definition", + "AiServiceProviders.AddEditAiServiceProvider.apiDefinition.description": "Upload API Definition of the AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.connectorType": "Connector Type for AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.connectorType.description": "Reference to the connector model for the AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.form.add": "Add", + "AiServiceProviders.AddEditAiServiceProvider.form.apiVersion.help": "API Version of the AI Service Provider.", + "AiServiceProviders.AddEditAiServiceProvider.form.cancel": "Cancel", + "AiServiceProviders.AddEditAiServiceProvider.form.connectorType": "Connector Type", + "AiServiceProviders.AddEditAiServiceProvider.form.connectorType.help": "Connector Type for AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.form.description": "Description", + "AiServiceProviders.AddEditAiServiceProvider.form.description.help": "Description of the AI Service Provider.", + "AiServiceProviders.AddEditAiServiceProvider.form.has.errors": "One or more fields contain errors.", + "AiServiceProviders.AddEditAiServiceProvider.form.name": "Name", + "AiServiceProviders.AddEditAiServiceProvider.form.name.help": "Name of the AI Service Provider.", + "AiServiceProviders.AddEditAiServiceProvider.form.update.btn": "Update", + "AiServiceProviders.AddEditAiServiceProvider.general.details": "General Details", + "AiServiceProviders.AddEditAiServiceProvider.general.details.description": "Provide name and description of the AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error": "is empty", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.apiVersion": "Required field is empty.", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.attributeIdentifier": "Attribute identifier is required.", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.connectorType": "Connector type is required.", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.inputSource": "Input source is required.", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.providerName": "Provider name is required.", + "AiServiceProviders.AddEditAiServiceProvider.model.providers": "Model Provider(s)", + "AiServiceProviders.AddEditAiServiceProvider.model.providers.description": "Configure model provider(s) for the AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.modelList.placeholder": "Type Model name and press Enter", + "AiServiceProviders.AddEditAiServiceProvider.multi.model.provider": "Multi Model Provider", + "AiServiceProviders.AddEditAiServiceProvider.single.model.provider": "Single Model Provider", + "AiServiceProviders.AddEditAiServiceProvider.title.edit": "AI Service Provider - Edit", + "AiServiceProviders.AddEditAiServiceProvider.title.new": "AI Service Provider - Create new", + "AiServiceProviders.AiApiDefinition.apiDefinition.upload": "Upload API Definition", + "AiServiceProviders.DeleteAiServiceProvider.Delete.form.delete.successful": "AI Service Provider deleted successfully", + "AiServiceProviders.DeleteAiServiceProvider.delete.confirmation.message": "Are you sure you want to delete this AI Service Provider ?", + "AiServiceProviders.DeleteAiServiceProvider.delete.dialog.btn": "Delete", + "AiServiceProviders.DeleteAiServiceProvider.delete.dialog.title": "Delete AI Service Provider ?", + "AiServiceProviders.ListAiServiceProviders.List.title": "AI Service Providers", + "AiServiceProviders.ListAiServiceProviders.empty.title": "AI Service Providers", + "AiServiceProviders.ListAiServiceProviders.table.header.label.aiVendorName": "AI Service Provider Name", + "AiServiceProviders.ListAiServiceProviders.table.header.label.apiVersion": "API Version", + "AiServiceProviders.ListAiServiceProviders.table.header.label.builtInSupport": "Type", + "AiServiceProviders.ListAiServiceProviders.table.header.label.description": "Description", + "AiServiceProviders.ListAiServiceProviders.table.is.used.delete.tooltip": "Default AI Service Providers cannot be deleted", + "AiServiceProviders.ModelProviders.add.model.provider": "Add Model Provider", + "AiServiceProviders.ModelProviders.model.provider.delete": "Delete", + "AiServiceProviders.ModelProviders.model.provider.models.placeholder": "Type Model name and press Enter", + "AiServiceProviders.ModelProviders.model.provider.name": "Provider Name", + "AiVendor.add.success.msg": "- AI Service Provider added successfully.", + "AiVendor.edit.success": "- AI Service Provider edited successfully.", "AiVendors.AiAPIDefinition.browse.files.to.upload": "Browse File to Upload", "AiVendors.AiAPIDefinition.drag.and.drop.message": "Drag and Drop files here {break} or {break}", - "AiVendors.ListAiVendors.List.title": "AI/LLM Vendors", - "AiVendors.ListAiVendors.addNewAiVendor": "Add AI/LLM Vendor", - "AiVendors.ListAiVendors.empty.title": "AI/LLM Vendors", - "AiVendors.ListAiVendors.table.header.label.aiVendorName": "AI/LLM Vendor Name", - "AiVendors.ListAiVendors.table.header.label.apiVersion": "API Version", - "AiVendors.ListAiVendors.table.header.label.builtInSupport": "Type", - "AiVendors.ListAiVendors.table.header.label.description": "Description", - "AiVendors.ListAiVendors.table.is.used.delete.tooltip": "Default AI/LLM Vendors cannot be deleted", + "AiVendors.ListAiVendors.addNewAiVendor": "Add AI Service Provider", "AiVendors.OpenAPI.file.error": "Error reading file", "Api.Name": "API Name", "Api.Provider": "Provider", @@ -343,9 +345,9 @@ "Base.RouteMenuMapping.advanced.throttling.policies": "Advanced Policies", "Base.RouteMenuMapping.advanced.throttling.policies.Adding": "Add Advanced Policy", "Base.RouteMenuMapping.advanced.throttling.policies.Editing": "Edit Advanced Policy", - "Base.RouteMenuMapping.aivendors": "AI/LLM Vendors", - "Base.RouteMenuMapping.aivendors.items.Adding": "Add AI/LLM Vendor", - "Base.RouteMenuMapping.aivendors.items.Editing": "Edit AI/LLM Vendor", + "Base.RouteMenuMapping.aiServiceProviders": "AI Service Providers", + "Base.RouteMenuMapping.aiServiceProviders.items.Adding": "Add AI Service Provider", + "Base.RouteMenuMapping.aiServiceProviders.items.Editing": "Edit AI Service Provider", "Base.RouteMenuMapping.api.categories": "API Categories", "Base.RouteMenuMapping.api.product.state.change": "API Product State Change", "Base.RouteMenuMapping.api.revision.deployment": "API Revision Deployment", diff --git a/portals/admin/src/main/webapp/site/public/locales/fr.json b/portals/admin/src/main/webapp/site/public/locales/fr.json index 8f244c79768..ab14189bdc4 100644 --- a/portals/admin/src/main/webapp/site/public/locales/fr.json +++ b/portals/admin/src/main/webapp/site/public/locales/fr.json @@ -1,5 +1,6 @@ { "Admin.Addons.Help.Base.title": "Help", + "Admin.AiVendor.form.llm.auth.aws.service.label": "AWS Service Name", "Admin.AiVendor.label.apiVersion": "API Version", "Admin.GatewayEnvironment.form.type": "Gateway Environment Type", "Admin.KeyManager.form.type": "Key Manager Type", @@ -101,11 +102,7 @@ "AdminPages.Addons.ListBaseWithPagination.noDataError": "Error while retrieving data.", "AdminPages.Addons.ListBaseWithPagination.nodata.message": "No items yet", "AdminPages.Addons.ListBaseWithPagination.reload": "Reload", - "AdminPages.AiVendor.Delete.form.delete.confirmation.message": "Are you sure you want to delete this AI/LLM Vendor ?", - "AdminPages.AiVendor.Delete.form.delete.dialog.btn": "Delete", - "AdminPages.AiVendor.Delete.form.delete.dialog.title": "Delete AI/LLM Vendor ?", - "AdminPages.AiVendor.Delete.form.delete.successful": "AI/LLM Vendor deleted successfully", - "AdminPages.AiVendors.List.empty.content.Aivendors": "It is possible to register an AI/LLM Vendor.", + "AdminPages.AiVendors.List.empty.content.Aivendors": "It is possible to register an AI Service Provider.", "AdminPages.ApiCategories.AddEdit.form.add.successful": "API Category added successfully", "AdminPages.ApiCategories.AddEdit.form.description": "Description", "AdminPages.ApiCategories.AddEdit.form.description.helper.text": "Description of the API category", @@ -225,55 +222,61 @@ "AdminPages.Organizations.table.header.organization.name": "Organization Name", "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.vhost.duplicate": "VHosts are duplicated", "AdminPagesGatewayEnvironments.AddEditGWEnvironment.form.environment.vhost.empty": "VHost is empty", - "AiVendor.add.success.msg": "- AI/LLM Vendor added successfully.", - "AiVendor.edit.success": "- AI/LLM Vendor edited successfully.", - "AiVendors.AddEditAiVendor.AiVendor.configurations.llm": "LLM Configurations", - "AiVendors.AddEditAiVendor.AiVendor.configurations.llm.auth": "LLM Provider Auth Configurations", - "AiVendors.AddEditAiVendor.AiVendor.general.details.description.llm": "Configure to extract LLM related metadata", - "AiVendors.AddEditAiVendor.AiVendor.general.details.description.llm.auth": "Configure to add LLM provider authorization", - "AiVendors.AddEditAiVendor.AiVendor.provider.configurations.description": "Define provider configurations of the AI/LLM Vendor", - "AiVendors.AddEditAiVendor.add.provider": "Add Provider", - "AiVendors.AddEditAiVendor.apiDefinition": "API Definition", - "AiVendors.AddEditAiVendor.apiDefinition.description": "Upload API Definition of the AI/LLM Vendor", - "AiVendors.AddEditAiVendor.apiDefinition.upload": "Upload API Definition", - "AiVendors.AddEditAiVendor.connectorType": "Connector Type for AI/LLM Vendor", - "AiVendors.AddEditAiVendor.connectorType.description": "Reference to the connector model for the AI/LLM vendor", - "AiVendors.AddEditAiVendor.form.add": "Add", - "AiVendors.AddEditAiVendor.form.cancel": "Cancel", - "AiVendors.AddEditAiVendor.form.connectorType": "Connector Type", - "AiVendors.AddEditAiVendor.form.connectorType.help": "Connector Type for AI/LLM Vendor", - "AiVendors.AddEditAiVendor.form.description": "Description", - "AiVendors.AddEditAiVendor.form.description.help": "Description of the AI/LLM Vendor.", - "AiVendors.AddEditAiVendor.form.displayName.help": "API Version of the AI/LLM Vendor.", - "AiVendors.AddEditAiVendor.form.has.errors": "One or more fields contain errors.", - "AiVendors.AddEditAiVendor.form.name": "Name", - "AiVendors.AddEditAiVendor.form.name.help": "Name of the AI/LLM Vendor.", - "AiVendors.AddEditAiVendor.form.update.btn": "Update", - "AiVendors.AddEditAiVendor.general.details": "General Details", - "AiVendors.AddEditAiVendor.general.details.description": "Provide name and description of the AI/LLM Vendor", - "AiVendors.AddEditAiVendor.is.empty.error": "is empty", - "AiVendors.AddEditAiVendor.is.empty.error.apiVersion": "Required field is empty.", - "AiVendors.AddEditAiVendor.is.empty.error.attributeIdentifier": "Attribute identifier is required.", - "AiVendors.AddEditAiVendor.is.empty.error.connectorType": "Connector type is required.", - "AiVendors.AddEditAiVendor.is.empty.error.inputSource": "Input source is required.", - "AiVendors.AddEditAiVendor.is.empty.error.providerName": "Provider name is required.", - "AiVendors.AddEditAiVendor.modelList.placeholder": "Type Model name and press Enter", - "AiVendors.AddEditAiVendor.provider.configurations": "Provider Configurations", - "AiVendors.AddEditAiVendor.provider.delete": "Delete", - "AiVendors.AddEditAiVendor.provider.models.placeholder": "Type Model name and press Enter", - "AiVendors.AddEditAiVendor.provider.name": "Provider Name", - "AiVendors.AddEditAiVendor.title.edit": "AI/LLM Vendor - Edit", - "AiVendors.AddEditAiVendor.title.new": "AI/LLM Vendor - Create new", + "AiServiceProviders.AddEditAiServiceProvider.AiVendor.configurations.llm": "LLM Configurations", + "AiServiceProviders.AddEditAiServiceProvider.AiVendor.configurations.llm.auth": "LLM Provider Auth Configurations", + "AiServiceProviders.AddEditAiServiceProvider.AiVendor.general.details.description.llm": "Configure to extract LLM related metadata", + "AiServiceProviders.AddEditAiServiceProvider.AiVendor.general.details.description.llm.auth": "Configure to add LLM provider authorization", + "AiServiceProviders.AddEditAiServiceProvider.apiDefinition": "API Definition", + "AiServiceProviders.AddEditAiServiceProvider.apiDefinition.description": "Upload API Definition of the AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.connectorType": "Connector Type for AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.connectorType.description": "Reference to the connector model for the AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.form.add": "Add", + "AiServiceProviders.AddEditAiServiceProvider.form.apiVersion.help": "API Version of the AI Service Provider.", + "AiServiceProviders.AddEditAiServiceProvider.form.cancel": "Cancel", + "AiServiceProviders.AddEditAiServiceProvider.form.connectorType": "Connector Type", + "AiServiceProviders.AddEditAiServiceProvider.form.connectorType.help": "Connector Type for AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.form.description": "Description", + "AiServiceProviders.AddEditAiServiceProvider.form.description.help": "Description of the AI Service Provider.", + "AiServiceProviders.AddEditAiServiceProvider.form.has.errors": "One or more fields contain errors.", + "AiServiceProviders.AddEditAiServiceProvider.form.name": "Name", + "AiServiceProviders.AddEditAiServiceProvider.form.name.help": "Name of the AI Service Provider.", + "AiServiceProviders.AddEditAiServiceProvider.form.update.btn": "Update", + "AiServiceProviders.AddEditAiServiceProvider.general.details": "General Details", + "AiServiceProviders.AddEditAiServiceProvider.general.details.description": "Provide name and description of the AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error": "is empty", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.apiVersion": "Required field is empty.", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.attributeIdentifier": "Attribute identifier is required.", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.connectorType": "Connector type is required.", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.inputSource": "Input source is required.", + "AiServiceProviders.AddEditAiServiceProvider.is.empty.error.providerName": "Provider name is required.", + "AiServiceProviders.AddEditAiServiceProvider.model.providers": "Model Provider(s)", + "AiServiceProviders.AddEditAiServiceProvider.model.providers.description": "Configure model provider(s) for the AI Service Provider", + "AiServiceProviders.AddEditAiServiceProvider.modelList.placeholder": "Type Model name and press Enter", + "AiServiceProviders.AddEditAiServiceProvider.multi.model.provider": "Multi Model Provider", + "AiServiceProviders.AddEditAiServiceProvider.single.model.provider": "Single Model Provider", + "AiServiceProviders.AddEditAiServiceProvider.title.edit": "AI Service Provider - Edit", + "AiServiceProviders.AddEditAiServiceProvider.title.new": "AI Service Provider - Create new", + "AiServiceProviders.AiApiDefinition.apiDefinition.upload": "Upload API Definition", + "AiServiceProviders.DeleteAiServiceProvider.Delete.form.delete.successful": "AI Service Provider deleted successfully", + "AiServiceProviders.DeleteAiServiceProvider.delete.confirmation.message": "Are you sure you want to delete this AI Service Provider ?", + "AiServiceProviders.DeleteAiServiceProvider.delete.dialog.btn": "Delete", + "AiServiceProviders.DeleteAiServiceProvider.delete.dialog.title": "Delete AI Service Provider ?", + "AiServiceProviders.ListAiServiceProviders.List.title": "AI Service Providers", + "AiServiceProviders.ListAiServiceProviders.empty.title": "AI Service Providers", + "AiServiceProviders.ListAiServiceProviders.table.header.label.aiVendorName": "AI Service Provider Name", + "AiServiceProviders.ListAiServiceProviders.table.header.label.apiVersion": "API Version", + "AiServiceProviders.ListAiServiceProviders.table.header.label.builtInSupport": "Type", + "AiServiceProviders.ListAiServiceProviders.table.header.label.description": "Description", + "AiServiceProviders.ListAiServiceProviders.table.is.used.delete.tooltip": "Default AI Service Providers cannot be deleted", + "AiServiceProviders.ModelProviders.add.model.provider": "Add Model Provider", + "AiServiceProviders.ModelProviders.model.provider.delete": "Delete", + "AiServiceProviders.ModelProviders.model.provider.models.placeholder": "Type Model name and press Enter", + "AiServiceProviders.ModelProviders.model.provider.name": "Provider Name", + "AiVendor.add.success.msg": "- AI Service Provider added successfully.", + "AiVendor.edit.success": "- AI Service Provider edited successfully.", "AiVendors.AiAPIDefinition.browse.files.to.upload": "Browse File to Upload", "AiVendors.AiAPIDefinition.drag.and.drop.message": "Drag and Drop files here {break} or {break}", - "AiVendors.ListAiVendors.List.title": "AI/LLM Vendors", - "AiVendors.ListAiVendors.addNewAiVendor": "Add AI/LLM Vendor", - "AiVendors.ListAiVendors.empty.title": "AI/LLM Vendors", - "AiVendors.ListAiVendors.table.header.label.aiVendorName": "AI/LLM Vendor Name", - "AiVendors.ListAiVendors.table.header.label.apiVersion": "API Version", - "AiVendors.ListAiVendors.table.header.label.builtInSupport": "Type", - "AiVendors.ListAiVendors.table.header.label.description": "Description", - "AiVendors.ListAiVendors.table.is.used.delete.tooltip": "Default AI/LLM Vendors cannot be deleted", + "AiVendors.ListAiVendors.addNewAiVendor": "Add AI Service Provider", "AiVendors.OpenAPI.file.error": "Error reading file", "Api.Name": "API Name", "Api.Provider": "Provider", @@ -342,9 +345,9 @@ "Base.RouteMenuMapping.advanced.throttling.policies": "Advanced Policies", "Base.RouteMenuMapping.advanced.throttling.policies.Adding": "Add Advanced Policy", "Base.RouteMenuMapping.advanced.throttling.policies.Editing": "Edit Advanced Policy", - "Base.RouteMenuMapping.aivendors": "AI/LLM Vendors", - "Base.RouteMenuMapping.aivendors.items.Adding": "Add AI/LLM Vendor", - "Base.RouteMenuMapping.aivendors.items.Editing": "Edit AI/LLM Vendor", + "Base.RouteMenuMapping.aiServiceProviders": "AI Service Providers", + "Base.RouteMenuMapping.aiServiceProviders.items.Adding": "Add AI Service Provider", + "Base.RouteMenuMapping.aiServiceProviders.items.Editing": "Edit AI Service Provider", "Base.RouteMenuMapping.api.categories": "API Categories", "Base.RouteMenuMapping.api.product.state.change": "API Product State Change", "Base.RouteMenuMapping.api.revision.deployment": "API Revision Deployment", diff --git a/portals/admin/src/main/webapp/source/dev/keys.json b/portals/admin/src/main/webapp/source/dev/keys.json index f2a6b85f2e3..64db90c0920 100644 --- a/portals/admin/src/main/webapp/source/dev/keys.json +++ b/portals/admin/src/main/webapp/source/dev/keys.json @@ -1 +1 @@ -{"clientId":"z9YvEMbi6peHfG2_hgw__A0Rsy8a","clientName":"admin_webpack_dev","callBackURL":"https://localhost:8083/admin/services/auth/callback/login","clientSecret":"EfNlzsJ7jBAoyg0CP8yq62_EMnEa","isSaasApplication":true,"appOwner":"admin","jsonString":"{\"grant_types\":\"authorization_code refresh_token\"}","jsonAppAttribute":"{}","applicationUUID":null,"tokenType":"DEFAULT"} \ No newline at end of file +{"clientId":"B4OBGpJ6vx5UKw078qfkGBfQUa4a","clientName":"admin_webpack_dev","callBackURL":"https://localhost:8083/admin/services/auth/callback/login","clientSecret":"Qwe57yQorONvQ2lNEbSYfE8mjLEa","isSaasApplication":true,"appOwner":"admin","jsonString":"{\"grant_types\":\"authorization_code refresh_token\"}","jsonAppAttribute":"{}","applicationUUID":null,"tokenType":"DEFAULT"} \ No newline at end of file diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx similarity index 72% rename from portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx rename to portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx index 2c939be350f..41618e2875a 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AddEditAiVendor.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx @@ -41,8 +41,8 @@ import RadioGroup from '@mui/material/RadioGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; import { MuiChipsInput } from 'mui-chips-input'; import ContentBase from 'AppComponents/AdminPages/Addons/ContentBase'; -import AIAPIDefinition from './AIAPIDefinition'; -import ModelFamily from './ModelFamily'; +import AIAPIDefinition from './AiApiDefinition'; +import ModelProviders from './ModelProviders'; const StyledSpan = styled('span')(({ theme }) => ({ color: theme.palette.error.dark })); @@ -74,7 +74,7 @@ function reducer(state, newValue) { case 'modelList': case 'multipleModelProviderSupport': case 'apiDefinition': - case 'models': // New case for handling model vendor entries + case 'models': return { ...state, [field]: value }; case 'requestModel': case 'responseModel': @@ -111,11 +111,11 @@ function reducer(state, newValue) { } /** - * AddEditAiVendor component + * AddEditAiServiceProvider component * @param {*} props props passed from parents. - * @returns {JSX} AddEditAiVendor component. + * @returns {JSX} AddEditAiServiceProvider component. */ -export default function AddEditAiVendor(props) { +export default function AddEditAiServiceProvider(props) { const intl = useIntl(); const [saving, setSaving] = useState(false); const { match: { params: { id: vendorId } }, history } = props; @@ -128,6 +128,7 @@ export default function AddEditAiVendor(props) { const authSources = ['none', 'apikey', 'aws']; const [validating, setValidating] = useState(false); const [file, setFile] = useState(null); + const [loading, setLoading] = useState(!!vendorId); // Set to true if editing (vendorId exists) const location = useLocation(); const [initialState] = useState({ @@ -186,74 +187,82 @@ export default function AddEditAiVendor(props) { const [state, dispatch] = useReducer(reducer, initialState); const pageTitle = vendorId ? `${intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.title.edit', - defaultMessage: 'AI/LLM Vendor - Edit ', + id: 'AiServiceProviders.AddEditAiServiceProvider.title.edit', + defaultMessage: 'AI Service Provider - Edit ', })} ${state.name}` : intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.title.new', - defaultMessage: 'AI/LLM Vendor - Create new', + id: 'AiServiceProviders.AddEditAiServiceProvider.title.new', + defaultMessage: 'AI Service Provider - Create new', }); useEffect(() => { const fetchData = async () => { if (vendorId) { - const aiVendorResult = await new API().aiVendorGet(vendorId); - const aiVendorBody = aiVendorResult.body; - if (aiVendorBody) { - let models = []; - let modelList = []; - if (aiVendorBody.modelProviders) { - models = JSON.parse(aiVendorBody.modelProviders); - modelList = models.find((item) => item.name === aiVendorBody.name); - modelList = modelList ? modelList.models : []; - models = models.map((model) => ({ - ...model, id: uuidv4(), - })); - } - if (aiVendorBody.configurations) { - const config = JSON.parse(aiVendorBody.configurations); - if (config.authenticationConfiguration) { - setAuthenticationConfiguration({ - enabled: config.authenticationConfiguration.enabled, - type: config.authenticationConfiguration.type, - parameters: config.authenticationConfiguration.parameters ?? {}, - }); - } else { - const hasAuthHeader = config.authHeader && config.authHeader.trim() !== ''; - const hasAuthQueryParameter = config.authQueryParameter - && config.authQueryParameter.trim() !== ''; - if (hasAuthHeader || hasAuthQueryParameter) { + try { + const aiVendorResult = await new API().getAiServiceProvider(vendorId); + const aiVendorBody = aiVendorResult.body; + if (aiVendorBody) { + let models = []; + let modelList = []; + if (aiVendorBody.modelProviders) { + models = JSON.parse(aiVendorBody.modelProviders); + modelList = models.find((item) => item.name === aiVendorBody.name); + modelList = modelList ? modelList.models : []; + models = models.map((model) => ({ + ...model, id: uuidv4(), + })); + } + if (aiVendorBody.configurations) { + const config = JSON.parse(aiVendorBody.configurations); + if (config.authenticationConfiguration) { setAuthenticationConfiguration({ - enabled: 'true', - type: 'apikey', - parameters: { - headersEnabled: !!hasAuthHeader, - headerName: config.authHeader || '', - queryParameterEnabled: !!hasAuthQueryParameter, - queryParameterName: config.authQueryParameter || '', - }, + enabled: config.authenticationConfiguration.enabled, + type: config.authenticationConfiguration.type, + parameters: config.authenticationConfiguration.parameters ?? {}, }); } else { - setAuthenticationConfiguration({ - enabled: 'false', - type: 'none', - }); + const hasAuthHeader = config.authHeader && config.authHeader.trim() !== ''; + const hasAuthQueryParameter = config.authQueryParameter + && config.authQueryParameter.trim() !== ''; + if (hasAuthHeader || hasAuthQueryParameter) { + setAuthenticationConfiguration({ + enabled: 'true', + type: 'apikey', + parameters: { + headersEnabled: !!hasAuthHeader, + headerName: config.authHeader || '', + queryParameterEnabled: !!hasAuthQueryParameter, + queryParameterName: config.authQueryParameter || '', + }, + }); + } else { + setAuthenticationConfiguration({ + enabled: 'false', + type: 'none', + }); + } } } - } - const newState = { - name: aiVendorBody.name || '', - apiVersion: aiVendorBody.apiVersion || '', - description: aiVendorBody.description || '', - configurations: JSON.parse(aiVendorBody.configurations), - apiDefinition: aiVendorBody.apiDefinition || '', - modelList, - models, - multipleModelProviderSupport: aiVendorBody.multipleModelProviderSupport || false, - }; - dispatch({ field: 'all', value: newState }); + const newState = { + name: aiVendorBody.name || '', + apiVersion: aiVendorBody.apiVersion || '', + description: aiVendorBody.description || '', + configurations: JSON.parse(aiVendorBody.configurations), + apiDefinition: aiVendorBody.apiDefinition || '', + modelList, + models, + multipleModelProviderSupport: aiVendorBody.multipleModelProviderSupport || false, + }; + dispatch({ field: 'all', value: newState }); - setFile(new Blob([aiVendorBody.apiDefinition || ''], { type: 'text/plain;charset=utf-8' })); + setFile(new Blob([aiVendorBody.apiDefinition || ''], { type: 'text/plain;charset=utf-8' })); + } + } catch (error) { + console.error('Error fetching AI Service Provider data:', error); + } finally { + setLoading(false); } + } else { + setLoading(false); // No vendorId means create mode, no loading needed } }; @@ -293,8 +302,8 @@ export default function AddEditAiVendor(props) { switch (fieldName) { case 'name': if (fieldValue.trim() === '') { - error = `AI/LLM Vendor name ${intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.is.empty.error', + error = `AI Service Provider name ${intl.formatMessage({ + id: 'AiServiceProviders.AddEditAiServiceProvider.is.empty.error', defaultMessage: ' is empty', })}`; } @@ -302,7 +311,7 @@ export default function AddEditAiVendor(props) { case 'apiVersion': if (fieldValue.trim() === '') { error = intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.is.empty.error.apiVersion', + id: 'AiServiceProviders.AddEditAiServiceProvider.is.empty.error.apiVersion', defaultMessage: 'Required field is empty.', }); } @@ -310,7 +319,7 @@ export default function AddEditAiVendor(props) { case 'inputSource': if (fieldValue.trim() === '') { error = intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.is.empty.error.inputSource', + id: 'AiServiceProviders.AddEditAiServiceProvider.is.empty.error.inputSource', defaultMessage: 'Input source is required.', }); } @@ -318,7 +327,7 @@ export default function AddEditAiVendor(props) { case 'attributeIdentifier': if (fieldValue.required && fieldValue.attributeIdentifier.trim() === '') { error = intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.is.empty.error.attributeIdentifier', + id: 'AiServiceProviders.AddEditAiServiceProvider.is.empty.error.attributeIdentifier', defaultMessage: 'Attribute identifier is required.', }); } @@ -326,7 +335,7 @@ export default function AddEditAiVendor(props) { case 'connectorType': if (fieldValue.trim() === '') { error = intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.is.empty.error.connectorType', + id: 'AiServiceProviders.AddEditAiServiceProvider.is.empty.error.connectorType', defaultMessage: 'Connector type is required.', }); } @@ -334,7 +343,7 @@ export default function AddEditAiVendor(props) { case 'providerName': if (fieldValue.trim() === '') { error = intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.is.empty.error.providerName', + id: 'AiServiceProviders.AddEditAiServiceProvider.is.empty.error.providerName', defaultMessage: 'Provider name is required.', }); } @@ -365,7 +374,7 @@ export default function AddEditAiVendor(props) { setValidating(true); if (formHasErrors(true)) { Alert.error(intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.form.has.errors', + id: 'AiServiceProviders.AddEditAiServiceProvider.form.has.errors', defaultMessage: 'One or more fields contain errors.', })); return false; @@ -397,21 +406,21 @@ export default function AddEditAiVendor(props) { }; if (vendorId) { // <-- Use vendorId instead of id - await new API().updateAiVendor(vendorId, { ...newState, apiDefinition: file }); + await new API().updateAIServiceProvider(vendorId, { ...newState, apiDefinition: file }); Alert.success(`${state.name} ${intl.formatMessage({ id: 'AiVendor.edit.success', - defaultMessage: ' - AI/LLM Vendor edited successfully.', + defaultMessage: ' - AI Service Provider edited successfully.', })}`); } else { - await new API().addAiVendor({ ...newState, apiDefinition: file }); + await new API().addAIServiceProvider({ ...newState, apiDefinition: file }); Alert.success(`${state.name} ${intl.formatMessage({ id: 'AiVendor.add.success.msg', - defaultMessage: ' - AI/LLM Vendor added successfully.', + defaultMessage: ' - AI Service Provider added successfully.', })}`); } setSaving(false); - history.push('/settings/ai-vendors/'); + history.push('/settings/ai-service-providers/'); } catch (e) { if (e.message) { Alert.error(e.message); @@ -467,6 +476,7 @@ export default function AddEditAiVendor(props) { /** * Handles changes to the API key identifier input. * Updates headerName or queryParameterName in authConfig.parameters. + * @param {Event} e - The input change event */ const handleApiKeyIdentifierChange = (e) => { const { value } = e.target; @@ -483,6 +493,29 @@ export default function AddEditAiVendor(props) { }, })); }; + + // Show loading spinner while fetching data in edit mode + if (loading) { + return ( + } + > + + + + + ); + } + return ( @@ -507,11 +540,11 @@ export default function AddEditAiVendor(props) { color='inherit' variant='caption' component='p' - id='AiVendors.AddEditAiVendor.general.details.description.div' + id='AiServiceProviders.AddEditAiServiceProvider.general.details.description.div' > @@ -527,7 +560,7 @@ export default function AddEditAiVendor(props) { label={( @@ -544,8 +577,8 @@ export default function AddEditAiVendor(props) { })} error={hasErrors('name', state.name, validating)} helperText={hasErrors('name', state.name, validating) || intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.form.name.help', - defaultMessage: 'Name of the AI/LLM Vendor.', + id: 'AiServiceProviders.AddEditAiServiceProvider.form.name.help', + defaultMessage: 'Name of the AI Service Provider.', })} /> @@ -573,11 +606,14 @@ export default function AddEditAiVendor(props) { )} error={hasErrors('apiVersion', state.apiVersion, validating)} - helperText={hasErrors('apiVersion', state.apiVersion, validating) + helperText={ + hasErrors('apiVersion', state.apiVersion, validating) || intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.form.displayName.help', - defaultMessage: 'API Version of the AI/LLM Vendor.', - })} + id: 'AiServiceProviders.AddEditAiServiceProvider.' + + 'form.apiVersion.help', + defaultMessage: 'API Version of the AI Service Provider.', + }) + } /> @@ -592,7 +628,7 @@ export default function AddEditAiVendor(props) { name='description' label={( )} @@ -604,8 +640,8 @@ export default function AddEditAiVendor(props) { value: e.target.value, })} helperText={intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.form.description.help', - defaultMessage: 'Description of the AI/LLM Vendor.', + id: 'AiServiceProviders.AddEditAiServiceProvider.form.description.help', + defaultMessage: 'Description of the AI Service Provider.', })} /> @@ -620,62 +656,108 @@ export default function AddEditAiVendor(props) { color='inherit' variant='subtitle2' component='div' - id='AiVendors.AddEditAiVendor.provider.configurations.header' + id='AiServiceProviders.AddEditAiServiceProvider.model.providers.header' > - {state.multipleModelProviderSupport ? ( - dispatch({ - field: 'models', - value: newModels, - })} - hasErrors={hasErrors} - validating={validating} - /> - ) : ( - - - { - const updatedList = [...state.modelList, model]; - dispatch({ field: 'modelList', value: updatedList }); - }} - onDeleteChip={(model) => { - const filteredModelList = state.modelList.filter( - (modelItem) => modelItem !== model, - ); - dispatch({ field: 'modelList', value: filteredModelList }); + + + + { + const isMultiple = e.target.value === 'true'; + dispatch({ + field: 'multipleModelProviderSupport', + value: isMultiple, + }); + // Clear the state when switching modes (only if not in edit mode) + if (!vendorId) { + if (isMultiple) { + // Switching to multiple provider mode - clear single provider state + dispatch({ field: 'modelList', value: [] }); + } else { + // Switching to single provider mode - clear multiple provider state + dispatch({ field: 'models', value: [] }); + } + } }} - placeholder={intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.modelList.placeholder', - defaultMessage: 'Type Model name and press Enter', - })} - data-testid='ai-vendor-llm-model-list' - /> + sx={{ display: 'flex', flexDirection: 'row' }} + > + } + label={intl.formatMessage({ + id: 'AiServiceProviders.AddEditAiServiceProvider.single.model.provider', + defaultMessage: 'Single Model Provider', + })} + disabled={!!vendorId} + /> + } + label={intl.formatMessage({ + id: 'AiServiceProviders.AddEditAiServiceProvider.multi.model.provider', + defaultMessage: 'Multi Model Provider', + })} + disabled={!!vendorId} + /> + - - )} + + {state.multipleModelProviderSupport ? ( + dispatch({ + field: 'models', + value: newModels, + })} + hasErrors={hasErrors} + validating={validating} + /> + ) : ( + { + const updatedList = [...state.modelList, model]; + dispatch({ field: 'modelList', value: updatedList }); + }} + onDeleteChip={(model) => { + const filteredModelList = state.modelList.filter( + (modelItem) => modelItem !== model, + ); + dispatch({ field: 'modelList', value: filteredModelList }); + }} + placeholder={intl.formatMessage({ + id: 'AiServiceProviders.AddEditAiServiceProvider.modelList.placeholder', + defaultMessage: 'Type Model name and press Enter', + })} + data-testid='ai-vendor-llm-model-list' + /> + )} + + + <> @@ -692,7 +774,7 @@ export default function AddEditAiVendor(props) { id='llm-configurations' > @@ -700,10 +782,10 @@ export default function AddEditAiVendor(props) { color='inherit' variant='caption' component='p' - id='AddEditAiVendor.External.AiVendor.configurations.llm.container' + id='AddEditAiServiceProvider.External.AiVendor.configurations.llm.container' > @@ -718,13 +800,13 @@ export default function AddEditAiVendor(props) { variant='subtitle2' component='div' id={ - 'AiVendors.AddEditAiVendor.llm.configuration.' + 'AiServiceProviders.AddEditAiServiceProvider.llm.configuration.' + `${metadata.attributeName}.div` } > @@ -818,11 +900,11 @@ export default function AddEditAiVendor(props) { color='inherit' variant='caption' component='p' - id='AiVendors.AddEditAiVendor.apiDefinition.body' + id='AiServiceProviders.AddEditAiServiceProvider.apiDefinition.body' > @@ -852,7 +934,7 @@ export default function AddEditAiVendor(props) { id='llm-auth-configurations' > @@ -860,10 +942,10 @@ export default function AddEditAiVendor(props) { color='inherit' variant='caption' component='p' - id='AddEditAiVendor.External.AiVendor.configurations.llm.auth.container' + id='AddEditAiServiceProvider.External.AiVendor.configurations.llm.auth.container' > @@ -994,22 +1076,22 @@ export default function AddEditAiVendor(props) { color='inherit' variant='subtitle2' component='div' - id='AiVendors.AddEditAiVendor.connectorType.header' + id='AiServiceProviders.AddEditAiServiceProvider.connectorType.header' > @@ -1022,7 +1104,7 @@ export default function AddEditAiVendor(props) { label={( @@ -1043,8 +1125,8 @@ export default function AddEditAiVendor(props) { state.configurations.connectorType, validating, ) || intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.form.connectorType.help', - defaultMessage: 'Connector Type for AI/LLM Vendor', + id: 'AiServiceProviders.AddEditAiServiceProvider.form.connectorType.help', + defaultMessage: 'Connector Type for AI Service Provider', })} /> @@ -1067,12 +1149,12 @@ export default function AddEditAiVendor(props) { <> {vendorId ? ( ) : ( )} @@ -1080,10 +1162,10 @@ export default function AddEditAiVendor(props) { )} - + diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AIAPIDefinition.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AiApiDefinition.jsx similarity index 97% rename from portals/admin/src/main/webapp/source/src/app/components/AiVendors/AIAPIDefinition.jsx rename to portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AiApiDefinition.jsx index a3ea9d6952e..77a1eb579c7 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/AIAPIDefinition.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AiApiDefinition.jsx @@ -35,7 +35,7 @@ const StyledSpan = styled('span')(({ theme }) => ({ color: theme.palette.error.d * @param {JSON} props Input props form parent components. * @returns {JSX} AI Vendor API Definition manage UI. */ -export default function AIAPIDefinition(props) { +export default function AiApiDefinition(props) { const intl = useIntl(); const { apiDefinition, @@ -107,7 +107,7 @@ export default function AIAPIDefinition(props) { <> * diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/DeleteAiVendor.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/DeleteAiServiceProvider.jsx similarity index 77% rename from portals/admin/src/main/webapp/source/src/app/components/AiVendors/DeleteAiVendor.jsx rename to portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/DeleteAiServiceProvider.jsx index 73a7d21d2f0..5d5670a5135 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/DeleteAiVendor.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/DeleteAiServiceProvider.jsx @@ -29,16 +29,16 @@ import FormDialogBase from 'AppComponents/AdminPages/Addons/FormDialogBase'; * @param {JSON} props component props. * @returns {JSX} Loading animation. */ -function DeleteAiVendor({ updateList, dataRow }) { +function DeleteAiServiceProvider({ updateList, dataRow }) { const { id, builtInSupport } = dataRow; const intl = useIntl(); const formSaveCallback = () => { return new API() - .deleteAiVendor(id) + .deleteAIServiceProvider(id) .then(() => ( )) .catch((error) => { @@ -52,11 +52,11 @@ function DeleteAiVendor({ updateList, dataRow }) { return ( } @@ -69,17 +69,17 @@ function DeleteAiVendor({ updateList, dataRow }) { > ); } -DeleteAiVendor.propTypes = { +DeleteAiServiceProvider.propTypes = { updateList: PropTypes.func.isRequired, dataRow: PropTypes.shape({ id: PropTypes.string.isRequired, }).isRequired, }; -export default DeleteAiVendor; +export default DeleteAiServiceProvider; diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ListAiVendors.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/ListAiServiceProviders.jsx similarity index 81% rename from portals/admin/src/main/webapp/source/src/app/components/AiVendors/ListAiVendors.jsx rename to portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/ListAiServiceProviders.jsx index 923d6aca196..d5ead787e8a 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ListAiVendors.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/ListAiServiceProviders.jsx @@ -22,11 +22,11 @@ import React, { useEffect, useState } from 'react'; import API from 'AppData/api'; import { useIntl, FormattedMessage } from 'react-intl'; import Typography from '@mui/material/Typography'; -import DeleteAiVendor from 'AppComponents/AiVendors/DeleteAiVendor'; +import DeleteAiServiceProvider from 'AppComponents/AiServiceProviders/DeleteAiServiceProvider'; import { Link as RouterLink } from 'react-router-dom'; import Alert from '@mui/material/Alert'; import Button from '@mui/material/Button'; -import { Menu, MenuItem, styled } from '@mui/material'; +import { styled } from '@mui/material'; import ContentBase from 'AppComponents/AdminPages/Addons/ContentBase'; import Toolbar from '@mui/material/Toolbar'; import Grid from '@mui/material/Grid'; @@ -41,7 +41,6 @@ import InlineProgress from 'AppComponents/AdminPages/Addons/InlineProgress'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import CardActions from '@mui/material/CardActions'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; const styles = { searchBar: { @@ -70,33 +69,19 @@ const StyledDiv = styled('div')({}); * Render a list * @returns {JSX} Header AppBar components. */ -export default function ListAiVendors() { +export default function ListAiServiceProviders() { // eslint-disable-next-line no-unused-vars const intl = useIntl(); const [aiVendorsList, setAiVendorsList] = useState(null); const [searchText, setSearchText] = useState(''); const [error, setError] = useState(null); - const [anchorEl, setAnchorEl] = useState(null); - - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleMenuItemClick = () => { - setAnchorEl(null); - }; const fetchData = async () => { // Fetch data from backend when an apiCall is provided setAiVendorsList(null); try { - const result = await new API().getAiVendorsList(); - console.log(result); + const result = await new API().getAiServiceProviderList(); if (result) { setError(null); setAiVendorsList(result.body.list); @@ -114,44 +99,18 @@ export default function ListAiVendors() { const addCreateButton = () => { return ( - <> - - - handleMenuItemClick(false)} - component={RouterLink} - to={{ pathname: '/settings/ai-vendors/create', state: { isSingleProvider: true } }} - data-testid='add-single-provider-vendor-menu-item' - > - Add Single Provider Vendor - - handleMenuItemClick(true)} - component={RouterLink} - to={{ pathname: '/settings/ai-vendors/create', state: { isSingleProvider: false } }} - data-testid='add-multi-provider-vendor-menu-item' - > - Add Multi-Provider Vendor - - - + ); }; @@ -159,8 +118,8 @@ export default function ListAiVendors() { { name: 'name', label: intl.formatMessage({ - id: 'AiVendors.ListAiVendors.table.header.label.aiVendorName', - defaultMessage: 'AI/LLM Vendor Name', + id: 'AiServiceProviders.ListAiServiceProviders.table.header.label.aiVendorName', + defaultMessage: 'AI Service Provider Name', }), options: { customBodyRender: (value, tableMeta) => { @@ -170,7 +129,7 @@ export default function ListAiVendors() { return ( {value} @@ -185,7 +144,7 @@ export default function ListAiVendors() { { name: 'description', label: intl.formatMessage({ - id: 'AiVendors.ListAiVendors.table.header.label.description', + id: 'AiServiceProviders.ListAiServiceProviders.table.header.label.description', defaultMessage: 'Description', }), options: { @@ -217,7 +176,7 @@ export default function ListAiVendors() { { name: 'apiVersion', label: intl.formatMessage({ - id: 'AiVendors.ListAiVendors.table.header.label.apiVersion', + id: 'AiServiceProviders.ListAiServiceProviders.table.header.label.apiVersion', defaultMessage: 'API Version', }), options: { @@ -227,7 +186,7 @@ export default function ListAiVendors() { { name: 'builtInSupport', label: intl.formatMessage({ - id: 'AiVendors.ListAiVendors.table.header.label.builtInSupport', + id: 'AiServiceProviders.ListAiServiceProviders.table.header.label.builtInSupport', defaultMessage: 'Type', }), options: { @@ -254,8 +213,8 @@ export default function ListAiVendors() { let tooltipTitle = ''; if (dataRow.builtInSupport) { tooltipTitle = intl.formatMessage({ - id: 'AiVendors.ListAiVendors.table.is.used.delete.tooltip', - defaultMessage: 'Default AI/LLM Vendors cannot be deleted', + id: 'AiServiceProviders.ListAiServiceProviders.table.is.used.delete.tooltip', + defaultMessage: 'Default AI Service Providers cannot be deleted', }); } return ( @@ -264,7 +223,7 @@ export default function ListAiVendors() { title={tooltipTitle} > - @@ -281,8 +240,8 @@ export default function ListAiVendors() { const pageProps = { pageStyle: 'paperLess', title: intl.formatMessage({ - id: 'AiVendors.ListAiVendors.List.title', - defaultMessage: 'AI/LLM Vendors', + id: 'AiServiceProviders.ListAiServiceProviders.List.title', + defaultMessage: 'AI Service Providers', }), }; @@ -296,7 +255,7 @@ export default function ListAiVendors() { > ), @@ -305,11 +264,11 @@ export default function ListAiVendors() { gutterBottom variant='h5' component='h2' - id='AiVendors.ListAiVendors.empty.header' + id='AiServiceProviders.ListAiServiceProviders.empty.header' > ), diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/ModelProviders.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/ModelProviders.jsx new file mode 100644 index 00000000000..b222261d506 --- /dev/null +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/ModelProviders.jsx @@ -0,0 +1,190 @@ +/* + * + * Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import React from 'react'; +import { useIntl, FormattedMessage } from 'react-intl'; +import PropTypes from 'prop-types'; +import { v4 as uuidv4 } from 'uuid'; +import { + Box, Grid, Button, TextField, + IconButton, Tooltip, +} from '@mui/material'; +import AddCircle from '@mui/icons-material/AddCircle'; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import { MuiChipsInput } from 'mui-chips-input'; + +const ModelProviders = ({ + models, + onModelsChange, + hasErrors, + validating, +}) => { + const intl = useIntl(); + + /** + * Handles adding a new model family. + * Creates a new model object with a unique ID and empty multipleModelProviderSupport and values. + */ + const handleAddProvider = () => { + const newModel = { + id: uuidv4(), + name: '', + models: [], + }; + onModelsChange([...models, newModel]); + }; + + /** + * Handles deleting a model family. + * @param {*} event - The event object from the click event. + */ + const handleProviderDelete = (event) => { + const index = event.currentTarget.id; + const updatedModels = models.filter((_, i) => i !== parseInt(index, 10)); + onModelsChange(updatedModels); + }; + + /** + * Handles changes to the provider name of a model family. + * @param {*} id - The ID of the model family. + * @param {*} event - The event object from the change event. + */ + const handleProviderChange = (id, event) => { + const updatedModels = models.map((model) => { + if (model.id === id) { + return { + ...model, + name: event.target.value, + }; + } + return model; + }); + onModelsChange(updatedModels); + }; + + return ( + + + + + {models.map((model, index) => ( + + + handleProviderChange(model.id, e)} + error={hasErrors('providerName', model.name, validating)} + /> + + + { + const updatedModels = models.map((m) => { + if (m.id === model.id) { + return { + ...m, + models: [...m.models, modelName], + }; + } + return m; + }); + onModelsChange(updatedModels); + }} + onDeleteChip={(modelName) => { + const updatedModels = models.map((m) => { + if (m.id === model.id) { + return { + ...m, + models: m.models.filter( + (v) => v !== modelName, + ), + }; + } + return m; + }); + onModelsChange(updatedModels); + }} + placeholder={intl.formatMessage({ + id: 'AiServiceProviders.ModelProviders.model.provider.models.placeholder', + defaultMessage: 'Type Model name and press Enter', + })} + data-testid={`ai-vendor-llm-models-${index}`} + /> + + + + )} + placement='right-end' + interactive + > + + + + + + + ))} + + ); +}; + +ModelProviders.propTypes = { + models: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string, + values: PropTypes.arrayOf(PropTypes.string), + })).isRequired, + onModelsChange: PropTypes.func.isRequired, + hasErrors: PropTypes.func.isRequired, + validating: PropTypes.bool.isRequired, +}; + +export default ModelProviders; diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/index.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/index.jsx new file mode 100755 index 00000000000..85733d48686 --- /dev/null +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/index.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Route, Switch, withRouter } from 'react-router-dom'; +import ResourceNotFound from 'AppComponents/Base/Errors/ResourceNotFound'; +import ListAiServiceProviders from './ListAiServiceProviders'; +import AddEditAiServiceProvider from './AddEditAiServiceProvider'; + +/** + * Render a list + * @returns {JSX} Header AppBar components. + */ +function AiServiceProviders() { + return ( + + + + + + + ); +} + +export default withRouter(AiServiceProviders); diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx deleted file mode 100644 index 03b1f0e2866..00000000000 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/ModelFamily.jsx +++ /dev/null @@ -1,194 +0,0 @@ -/* - * - * Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -import React from 'react'; -import { useIntl, FormattedMessage } from 'react-intl'; -import PropTypes from 'prop-types'; -import { v4 as uuidv4 } from 'uuid'; -import { - Box, Grid, Button, TextField, - IconButton, Tooltip, -} from '@mui/material'; -import AddCircle from '@mui/icons-material/AddCircle'; -import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; -import { MuiChipsInput } from 'mui-chips-input'; - -const ModelFamily = ({ - models, - onModelsChange, - hasErrors, - validating, -}) => { - const intl = useIntl(); - - /** - * Handles adding a new model family. - * Creates a new model object with a unique ID and empty multipleModelProviderSupport and values. - */ - const handleAddProvider = () => { - const newModel = { - id: uuidv4(), - name: '', - models: [], - }; - onModelsChange([...models, newModel]); - }; - - /** - * Handles deleting a model family. - * @param {*} event - The event object from the click event. - */ - const handleProviderDelete = (event) => { - const index = event.currentTarget.id; - const updatedModels = models.filter((_, i) => i !== parseInt(index, 10)); - onModelsChange(updatedModels); - }; - - /** - * Handles changes to the provider name of a model family. - * @param {*} id - The ID of the model family. - * @param {*} event - The event object from the change event. - */ - const handleProviderChange = (id, event) => { - const updatedModels = models.map((model) => { - if (model.id === id) { - return { - ...model, - name: event.target.value, - }; - } - return model; - }); - onModelsChange(updatedModels); - }; - - return ( - - - - - - - {models.map((model, index) => ( - - - handleProviderChange(model.id, e)} - error={hasErrors('providerName', model.name, validating)} - /> - - - { - const updatedModels = models.map((m) => { - if (m.id === model.id) { - return { - ...m, - models: [...m.models, modelName], - }; - } - return m; - }); - onModelsChange(updatedModels); - }} - onDeleteChip={(modelName) => { - const updatedModels = models.map((m) => { - if (m.id === model.id) { - return { - ...m, - models: m.models.filter( - (v) => v !== modelName, - ), - }; - } - return m; - }); - onModelsChange(updatedModels); - }} - placeholder={intl.formatMessage({ - id: 'AiVendors.AddEditAiVendor.provider.models.placeholder', - defaultMessage: 'Type Model name and press Enter', - })} - data-testid={`ai-vendor-llm-models-${index}`} - /> - - - - )} - placement='right-end' - interactive - > - - - - - - - ))} - - - - ); -}; - -ModelFamily.propTypes = { - models: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string, - values: PropTypes.arrayOf(PropTypes.string), - })).isRequired, - onModelsChange: PropTypes.func.isRequired, - hasErrors: PropTypes.func.isRequired, - validating: PropTypes.bool.isRequired, -}; - -export default ModelFamily; diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/index.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiVendors/index.jsx deleted file mode 100755 index 15f20fd306a..00000000000 --- a/portals/admin/src/main/webapp/source/src/app/components/AiVendors/index.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Route, Switch, withRouter } from 'react-router-dom'; -import ResourceNotFound from 'AppComponents/Base/Errors/ResourceNotFound'; -import ListAiVendors from './ListAiVendors'; -import AddEditAiVendor from './AddEditAiVendor'; - -/** - * Render a list - * @returns {JSX} Header AppBar components. - */ -function AiVendors() { - return ( - - - - - - - ); -} - -export default withRouter(AiVendors); diff --git a/portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx b/portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx index b7b754d76f3..486c9afe949 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx @@ -35,7 +35,7 @@ import AdvancedThrottlePolicies from 'AppComponents/Throttling/Advanced'; import CustomThrottlingPolicies from 'AppComponents/Throttling/Custom'; import TenantTheme from 'AppComponents/TenantTheme/UploadTheme'; import KeyManagers from 'AppComponents/KeyManagers'; -import AiVendors from 'AppComponents/AiVendors'; +import AiServiceProviders from 'AppComponents/AiServiceProviders'; import ListRoles from 'AppComponents//RolePermissions/ListRoles.jsx'; import TenantConfSave from 'AppComponents/AdvancedSettings/TenantConfSave'; import Policies from 'AppComponents/Governance/Policies'; @@ -273,30 +273,30 @@ const RouteMenuMapping = (intl) => [ ], }, { - id: 'Ai Vendors', + id: 'AI Service Providers', displayText: intl.formatMessage({ - id: 'Base.RouteMenuMapping.aivendors', - defaultMessage: 'AI/LLM Vendors', + id: 'Base.RouteMenuMapping.aiServiceProviders', + defaultMessage: 'AI Service Providers', }), - path: '/settings/ai-vendors', - component: AiVendors, + path: '/settings/ai-service-providers', + component: AiServiceProviders, icon: , addEditPageDetails: [ { - id: 'Add AI Vendor', + id: 'Add AI Service Provider', displayText: intl.formatMessage({ - id: 'Base.RouteMenuMapping.aivendors.items.Adding', - defaultMessage: 'Add AI/LLM Vendor', + id: 'Base.RouteMenuMapping.aiServiceProviders.items.Adding', + defaultMessage: 'Add AI Service Provider', }), - path: '/settings/ai-vendors/create', + path: '/settings/ai-service-providers/create', }, { - id: 'Edit AI Vendor', + id: 'Edit AI Service Provider', displayText: intl.formatMessage({ - id: 'Base.RouteMenuMapping.aivendors.items.Editing', - defaultMessage: 'Edit AI/LLM Vendor', + id: 'Base.RouteMenuMapping.aiServiceProviders.items.Editing', + defaultMessage: 'Edit AI Service Provider', }), - path: '/settings/ai-vendors/(.*?)$', + path: '/settings/ai-service-providers/(.*?)$', }, ], }, diff --git a/portals/admin/src/main/webapp/source/src/app/data/api.js b/portals/admin/src/main/webapp/source/src/app/data/api.js index fe7f0363410..9d670d1cf30 100644 --- a/portals/admin/src/main/webapp/source/src/app/data/api.js +++ b/portals/admin/src/main/webapp/source/src/app/data/api.js @@ -1168,9 +1168,9 @@ class API extends Resource { } /** - * Get list of AI Vendors Configured + * Get list of AI Service Providers Configured */ - getAiVendorsList() { + getAiServiceProviderList() { return this.client.then((client) => { return client.apis['AIServiceProviders'].getAIServiceProviders( this._requestMetaData(), @@ -1179,37 +1179,37 @@ class API extends Resource { } /** - * Get AI Vendor Configuration by id - * @param aiVendorId AI Vendor configuration id + * Get AI Service Provider Configuration by id + * @param aiServiceProviderId AI Service Provider id * @returns {*} */ - aiVendorGet(aiVendorId) { + getAiServiceProvider(aiServiceProviderId) { return this.client.then((client) => { return client.apis['AIServiceProvider'].getAIServiceProvider( - { aiServiceProviderId: aiVendorId }, + { aiServiceProviderId }, this._requestMetaData(), ); }); } /** - * Delete an AI Vendor - * @param aiVendorId AI Vendor configuration id + * Delete an AI Service Provider + * @param aiServiceProviderId AI Service Provider id * @returns {*} */ - deleteAiVendor(aiVendorId) { + deleteAIServiceProvider(aiServiceProviderId) { return this.client.then((client) => { return client.apis['AIServiceProvider'].deleteAIServiceProvider( - { aiServiceProviderId: aiVendorId }, + { aiServiceProviderId }, this._requestMetaData(), ); }); } /** - * Add an AI Vendor + * Add an AI Service Provider */ - addAiVendor(aiVendorBody) { + addAIServiceProvider(aiServiceProviderBody) { return this.client.then((client) => { const payload = { 'Content-Type': 'multipart/form-data', @@ -1217,8 +1217,8 @@ class API extends Resource { return client.apis['AIServiceProviders'].addAIServiceProvider( payload, { requestBody: { - ...aiVendorBody, - modelProviders: JSON.stringify(aiVendorBody.modelList) + ...aiServiceProviderBody, + modelProviders: JSON.stringify(aiServiceProviderBody.modelList) }}, this._requestMetaData(), ); @@ -1226,20 +1226,20 @@ class API extends Resource { } /** - * Update an AI Vendor + * Update an AI Service Provider */ - updateAiVendor(aiVendorId, aiVendorBody) { + updateAIServiceProvider(aiServiceProviderId, aiServiceProviderBody) { return this.client.then((client) => { const payload = { - aiServiceProviderId: aiVendorId, + aiServiceProviderId, 'Content-Type': 'multipart/form-data', }; return client.apis['AIServiceProvider'].updateAIServiceProvider( payload, { requestBody: { - ...aiVendorBody, - aiServiceProviderId: aiVendorId, - modelProviders: JSON.stringify(aiVendorBody.modelList) + ...aiServiceProviderBody, + aiServiceProviderId: aiServiceProviderId, + modelProviders: JSON.stringify(aiServiceProviderBody.modelList) }}, this._requestMetaData(), ); diff --git a/portals/admin/src/main/webapp/webpack.config.js b/portals/admin/src/main/webapp/webpack.config.js index bd33bd7a646..8578ce9c295 100644 --- a/portals/admin/src/main/webapp/webpack.config.js +++ b/portals/admin/src/main/webapp/webpack.config.js @@ -48,7 +48,7 @@ module.exports = function (env, args) { hot: true, devMiddleware: { index: false, - writeToDisk: true, + writeToDisk: false, publicPath: '/site/public/dist/', }, client: { From 51e2272ec3c232cf72a1a4ff7cbeecf198d75a60 Mon Sep 17 00:00:00 2001 From: Ashera Silva Date: Thu, 31 Jul 2025 01:12:21 +0530 Subject: [PATCH 8/9] Fix multi model provider add issue --- .../AddEditAiServiceProvider.jsx | 21 +++++++++++-------- .../main/webapp/source/src/app/data/api.js | 15 ++++++++++--- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx index 41618e2875a..c29f0ae28e1 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx @@ -397,22 +397,25 @@ export default function AddEditAiServiceProvider(props) { } else { models = [{ name: state.name, models: state.modelList }]; } - const newState = { - ...state, - configurations: updatedConfigurations, - // modelList: JSON.stringify(state.modelList), - // Stringify modelVendorEntries before sending to API - modelList: JSON.stringify(models), + + const requestPayload = { + name: state.name, + apiVersion: state.apiVersion, + description: state.description, + multipleModelProviderSupport: state.multipleModelProviderSupport, + configurations: JSON.stringify(updatedConfigurations), + modelList: models, + apiDefinition: file, }; - if (vendorId) { // <-- Use vendorId instead of id - await new API().updateAIServiceProvider(vendorId, { ...newState, apiDefinition: file }); + if (vendorId) { + await new API().updateAIServiceProvider(vendorId, requestPayload); Alert.success(`${state.name} ${intl.formatMessage({ id: 'AiVendor.edit.success', defaultMessage: ' - AI Service Provider edited successfully.', })}`); } else { - await new API().addAIServiceProvider({ ...newState, apiDefinition: file }); + await new API().addAIServiceProvider(requestPayload); Alert.success(`${state.name} ${intl.formatMessage({ id: 'AiVendor.add.success.msg', defaultMessage: ' - AI Service Provider added successfully.', diff --git a/portals/admin/src/main/webapp/source/src/app/data/api.js b/portals/admin/src/main/webapp/source/src/app/data/api.js index 9d670d1cf30..a194468f890 100644 --- a/portals/admin/src/main/webapp/source/src/app/data/api.js +++ b/portals/admin/src/main/webapp/source/src/app/data/api.js @@ -1217,7 +1217,12 @@ class API extends Resource { return client.apis['AIServiceProviders'].addAIServiceProvider( payload, { requestBody: { - ...aiServiceProviderBody, + name: aiServiceProviderBody.name, + apiVersion: aiServiceProviderBody.apiVersion, + description: aiServiceProviderBody.description, + multitpleModelProviderSupport: aiServiceProviderBody.multipleModelProviderSupport, + configurations: aiServiceProviderBody.configurations, + apiDefinition: aiServiceProviderBody.apiDefinition, modelProviders: JSON.stringify(aiServiceProviderBody.modelList) }}, this._requestMetaData(), @@ -1237,8 +1242,12 @@ class API extends Resource { return client.apis['AIServiceProvider'].updateAIServiceProvider( payload, { requestBody: { - ...aiServiceProviderBody, - aiServiceProviderId: aiServiceProviderId, + name: aiServiceProviderBody.name, + apiVersion: aiServiceProviderBody.apiVersion, + description: aiServiceProviderBody.description, + multitpleModelProviderSupport: aiServiceProviderBody.multipleModelProviderSupport, + configurations: aiServiceProviderBody.configurations, + apiDefinition: aiServiceProviderBody.apiDefinition, modelProviders: JSON.stringify(aiServiceProviderBody.modelList) }}, this._requestMetaData(), From 26ea7882c3cb761e7c00ab70809ef6025a8aee74 Mon Sep 17 00:00:00 2001 From: Tharindu Dharmarathna Date: Tue, 5 Aug 2025 00:40:36 +0530 Subject: [PATCH 9/9] add rest api changes --- .../src/main/webapp/source/dev/keys.json | 2 +- .../AddEditAiServiceProvider.jsx | 2 +- .../components/AiServiceProviders/index.jsx | 19 +++++++++++++++++++ .../main/webapp/source/src/app/data/api.js | 4 ++-- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/portals/admin/src/main/webapp/source/dev/keys.json b/portals/admin/src/main/webapp/source/dev/keys.json index 64db90c0920..7c325678552 100644 --- a/portals/admin/src/main/webapp/source/dev/keys.json +++ b/portals/admin/src/main/webapp/source/dev/keys.json @@ -1 +1 @@ -{"clientId":"B4OBGpJ6vx5UKw078qfkGBfQUa4a","clientName":"admin_webpack_dev","callBackURL":"https://localhost:8083/admin/services/auth/callback/login","clientSecret":"Qwe57yQorONvQ2lNEbSYfE8mjLEa","isSaasApplication":true,"appOwner":"admin","jsonString":"{\"grant_types\":\"authorization_code refresh_token\"}","jsonAppAttribute":"{}","applicationUUID":null,"tokenType":"DEFAULT"} \ No newline at end of file +{"clientId":"Xd75fiONwC3Ac44zARH1eDbC51Aa","clientName":"admin_webpack_dev","callBackURL":"https://localhost:8083/admin/services/auth/callback/login","clientSecret":"0lXy45lQW9ypFQ99xHAaLqez0gYa","isSaasApplication":true,"appOwner":"admin","jsonString":"{\"grant_types\":\"refresh_token authorization_code \"}","jsonAppAttribute":"{}","applicationUUID":null,"tokenType":null} \ No newline at end of file diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx index c29f0ae28e1..3db912595d8 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/AddEditAiServiceProvider.jsx @@ -204,7 +204,7 @@ export default function AddEditAiServiceProvider(props) { let models = []; let modelList = []; if (aiVendorBody.modelProviders) { - models = JSON.parse(aiVendorBody.modelProviders); + models = aiVendorBody.modelProviders; modelList = models.find((item) => item.name === aiVendorBody.name); modelList = modelList ? modelList.models : []; models = models.map((model) => ({ diff --git a/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/index.jsx b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/index.jsx index 85733d48686..d96dd13b9e1 100755 --- a/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/index.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AiServiceProviders/index.jsx @@ -1,3 +1,22 @@ +/* + * + * Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ import React from 'react'; import { Route, Switch, withRouter } from 'react-router-dom'; import ResourceNotFound from 'AppComponents/Base/Errors/ResourceNotFound'; diff --git a/portals/admin/src/main/webapp/source/src/app/data/api.js b/portals/admin/src/main/webapp/source/src/app/data/api.js index a194468f890..e36dc8e4af1 100644 --- a/portals/admin/src/main/webapp/source/src/app/data/api.js +++ b/portals/admin/src/main/webapp/source/src/app/data/api.js @@ -1220,7 +1220,7 @@ class API extends Resource { name: aiServiceProviderBody.name, apiVersion: aiServiceProviderBody.apiVersion, description: aiServiceProviderBody.description, - multitpleModelProviderSupport: aiServiceProviderBody.multipleModelProviderSupport, + multipleModelProviderSupport: aiServiceProviderBody.multipleModelProviderSupport, configurations: aiServiceProviderBody.configurations, apiDefinition: aiServiceProviderBody.apiDefinition, modelProviders: JSON.stringify(aiServiceProviderBody.modelList) @@ -1245,7 +1245,7 @@ class API extends Resource { name: aiServiceProviderBody.name, apiVersion: aiServiceProviderBody.apiVersion, description: aiServiceProviderBody.description, - multitpleModelProviderSupport: aiServiceProviderBody.multipleModelProviderSupport, + multipleModelProviderSupport: aiServiceProviderBody.multipleModelProviderSupport, configurations: aiServiceProviderBody.configurations, apiDefinition: aiServiceProviderBody.apiDefinition, modelProviders: JSON.stringify(aiServiceProviderBody.modelList)