diff --git a/dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java b/dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java index 8f88e214d9ca..b47f2d12a392 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java +++ b/dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java @@ -2,6 +2,7 @@ import com.dotcms.ai.model.OpenAIModel; import com.dotcms.ai.model.OpenAIModels; +import com.dotcms.ai.model.SimpleModel; import com.dotcms.http.CircuitBreakerUrl; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; @@ -15,7 +16,7 @@ import org.apache.commons.collections4.CollectionUtils; import java.time.Duration; -import java.util.HashSet; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; @@ -177,18 +178,23 @@ public List getOrPullSupportedModels() { * * @return a list of available model names */ - public List getAvailableModels() { - final Set configured = internalModels.entrySet().stream().flatMap(entry -> entry.getValue().stream()) + public List getAvailableModels() { + final Set configured = internalModels.entrySet() + .stream() + .flatMap(entry -> entry.getValue().stream()) .map(Tuple2::_2) - .flatMap(model -> model.getNames().stream()) + .flatMap(model -> model.getNames().stream().map(name -> new SimpleModel(name, model.getType()))) + .collect(Collectors.toSet()); + final Set supported = getOrPullSupportedModels() + .stream() + .map(SimpleModel::new) .collect(Collectors.toSet()); - final Set supported = new HashSet<>(getOrPullSupportedModels()); configured.retainAll(supported); - return configured.stream().sorted().collect(Collectors.toList()); + + return new ArrayList<>(configured); } private static CircuitBreakerUrl.Response fetchOpenAIModels(final AppConfig appConfig) { - final CircuitBreakerUrl.Response response = CircuitBreakerUrl.builder() .setMethod(CircuitBreakerUrl.Method.GET) .setUrl(OPEN_AI_MODELS_URL) diff --git a/dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java b/dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java index 630ca4138a1e..ca315356009e 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java +++ b/dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java @@ -13,6 +13,7 @@ import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * The AppConfig class provides a configuration for the AI application. @@ -294,8 +295,13 @@ public static void debugLogger(final Class clazz, final Supplier mess } } + /** + * Checks if the configuration is enabled. + * + * @return true if the configuration is enabled, false otherwise + */ public boolean isEnabled() { - return StringUtils.isNotBlank(apiKey); + return Stream.of(apiUrl, apiImageUrl, apiEmbeddingsUrl, apiKey).allMatch(StringUtils::isNotBlank); } } diff --git a/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java b/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java index 2f8bcdcf1c55..d6a955294897 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java +++ b/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java @@ -22,12 +22,12 @@ public enum AppKeys { IMAGE_MODEL_TOKENS_PER_MINUTE("imageModelTokensPerMinute", "0"), IMAGE_MODEL_API_PER_MINUTE("imageModelApiPerMinute", "50"), IMAGE_MODEL_MAX_TOKENS("imageModelMaxTokens", "0"), - IMAGE_MODEL_COMPLETION("imageModelCompletion", "false"), + IMAGE_MODEL_COMPLETION("imageModelCompletion", AppKeys.FALSE), EMBEDDINGS_MODEL_NAMES("embeddingsModelNames", null), EMBEDDINGS_MODEL_TOKENS_PER_MINUTE("embeddingsModelTokensPerMinute", "1000000"), EMBEDDINGS_MODEL_API_PER_MINUTE("embeddingsModelApiPerMinute", "3000"), EMBEDDINGS_MODEL_MAX_TOKENS("embeddingsModelMaxTokens", "8191"), - EMBEDDINGS_MODEL_COMPLETION("embeddingsModelCompletion", "false"), + EMBEDDINGS_MODEL_COMPLETION("embeddingsModelCompletion", AppKeys.FALSE), EMBEDDINGS_SPLIT_AT_TOKENS("com.dotcms.ai.embeddings.split.at.tokens", "512"), EMBEDDINGS_MINIMUM_TEXT_LENGTH_TO_INDEX("com.dotcms.ai.embeddings.minimum.text.length", "64"), EMBEDDINGS_MINIMUM_FILE_SIZE_TO_INDEX("com.dotcms.ai.embeddings.minimum.file.size", "1024"), @@ -39,7 +39,7 @@ public enum AppKeys { EMBEDDINGS_CACHE_TTL_SECONDS("com.dotcms.ai.embeddings.cache.ttl.seconds", "600"), EMBEDDINGS_CACHE_SIZE("com.dotcms.ai.embeddings.cache.size", "1000"), EMBEDDINGS_DB_DELETE_OLD_ON_UPDATE("com.dotcms.ai.embeddings.delete.old.on.update", "true"), - DEBUG_LOGGING("com.dotcms.ai.debug.logging", "false"), + DEBUG_LOGGING("com.dotcms.ai.debug.logging", AppKeys.FALSE), COMPLETION_TEMPERATURE("com.dotcms.ai.completion.default.temperature", "1"), COMPLETION_ROLE_PROMPT( "com.dotcms.ai.completion.role.prompt", @@ -52,6 +52,8 @@ public enum AppKeys { AI_MODELS_CACHE_TTL("com.dotcms.ai.models.supported.ttl", "28800"), AI_MODELS_CACHE_SIZE("com.dotcms.ai.models.supported.size", "64"); + private static final String FALSE = "false"; + public static final String APP_KEY = "dotAI"; public final String key; diff --git a/dotCMS/src/main/java/com/dotcms/ai/listener/EmbeddingContentListener.java b/dotCMS/src/main/java/com/dotcms/ai/listener/EmbeddingContentListener.java index 24f7b2b21b7c..16d4a5c26f0f 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/listener/EmbeddingContentListener.java +++ b/dotCMS/src/main/java/com/dotcms/ai/listener/EmbeddingContentListener.java @@ -83,7 +83,7 @@ private AppConfig getAppConfig(final String hostId) { final AppConfig appConfig = ConfigService.INSTANCE.config(host); if (!appConfig.isEnabled()) { - throw new DotRuntimeException("No API key found in app config"); + throw new DotRuntimeException("No API urls or API key found in app config"); } return appConfig; diff --git a/dotCMS/src/main/java/com/dotcms/ai/model/SimpleModel.java b/dotCMS/src/main/java/com/dotcms/ai/model/SimpleModel.java new file mode 100644 index 000000000000..c5486b61191f --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/model/SimpleModel.java @@ -0,0 +1,53 @@ +package com.dotcms.ai.model; + +import com.dotcms.ai.app.AIModelType; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Represents a simple model with a name and type. + * This class is immutable and uses Jackson annotations for JSON serialization and deserialization. + * + * @author vico + */ +public class SimpleModel implements Serializable { + + private final String name; + private final AIModelType type; + + @JsonCreator + public SimpleModel(@JsonProperty("name") final String name, @JsonProperty("type") final AIModelType type) { + this.name = name; + this.type = type; + } + + @JsonCreator + public SimpleModel(@JsonProperty("name") final String name) { + this(name, null); + } + + public String getName() { + return name; + } + + public AIModelType getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SimpleModel that = (SimpleModel) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/rest/CompletionsResource.java b/dotCMS/src/main/java/com/dotcms/ai/rest/CompletionsResource.java index 98591c6502f3..e7b62cf46712 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/rest/CompletionsResource.java +++ b/dotCMS/src/main/java/com/dotcms/ai/rest/CompletionsResource.java @@ -5,6 +5,7 @@ import com.dotcms.ai.app.AppConfig; import com.dotcms.ai.app.AppKeys; import com.dotcms.ai.app.ConfigService; +import com.dotcms.ai.model.SimpleModel; import com.dotcms.ai.rest.forms.CompletionsForm; import com.dotcms.ai.util.LineReadingOutputStream; import com.dotcms.rest.WebResource; @@ -118,7 +119,7 @@ public final Response getConfig(@Context final HttpServletRequest request, final String apiKey = UtilMethods.isSet(app.getApiKey()) ? "*****" : "NOT SET"; map.put(AppKeys.API_KEY.key, apiKey); - final List models = AIModels.get().getAvailableModels(); + final List models = AIModels.get().getAvailableModels(); map.put(AiKeys.AVAILABLE_MODELS, models); return Response.ok(map).build(); diff --git a/dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java b/dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java index daf29ec8b846..4e916e976db7 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java +++ b/dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java @@ -54,8 +54,7 @@ public static void doRequest(final String urlIn, final OutputStream out) { if (!appConfig.isEnabled()) { - Logger.debug(OpenAIRequest.class, "OpenAI is not enabled and will not send request."); - return; + throw new DotRuntimeException("OpenAI is not enabled and will not send request."); } final AIModel model = appConfig.resolveModelOrThrow(json.optString(AiKeys.MODEL)); diff --git a/dotCMS/src/main/webapp/html/portlet/ext/dotai/dotai.js b/dotCMS/src/main/webapp/html/portlet/ext/dotai/dotai.js index b76879dfd125..088436aef605 100644 --- a/dotCMS/src/main/webapp/html/portlet/ext/dotai/dotai.js +++ b/dotCMS/src/main/webapp/html/portlet/ext/dotai/dotai.js @@ -131,10 +131,13 @@ const writeModelToDropdown = async () => { } for (i = 0; i < dotAiState.config.availableModels.length; i++) { + if (dotAiState.config.availableModels[i].type !== 'TEXT') { + continue; + } const newOption = document.createElement("option"); - newOption.value = dotAiState.config.availableModels[i]; - newOption.text = `${dotAiState.config.availableModels[i]}` + newOption.value = dotAiState.config.availableModels[i].name; + newOption.text = `${dotAiState.config.availableModels[i].name}` if (dotAiState.config.availableModels[i] === dotAiState.config.model) { newOption.selected = true; newOption.text = `${dotAiState.config.availableModels[i]} (default)`