This is an automated email from the ASF dual-hosted git repository. gongchao pushed a commit to branch ai-enhance in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
commit 331adc7b2563f8d419832b683b83fe3ac0e13a3b Author: tomsun28 <[email protected]> AuthorDate: Sat Oct 18 17:48:18 2025 +0800 [chore] refactor ai service Signed-off-by: tomsun28 <[email protected]> --- hertzbeat-ai-agent/pom.xml | 16 +- .../ai/agent/config/DynamicOpenAiApiKey.java | 43 +++-- .../hertzbeat/ai/agent/config/LlmConfig.java | 8 +- .../ai/agent/config/OpenAiYamlConfig.java | 43 ----- .../agent/controller/OpenAiConfigController.java | 160 ----------------- .../hertzbeat/ai/agent/dao/OpenAiConfigDao.java | 37 ---- ...Event.java => AiProviderConfigChangeEvent.java} | 8 +- ...enAiConfigDto.java => ModelProviderConfig.java} | 40 +++-- ...enAiConfigService.java => AiConfigService.java} | 34 +--- .../ai/agent/service/impl/AiConfigServiceImpl.java | 128 +++++++++++++ .../service/impl/ConversationServiceImpl.java | 8 +- .../service/impl/OpenAiConfigServiceImpl.java | 200 --------------------- .../common/constants/GeneralConfigTypeEnum.java | 7 +- .../controller/GeneralConfigController.java | 4 +- .../impl/ModelProviderConfigServiceImpl.java | 59 ++++++ .../src/main/resources/application-test.yml | 13 -- .../src/main/resources/application.yml | 21 --- home/docs/download.md | 2 +- .../current/download.md | 2 +- web-app/src/app/pojo/ModelProviderConfig.ts | 29 +++ web-app/src/app/service/general-config.service.ts | 9 + web-app/src/app/service/openai-config.service.ts | 58 ------ .../shared/components/ai-chat/chat.component.html | 10 +- .../shared/components/ai-chat/chat.component.ts | 81 +++------ 24 files changed, 337 insertions(+), 683 deletions(-) diff --git a/hertzbeat-ai-agent/pom.xml b/hertzbeat-ai-agent/pom.xml index b9d06e59c..9bb22303a 100644 --- a/hertzbeat-ai-agent/pom.xml +++ b/hertzbeat-ai-agent/pom.xml @@ -26,7 +26,7 @@ <artifactId>hertzbeat-ai-agent</artifactId> <version>${hertzbeat.version}</version> <properties> - <spring-ai.version>1.0.1</spring-ai.version> + <spring-ai.version>1.0.3</spring-ai.version> <java.version>17</java.version> </properties> @@ -51,12 +51,22 @@ </dependency> <dependency> <groupId>org.springframework.ai</groupId> - <artifactId>spring-ai-starter-model-openai</artifactId> + <artifactId>spring-ai-openai</artifactId> </dependency> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-client-chat</artifactId> + </dependency> <dependency> <groupId>org.apache.hertzbeat</groupId> <artifactId>hertzbeat-common</artifactId> </dependency> + <!-- common --> + <dependency> + <groupId>org.apache.hertzbeat</groupId> + <artifactId>hertzbeat-base</artifactId> + <scope>provided</scope> + </dependency> <dependency> <groupId>org.apache.hertzbeat</groupId> <artifactId>hertzbeat-alerter</artifactId> @@ -90,4 +100,4 @@ </plugins> </build> -</project> \ No newline at end of file +</project> diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/DynamicOpenAiApiKey.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/DynamicOpenAiApiKey.java index 2eaa6b912..bad25be51 100644 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/DynamicOpenAiApiKey.java +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/DynamicOpenAiApiKey.java @@ -18,40 +18,39 @@ package org.apache.hertzbeat.ai.agent.config; import lombok.extern.slf4j.Slf4j; -import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto; -import org.apache.hertzbeat.ai.agent.service.OpenAiConfigService; +import org.apache.hertzbeat.ai.agent.pojo.dto.ModelProviderConfig; +import org.apache.hertzbeat.base.dao.GeneralConfigDao; +import org.apache.hertzbeat.common.entity.manager.GeneralConfig; +import org.apache.hertzbeat.common.util.JsonUtil; +import org.jetbrains.annotations.NotNull; import org.springframework.ai.model.ApiKey; import org.springframework.stereotype.Component; /** - * Dynamic OpenAI API Key implementation that retrieves the API key - * from our configuration service (database first, then YAML fallback) + * Dynamic LLM Provider API Key implementation that retrieves the API key */ @Slf4j @Component public class DynamicOpenAiApiKey implements ApiKey { - private final OpenAiConfigService openAiConfigService; - - public DynamicOpenAiApiKey(OpenAiConfigService openAiConfigService) { - this.openAiConfigService = openAiConfigService; + private final GeneralConfigDao generalConfigDao; + + public DynamicOpenAiApiKey(GeneralConfigDao generalConfigDao) { + this.generalConfigDao = generalConfigDao; } + @NotNull @Override public String getValue() { - try { - OpenAiConfigDto effectiveConfig = openAiConfigService.getEffectiveConfig(); - - if (effectiveConfig != null && effectiveConfig.isEnable() && effectiveConfig.getApiKey() != null) { - log.debug("Retrieved OpenAI API key from configuration service"); - return effectiveConfig.getApiKey(); - } else { - log.warn("No valid OpenAI API key found in configuration"); - return null; - } - } catch (Exception e) { - log.error("Error retrieving OpenAI API key from configuration", e); - return null; + GeneralConfig providerConfig = generalConfigDao.findByType("provider"); + ModelProviderConfig modelProviderConfig = JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class); + + if (modelProviderConfig != null && modelProviderConfig.isEnable() && modelProviderConfig.isStatus()) { + log.debug("Retrieved {} API key from configuration service", modelProviderConfig.getCode()); + return modelProviderConfig.getApiKey(); + } else { + log.warn("No valid LLM Provider API key found in configuration"); + return ""; } } -} \ No newline at end of file +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/LlmConfig.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/LlmConfig.java index cfb3ecf52..ebe38342a 100644 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/LlmConfig.java +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/LlmConfig.java @@ -22,7 +22,6 @@ import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,9 +32,6 @@ import org.springframework.context.annotation.Configuration; @Configuration public class LlmConfig { - @Value("${spring.ai.openai.chat.options.model}") - private String model; - /** * Create OpenAI API instance with dynamic API key */ @@ -52,7 +48,7 @@ public class LlmConfig { @Bean public OpenAiChatOptions openAiChatOptions() { return OpenAiChatOptions.builder() - .model(model) + .model("model") .temperature(0.3) .build(); } @@ -73,4 +69,4 @@ public class LlmConfig { return ChatClient.create(openAiChatModel); } -} \ No newline at end of file +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/OpenAiYamlConfig.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/OpenAiYamlConfig.java deleted file mode 100644 index 3a0bf507d..000000000 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/config/OpenAiYamlConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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. - */ - -package org.apache.hertzbeat.ai.agent.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * OpenAI YAML Configuration - reads from spring.ai.openai.api-key - */ -@Data -@Component -@ConfigurationProperties(prefix = "spring.ai.openai") -public class OpenAiYamlConfig { - - /** - * OpenAI API key from spring.ai.openai.api-key - */ - private String apiKey; - - /** - * Check if OpenAI is enabled (has API key) - */ - public boolean isEnable() { - return apiKey != null && !apiKey.trim().isEmpty(); - } -} \ No newline at end of file diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/OpenAiConfigController.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/OpenAiConfigController.java deleted file mode 100644 index 195febf59..000000000 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/controller/OpenAiConfigController.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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. - */ - -package org.apache.hertzbeat.ai.agent.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto; -import org.apache.hertzbeat.ai.agent.service.OpenAiConfigService; -import org.springframework.http.ResponseEntity; - -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.HashMap; -import java.util.Map; - -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; - -/** - * OpenAI Configuration API - */ -@RestController -@RequestMapping(value = "/api/ai-agent/config", produces = {APPLICATION_JSON_VALUE}) -@Tag(name = "OpenAI Configuration API") -@Slf4j -public class OpenAiConfigController { - - private final OpenAiConfigService openAiConfigService; - - public OpenAiConfigController(OpenAiConfigService openAiConfigService) { - this.openAiConfigService = openAiConfigService; - } - - @PostMapping("/openai") - @Operation(summary = "Save OpenAI configuration", description = "Save or update OpenAI configuration") - public ResponseEntity<Map<String, Object>> saveOpenAiConfig(@Valid @RequestBody OpenAiConfigDto config) { - try { - Map<String, Object> response = new HashMap<>(); - - // Validate API key if enabled - if (config.isEnable() && config.getApiKey() != null && !config.getApiKey().trim().isEmpty()) { - OpenAiConfigService.ValidationResult validationResult = openAiConfigService.validateApiKey(config.getApiKey()); - - if (!validationResult.isValid()) { - log.warn("API key validation failed during save: {}", validationResult.getMessage()); - response.put("code", 1); - response.put("msg", "API key validation failed: " + validationResult.getMessage()); - return ResponseEntity.ok(response); - } - log.info("API key validation successful during save"); - } - - // Save the configuration - openAiConfigService.saveConfig(config); - - response.put("code", 0); - response.put("msg", "OpenAI configuration saved successfully"); - - return ResponseEntity.ok(response); - } catch (Exception e) { - log.error("Failed to save OpenAI configuration", e); - - Map<String, Object> response = new HashMap<>(); - response.put("code", 1); - response.put("msg", "Failed to save configuration: " + e.getMessage()); - - return ResponseEntity.ok(response); - } - } - - @GetMapping("/openai") - @Operation(summary = "Get OpenAI configuration", description = "Get current OpenAI configuration") - public ResponseEntity<Map<String, Object>> getOpenAiConfig() { - try { - OpenAiConfigDto config = openAiConfigService.getConfig(); - - Map<String, Object> response = new HashMap<>(); - response.put("code", 0); - response.put("data", config); - response.put("msg", "Success"); - - return ResponseEntity.ok(response); - } catch (Exception e) { - log.error("Failed to get OpenAI configuration", e); - - Map<String, Object> response = new HashMap<>(); - response.put("code", 1); - response.put("msg", "Failed to get configuration: " + e.getMessage()); - - return ResponseEntity.ok(response); - } - } - - @GetMapping("/openai/status") - @Operation(summary = "Check OpenAI configuration status", description = "Check if OpenAI is properly configured") - public ResponseEntity<Map<String, Object>> getOpenAiConfigStatus() { - try { - boolean configured = openAiConfigService.isConfigured(); - OpenAiConfigDto effectiveConfig = openAiConfigService.getEffectiveConfig(); - boolean hasDbConfig = openAiConfigService.getConfig() != null; - boolean hasYamlConfig = effectiveConfig != null && !hasDbConfig; - - // Validate the effective configuration - boolean validationPassed = false; - String validationMessage = "No configuration found"; - - if (effectiveConfig != null && effectiveConfig.isEnable() && effectiveConfig.getApiKey() != null && !effectiveConfig.getApiKey().trim().isEmpty()) { - OpenAiConfigService.ValidationResult validationResult = openAiConfigService.validateApiKey(effectiveConfig.getApiKey()); - validationPassed = validationResult.isValid(); - validationMessage = validationResult.getMessage(); - - if (!validationPassed) { - log.warn("OpenAI API key validation failed during status check: {}", validationMessage); - } - } - - Map<String, Object> response = new HashMap<>(); - response.put("code", 0); - response.put("data", Map.of( - "configured", configured && validationPassed, - "hasDbConfig", hasDbConfig, - "hasYamlConfig", hasYamlConfig, - "validationPassed", validationPassed, - "validationMessage", validationMessage - )); - response.put("msg", "Success"); - - return ResponseEntity.ok(response); - } catch (Exception e) { - log.error("Failed to get OpenAI configuration status", e); - - Map<String, Object> response = new HashMap<>(); - response.put("code", 1); - response.put("msg", "Failed to get status: " + e.getMessage()); - - return ResponseEntity.ok(response); - } - } - -} \ No newline at end of file diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/OpenAiConfigDao.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/OpenAiConfigDao.java deleted file mode 100644 index b1ba96da2..000000000 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/dao/OpenAiConfigDao.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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. - */ - -package org.apache.hertzbeat.ai.agent.dao; - -import org.apache.hertzbeat.ai.agent.entity.OpenAiConfig; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.stereotype.Repository; - -/** - * OpenAI Agent Configuration Dao - */ -@Repository -public interface OpenAiConfigDao extends JpaRepository<OpenAiConfig, String>, JpaSpecificationExecutor<OpenAiConfig> { - - /** - * Query by type - * @param type type - * @return Return the queried configuration information - */ - OpenAiConfig findByType(String type); -} \ No newline at end of file diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/OpenAiConfigChangeEvent.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/AiProviderConfigChangeEvent.java similarity index 84% rename from hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/OpenAiConfigChangeEvent.java rename to hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/AiProviderConfigChangeEvent.java index b6bb92f37..410b57da9 100644 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/OpenAiConfigChangeEvent.java +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/AiProviderConfigChangeEvent.java @@ -21,11 +21,11 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; /** - * OpenAI configuration change event + * Ai Provider configuration change event */ -public class OpenAiConfigChangeEvent extends ApplicationEvent { +public class AiProviderConfigChangeEvent extends ApplicationEvent { - public OpenAiConfigChangeEvent(ApplicationContext source) { + public AiProviderConfigChangeEvent(ApplicationContext source) { super(source); } -} \ No newline at end of file +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/OpenAiConfigDto.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/ModelProviderConfig.java similarity index 57% rename from hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/OpenAiConfigDto.java rename to hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/ModelProviderConfig.java index c443e4b49..af96cfd03 100644 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/OpenAiConfigDto.java +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/pojo/dto/ModelProviderConfig.java @@ -24,24 +24,36 @@ import lombok.Data; import lombok.NoArgsConstructor; /** - * OpenAI Configuration DTO - simplified to handle only API key + * Model Provider Configuration */ @Data @AllArgsConstructor @NoArgsConstructor -@Schema(description = "OpenAI configuration") -public class OpenAiConfigDto { - - /** - * Whether to enable OpenAI, default is false - */ - @Schema(title = "Enable OpenAI", description = "Whether OpenAI is enabled", example = "true") +@Schema(description = "LLM Model Provider configuration") +public class ModelProviderConfig { + + @Schema(title = "Enable Provider", description = "Whether Provider is enabled", example = "true") private boolean enable = false; - - /** - * OpenAI API key - */ - @Schema(title = "API Key", description = "OpenAI API key", example = "sk-...") + + @Schema(title = "Check the provider available status") + private boolean status = false; + + @Schema(title = "The error message when provider status check failed") + private String error; + + @Schema(title = "Model type, text-generate, vision") + private String type; + + @Schema(title = "Model Provider code, like openai, zai, bigmodel") + private String code; + + @Schema(title = "custom the provider server base url") + private String baseUrl; + + @Schema(title = "use the model id name, eg: gpt-5, glm-4.6") + private String model; + + @Schema(title = "API Key", description = "API key", example = "sk-...") @NotBlank(message = "API Key cannot be empty when enabled") private String apiKey; -} \ No newline at end of file +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/OpenAiConfigService.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/AiConfigService.java similarity index 69% rename from hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/OpenAiConfigService.java rename to hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/AiConfigService.java index 1d15c46d5..ad379ba2b 100644 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/OpenAiConfigService.java +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/AiConfigService.java @@ -17,26 +17,10 @@ package org.apache.hertzbeat.ai.agent.service; -import org.apache.hertzbeat.ai.agent.event.OpenAiConfigChangeEvent; -import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto; - /** - * OpenAI Configuration Service - * Consolidated service for OpenAI configuration, validation, and client factory management + * Ai Configuration Service */ -public interface OpenAiConfigService { - - /** - * Save OpenAI configuration - * @param config OpenAI configuration - */ - void saveConfig(OpenAiConfigDto config); - - /** - * Get OpenAI configuration - * @return OpenAI configuration - */ - OpenAiConfigDto getConfig(); +public interface AiConfigService { /** * Check if OpenAI is properly configured @@ -44,12 +28,6 @@ public interface OpenAiConfigService { */ boolean isConfigured(); - /** - * Get effective OpenAI configuration (DB first, then YAML fallback) - * @return effective configuration or null if not configured - */ - OpenAiConfigDto getEffectiveConfig(); - /** * Validate OpenAI API key by calling the OpenAI API * @param apiKey the API key to validate @@ -63,12 +41,6 @@ public interface OpenAiConfigService { */ void reloadConfig(); - /** - * Handle OpenAI configuration change events - * @param event OpenAI configuration change event - */ - void onOpenAiConfigChange(OpenAiConfigChangeEvent event); - /** * Validation result class */ @@ -97,4 +69,4 @@ public interface OpenAiConfigService { return message; } } -} \ No newline at end of file +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/AiConfigServiceImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/AiConfigServiceImpl.java new file mode 100644 index 000000000..2f4ee7c4d --- /dev/null +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/AiConfigServiceImpl.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + +package org.apache.hertzbeat.ai.agent.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.ai.agent.event.AiProviderConfigChangeEvent; +import org.apache.hertzbeat.ai.agent.pojo.dto.ModelProviderConfig; +import org.apache.hertzbeat.ai.agent.service.AiConfigService; +import org.apache.hertzbeat.base.dao.GeneralConfigDao; +import org.apache.hertzbeat.common.entity.manager.GeneralConfig; +import org.apache.hertzbeat.common.util.JsonUtil; +import org.springframework.context.event.EventListener; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +/** + * Ai Configuration Service Implementation + */ +@Slf4j +@Service +public class AiConfigServiceImpl implements AiConfigService { + + private static final String OPENAI_MODELS_ENDPOINT = "https://api.openai.com/v1/models"; + private final RestTemplate restTemplate; + + private final GeneralConfigDao generalConfigDao; + + public AiConfigServiceImpl(GeneralConfigDao generalConfigDao, RestTemplate restTemplate) { + this.restTemplate = restTemplate; + this.generalConfigDao = generalConfigDao; + } + + @Override + public boolean isConfigured() { + GeneralConfig providerConfig = generalConfigDao.findByType("provider"); + ModelProviderConfig modelProviderConfig = JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class); + return modelProviderConfig != null && modelProviderConfig.isStatus(); + } + + @Override + public ValidationResult validateApiKey(String apiKey) { + if (!StringUtils.hasText(apiKey)) { + return ValidationResult.failure("API key cannot be empty"); + } + + if (!apiKey.startsWith("sk-")) { + return ValidationResult.failure("Invalid API key format. OpenAI API keys should start with 'sk-'"); + } + + try { + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + apiKey); + headers.set("Content-Type", "application/json"); + + HttpEntity<String> entity = new HttpEntity<>(headers); + + log.debug("Validating OpenAI API key by calling models endpoint"); + ResponseEntity<String> response = restTemplate.exchange( + OPENAI_MODELS_ENDPOINT, + HttpMethod.GET, + entity, + String.class + ); + + if (response.getStatusCode() == HttpStatus.OK) { + log.info("OpenAI API key validation successful"); + return ValidationResult.success("API key is valid"); + } else { + log.warn("OpenAI API key validation failed with status: {}", response.getStatusCode()); + return ValidationResult.failure("API key validation failed: " + response.getStatusCode()); + } + + } catch (Exception e) { + log.error("Error validating OpenAI API key", e); + String errorMessage = e.getMessage(); + + // Parse common error messages + if (errorMessage.contains("401")) { + return ValidationResult.failure("Invalid API key - authentication failed"); + } else if (errorMessage.contains("403")) { + return ValidationResult.failure("API key does not have permission to access models"); + } else if (errorMessage.contains("429")) { + return ValidationResult.failure("Rate limit exceeded - please try again later"); + } else if (errorMessage.contains("timeout") || errorMessage.contains("connect")) { + return ValidationResult.failure("Network error - unable to connect to OpenAI API"); + } else { + return ValidationResult.failure("API key validation failed: " + errorMessage); + } + } + } + + @Override + public void reloadConfig() { + synchronized (this) { + + } + } + + /** + * OpenAI configuration change event listener + */ + @EventListener(AiProviderConfigChangeEvent.class) + public void onAiProviderConfigChange(AiProviderConfigChangeEvent event) { + log.info("Provider configuration change event received"); + reloadConfig(); + } +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ConversationServiceImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ConversationServiceImpl.java index c63bb29b6..534435e28 100644 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ConversationServiceImpl.java +++ b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ConversationServiceImpl.java @@ -24,7 +24,7 @@ import org.apache.hertzbeat.ai.agent.pojo.dto.ConversationDto; import org.apache.hertzbeat.ai.agent.pojo.dto.MessageDto; import org.apache.hertzbeat.ai.agent.service.ChatClientProviderService; import org.apache.hertzbeat.ai.agent.service.ConversationService; -import org.apache.hertzbeat.ai.agent.service.OpenAiConfigService; +import org.apache.hertzbeat.ai.agent.service.AiConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.codec.ServerSentEvent; import org.springframework.stereotype.Service; @@ -53,7 +53,7 @@ public class ConversationServiceImpl implements ConversationService { private ChatClientProviderService chatClientProviderService; @Autowired - private OpenAiConfigService openAiConfigService; + private AiConfigService openAiConfigService; @Override public ConversationDto createConversation() { @@ -78,7 +78,7 @@ public class ConversationServiceImpl implements ConversationService { if (!openAiConfigService.isConfigured()) { ChatResponseDto errorResponse = ChatResponseDto.builder() .conversationId(conversationId) - .response("OpenAI is not configured. Please configure your OpenAI API key in the settings or application.yml file.") + .response("Provider is not configured. Please configure your OpenAI API key in the settings or application.yml file.") .build(); return Flux.just(ServerSentEvent.builder(errorResponse) .event("error") @@ -273,4 +273,4 @@ public class ConversationServiceImpl implements ConversationService { -} \ No newline at end of file +} diff --git a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/OpenAiConfigServiceImpl.java b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/OpenAiConfigServiceImpl.java deleted file mode 100644 index 7014de2e4..000000000 --- a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/OpenAiConfigServiceImpl.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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. - */ - -package org.apache.hertzbeat.ai.agent.service.impl; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.apache.hertzbeat.ai.agent.config.OpenAiYamlConfig; -import org.apache.hertzbeat.ai.agent.dao.OpenAiConfigDao; -import org.apache.hertzbeat.ai.agent.entity.OpenAiConfig; -import org.apache.hertzbeat.ai.agent.event.OpenAiConfigChangeEvent; -import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto; -import org.apache.hertzbeat.ai.agent.service.OpenAiConfigService; -import org.springframework.context.ApplicationContext; -import org.springframework.context.event.EventListener; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; - -/** - * OpenAI Configuration Service Implementation - * Consolidated service for OpenAI configuration, validation, and client factory management - */ -@Slf4j -@Service -public class OpenAiConfigServiceImpl implements OpenAiConfigService { - - private static final String CONFIG_TYPE = "openai"; - private static final String OPENAI_MODELS_ENDPOINT = "https://api.openai.com/v1/models"; - - private final OpenAiConfigDao openAiConfigDao; - private final ObjectMapper objectMapper; - private final ApplicationContext applicationContext; - private final OpenAiYamlConfig yamlConfig; - private final RestTemplate restTemplate; - - // Client factory cache - private volatile OpenAiConfigDto currentConfig; - - public OpenAiConfigServiceImpl(OpenAiConfigDao openAiConfigDao, - ObjectMapper objectMapper, - ApplicationContext applicationContext, - OpenAiYamlConfig yamlConfig) { - this.openAiConfigDao = openAiConfigDao; - this.objectMapper = objectMapper; - this.applicationContext = applicationContext; - this.yamlConfig = yamlConfig; - this.restTemplate = new RestTemplate(); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void saveConfig(OpenAiConfigDto config) { - try { - String contentJson = objectMapper.writeValueAsString(config); - - OpenAiConfig openAiConfig = OpenAiConfig.builder() - .type(CONFIG_TYPE) - .content(contentJson) - .build(); - - openAiConfigDao.save(openAiConfig); - log.info("OpenAI configuration saved successfully"); - - applicationContext.publishEvent(new OpenAiConfigChangeEvent(applicationContext)); - - } catch (JsonProcessingException e) { - throw new IllegalArgumentException("Failed to save OpenAI configuration: " + e.getMessage()); - } - } - - @Override - public OpenAiConfigDto getConfig() { - OpenAiConfig config = openAiConfigDao.findByType(CONFIG_TYPE); - if (config == null || !StringUtils.hasText(config.getContent())) { - return null; - } - - try { - return objectMapper.readValue(config.getContent(), OpenAiConfigDto.class); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException("Failed to parse OpenAI configuration: " + e.getMessage()); - } - } - - @Override - public boolean isConfigured() { - OpenAiConfigDto effective = getEffectiveConfig(); - return effective != null && effective.isEnable() && StringUtils.hasText(effective.getApiKey()); - } - - @Override - public OpenAiConfigDto getEffectiveConfig() { - OpenAiConfigDto dbConfig = getConfig(); - if (dbConfig != null && dbConfig.isEnable() && StringUtils.hasText(dbConfig.getApiKey())) { - log.debug("Using database OpenAI configuration"); - return dbConfig; - } - - if (yamlConfig != null && yamlConfig.isEnable() && StringUtils.hasText(yamlConfig.getApiKey())) { - log.debug("Using YAML OpenAI configuration from spring.ai.openai.api-key"); - OpenAiConfigDto yamlDto = new OpenAiConfigDto(); - yamlDto.setEnable(true); - yamlDto.setApiKey(yamlConfig.getApiKey()); - return yamlDto; - } - - log.debug("No valid OpenAI configuration found"); - return null; - } - - @Override - public ValidationResult validateApiKey(String apiKey) { - if (!StringUtils.hasText(apiKey)) { - return ValidationResult.failure("API key cannot be empty"); - } - - if (!apiKey.startsWith("sk-")) { - return ValidationResult.failure("Invalid API key format. OpenAI API keys should start with 'sk-'"); - } - - try { - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", "Bearer " + apiKey); - headers.set("Content-Type", "application/json"); - - HttpEntity<String> entity = new HttpEntity<>(headers); - - log.debug("Validating OpenAI API key by calling models endpoint"); - ResponseEntity<String> response = restTemplate.exchange( - OPENAI_MODELS_ENDPOINT, - HttpMethod.GET, - entity, - String.class - ); - - if (response.getStatusCode() == HttpStatus.OK) { - log.info("OpenAI API key validation successful"); - return ValidationResult.success("API key is valid"); - } else { - log.warn("OpenAI API key validation failed with status: {}", response.getStatusCode()); - return ValidationResult.failure("API key validation failed: " + response.getStatusCode()); - } - - } catch (Exception e) { - log.error("Error validating OpenAI API key", e); - String errorMessage = e.getMessage(); - - // Parse common error messages - if (errorMessage.contains("401")) { - return ValidationResult.failure("Invalid API key - authentication failed"); - } else if (errorMessage.contains("403")) { - return ValidationResult.failure("API key does not have permission to access models"); - } else if (errorMessage.contains("429")) { - return ValidationResult.failure("Rate limit exceeded - please try again later"); - } else if (errorMessage.contains("timeout") || errorMessage.contains("connect")) { - return ValidationResult.failure("Network error - unable to connect to OpenAI API"); - } else { - return ValidationResult.failure("API key validation failed: " + errorMessage); - } - } - } - - @Override - public void reloadConfig() { - synchronized (this) { - currentConfig = null; // Force reload - } - } - - /** - * OpenAI configuration change event listener - */ - @EventListener(OpenAiConfigChangeEvent.class) - public void onOpenAiConfigChange(OpenAiConfigChangeEvent event) { - log.info("[OpenAiConfigService] OpenAI configuration change event received"); - reloadConfig(); - } -} \ No newline at end of file diff --git a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/GeneralConfigTypeEnum.java b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/GeneralConfigTypeEnum.java index 9a461dfcd..ce54a7030 100644 --- a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/GeneralConfigTypeEnum.java +++ b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/GeneralConfigTypeEnum.java @@ -55,5 +55,10 @@ public enum GeneralConfigTypeEnum { /** * system store config */ - oss + oss, + + /** + * model llm provider + */ + provider } diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/GeneralConfigController.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/GeneralConfigController.java index f88444e7b..5d365431f 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/GeneralConfigController.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/GeneralConfigController.java @@ -51,11 +51,11 @@ import java.util.stream.Collectors; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; /** - * Alert sender Configuration API + * Generate Configuration API */ @RestController @RequestMapping(value = "/api/config", produces = {APPLICATION_JSON_VALUE}) -@Tag(name = "Alert sender Configuration API") +@Tag(name = "Generate Configuration API") @Slf4j public class GeneralConfigController { diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/ModelProviderConfigServiceImpl.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/ModelProviderConfigServiceImpl.java new file mode 100644 index 000000000..e5755a1af --- /dev/null +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/ModelProviderConfigServiceImpl.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + +package org.apache.hertzbeat.manager.service.impl; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.lang.reflect.Type; +import org.apache.hertzbeat.ai.agent.pojo.dto.ModelProviderConfig; +import org.apache.hertzbeat.base.dao.GeneralConfigDao; +import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum; +import org.springframework.stereotype.Service; + +/** + * llm model provider config service impl + */ +@Service +public class ModelProviderConfigServiceImpl extends AbstractGeneralConfigServiceImpl<ModelProviderConfig> { + + /** + * + * <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p> + * + * @param generalConfigDao ConfigDao object + * @param objectMapper JSON tool object + */ + public ModelProviderConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) { + super(generalConfigDao, objectMapper); + } + + @Override + public String type() { + return GeneralConfigTypeEnum.provider.name(); + } + + @Override + public TypeReference<ModelProviderConfig> getTypeReference() { + return new TypeReference<>() { + @Override + public Type getType() { + return ModelProviderConfig.class; + } + }; + } +} diff --git a/hertzbeat-manager/src/main/resources/application-test.yml b/hertzbeat-manager/src/main/resources/application-test.yml index bd76e9d93..4aa13715f 100644 --- a/hertzbeat-manager/src/main/resources/application-test.yml +++ b/hertzbeat-manager/src/main/resources/application-test.yml @@ -189,16 +189,3 @@ grafana: expose-url: http://127.0.0.1:3000 username: admin password: admin - -# See the documentation for details : https://hertzbeat.apache.org/zh-cn/docs/help/aiConfig -ai: - # AI Type:zhiPu、alibabaAi、kimiAi、sparkDesk、ollama、openRouter - type: - # Model name:glm-4、qwen-turboo、moonshot-v1-8k、generalv3.5 - model: - # api key - api-key: - #At present, only IFLYTEK large model needs to be filled in - api-secret: - # The URL of the ollama AI service - api-url: diff --git a/hertzbeat-manager/src/main/resources/application.yml b/hertzbeat-manager/src/main/resources/application.yml index 9679a359b..c4f463601 100644 --- a/hertzbeat-manager/src/main/resources/application.yml +++ b/hertzbeat-manager/src/main/resources/application.yml @@ -37,14 +37,6 @@ spring: resource: true prompt: true completion: true - chat: - client: - enabled: false - openai: - api-key: OPENAI_API_KEY - chat: - options: - model: gpt-4.1-nano-2025-04-14 mvc: static-path-pattern: /** @@ -321,16 +313,3 @@ grafana: expose-url: http://127.0.0.1:3000 username: admin password: admin - -# See the documentation for details : https://hertzbeat.apache.org/zh-cn/docs/help/aiConfig -ai: - # AI Type:zhiPu、alibabaAi、kimiAi、sparkDesk、ollama、openRouter - type: - # Model name:glm-4、qwen-turboo、moonshot-v1-8k、generalv3.5 - model: - # api key - api-key: - #At present, only IFLYTEK large model needs to be filled in - api-secret: - # The URL of the ollama AI service - api-url: diff --git a/home/docs/download.md b/home/docs/download.md index 269cf1f09..ed0edb64f 100644 --- a/home/docs/download.md +++ b/home/docs/download.md @@ -22,7 +22,7 @@ Previous releases of HertzBeat may be affected by security issues, please use th | Version | Date | Download [...] |---------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ [...] -| v1.7.3 | 2025.09.05 | [apache-hertzbeat-1.7.3-bin.tar.gz](https://www.apache.org/dyn/closer.lua/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz) (Server) ( [signature](https://downloads.apache.org/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.7.3-bin.tar.gz](https://www.apache.org/dyn/closer.lua/hertzbeat/1.7.3/apache-hertzbeat-collector-1. [...] +| v1.7.3 | 2025.09.05 | [apache-hertzbeat-1.7.3-bin.tar.gz](https://www.apache.org/dyn/closer.lua/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz) (Server) ( [signature](https://downloads.apache.org/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.7.3-bin.tar.gz](https://www.apache.org/dyn/closer.lua/hertzbeat/1.7.3/apache-hertzbeat-collector-1. [...] ## Release Docker Image diff --git a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/download.md b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/download.md index 72c0abbd4..04ec4a2a6 100644 --- a/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/download.md +++ b/home/i18n/zh-cn/docusaurus-plugin-content-docs/current/download.md @@ -22,7 +22,7 @@ sidebar_label: Download | 版本 | 日期 | 下载 [...] |--------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [...] -| v1.7.3 | 2025.09.05 | [apache-hertzbeat-1.7.3-bin.tar.gz](https://www.apache.org/dyn/closer.lua/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz) (Server) ( [signature](https://downloads.apache.org/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.7.3-bin.tar.gz](https://www.apache.org/dyn/closer.lua/hertzbeat/1.7.3/apache-hertzbeat-collector-1.7 [...] +| v1.7.3 | 2025.09.05 | [apache-hertzbeat-1.7.3-bin.tar.gz](https://www.apache.org/dyn/closer.lua/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz) (Server) ( [signature](https://downloads.apache.org/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/hertzbeat/1.7.3/apache-hertzbeat-1.7.3-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.7.3-bin.tar.gz](https://www.apache.org/dyn/closer.lua/hertzbeat/1.7.3/apache-hertzbeat-collector-1.7 [...] ## Docker 镜像版本 diff --git a/web-app/src/app/pojo/ModelProviderConfig.ts b/web-app/src/app/pojo/ModelProviderConfig.ts new file mode 100644 index 000000000..717409609 --- /dev/null +++ b/web-app/src/app/pojo/ModelProviderConfig.ts @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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. + */ + +export class ModelProviderConfig { + enable: boolean = false; + status: boolean = false; + error!: string; + type!: string; + code!: string; + baseUrl!: string; + model!: string; + apiKey!: string; +} diff --git a/web-app/src/app/service/general-config.service.ts b/web-app/src/app/service/general-config.service.ts index 02141ee5b..f72c9b11b 100644 --- a/web-app/src/app/service/general-config.service.ts +++ b/web-app/src/app/service/general-config.service.ts @@ -22,6 +22,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Message } from '../pojo/Message'; +import { ModelProviderConfig } from '../pojo/ModelProviderConfig'; const general_config_uri = '/config'; @@ -39,6 +40,14 @@ export class GeneralConfigService { return this.http.get<Message<any>>(`${general_config_uri}/${type}`); } + public saveModelProviderConfig(body: ModelProviderConfig): Observable<Message<any>> { + return this.http.post<Message<any>>(`${general_config_uri}/provider`, body); + } + + public getModelProviderConfig(): Observable<Message<ModelProviderConfig>> { + return this.http.get<Message<ModelProviderConfig>>(`${general_config_uri}/provider`); + } + public updateAppTemplateConfig(body: any, app: string): Observable<Message<void>> { return this.http.put<Message<void>>(`${general_config_uri}/template/${app}`, body); } diff --git a/web-app/src/app/service/openai-config.service.ts b/web-app/src/app/service/openai-config.service.ts deleted file mode 100644 index a82c47815..000000000 --- a/web-app/src/app/service/openai-config.service.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; - -import { Message } from '../pojo/Message'; - -export interface OpenAiConfig { - enable: boolean; - apiKey: string; -} - -export interface OpenAiConfigStatus { - configured: boolean; - hasDbConfig: boolean; - hasYamlConfig: boolean; - validationPassed: boolean; - validationMessage: string; -} - -const openai_config_uri = '/ai-agent/config'; - -@Injectable({ - providedIn: 'root' -}) -export class OpenAiConfigService { - constructor(private http: HttpClient) {} - - public saveOpenAiConfig(config: OpenAiConfig): Observable<Message<any>> { - return this.http.post<Message<any>>(`${openai_config_uri}/openai`, config); - } - - public getOpenAiConfig(): Observable<Message<OpenAiConfig>> { - return this.http.get<Message<OpenAiConfig>>(`${openai_config_uri}/openai`); - } - - public getOpenAiConfigStatus(): Observable<Message<OpenAiConfigStatus>> { - return this.http.get<Message<OpenAiConfigStatus>>(`${openai_config_uri}/openai/status`); - } -} diff --git a/web-app/src/app/shared/components/ai-chat/chat.component.html b/web-app/src/app/shared/components/ai-chat/chat.component.html index 5642b1276..f55bd9387 100644 --- a/web-app/src/app/shared/components/ai-chat/chat.component.html +++ b/web-app/src/app/shared/components/ai-chat/chat.component.html @@ -160,9 +160,9 @@ <!-- OpenAI Configuration Modal --> <nz-modal [(nzVisible)]="showConfigModal" - nzTitle="OpenAI Configuration" + nzTitle="Ai Provider Configuration" (nzOnCancel)="onCloseConfigModal()" - (nzOnOk)="onSaveOpenAiConfig()" + (nzOnOk)="onSaveAiProviderConfig()" nzMaskClosable="false" [nzClosable]="false" nzWidth="600px" @@ -173,10 +173,10 @@ <div *nzModalContent class="-inner-content"> <form nz-form nzLayout="vertical"> <nz-form-item> - <nz-form-label nzRequired="true">OpenAI API Key</nz-form-label> + <nz-form-label nzRequired="true">API Key</nz-form-label> <nz-form-control nzErrorTip="API Key is required"> - <input nz-input [(ngModel)]="openAiConfig.apiKey" name="apiKey" type="password" placeholder="sk-..." required /> - <p class="form-help">Your OpenAI API key (starts with sk-). The key will be validated when saved.</p> + <input nz-input [(ngModel)]="aiProviderConfig.apiKey" name="apiKey" type="password" placeholder="sk-..." required /> + <p class="form-help">Your Provider API key. The key will be validated when saved.</p> </nz-form-control> </nz-form-item> </form> diff --git a/web-app/src/app/shared/components/ai-chat/chat.component.ts b/web-app/src/app/shared/components/ai-chat/chat.component.ts index 3a3efa5a4..c7f8f1436 100644 --- a/web-app/src/app/shared/components/ai-chat/chat.component.ts +++ b/web-app/src/app/shared/components/ai-chat/chat.component.ts @@ -22,8 +22,9 @@ import { I18NService } from '@core'; import { NzMessageService } from 'ng-zorro-antd/message'; import { NzModalService } from 'ng-zorro-antd/modal'; +import { ModelProviderConfig } from '../../../pojo/ModelProviderConfig'; import { AiChatService, ChatMessage, ConversationDto } from '../../../service/ai-chat.service'; -import { OpenAiConfigService, OpenAiConfig, OpenAiConfigStatus } from '../../../service/openai-config.service'; +import { GeneralConfigService } from '../../../service/general-config.service'; import { ThemeService } from '../../../service/theme.service'; @Component({ @@ -46,10 +47,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { isOpenAiConfigured = false; showConfigModal = false; configLoading = false; - openAiConfig: OpenAiConfig = { - enable: false, - apiKey: '' - }; + aiProviderConfig: ModelProviderConfig = new ModelProviderConfig(); constructor( private aiChatService: AiChatService, @@ -58,7 +56,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { private i18n: I18NService, private cdr: ChangeDetectorRef, private themeSvc: ThemeService, - private openAiConfigService: OpenAiConfigService + private generalConfigSvc: GeneralConfigService ) {} ngOnInit(): void { @@ -428,61 +426,46 @@ export class ChatComponent implements OnInit, AfterViewChecked { } /** - * Check OpenAI configuration status + * Check provider configuration status */ checkOpenAiConfiguration(): void { - this.openAiConfigService.getOpenAiConfigStatus().subscribe({ + this.generalConfigSvc.getModelProviderConfig().subscribe({ next: response => { - if (response.code === 0) { - this.isOpenAiConfigured = response.data.configured; - if (this.isOpenAiConfigured) { + if (response.code === 0 && response.data) { + this.aiProviderConfig = response.data; + if (!response.data.enable) { this.loadConversations(); } else { - this.showOpenAiConfigDialog(response.data); + this.showAiProviderConfigDialog(response.data.error); } - } else { - console.error('Failed to check OpenAI configuration:', response.msg); - this.showOpenAiConfigDialog(); } }, error: error => { - console.error('Error checking OpenAI configuration:', error); - this.showOpenAiConfigDialog(); + console.error('Failed to load model provider config:', error); + this.showAiProviderConfigDialog(); } }); } /** - * Show OpenAI configuration dialog + * Show ai configuration dialog */ - showOpenAiConfigDialog(status?: OpenAiConfigStatus): void { - // Load existing configuration if available - this.loadOpenAiConfig(); - + showAiProviderConfigDialog(error?: string): void { let contentMessage = ` <div style="margin-bottom: 16px;"> - <p>To use AI Agent Chat, please configure your OpenAI API key.</p> + <p>To use AI Agent Chat, please configure your Model Provider.</p> `; - if (status && !status.validationPassed && status.validationMessage) { + if (error) { contentMessage += ` <div style="margin-bottom: 12px; padding: 8px; background: #fff2f0; border: 1px solid #ffccc7; border-radius: 4px;"> - <strong>Configuration Issue:</strong> ${status.validationMessage} + <strong>Configuration Issue:</strong> ${error} </div> `; } - contentMessage += ` - <p>You can either:</p> - <ul> - <li>Configure it here (stored in database)</li> - <li>Add it to your application.yml file under <code>spring.ai.openai.api-key</code></li> - </ul> - </div> - `; - const modalRef = this.modal.create({ - nzTitle: 'OpenAI Configuration Required', + nzTitle: 'Ai Model Provider Configuration Required', nzContent: contentMessage, nzWidth: 600, nzClosable: false, @@ -500,27 +483,11 @@ export class ChatComponent implements OnInit, AfterViewChecked { }); } - /** - * Load OpenAI configuration - */ - loadOpenAiConfig(): void { - this.openAiConfigService.getOpenAiConfig().subscribe({ - next: response => { - if (response.code === 0 && response.data) { - this.openAiConfig = { ...this.openAiConfig, ...response.data }; - } - }, - error: error => { - console.error('Failed to load OpenAI config:', error); - } - }); - } - /** * Show configuration modal */ onShowConfigModal(): void { - this.loadOpenAiConfig(); + this.checkOpenAiConfiguration(); this.showConfigModal = true; } @@ -534,23 +501,23 @@ export class ChatComponent implements OnInit, AfterViewChecked { /** * Save OpenAI configuration */ - onSaveOpenAiConfig(): void { - if (!this.openAiConfig.apiKey.trim()) { + onSaveAiProviderConfig(): void { + if (!this.aiProviderConfig.apiKey.trim()) { this.message.error('API Key is required'); return; } // Always enable when saving an API key - this.openAiConfig.enable = true; + this.aiProviderConfig.enable = true; this.configLoading = true; this.message.info('Validating API key...', { nzDuration: 2000 }); - this.openAiConfigService.saveOpenAiConfig(this.openAiConfig).subscribe({ + this.generalConfigSvc.saveModelProviderConfig(this.aiProviderConfig).subscribe({ next: response => { this.configLoading = false; if (response.code === 0) { - this.message.success('OpenAI API key validated and saved successfully!'); + this.message.success('Model Provider configuration saved successfully!'); this.showConfigModal = false; this.isOpenAiConfigured = true; this.loadConversations(); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
