feat: Google AI integration (#29620)
## Description Here's PR for adding Google AI Gemini model as a data source integration. Features: 1. Text generation based on text inputs Fixes https://github.com/appsmithorg/appsmith/issues/29621 #### Type of change - New feature (non-breaking change which adds functionality) ## Testing #### How Has This Been Tested? - [x] Manual - [x] JUnit ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Integrated Google AI plugin for advanced AI functionality. - Added Google AI plugin to the available plugins. - Implemented new commands and utilities for Google AI services. - **Enhancements** - Expanded plugin constants to include Google AI references. - Developed a method strategy for Google AI plugin execution. - **Documentation** - Updated plugin properties to include Google AI plugin details. - **Database Changes** - Performed a database migration to add the Google AI plugin to existing workspaces. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
c1487a6125
commit
b4441969d0
|
|
@ -14,6 +14,7 @@ public interface PluginConstants {
|
|||
String GRAPH_QL_PLUGIN = "graphql-plugin";
|
||||
String OPEN_AI_PLUGIN = "openai-plugin";
|
||||
String ANTHROPIC_PLUGIN = "anthropic-plugin";
|
||||
String GOOGLE_AI_PLUGIN = "googleai-plugin";
|
||||
}
|
||||
|
||||
public static final String DEFAULT_REST_DATASOURCE = "DEFAULT_REST_DATASOURCE";
|
||||
|
|
@ -39,6 +40,7 @@ public interface PluginConstants {
|
|||
|
||||
public static final String OPEN_AI_PLUGIN_NAME = "Open AI";
|
||||
public static final String ANTHROPIC_PLUGIN_NAME = "Anthropic";
|
||||
public static final String GOOGLE_AI_PLUGIN_NAME = "Google AI";
|
||||
}
|
||||
|
||||
interface HostName {
|
||||
|
|
|
|||
108
app/server/appsmith-plugins/googleAiPlugin/pom.xml
Normal file
108
app/server/appsmith-plugins/googleAiPlugin/pom.xml
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.appsmith</groupId>
|
||||
<artifactId>appsmith-plugins</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.external.plugins</groupId>
|
||||
<artifactId>googleAiPlugin</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<name>googleAiPlugin</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson-bom.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
<version>${jackson-bom.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>${jackson-bom.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>32.0.1-jre</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Ideally this dependency should have been added with 'compile' scope here. But that is causing 'java.lang
|
||||
.NoClassDefFoundError'. After trying to fix it right way many times, I decided to move the dependency to
|
||||
the main server module's pom.xml file and keep the dependency here with 'provided' scope. This seems to
|
||||
fix the problem for now.
|
||||
-->
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.netty</groupId>
|
||||
<artifactId>reactor-netty-http</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
209
app/server/appsmith-plugins/googleAiPlugin/src/main/java/com/external/plugins/GoogleAiPlugin.java
vendored
Normal file
209
app/server/appsmith-plugins/googleAiPlugin/src/main/java/com/external/plugins/GoogleAiPlugin.java
vendored
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.dtos.ExecuteActionDTO;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.external.helpers.restApiUtils.connections.APIConnection;
|
||||
import com.appsmith.external.helpers.restApiUtils.helpers.RequestCaptureFilter;
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionRequest;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.ApiKeyAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.TriggerRequestDTO;
|
||||
import com.appsmith.external.models.TriggerResultDTO;
|
||||
import com.appsmith.external.plugins.BasePlugin;
|
||||
import com.appsmith.external.plugins.BaseRestApiPluginExecutor;
|
||||
import com.appsmith.external.services.SharedConfig;
|
||||
import com.external.plugins.commands.GoogleAICommand;
|
||||
import com.external.plugins.constants.GoogleAIConstants;
|
||||
import com.external.plugins.models.GoogleAIRequestDTO;
|
||||
import com.external.plugins.utils.GoogleAIMethodStrategy;
|
||||
import com.external.plugins.utils.RequestUtils;
|
||||
import com.google.gson.Gson;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.external.plugins.constants.GoogleAIConstants.BODY;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.GOOGLE_AI_API_ENDPOINT;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.LABEL;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.MODELS;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.VALUE;
|
||||
import static com.external.plugins.constants.GoogleAIErrorMessages.EMPTY_API_KEY;
|
||||
import static com.external.plugins.constants.GoogleAIErrorMessages.INVALID_API_KEY;
|
||||
import static com.external.plugins.constants.GoogleAIErrorMessages.QUERY_FAILED_TO_EXECUTE;
|
||||
|
||||
@Slf4j
|
||||
public class GoogleAiPlugin extends BasePlugin {
|
||||
public GoogleAiPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
public static class GoogleAiPluginExecutor extends BaseRestApiPluginExecutor {
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
protected GoogleAiPluginExecutor(SharedConfig sharedConfig) {
|
||||
super(sharedConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to fetch the models list from GoogleAI API and if request succeed, then datasource configuration is valid
|
||||
*/
|
||||
@Override
|
||||
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||
final ApiKeyAuth apiKeyAuth = (ApiKeyAuth) datasourceConfiguration.getAuthentication();
|
||||
if (!StringUtils.hasText(apiKeyAuth.getValue())) {
|
||||
return Mono.error(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, EMPTY_API_KEY));
|
||||
}
|
||||
|
||||
URI uri = UriComponentsBuilder.fromUriString(GOOGLE_AI_API_ENDPOINT)
|
||||
.path(MODELS)
|
||||
.build()
|
||||
.toUri();
|
||||
HttpMethod httpMethod = HttpMethod.GET;
|
||||
|
||||
return RequestUtils.makeRequest(httpMethod, uri, apiKeyAuth, BodyInserters.empty())
|
||||
.map(responseEntity -> {
|
||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
// valid credentials
|
||||
return new DatasourceTestResult();
|
||||
}
|
||||
return new DatasourceTestResult(INVALID_API_KEY);
|
||||
})
|
||||
.onErrorResume(error -> Mono.just(new DatasourceTestResult(
|
||||
"Error while trying to test the datasource configurations" + error.getMessage())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ActionExecutionResult> executeParameterized(
|
||||
APIConnection connection,
|
||||
ExecuteActionDTO executeActionDTO,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
// Get prompt from action configuration
|
||||
List<Map.Entry<String, String>> parameters = new ArrayList<>();
|
||||
|
||||
prepareConfigurationsForExecution(executeActionDTO, actionConfiguration, datasourceConfiguration);
|
||||
|
||||
// Initializing object for error condition
|
||||
ActionExecutionResult errorResult = new ActionExecutionResult();
|
||||
initUtils.initializeResponseWithError(errorResult);
|
||||
|
||||
GoogleAICommand googleAICommand = GoogleAIMethodStrategy.selectExecutionMethod(actionConfiguration, gson);
|
||||
googleAICommand.validateRequest(actionConfiguration);
|
||||
GoogleAIRequestDTO googleAIRequestDTO = googleAICommand.makeRequestBody(actionConfiguration);
|
||||
|
||||
URI uri = googleAICommand.createExecutionUri(actionConfiguration);
|
||||
HttpMethod httpMethod = googleAICommand.getExecutionMethod();
|
||||
ActionExecutionRequest actionExecutionRequest =
|
||||
RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, parameters, objectMapper);
|
||||
|
||||
final ApiKeyAuth apiKeyAuth = (ApiKeyAuth) datasourceConfiguration.getAuthentication();
|
||||
|
||||
if (!StringUtils.hasText(apiKeyAuth.getValue())) {
|
||||
ActionExecutionResult apiKeyNotPresentErrorResult = new ActionExecutionResult();
|
||||
apiKeyNotPresentErrorResult.setIsExecutionSuccess(false);
|
||||
apiKeyNotPresentErrorResult.setErrorInfo(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, EMPTY_API_KEY));
|
||||
return Mono.just(apiKeyNotPresentErrorResult);
|
||||
}
|
||||
|
||||
return RequestUtils.makeRequest(httpMethod, uri, apiKeyAuth, BodyInserters.fromValue(googleAIRequestDTO))
|
||||
.flatMap(responseEntity -> {
|
||||
HttpStatusCode statusCode = responseEntity.getStatusCode();
|
||||
|
||||
ActionExecutionResult actionExecutionResult = new ActionExecutionResult();
|
||||
actionExecutionResult.setRequest(actionExecutionRequest);
|
||||
actionExecutionResult.setStatusCode(statusCode.toString());
|
||||
|
||||
if (HttpStatusCode.valueOf(401).isSameCodeAs(statusCode)) {
|
||||
actionExecutionResult.setIsExecutionSuccess(false);
|
||||
String errorMessage = "";
|
||||
if (responseEntity.getBody() != null && responseEntity.getBody().length > 0) {
|
||||
errorMessage = new String(responseEntity.getBody());
|
||||
}
|
||||
actionExecutionResult.setErrorInfo(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_AUTHENTICATION_ERROR, errorMessage));
|
||||
return Mono.just(actionExecutionResult);
|
||||
}
|
||||
|
||||
if (statusCode.is4xxClientError()) {
|
||||
actionExecutionResult.setIsExecutionSuccess(false);
|
||||
String errorMessage = "";
|
||||
if (responseEntity.getBody() != null && responseEntity.getBody().length > 0) {
|
||||
errorMessage = new String(responseEntity.getBody());
|
||||
}
|
||||
actionExecutionResult.setErrorInfo(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_DATASOURCE_ERROR, errorMessage));
|
||||
|
||||
return Mono.just(actionExecutionResult);
|
||||
}
|
||||
|
||||
Object body;
|
||||
try {
|
||||
body = objectMapper.readValue(responseEntity.getBody(), Object.class);
|
||||
actionExecutionResult.setBody(body);
|
||||
} catch (IOException ex) {
|
||||
actionExecutionResult.setIsExecutionSuccess(false);
|
||||
actionExecutionResult.setErrorInfo(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_JSON_PARSE_ERROR, BODY, ex.getMessage()));
|
||||
return Mono.just(actionExecutionResult);
|
||||
}
|
||||
|
||||
if (!statusCode.is2xxSuccessful()) {
|
||||
actionExecutionResult.setIsExecutionSuccess(false);
|
||||
actionExecutionResult.setErrorInfo(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR, QUERY_FAILED_TO_EXECUTE, body));
|
||||
return Mono.just(actionExecutionResult);
|
||||
}
|
||||
|
||||
actionExecutionResult.setIsExecutionSuccess(true);
|
||||
|
||||
return Mono.just(actionExecutionResult);
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
errorResult.setIsExecutionSuccess(false);
|
||||
log.error(
|
||||
"An error has occurred while trying to run the Google AI API query command with error {}",
|
||||
error.getMessage());
|
||||
if (!(error instanceof AppsmithPluginException)) {
|
||||
error = new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR, error.getMessage(), error);
|
||||
}
|
||||
errorResult.setErrorInfo(error);
|
||||
return Mono.just(errorResult);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<TriggerResultDTO> trigger(
|
||||
APIConnection connection, DatasourceConfiguration datasourceConfiguration, TriggerRequestDTO request) {
|
||||
return Mono.just(new TriggerResultDTO(getDataToMap(GoogleAIConstants.GOOGLE_AI_MODELS)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> validateDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||
return RequestUtils.validateApiKeyAuthDatasource(datasourceConfiguration);
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getDataToMap(List<String> data) {
|
||||
return data.stream().sorted().map(x -> Map.of(LABEL, x, VALUE, x)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
package com.external.plugins.commands;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.external.plugins.constants.GoogleAIConstants;
|
||||
import com.external.plugins.models.GoogleAIRequestDTO;
|
||||
import com.external.plugins.models.Role;
|
||||
import com.external.plugins.utils.RequestUtils;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.external.plugins.constants.GoogleAIConstants.COMPONENT;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.COMPONENT_DATA;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.CONTENT;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.DATA;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.GENERATE_CONTENT_MODEL;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.JSON;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.MESSAGES;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.ROLE;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.TYPE;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.VIEW_TYPE;
|
||||
import static com.external.plugins.constants.GoogleAIErrorMessages.EXECUTION_FAILURE;
|
||||
import static com.external.plugins.constants.GoogleAIErrorMessages.INCORRECT_MESSAGE_FORMAT;
|
||||
import static com.external.plugins.constants.GoogleAIErrorMessages.MODEL_NOT_SELECTED;
|
||||
import static com.external.plugins.constants.GoogleAIErrorMessages.QUERY_NOT_CONFIGURED;
|
||||
import static com.external.plugins.constants.GoogleAIErrorMessages.STRING_APPENDER;
|
||||
|
||||
public class GenerateContentCommand implements GoogleAICommand {
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Override
|
||||
public HttpMethod getTriggerHTTPMethod() {
|
||||
return HttpMethod.GET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpMethod getExecutionMethod() {
|
||||
return HttpMethod.POST;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be implemented in later stage once we integrate all the functions provided by Google AI
|
||||
*/
|
||||
@Override
|
||||
public URI createTriggerUri() {
|
||||
return URI.create("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI createExecutionUri(ActionConfiguration actionConfiguration) {
|
||||
return RequestUtils.createUriFromCommand(GoogleAIConstants.GENERATE_CONTENT, selectModel(actionConfiguration));
|
||||
}
|
||||
|
||||
private String selectModel(ActionConfiguration actionConfiguration) {
|
||||
if (actionConfiguration != null
|
||||
&& actionConfiguration.getFormData() != null
|
||||
&& actionConfiguration.getFormData().containsKey(GENERATE_CONTENT_MODEL)) {
|
||||
return ((Map<String, String>) actionConfiguration.getFormData().get(GENERATE_CONTENT_MODEL)).get(DATA);
|
||||
}
|
||||
// throw error if no model selected
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
"No generate content model is selected in the configuration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoogleAIRequestDTO makeRequestBody(ActionConfiguration actionConfiguration) {
|
||||
Map<String, Object> formData = actionConfiguration.getFormData();
|
||||
GoogleAIRequestDTO googleAIRequestDTO = new GoogleAIRequestDTO();
|
||||
List<Map<String, String>> messages = getMessages((Map<String, Object>) formData.get(MESSAGES));
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, INCORRECT_MESSAGE_FORMAT));
|
||||
}
|
||||
// as of today, we are going to support only text input to text output, so we will condense user messages in
|
||||
// a single content parts of request body
|
||||
List<GoogleAIRequestDTO.Part> userQueryParts = new ArrayList<>();
|
||||
for (Map<String, String> message : messages) {
|
||||
if (message.containsKey(ROLE) && message.containsKey(TYPE) && message.containsKey(CONTENT)) {
|
||||
String role = message.get(ROLE);
|
||||
String type = message.get(TYPE);
|
||||
String content = message.get(CONTENT);
|
||||
if (content.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Role.USER.getValue().equals(role)
|
||||
&& com.external.plugins.models.Type.TEXT.toString().equals(type)) {
|
||||
userQueryParts.add(new GoogleAIRequestDTO.Part(content));
|
||||
}
|
||||
}
|
||||
}
|
||||
// no content is configured completely
|
||||
if (userQueryParts.isEmpty()) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, INCORRECT_MESSAGE_FORMAT));
|
||||
}
|
||||
googleAIRequestDTO.setContents(List.of(new GoogleAIRequestDTO.Content(Role.USER, userQueryParts)));
|
||||
return googleAIRequestDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Place all necessary validation checks here
|
||||
*/
|
||||
@Override
|
||||
public void validateRequest(ActionConfiguration actionConfiguration) {
|
||||
Map<String, Object> formData = actionConfiguration.getFormData();
|
||||
if (CollectionUtils.isEmpty(formData)) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, QUERY_NOT_CONFIGURED));
|
||||
}
|
||||
|
||||
String model = RequestUtils.extractDataFromFormData(formData, GENERATE_CONTENT_MODEL);
|
||||
if (!StringUtils.hasText(model)) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, MODEL_NOT_SELECTED));
|
||||
}
|
||||
if (!formData.containsKey(MESSAGES) || formData.get(MESSAGES) == null) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, INCORRECT_MESSAGE_FORMAT));
|
||||
}
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getMessages(Map<String, Object> messages) {
|
||||
Type listType = new TypeToken<List<Map<String, String>>>() {}.getType();
|
||||
if (messages.containsKey(VIEW_TYPE)) {
|
||||
if (JSON.equals(messages.get(VIEW_TYPE))) {
|
||||
// data is present in data key as String
|
||||
return gson.fromJson((String) messages.get(DATA), listType);
|
||||
} else if (COMPONENT.equals(messages.get(VIEW_TYPE))) {
|
||||
return (List<Map<String, String>>) messages.get(COMPONENT_DATA);
|
||||
}
|
||||
}
|
||||
// return object stored in data key
|
||||
return (List<Map<String, String>>) messages.get(DATA);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.external.plugins.commands;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.external.plugins.models.GoogleAIRequestDTO;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public interface GoogleAICommand {
|
||||
HttpMethod getTriggerHTTPMethod();
|
||||
|
||||
HttpMethod getExecutionMethod();
|
||||
|
||||
URI createTriggerUri();
|
||||
|
||||
URI createExecutionUri(ActionConfiguration actionConfiguration);
|
||||
|
||||
GoogleAIRequestDTO makeRequestBody(ActionConfiguration actionConfiguration);
|
||||
|
||||
void validateRequest(ActionConfiguration actionConfiguration);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.external.plugins.constants;
|
||||
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GoogleAIConstants {
|
||||
public static final String GOOGLE_AI_API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta";
|
||||
public static final String MODELS = "/models";
|
||||
public static final String GENERATE_CONTENT_MODELS = "GENERATE_CONTENT_MODELS";
|
||||
public static final String GENERATE_CONTENT = "GENERATE_CONTENT";
|
||||
public static final String GENERATE_CONTENT_MODEL = "generateContentModel";
|
||||
public static final String GENERATE_CONTENT_ACTION = ":generateContent";
|
||||
public static final String COMMAND = "command";
|
||||
public static final String DATA = "data";
|
||||
public static final String VIEW_TYPE = "viewType";
|
||||
public static final String COMPONENT_DATA = "componentData";
|
||||
public static final String BODY = "body";
|
||||
public static final String ROLE = "role";
|
||||
public static final String TYPE = "type";
|
||||
public static final String CONTENT = "content";
|
||||
public static final String KEY = "key";
|
||||
public static final String MESSAGES = "messages";
|
||||
public static final String LABEL = "label";
|
||||
public static final String VALUE = "value";
|
||||
public static final List<String> GOOGLE_AI_MODELS = List.of("gemini-pro");
|
||||
public static final ExchangeStrategies EXCHANGE_STRATEGIES = ExchangeStrategies.builder()
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(/* 10MB */ 10 * 1024 * 1024))
|
||||
.build();
|
||||
public static final String JSON = "json";
|
||||
public static final String COMPONENT = "component";
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.external.plugins.constants;
|
||||
|
||||
public class GoogleAIErrorMessages {
|
||||
public static final String STRING_APPENDER = "%s %s";
|
||||
public static final String EXECUTION_FAILURE = "Query failed to execute because";
|
||||
public static final String QUERY_FAILED_TO_EXECUTE = "Your query failed to execute";
|
||||
public static final String MODEL_NOT_SELECTED = "model hasn't been selected. Please select a model";
|
||||
public static final String QUERY_NOT_CONFIGURED = "query is not configured.";
|
||||
public static final String INCORRECT_MESSAGE_FORMAT =
|
||||
"messages object is not correctly configured. Please provide a list of messages";
|
||||
public static final String EMPTY_API_KEY = "API key should not be empty. Please add an API key";
|
||||
public static final String INVALID_API_KEY =
|
||||
"Invalid authentication credentials provided in datasource configurations";
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.external.plugins.models;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class GoogleAIRequestDTO {
|
||||
List<Content> contents;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class Content {
|
||||
Role role;
|
||||
List<Part> parts;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class Part {
|
||||
String text;
|
||||
}
|
||||
}
|
||||
17
app/server/appsmith-plugins/googleAiPlugin/src/main/java/com/external/plugins/models/Role.java
vendored
Normal file
17
app/server/appsmith-plugins/googleAiPlugin/src/main/java/com/external/plugins/models/Role.java
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package com.external.plugins.models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum Role {
|
||||
USER("user");
|
||||
|
||||
private final String value;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
16
app/server/appsmith-plugins/googleAiPlugin/src/main/java/com/external/plugins/models/Type.java
vendored
Normal file
16
app/server/appsmith-plugins/googleAiPlugin/src/main/java/com/external/plugins/models/Type.java
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package com.external.plugins.models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum Type {
|
||||
TEXT("text");
|
||||
private final String value;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.external.plugins.utils;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.TriggerRequestDTO;
|
||||
import com.external.plugins.commands.GenerateContentCommand;
|
||||
import com.external.plugins.commands.GoogleAICommand;
|
||||
import com.external.plugins.constants.GoogleAIConstants;
|
||||
import com.google.gson.Gson;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.Exceptions;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static com.external.plugins.utils.RequestUtils.extractDataFromFormData;
|
||||
|
||||
public class GoogleAIMethodStrategy {
|
||||
public static GoogleAICommand selectTriggerMethod(TriggerRequestDTO triggerRequestDTO, Gson gson) {
|
||||
String requestType = triggerRequestDTO.getRequestType();
|
||||
|
||||
return switch (requestType) {
|
||||
case GoogleAIConstants.GENERATE_CONTENT_MODELS -> new GenerateContentCommand();
|
||||
default -> throw Exceptions.propagate(
|
||||
new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR));
|
||||
};
|
||||
}
|
||||
|
||||
public static GoogleAICommand selectExecutionMethod(ActionConfiguration actionConfiguration, Gson gson) {
|
||||
Map<String, Object> formData = actionConfiguration.getFormData();
|
||||
if (CollectionUtils.isEmpty(formData)) {
|
||||
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR);
|
||||
}
|
||||
|
||||
String command = extractDataFromFormData(formData, GoogleAIConstants.COMMAND);
|
||||
|
||||
return selectExecutionMethod(command);
|
||||
}
|
||||
|
||||
public static GoogleAICommand selectExecutionMethod(String command) {
|
||||
return switch (command) {
|
||||
case GoogleAIConstants.GENERATE_CONTENT -> new GenerateContentCommand();
|
||||
default -> throw Exceptions.propagate(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Unsupported command: " + command));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.external.plugins.utils;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.external.models.ApiKeyAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.external.plugins.constants.GoogleAIConstants;
|
||||
import com.external.plugins.constants.GoogleAIErrorMessages;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.BodyInserter;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.resources.ConnectionProvider;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.external.plugins.constants.GoogleAIConstants.GENERATE_CONTENT_ACTION;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.GOOGLE_AI_API_ENDPOINT;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.KEY;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.MODELS;
|
||||
|
||||
public class RequestUtils {
|
||||
|
||||
private static final WebClient webClient = createWebClient();
|
||||
|
||||
public static String extractDataFromFormData(Map<String, Object> formData, String key) {
|
||||
return (String) ((Map<String, Object>) formData.get(key)).get(GoogleAIConstants.DATA);
|
||||
}
|
||||
|
||||
public static String extractValueFromFormData(Map<String, Object> formData, String key) {
|
||||
return (String) formData.get(key);
|
||||
}
|
||||
|
||||
public static URI createUriFromCommand(String command, String model) {
|
||||
if (GoogleAIConstants.GENERATE_CONTENT.equals(command)) {
|
||||
return URI.create(GOOGLE_AI_API_ENDPOINT + MODELS + "/" + model + GENERATE_CONTENT_ACTION);
|
||||
} else {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Unsupported command: " + command);
|
||||
}
|
||||
}
|
||||
|
||||
public static Mono<ResponseEntity<byte[]>> makeRequest(
|
||||
HttpMethod httpMethod, URI uri, ApiKeyAuth apiKeyAuth, BodyInserter<?, ? super ClientHttpRequest> body) {
|
||||
|
||||
if (!StringUtils.hasText(apiKeyAuth.getValue())) {
|
||||
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, GoogleAIErrorMessages.EMPTY_API_KEY);
|
||||
}
|
||||
|
||||
return webClient
|
||||
.method(httpMethod)
|
||||
.uri(appendKeyInUri(apiKeyAuth.getValue(), uri))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(body)
|
||||
.exchangeToMono(clientResponse -> clientResponse.toEntity(byte[].class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add key query params in requests
|
||||
* @param apiKey - Google AI API Key
|
||||
* @param uri - Actual request URI
|
||||
*/
|
||||
private static URI appendKeyInUri(String apiKey, URI uri) {
|
||||
return UriComponentsBuilder.fromUri(uri).queryParam(KEY, apiKey).build().toUri();
|
||||
}
|
||||
|
||||
private static WebClient createWebClient() {
|
||||
// Initializing webClient to be used for http call
|
||||
WebClient.Builder webClientBuilder = WebClient.builder();
|
||||
return webClientBuilder
|
||||
.exchangeStrategies(GoogleAIConstants.EXCHANGE_STRATEGIES)
|
||||
.clientConnector(new ReactorClientHttpConnector(HttpClient.create(connectionProvider())))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static ConnectionProvider connectionProvider() {
|
||||
return ConnectionProvider.builder("googleAi")
|
||||
.maxConnections(100)
|
||||
.maxIdleTime(Duration.ofSeconds(60))
|
||||
.maxLifeTime(Duration.ofSeconds(60))
|
||||
.pendingAcquireTimeout(Duration.ofSeconds(30))
|
||||
.evictInBackground(Duration.ofSeconds(120))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Set<String> validateApiKeyAuthDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||
Set<String> invalids = new HashSet<>();
|
||||
final ApiKeyAuth apiKeyAuth = (ApiKeyAuth) datasourceConfiguration.getAuthentication();
|
||||
|
||||
if (apiKeyAuth == null || !StringUtils.hasText(apiKeyAuth.getValue())) {
|
||||
invalids.add(GoogleAIErrorMessages.EMPTY_API_KEY);
|
||||
}
|
||||
|
||||
return invalids;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"identifier": "CHAT",
|
||||
"controlType": "SECTION",
|
||||
"conditionals": {
|
||||
"show": "{{actionConfiguration.formData.command.data === 'GENERATE_CONTENT'}}"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"label": "Models",
|
||||
"tooltipText": "Select the model for content generation",
|
||||
"subtitle": "ID of the model to use.",
|
||||
"isRequired": true,
|
||||
"propertyName": "generate_content_model_id",
|
||||
"configProperty": "actionConfiguration.formData.generateContentModel.data",
|
||||
"controlType": "DROP_DOWN",
|
||||
"initialValue": "",
|
||||
"options": [],
|
||||
"placeholderText": "All models will be fetched.",
|
||||
"fetchOptionsConditionally": true,
|
||||
"setFirstOptionAsDefault": true,
|
||||
"alternateViewTypes": ["json"],
|
||||
"conditionals": {
|
||||
"enable": "{{true}}",
|
||||
"fetchDynamicValues": {
|
||||
"condition": "{{actionConfiguration.formData.command.data === 'GENERATE_CONTENT'}}",
|
||||
"config": {
|
||||
"params": {
|
||||
"requestType": "GENERATE_CONTENT_MODELS",
|
||||
"displayType": "DROP_DOWN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Messages",
|
||||
"tooltipText": "Ask a question",
|
||||
"subtitle": "A list of messages to generate the content",
|
||||
"propertyName": "messages",
|
||||
"isRequired": true,
|
||||
"configProperty": "actionConfiguration.formData.messages.data",
|
||||
"controlType": "ARRAY_FIELD",
|
||||
"addMoreButtonLabel": "Add message",
|
||||
"alternateViewTypes": ["json"],
|
||||
"schema": [
|
||||
{
|
||||
"label": "Role",
|
||||
"key": "role",
|
||||
"controlType": "DROP_DOWN",
|
||||
"initialValue": "user",
|
||||
"options": [
|
||||
{
|
||||
"label": "User",
|
||||
"value": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Type",
|
||||
"key": "type",
|
||||
"controlType": "DROP_DOWN",
|
||||
"initialValue": "text",
|
||||
"options": [
|
||||
{
|
||||
"label": "Text",
|
||||
"value": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Content",
|
||||
"key": "content",
|
||||
"controlType": "QUERY_DYNAMIC_INPUT_TEXT",
|
||||
"placeholderText": "{{ UserInput.text }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"editor": [
|
||||
{
|
||||
"controlType": "SECTION",
|
||||
"identifier": "SELECTOR",
|
||||
"children": [
|
||||
{
|
||||
"label": "Commands",
|
||||
"description": "Choose the method you would like to use",
|
||||
"configProperty": "actionConfiguration.formData.command.data",
|
||||
"controlType": "DROP_DOWN",
|
||||
"isRequired": true,
|
||||
"initialValue": "GENERATE_CONTENT",
|
||||
"options": [
|
||||
{
|
||||
"label": "Generate Content",
|
||||
"value": "GENERATE_CONTENT"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"generate.json"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"form": [
|
||||
{
|
||||
"sectionName": "Details",
|
||||
"id": 1,
|
||||
"children": [
|
||||
{
|
||||
"label": "Authentication type",
|
||||
"description": "Select the authentication type to use",
|
||||
"configProperty": "datasourceConfiguration.authentication.authenticationType",
|
||||
"controlType": "DROP_DOWN",
|
||||
"initialValue" : "apiKey",
|
||||
"setFirstOptionAsDefault": true,
|
||||
"options": [
|
||||
{
|
||||
"label": "API Key",
|
||||
"value": "apiKey"
|
||||
}
|
||||
],
|
||||
"isRequired": true
|
||||
},
|
||||
{
|
||||
"label": "API Key",
|
||||
"configProperty": "datasourceConfiguration.authentication.value",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"dataType": "PASSWORD",
|
||||
"initialValue": "",
|
||||
"isRequired": true,
|
||||
"encrypted": true
|
||||
},
|
||||
{
|
||||
"label": "Endpoint URL (with or without protocol and port no)",
|
||||
"configProperty": "datasourceConfiguration.url",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"initialValue": "https://generativelanguage.googleapis.com/",
|
||||
"isRequired": true,
|
||||
"hidden": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"formButton" : ["TEST", "CANCEL", "SAVE"]
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
plugin.id=googleai-plugin
|
||||
plugin.class=com.external.plugins.GoogleAiPlugin
|
||||
plugin.version=1.0-SNAPSHOT
|
||||
plugin.provider=tech@appsmith.com
|
||||
plugin.dependencies=
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"setting": [
|
||||
{
|
||||
"sectionName": "",
|
||||
"id": 1,
|
||||
"children": [
|
||||
{
|
||||
"label": "Run query on page load",
|
||||
"configProperty": "executeOnLoad",
|
||||
"controlType": "SWITCH",
|
||||
"subtitle": "Will refresh data each time the page is loaded"
|
||||
},
|
||||
{
|
||||
"label": "Request confirmation before running query",
|
||||
"configProperty": "confirmBeforeExecute",
|
||||
"controlType": "SWITCH",
|
||||
"subtitle": "Ask confirmation from the user each time before refreshing data"
|
||||
},
|
||||
{
|
||||
"label": "Query timeout (in milliseconds)",
|
||||
"subtitle": "Maximum time after which the query will return",
|
||||
"configProperty": "actionConfiguration.timeoutInMillisecond",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"initialValue": 60000,
|
||||
"dataType": "NUMBER"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.external.plugins.commands.GenerateContentCommand;
|
||||
import com.external.plugins.models.GoogleAIRequestDTO;
|
||||
import com.external.plugins.models.Role;
|
||||
import com.external.plugins.models.Type;
|
||||
import com.google.gson.Gson;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.external.plugins.constants.GoogleAIConstants.CONTENT;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.DATA;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.GENERATE_CONTENT_MODEL;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.MESSAGES;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.ROLE;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.TYPE;
|
||||
|
||||
public class GenerateContentCommandTest {
|
||||
GenerateContentCommand generateContentCommand = new GenerateContentCommand();
|
||||
|
||||
@Test
|
||||
public void testCreateTriggerUri() {
|
||||
Assertions.assertEquals(URI.create(""), generateContentCommand.createTriggerUri());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateExecutionUri() {
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setFormData(Map.of(GENERATE_CONTENT_MODEL, Map.of(DATA, "gemini-pro")));
|
||||
URI uri = generateContentCommand.createExecutionUri(actionConfiguration);
|
||||
Assertions.assertEquals(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent", uri.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMakeRequestBody_withValidData() {
|
||||
// Test with valid form data
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
Map<String, Object> formData = new HashMap<>();
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put(ROLE, Role.USER.toString());
|
||||
message.put(TYPE, Type.TEXT.toString());
|
||||
message.put(CONTENT, "Hello Gemini");
|
||||
formData.put(MESSAGES, Map.of(DATA, List.of(message)));
|
||||
actionConfiguration.setFormData(formData);
|
||||
GoogleAIRequestDTO googleAIRequestDTO = generateContentCommand.makeRequestBody(actionConfiguration);
|
||||
Assertions.assertEquals(
|
||||
"{\"contents\":[{\"role\":\"USER\",\"parts\":[{\"text\":\"Hello Gemini\"}]}]}",
|
||||
new Gson().toJson(googleAIRequestDTO));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.models.ApiKeyAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.TriggerRequestDTO;
|
||||
import com.appsmith.external.models.TriggerResultDTO;
|
||||
import com.appsmith.external.services.SharedConfig;
|
||||
import com.external.plugins.constants.GoogleAIConstants;
|
||||
import com.external.plugins.constants.GoogleAIErrorMessages;
|
||||
import mockwebserver3.MockResponse;
|
||||
import mockwebserver3.MockWebServer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.external.plugins.constants.GoogleAIConstants.LABEL;
|
||||
import static com.external.plugins.constants.GoogleAIConstants.VALUE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class GoogleAiPluginTest {
|
||||
private static MockWebServer mockEndpoint;
|
||||
|
||||
public static class MockSharedConfig implements SharedConfig {
|
||||
|
||||
@Override
|
||||
public int getCodecSize() {
|
||||
return 10 * 1024 * 1024;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxResponseSize() {
|
||||
return 10000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteExecutionUrl() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
GoogleAiPlugin.GoogleAiPluginExecutor pluginExecutor =
|
||||
new GoogleAiPlugin.GoogleAiPluginExecutor(new MockSharedConfig());
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws IOException {
|
||||
mockEndpoint = new MockWebServer();
|
||||
mockEndpoint.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws IOException {
|
||||
mockEndpoint.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDatasourceGivesNoInvalidsWhenConfiguredWithString() {
|
||||
ApiKeyAuth apiKeyAuth = new ApiKeyAuth();
|
||||
apiKeyAuth.setValue("apiKey");
|
||||
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setAuthentication(apiKeyAuth);
|
||||
|
||||
Set<String> invalids = pluginExecutor.validateDatasource(datasourceConfiguration);
|
||||
assertEquals(invalids.size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDatasourceGivesInvalids() {
|
||||
Set<String> invalids = pluginExecutor.validateDatasource(new DatasourceConfiguration());
|
||||
assertEquals(invalids.size(), 1);
|
||||
assertEquals(invalids, Set.of(GoogleAIErrorMessages.EMPTY_API_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyTestDatasourceReturnsFalse() {
|
||||
ApiKeyAuth apiKeyAuth = new ApiKeyAuth();
|
||||
apiKeyAuth.setValue("apiKey");
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setAuthentication(apiKeyAuth);
|
||||
|
||||
MockResponse mockResponse = new MockResponse();
|
||||
mockResponse.setResponseCode(401);
|
||||
mockEndpoint.enqueue(mockResponse);
|
||||
|
||||
Mono<DatasourceTestResult> datasourceTestResultMono = pluginExecutor.testDatasource(datasourceConfiguration);
|
||||
|
||||
StepVerifier.create(datasourceTestResultMono)
|
||||
.assertNext(datasourceTestResult -> {
|
||||
assertEquals(datasourceTestResult.getInvalids().size(), 1);
|
||||
assertFalse(datasourceTestResult.isSuccess());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyDatasourceTriggerResultsForChatModels() {
|
||||
ApiKeyAuth apiKeyAuth = new ApiKeyAuth();
|
||||
apiKeyAuth.setValue("apiKey");
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setAuthentication(apiKeyAuth);
|
||||
String responseBody = "[\"gemini-pro\"]";
|
||||
MockResponse mockResponse = new MockResponse().setBody(responseBody);
|
||||
mockResponse.setResponseCode(200);
|
||||
mockEndpoint.enqueue(mockResponse);
|
||||
|
||||
TriggerRequestDTO request = new TriggerRequestDTO();
|
||||
request.setRequestType(GoogleAIConstants.GENERATE_CONTENT_MODEL);
|
||||
Mono<TriggerResultDTO> datasourceTriggerResultMono =
|
||||
pluginExecutor.trigger(null, datasourceConfiguration, request);
|
||||
|
||||
StepVerifier.create(datasourceTriggerResultMono)
|
||||
.assertNext(result -> {
|
||||
assertTrue(result.getTrigger() instanceof List<?>);
|
||||
assertEquals(((List) result.getTrigger()).size(), 1);
|
||||
assertEquals(result.getTrigger(), getDataToMap(List.of("gemini-pro")));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getDataToMap(List<String> data) {
|
||||
return data.stream().sorted().map(x -> Map.of(LABEL, x, VALUE, x)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +62,7 @@
|
|||
|
||||
<module>openAiPlugin</module>
|
||||
<module>anthropicPlugin</module>
|
||||
<module>googleAiPlugin</module>
|
||||
|
||||
</modules>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package com.appsmith.server.migrations.db.ce;
|
||||
|
||||
import com.appsmith.external.constants.PluginConstants;
|
||||
import com.appsmith.external.models.PluginType;
|
||||
import com.appsmith.server.domains.Plugin;
|
||||
import io.mongock.api.annotations.ChangeUnit;
|
||||
import io.mongock.api.annotations.Execution;
|
||||
import io.mongock.api.annotations.RollbackExecution;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
|
||||
import static com.appsmith.server.migrations.DatabaseChangelog1.installPluginToAllWorkspaces;
|
||||
|
||||
@Slf4j
|
||||
@ChangeUnit(order = "038", id = "add-google-ai-plugin", author = " ")
|
||||
public class Migration038AddGoogleAIPlugin {
|
||||
private final MongoTemplate mongoTemplate;
|
||||
|
||||
public Migration038AddGoogleAIPlugin(MongoTemplate mongoTemplate) {
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
}
|
||||
|
||||
@RollbackExecution
|
||||
public void rollbackExecution() {}
|
||||
|
||||
@Execution
|
||||
public void addPluginToDbAndWorkspace() {
|
||||
Plugin plugin = new Plugin();
|
||||
plugin.setName(PluginConstants.PluginName.GOOGLE_AI_PLUGIN_NAME);
|
||||
plugin.setType(PluginType.AI);
|
||||
plugin.setPluginName(PluginConstants.PluginName.GOOGLE_AI_PLUGIN_NAME);
|
||||
plugin.setPackageName(PluginConstants.PackageName.GOOGLE_AI_PLUGIN);
|
||||
plugin.setUiComponent("UQIDbEditorForm");
|
||||
plugin.setDatasourceComponent("DbEditorForm");
|
||||
plugin.setResponseType(Plugin.ResponseType.JSON);
|
||||
plugin.setIconLocation("https://assets.appsmith.com/google-ai.svg");
|
||||
plugin.setDocumentationLink("https://docs.appsmith.com/connect-data/reference/google-ai");
|
||||
plugin.setDefaultInstall(true);
|
||||
try {
|
||||
mongoTemplate.insert(plugin);
|
||||
} catch (DuplicateKeyException e) {
|
||||
log.warn(plugin.getPackageName() + " already present in database.");
|
||||
}
|
||||
|
||||
if (plugin.getId() == null) {
|
||||
log.error("Failed to insert the Google AI plugin into the database.");
|
||||
}
|
||||
|
||||
installPluginToAllWorkspaces(mongoTemplate, plugin.getId());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user