Skip to content

Commit

Permalink
Merge pull request #744 from Lakith-Rambukkanage/vMain-AI-UI
Browse files Browse the repository at this point in the history
Add AI API create flow and Backend Rate Limiting
  • Loading branch information
Lakith-Rambukkanage committed Sep 19, 2024
2 parents b321493 + 9f484d7 commit f668bc1
Show file tree
Hide file tree
Showing 13 changed files with 1,182 additions and 5 deletions.
23 changes: 22 additions & 1 deletion portals/publisher/src/main/webapp/site/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@
"Apis.APIProductCreateWrapper.error.errorMessage.create.api.product": "Something went wrong while adding the API Product",
"Apis.APIProductCreateWrapper.error.errorMessage.create.revision": "Something went wrong while creating the API Product Revision",
"Apis.APIProductCreateWrapper.error.errorMessage.deploy.revision": "Something went wrong while deploying the API Product Revision",
"Apis.Create.AIAPI.ApiCreateAIAPI.back": "Back",
"Apis.Create.AIAPI.ApiCreateAIAPI.cancel": "Cancel",
"Apis.Create.AIAPI.ApiCreateAIAPI.create": "Create",
"Apis.Create.AIAPI.ApiCreateAIAPI.heading": "Create an API using an AI provider API definition.",
"Apis.Create.AIAPI.ApiCreateAIAPI.next": "Next",
"Apis.Create.AIAPI.ApiCreateAIAPI.sub.heading": "Create an API using an existing AI provider API definition.",
"Apis.Create.AIAPI.ApiCreateAIAPI.wizard.one": "Provide AI provider API",
"Apis.Create.AIAPI.ApiCreateAIAPI.wizard.two": "Create API",
"Apis.Create.AIAPI.Steps.ProvideAIOpenAPI.AI.model": "API version",
"Apis.Create.AIAPI.Steps.ProvideAIOpenAPI.AI.model.empty": "No API Provider selected.",
"Apis.Create.AIAPI.Steps.ProvideAIOpenAPI.AI.model.helper": "Select API Model version for the API",
"Apis.Create.AIAPI.Steps.ProvideAIOpenAPI.AI.model.placeholder": "Search API version",
"Apis.Create.AIAPI.Steps.ProvideAIOpenAPI.AI.provider": "API Provider",
"Apis.Create.AIAPI.Steps.ProvideAIOpenAPI.AI.provider.empty": "Loading API Providers...",
"Apis.Create.AIAPI.Steps.ProvideAIOpenAPI.AI.provider.helper.text": "Select AI API Provider for the API",
"Apis.Create.AIAPI.Steps.ProvideAIOpenAPI.AI.provider.placeholder": "Search AI API Provider",
"Apis.Create.APIProduct.APIProductCreateWrapper.back": "Back",
"Apis.Create.APIProduct.APIProductCreateWrapper.cancel": "Cancel",
"Apis.Create.APIProduct.APIProductCreateWrapper.create": "Create",
Expand Down Expand Up @@ -345,6 +361,8 @@
"Apis.Details.Configurartion.components.QueryAnalysis.update.complexity": "update complexity",
"Apis.Details.Configuration.ApiKeyHeader.helper.text": "ApiKey header name cannot contain spaces or special characters",
"Apis.Details.Configuration.AuthHeader.helper.text": "Authorization header name cannot contain spaces or special characters",
"Apis.Details.Configuration.Components.AI.BE.Rate.Limiting.prod": "Backend Rate Limiting",
"Apis.Details.Configuration.Components.AI.BE.Rate.Limiting.tooltip": "This option determines the type of Backend Rate Limiting that is applied to the API.",
"Apis.Details.Configuration.Components.APISecurity.Components.\n ApplicationLevel.Client.Websocket": "Client Websocket",
"Apis.Details.Configuration.Components.APISecurity.Components.\n ApplicationLevel.Websocket": "Application Level Security",
"Apis.Details.Configuration.Components.APISecurity.Components.ApplicationLevel.http": "Application Level Security",
Expand Down Expand Up @@ -1510,6 +1528,7 @@
"Apis.Details.TryOutConsole.token.helper": "Generate or provide an internal API Key",
"Apis.Details.TryOutConsole.token.label": "Internal API Key",
"Apis.Details.components.APIDetailsTopMenu.advertise.only.label": "Third Party",
"Apis.Details.components.APIDetailsTopMenu.ai.api.label": "AI API",
"Apis.Details.components.APIDetailsTopMenu.created.by": "Created by:",
"Apis.Details.components.APIDetailsTopMenu.current.api": "Current API",
"Apis.Details.components.APIDetailsTopMenu.error": "Something went wrong while downloading the API.",
Expand Down Expand Up @@ -1582,6 +1601,8 @@
"Apis.Listing.ApiThumb.owners.technical": "Technical",
"Apis.Listing.ApiThumb.version": "Version",
"Apis.Listing.Components.Create.API": "Create API",
"Apis.Listing.SampleAPI.SampleAPI.ai.api.create.title": "Create AI API",
"Apis.Listing.SampleAPI.SampleAPI.ai.api.import.content": "Create AI APIs by importing AI vendor APIs",
"Apis.Listing.SampleAPI.SampleAPI.create.new": "Let’s get started !",
"Apis.Listing.SampleAPI.SampleAPI.create.new.description": "Choose your option to create an API",
"Apis.Listing.SampleAPI.SampleAPI.graphql.api": "GraphQL",
Expand Down Expand Up @@ -2030,4 +2051,4 @@
"upload.image": "Click or drag the image to upload.",
"upload.image.size.error": "Uploaded File is too large. Maximum file size limit to 1MB",
"upload.image.size.info": "Maximum file size limit to 1MB"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. 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, { useReducer, useState } from 'react';
import PropTypes from 'prop-types';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import { FormattedMessage, useIntl } from 'react-intl';
import { usePublisherSettings } from 'AppComponents/Shared/AppContext';
import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepLabel from '@mui/material/StepLabel';
import Button from '@mui/material/Button';
import { Link } from 'react-router-dom';
import API from 'AppData/api';
import Alert from 'AppComponents/Shared/Alert';
import CircularProgress from '@mui/material/CircularProgress';
import DefaultAPIForm from 'AppComponents/Apis/Create/Components/DefaultAPIForm';
import APICreateBase from 'AppComponents/Apis/Create/Components/APICreateBase';
import ProvideAIOpenAPI from './Steps/ProvideAIOpenAPI';


/**
*
* Reduce the events triggered from API input fields to current state
* @param {*} currentState
* @param {*} inputAction
* @returns
*/
function apiInputsReducer(currentState, inputAction) {
const { action, value } = inputAction;
switch (action) {
case 'type':
case 'inputValue':
case 'name':
case 'version':
case 'endpoint':
case 'gatewayType':
case 'context':
case 'policies':
case 'llmProviderName':
case 'llmProviderApiVersion':
case 'isFormValid':
return { ...currentState, [action]: value };
case 'preSetAPI':
return {
...currentState,
name: value.name.replace(/[&/\\#,+()$~%.'":*?<>{}\s]/g, ''),
version: value.version,
context: value.context,
endpoint: value.endpoints && value.endpoints[0],
};
default:
return currentState;
}
}
/**
* Handle API creation from AI Provider API Definition.
*
* @export
* @param {*} props
* @returns
*/
export default function ApiCreateAIAPI(props) {
const [wizardStep, setWizardStep] = useState(0);
const { history, multiGateway } = props;
const { data: settings } = usePublisherSettings();

const [apiInputs, inputsDispatcher] = useReducer(apiInputsReducer, {
type: 'ApiCreateAIAPI',
inputValue: '',
formValidity: false,
});

const intl = useIntl();

/**
*
*
* @param {*} event
*/
function handleOnChange(event) {
const { name: action, value } = event.target;
inputsDispatcher({ action, value });
}

/**
*
* Set the validity of the API Inputs form
* @param {*} isValidForm
* @param {*} validationState
*/
function handleOnValidate(isFormValid) {
inputsDispatcher({
action: 'isFormValid',
value: isFormValid,
});
}

const [isCreating, setCreating] = useState();
/**
*
*
* @param {*} params
*/
function createAPI() {
setCreating(true);
const {
name, version, context, endpoint, gatewayType, policies = ["Unlimited"], inputValue,
llmProviderName, llmProviderApiVersion,
} = apiInputs;
let defaultGatewayType;
if (settings && settings.gatewayTypes.length === 1 && settings.gatewayTypes.includes('Regular')) {
defaultGatewayType = 'wso2/synapse';
} else if (settings && settings.gatewayTypes.length === 1 && settings.gatewayTypes.includes('APK')) {
defaultGatewayType = 'wso2/apk';
} else {
defaultGatewayType = 'default';
}

const additionalProperties = {
name,
version,
context,
gatewayType: defaultGatewayType === 'default' ? gatewayType : defaultGatewayType,
policies,
aiConfiguration: {
llmProviderName,
llmProviderApiVersion,
},
};
if (endpoint) {
additionalProperties.endpointConfig = {
endpoint_type: 'http',
sandbox_endpoints: {
url: endpoint,
},
production_endpoints: {
url: endpoint,
},
};
}
const newAPI = new API(additionalProperties);
const promisedResponse = newAPI.importOpenAPIByInlineDefinition(inputValue);
promisedResponse
.then((api) => {
Alert.info(intl.formatMessage({
id: 'Apis.Create.OpenAPI.ApiCreateOpenAPI.created.success',
defaultMessage: 'API created successfully',
}));
history.push(`/apis/${api.id}/overview`);
})
.catch((error) => {
if (error.response) {
Alert.error(error.response.body.description);
} else {
Alert.error(intl.formatMessage({
id: 'Apis.Create.OpenAPI.ApiCreateOpenAPI.created.error',
defaultMessage: 'Something went wrong while adding the API',
}));
}
console.error(error);
})
.finally(() => setCreating(false));
}

return (
<APICreateBase
title={(
<>
<Typography variant='h5'>
<FormattedMessage
id='Apis.Create.AIAPI.ApiCreateAIAPI.heading'
defaultMessage='Create an API using an AI provider API definition.'
/>
</Typography>
<Typography variant='caption'>
<FormattedMessage
id='Apis.Create.AIAPI.ApiCreateAIAPI.sub.heading'
defaultMessage='Create an API using an existing AI provider API definition.'
/>
</Typography>
</>
)}
>
<Box sx={{ mb: 2 }}>
<Stepper alternativeLabel activeStep={0}>
<Step>
<StepLabel>
<FormattedMessage
id='Apis.Create.AIAPI.ApiCreateAIAPI.wizard.one'
defaultMessage='Provide AI provider API'
/>
</StepLabel>
</Step>

<Step>
<StepLabel>
<FormattedMessage
id='Apis.Create.AIAPI.ApiCreateAIAPI.wizard.two'
defaultMessage='Create API'
/>
</StepLabel>
</Step>
</Stepper>
</Box>

<Grid container spacing={2}>
<Grid item xs={12}>
{wizardStep === 0 && (
<ProvideAIOpenAPI
onValidate={handleOnValidate}
apiInputs={apiInputs}
inputsDispatcher={inputsDispatcher}
/>
)}
{wizardStep === 1 && (
<DefaultAPIForm
onValidate={handleOnValidate}
onChange={handleOnChange}
multiGateway={multiGateway}
api={apiInputs}
isAPIProduct={false}
/>
)}
</Grid>
<Grid item xs={12}>
<Grid container direction='row' justifyContent='flex-start' alignItems='center' spacing={2}>
<Grid item>
{wizardStep === 0 && (
<Link to='/apis/'>
<Button>
<FormattedMessage
id='Apis.Create.AIAPI.ApiCreateAIAPI.cancel'
defaultMessage='Cancel'
/>
</Button>
</Link>
)}
{wizardStep === 1 && (
<Button onClick={() => setWizardStep((step) => step - 1)}>
<FormattedMessage
id='Apis.Create.AIAPI.ApiCreateAIAPI.back'
defaultMessage='Back'
/>
</Button>
)}
</Grid>
<Grid item>
{wizardStep === 0 && (
<Button
onClick={() => setWizardStep((step) => step + 1)}
variant='contained'
color='primary'
disabled={!apiInputs.isFormValid}
id='ai-api-create-next-btn'
>
<FormattedMessage
id='Apis.Create.AIAPI.ApiCreateAIAPI.next'
defaultMessage='Next'
/>
</Button>
)}
{wizardStep === 1 && (
<Button
variant='contained'
color='primary'
disabled={!apiInputs.isFormValid || isCreating}
onClick={createAPI}
id='ai-api-create-btn'
>
<FormattedMessage
id='Apis.Create.AIAPI.ApiCreateAIAPI.create'
defaultMessage='Create'
/>
{' '}
{isCreating && <CircularProgress size={24} />}
</Button>
)}
</Grid>
</Grid>
</Grid>
</Grid>
</APICreateBase>
);
}

ApiCreateAIAPI.propTypes = {
history: PropTypes.shape({ push: PropTypes.func }).isRequired,
multiGateway: PropTypes.string.isRequired,
};
Loading

0 comments on commit f668bc1

Please sign in to comment.