This is an automated email from the ASF dual-hosted git repository.
zhengqiwei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new e073bfee9 refactor: refactor ai feature (#3823)
e073bfee9 is described below
commit e073bfee91f1b57bc959b13bfb0607ebd04e0161
Author: Tomsun28 <[email protected]>
AuthorDate: Sat Oct 25 12:39:53 2025 +0800
refactor: refactor ai feature (#3823)
Signed-off-by: tomsun28 <[email protected]>
Co-authored-by: Calvin <[email protected]>
---
hertzbeat-ai-agent/pom.xml | 16 +-
.../ai/agent/config/DynamicOpenAiApiKey.java | 43 +++--
.../hertzbeat/ai/agent/config/LlmConfig.java | 137 +++++++++++---
.../ai/agent/config/OpenAiYamlConfig.java | 43 -----
.../agent/controller/OpenAiConfigController.java | 160 -----------------
.../hertzbeat/ai/agent/dao/OpenAiConfigDao.java | 37 ----
...enAiConfigDto.java => ModelProviderConfig.java} | 40 +++--
...enAiConfigService.java => AiConfigService.java} | 46 +----
.../agent/service/ChatClientProviderService.java | 8 +-
.../ai/agent/service/impl/AiConfigServiceImpl.java | 100 +++++++++++
.../impl/ChatClientProviderServiceImpl.java | 36 +++-
.../service/impl/ConversationServiceImpl.java | 12 +-
.../service/impl/OpenAiConfigServiceImpl.java | 200 ---------------------
.../common/constants/GeneralConfigTypeEnum.java | 7 +-
.../support/event/AiProviderConfigChangeEvent.java | 10 +-
.../controller/GeneralConfigController.java | 4 +-
.../impl/ModelProviderConfigServiceImpl.java | 69 +++++++
.../src/main/resources/application-test.yml | 13 --
.../src/main/resources/application.yml | 21 ---
home/docs/download.md | 2 +-
.../current/download.md | 2 +-
.../hertzbeat-mysql-iotdb/conf/application.yml | 10 +-
.../hertzbeat-mysql-tdengine/conf/application.yml | 8 -
.../conf/application.yml | 10 +-
.../conf/application.yml | 10 +-
web-app/src/app/layout/basic/basic.component.html | 30 ----
web-app/src/app/layout/basic/basic.component.less | 8 +-
web-app/src/app/layout/basic/basic.component.ts | 2 +-
web-app/src/app/pojo/ModelProviderConfig.ts | 57 ++++++
web-app/src/app/service/general-config.service.ts | 9 +
web-app/src/app/service/openai-config.service.ts | 58 ------
.../shared/components/ai-bot/ai-bot.component.html | 67 -------
.../shared/components/ai-bot/ai-bot.component.less | 147 ---------------
.../shared/components/ai-bot/ai-bot.component.scss | 158 ----------------
.../shared/components/ai-bot/ai-bot.component.ts | 72 --------
.../shared/components/ai-chat/ai-chat.module.ts | 2 +
.../shared/components/ai-chat/chat.component.html | 62 ++++++-
.../shared/components/ai-chat/chat.component.less | 35 ++++
.../shared/components/ai-chat/chat.component.ts | 158 +++++++++-------
web-app/src/app/shared/shared.module.ts | 2 -
40 files changed, 659 insertions(+), 1252 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..7f8c65733 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
@@ -18,59 +18,144 @@
package org.apache.hertzbeat.ai.agent.config;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.common.support.event.AiProviderConfigChangeEvent;
+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.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.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.EventListener;
/**
* Configuration class for Large Language Model (LLM) settings.
*/
@Configuration
+@Slf4j
public class LlmConfig {
- @Value("${spring.ai.openai.chat.options.model}")
- private String model;
+ private final GeneralConfigDao generalConfigDao;
+
+ private ApplicationContext applicationContext;
- /**
- * Create OpenAI API instance with dynamic API key
- */
- @Bean
- public OpenAiApi openAiApi(DynamicOpenAiApiKey dynamicApiKey) {
- return OpenAiApi.builder()
- .apiKey(dynamicApiKey)
- .build();
+ public LlmConfig(GeneralConfigDao generalConfigDao, ApplicationContext
applicationContext) {
+ this.generalConfigDao = generalConfigDao;
+ this.applicationContext = applicationContext;
}
/**
- * Create OpenAI Chat Options with custom settings
+ * Create ChatClient bean with all dependencies created internally
*/
@Bean
- public OpenAiChatOptions openAiChatOptions() {
- return OpenAiChatOptions.builder()
- .model(model)
- .temperature(0.3)
- .build();
+ public ChatClient openAiChatClient() {
+ return createChatClient();
}
/**
- * Create OpenAI Chat Model with custom API configuration
+ * Create ChatClient with all necessary components
*/
- @Bean
- public OpenAiChatModel openAiChatModel(OpenAiApi openAiApi,
OpenAiChatOptions openAiChatOptions) {
- return OpenAiChatModel.builder()
- .openAiApi(openAiApi)
+ private ChatClient createChatClient() {
+
+ GeneralConfig providerConfig = generalConfigDao.findByType("provider");
+ if (providerConfig == null || providerConfig.getContent() == null) {
+ log.warn("LLM Provider is not set, ChatClient bean will not be
created");
+ return null;
+ }
+ ModelProviderConfig modelProviderConfig =
JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class);
+
+ if (!modelProviderConfig.isEnable() ||
!modelProviderConfig.isStatus()) {
+ log.warn("LLM Provider is not enabled or status is not valid,
ChatClient bean will not be created");
+ return null;
+ }
+
+ if (modelProviderConfig.getApiKey() == null) {
+ log.warn("LLM Provider configuration is incomplete, ChatClient
bean will not be created");
+ return null;
+ }
+
+ if (modelProviderConfig.getBaseUrl() == null) {
+ if ("openai".equals(modelProviderConfig.getCode())) {
+ modelProviderConfig.setBaseUrl("https://api.openai.com/v1");
+ } else if ("zhipu".equals(modelProviderConfig.getCode())) {
+
modelProviderConfig.setBaseUrl("https://open.bigmodel.cn/api/paas/v4");
+ } else if ("zai".equals(modelProviderConfig.getCode())) {
+ modelProviderConfig.setBaseUrl("https://api.z.ai/api/paas/v4");
+ } else {
+ modelProviderConfig.setBaseUrl("https://api.openai.com/v1");
+ }
+ }
+
+ if (modelProviderConfig.getModel() == null) {
+ if ("openai".equals(modelProviderConfig.getCode())) {
+ modelProviderConfig.setModel("gpt-5");
+ } else if ("zhipu".equals(modelProviderConfig.getCode())) {
+ modelProviderConfig.setModel("glm-4.6");
+ } else if ("zai".equals(modelProviderConfig.getCode())) {
+ modelProviderConfig.setModel("glm-4.6");
+ } else {
+ modelProviderConfig.setModel("gpt-5");
+ }
+ }
+
+ OpenAiApi.Builder builder = new OpenAiApi.Builder();
+ builder.baseUrl(modelProviderConfig.getBaseUrl());
+ builder.apiKey(modelProviderConfig.getApiKey());
+ builder.completionsPath("/chat/completions");
+
+ // Create Chat Options
+ OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder()
+ .model(modelProviderConfig.getModel())
+ .temperature(0.3)
+ .build();
+
+ // Create Chat Model
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(builder.build())
.defaultOptions(openAiChatOptions)
.build();
+
+ // Create and return ChatClient
+ return ChatClient.create(openAiChatModel);
}
- @Bean
- public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel) {
- return ChatClient.create(openAiChatModel);
+ /**
+ * AI configuration change event listener
+ * Uses ApplicationContext to unregister and re-register the ChatClient
bean
+ */
+ @EventListener(AiProviderConfigChangeEvent.class)
+ public void onAiProviderConfigChange(AiProviderConfigChangeEvent event) {
+ log.info("Provider configuration change event received, refreshing
ChatClient bean");
+
+ try {
+ ConfigurableApplicationContext configurableContext =
(ConfigurableApplicationContext) applicationContext;
+ DefaultListableBeanFactory beanFactory =
(DefaultListableBeanFactory) configurableContext.getBeanFactory();
+
+ // Remove the existing ChatClient bean
+ if (beanFactory.containsSingleton("openAiChatClient")) {
+ beanFactory.destroySingleton("openAiChatClient");
+ log.info("Existing ChatClient bean destroyed");
+ }
+
+ // Create new ChatClient with updated configuration
+ ChatClient newChatClient = createChatClient();
+
+ // Register the new ChatClient bean
+ beanFactory.registerSingleton("openAiChatClient", newChatClient);
+
+ log.info("ChatClient bean refreshed successfully with new AI
provider configuration");
+
+ } catch (Exception e) {
+ log.error("Failed to refresh ChatClient bean after configuration
change", e);
+ }
}
-}
\ 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/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 59%
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..796c46986 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,38 +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();
-
- /**
- * Check if OpenAI is properly configured
- * @return true if configured and enabled
- */
- boolean isConfigured();
-
- /**
- * Get effective OpenAI configuration (DB first, then YAML fallback)
- * @return effective configuration or null if not configured
- */
- OpenAiConfigDto getEffectiveConfig();
+public interface AiConfigService {
/**
* Validate OpenAI API key by calling the OpenAI API
@@ -57,18 +29,6 @@ public interface OpenAiConfigService {
*/
ValidationResult validateApiKey(String apiKey);
- /**
- * Force reload of OpenAI configuration cache
- * This method is typically called when configuration changes
- */
- void reloadConfig();
-
- /**
- * Handle OpenAI configuration change events
- * @param event OpenAI configuration change event
- */
- void onOpenAiConfigChange(OpenAiConfigChangeEvent event);
-
/**
* Validation result class
*/
@@ -97,4 +57,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/ChatClientProviderService.java
b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ChatClientProviderService.java
index 2f65fb4a2..4b6de16f1 100644
---
a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ChatClientProviderService.java
+++
b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/ChatClientProviderService.java
@@ -33,4 +33,10 @@ public interface ChatClientProviderService {
* @return Flux of string chunks from the LLM response
*/
Flux<String> streamChat(ChatRequestContext context);
-}
\ No newline at end of file
+
+ /**
+ * Check if provider is properly configured
+ * @return true if configured and enabled
+ */
+ boolean isConfigured();
+}
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..1c4031a68
--- /dev/null
+++
b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/AiConfigServiceImpl.java
@@ -0,0 +1,100 @@
+/*
+ * 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.service.AiConfigService;
+import org.apache.hertzbeat.base.dao.GeneralConfigDao;
+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 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);
+ }
+ }
+ }
+}
diff --git
a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ChatClientProviderServiceImpl.java
b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ChatClientProviderServiceImpl.java
index 51d73b5b9..d8593a363 100644
---
a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ChatClientProviderServiceImpl.java
+++
b/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/service/impl/ChatClientProviderServiceImpl.java
@@ -21,7 +21,11 @@ package org.apache.hertzbeat.ai.agent.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.ai.agent.config.PromptProvider;
import org.apache.hertzbeat.ai.agent.pojo.dto.MessageDto;
+import org.apache.hertzbeat.ai.agent.pojo.dto.ModelProviderConfig;
import org.apache.hertzbeat.ai.agent.service.ChatClientProviderService;
+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.stereotype.Service;
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
import org.springframework.ai.chat.client.ChatClient;
@@ -31,6 +35,7 @@ import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
@@ -45,19 +50,25 @@ import java.util.List;
@Service
public class ChatClientProviderServiceImpl implements
ChatClientProviderService {
- private final ChatClient chatClient;
+ private final ApplicationContext applicationContext;
+
+ private final GeneralConfigDao generalConfigDao;
@Qualifier("hertzbeatTools")
@Autowired
private ToolCallbackProvider toolCallbackProvider;
+
+ private boolean isConfigured = false;
@Autowired
- public ChatClientProviderServiceImpl(ChatClient openAiChatClient) {
- this.chatClient = openAiChatClient;
+ public ChatClientProviderServiceImpl(ApplicationContext
applicationContext, GeneralConfigDao generalConfigDao) {
+ this.applicationContext = applicationContext;
+ this.generalConfigDao = generalConfigDao;
}
public String complete(String message) {
- return this.chatClient.prompt()
+ ChatClient chatClient = applicationContext.getBean("openAiChatClient",
ChatClient.class);
+ return chatClient.prompt()
.user(message)
.call()
.content();
@@ -66,6 +77,9 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
@Override
public Flux<String> streamChat(ChatRequestContext context) {
try {
+ // Get the current (potentially refreshed) ChatClient instance
+ ChatClient chatClient =
applicationContext.getBean("openAiChatClient", ChatClient.class);
+
List<Message> messages = new ArrayList<>();
// Add conversation history if available
@@ -83,7 +97,7 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
log.info("Starting streaming chat for conversation: {}",
context.getConversationId());
- return this.chatClient.prompt()
+ return chatClient.prompt()
.messages(messages)
.system(PromptProvider.HERTZBEAT_SYSTEM_PROMPT)
.toolCallbacks(toolCallbackProvider)
@@ -97,4 +111,14 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
return Flux.error(e);
}
}
-}
\ No newline at end of file
+
+ @Override
+ public boolean isConfigured() {
+ if (!isConfigured) {
+ GeneralConfig providerConfig =
generalConfigDao.findByType("provider");
+ ModelProviderConfig modelProviderConfig =
JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class);
+ isConfigured = modelProviderConfig != null &&
modelProviderConfig.isStatus();
+ }
+ return isConfigured;
+ }
+}
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..86ff84ff5 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,6 @@ 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.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Service;
@@ -51,10 +50,7 @@ public class ConversationServiceImpl implements
ConversationService {
@Autowired
private ChatClientProviderService chatClientProviderService;
-
- @Autowired
- private OpenAiConfigService openAiConfigService;
-
+
@Override
public ConversationDto createConversation() {
String conversationId = createNewConversation();
@@ -75,10 +71,10 @@ public class ConversationServiceImpl implements
ConversationService {
}
// Check if OpenAI is properly configured
- if (!openAiConfigService.isConfigured()) {
+ if (!chatClientProviderService.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 AI Provider.")
.build();
return Flux.just(ServerSentEvent.builder(errorResponse)
.event("error")
@@ -273,4 +269,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-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/OpenAiConfigChangeEvent.java
b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/support/event/AiProviderConfigChangeEvent.java
similarity index 80%
rename from
hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/OpenAiConfigChangeEvent.java
rename to
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/support/event/AiProviderConfigChangeEvent.java
index b6bb92f37..f539fb7d6 100644
---
a/hertzbeat-ai-agent/src/main/java/org/apache/hertzbeat/ai/agent/event/OpenAiConfigChangeEvent.java
+++
b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/support/event/AiProviderConfigChangeEvent.java
@@ -15,17 +15,17 @@
* limitations under the License.
*/
-package org.apache.hertzbeat.ai.agent.event;
+package org.apache.hertzbeat.common.support.event;
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-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..82c637e36
--- /dev/null
+++
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/ModelProviderConfigServiceImpl.java
@@ -0,0 +1,69 @@
+/*
+ * 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.apache.hertzbeat.common.support.event.AiProviderConfigChangeEvent;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Service;
+
+/**
+ * llm model provider config service impl
+ */
+@Service
+public class ModelProviderConfigServiceImpl extends
AbstractGeneralConfigServiceImpl<ModelProviderConfig> {
+
+ private final ApplicationContext applicationContext;
+
+ /**
+ *
+ * <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
+ *
+ * @param generalConfigDao ConfigDao object
+ * @param objectMapper JSON tool object
+ */
+ public ModelProviderConfigServiceImpl(ApplicationContext
applicationContext, GeneralConfigDao generalConfigDao, ObjectMapper
objectMapper) {
+ super(generalConfigDao, objectMapper);
+ this.applicationContext = applicationContext;
+ }
+
+ @Override
+ public String type() {
+ return GeneralConfigTypeEnum.provider.name();
+ }
+
+ @Override
+ public void handler(ModelProviderConfig config) {
+ applicationContext.publishEvent(new
AiProviderConfigChangeEvent(applicationContext));
+ }
+
+ @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/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml
b/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml
index 2c4b3cc2a..d98df3fe9 100644
--- a/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml
+++ b/script/docker-compose/hertzbeat-mysql-iotdb/conf/application.yml
@@ -38,14 +38,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: /**
jackson:
@@ -254,4 +246,4 @@ ai:
model:
api-key:
api-secret:
- api-url:
\ No newline at end of file
+ api-url:
diff --git
a/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml
b/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml
index 49f438421..b2f0e77af 100644
--- a/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml
+++ b/script/docker-compose/hertzbeat-mysql-tdengine/conf/application.yml
@@ -38,14 +38,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: /**
jackson:
diff --git
a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml
b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml
index f9f047195..c48aa31d8 100644
---
a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml
+++
b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/application.yml
@@ -38,14 +38,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: /**
jackson:
@@ -254,4 +246,4 @@ ai:
model:
api-key:
api-secret:
- api-url:
\ No newline at end of file
+ api-url:
diff --git
a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml
b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml
index 08590e289..15f3660b5 100644
---
a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml
+++
b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/application.yml
@@ -38,14 +38,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: /**
jackson:
@@ -253,4 +245,4 @@ ai:
model:
api-key:
api-secret:
- api-url:
\ No newline at end of file
+ api-url:
diff --git a/web-app/src/app/layout/basic/basic.component.html
b/web-app/src/app/layout/basic/basic.component.html
deleted file mode 100644
index c52473284..000000000
--- a/web-app/src/app/layout/basic/basic.component.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<div class="alain-default__content">
- <router-outlet></router-outlet>
-</div>
-
-<div class="ai-chatbot-container">
- <div class="ai-chatbot-button" (click)="toggleChatbot()">
- <span *ngIf="!isChatbotOpen">AI</span>
- <span *ngIf="isChatbotOpen">X</span>
- </div>
-
- <div class="ai-chatbot-window" *ngIf="isChatbotOpen">
- <div class="chatbot-header">
- <div class="chatbot-title">{{ 'ai.bot.title' | i18n }}</div>
- <div class="chatbot-close" (click)="toggleChatbot()">X</div>
- </div>
- <div class="chatbot-messages">
- <div *ngFor="let message of chatMessages"
[class.user-message]="message.isUser" [class.bot-message]="!message.isUser"
class="message">
- <div class="message-content">{{ message.content }}</div>
- <div class="message-time">{{ message.timestamp | date : 'HH:mm'
}}</div>
- </div>
- <div *ngIf="isLoading" class="bot-message loading-message">
- <nz-spin nzSimple></nz-spin>
- </div>
- </div>
- <div class="chatbot-input">
- <input nz-input placeholder="{{ 'ai.bot.input.placeholder' | i18n }}"
[(ngModel)]="currentMessage" (keyup.enter)="sendMessage()" />
- <button nz-button nzType="primary" [disabled]="!currentMessage.trim()"
(click)="sendMessage()"> {{ 'ai.bot.send' | i18n }} </button>
- </div>
- </div>
-</div>
diff --git a/web-app/src/app/layout/basic/basic.component.less
b/web-app/src/app/layout/basic/basic.component.less
index 0533e2857..9407b354b 100644
--- a/web-app/src/app/layout/basic/basic.component.less
+++ b/web-app/src/app/layout/basic/basic.component.less
@@ -338,9 +338,9 @@ global-footer {
}
.ai-chat-button {
- width: 56px;
- height: 56px;
- border-radius: 50%;
+ width: 44px;
+ height: 44px;
+ border-radius: 8px;
background-color: @primary-color;
color: white;
display: flex;
@@ -371,7 +371,7 @@ global-footer {
overflow: hidden !important;
}
-
+
.ant-modal-header {
border-radius: 16px 16px 0 0 !important;
diff --git a/web-app/src/app/layout/basic/basic.component.ts
b/web-app/src/app/layout/basic/basic.component.ts
index 30cf51ae7..38d7ad3c5 100644
--- a/web-app/src/app/layout/basic/basic.component.ts
+++ b/web-app/src/app/layout/basic/basic.component.ts
@@ -83,7 +83,7 @@ import { AiChatModalService } from
'../../shared/services/ai-chat-modal.service'
width="28"
height="28"
fill="white"
- style="min-width:28px; min-height:28px;"
+ style="min-width:36px; min-height:36px;"
>
<path
d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1
1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0
1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7h1V5.73c-.6-.34-1-.99-1-1.73a2 2 0 0 1
2-2M7.5 13A2.5 2.5 0 0 0 5 15.5A2.5 2.5 0 0 0 7.5 18a2.5 2.5 0 0 0 2.5-2.5A2.5
2.5 0 0 0 7.5 13m9 0a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0
2.5-2.5a2.5 2.5 0 0 0-2.5-2.5z"
diff --git a/web-app/src/app/pojo/ModelProviderConfig.ts
b/web-app/src/app/pojo/ModelProviderConfig.ts
new file mode 100644
index 000000000..5fe6dbbe3
--- /dev/null
+++ b/web-app/src/app/pojo/ModelProviderConfig.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 = true;
+ error!: string;
+ type!: string;
+ code: string = 'openai';
+ baseUrl: string = '';
+ model: string = '';
+ apiKey!: string;
+}
+
+export interface ProviderOption {
+ value: string;
+ label: string;
+ defaultBaseUrl: string;
+ defaultModel: string;
+}
+
+export const PROVIDER_OPTIONS: ProviderOption[] = [
+ {
+ value: 'openai',
+ label: 'OpenAI',
+ defaultBaseUrl: 'https://api.openai.com/v1',
+ defaultModel: 'gpt-4'
+ },
+ {
+ value: 'zai',
+ label: 'ZAI',
+ defaultBaseUrl: 'https://api.z.ai/api/paas/v4',
+ defaultModel: 'glm-4.6'
+ },
+ {
+ value: 'zhipu',
+ label: 'ZhiPu',
+ defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4',
+ defaultModel: 'glm-4.6'
+ }
+];
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-bot/ai-bot.component.html
b/web-app/src/app/shared/components/ai-bot/ai-bot.component.html
deleted file mode 100644
index 8c06951e9..000000000
--- a/web-app/src/app/shared/components/ai-bot/ai-bot.component.html
+++ /dev/null
@@ -1,67 +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.
--->
-
-<div class="ai-bot-container">
- <div class="ai-bot-icon" (click)="toggleChat()" [class.active]="isOpen">
- <div *ngIf="!isOpen" class="robot-icon">
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24" fill="currentColor">
- <path
- d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1 1
0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0
1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7h1V5.73c-.6-.34-1-.99-1-1.73a2 2 0 0 1
2-2M7.5 13A2.5 2.5 0 0 0 5 15.5A2.5 2.5 0 0 0 7.5 18a2.5 2.5 0 0 0 2.5-2.5A2.5
2.5 0 0 0 7.5 13m9 0a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0
2.5-2.5a2.5 2.5 0 0 0-2.5-2.5z"
- />
- </svg>
- </div>
- <div *ngIf="isOpen" class="close-icon">X</div>
- </div>
-
- <div class="ai-bot-chat-window" *ngIf="isOpen">
- <div class="chat-header">
- <div class="chat-title">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 24 24"
- width="20"
- height="20"
- fill="currentColor"
- style="margin-right: 6px; vertical-align: middle"
- >
- <path
- d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1
1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0
1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7h1V5.73c-.6-.34-1-.99-1-1.73a2 2 0 0 1
2-2M7.5 13A2.5 2.5 0 0 0 5 15.5A2.5 2.5 0 0 0 7.5 18a2.5 2.5 0 0 0 2.5-2.5A2.5
2.5 0 0 0 7.5 13m9 0a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0
2.5-2.5a2.5 2.5 0 0 0-2.5-2.5z"
- />
- </svg>
- {{ 'ai.bot.title' | i18n }}
- </div>
- <span class="close-btn" (click)="toggleChat()">X</span>
- </div>
-
- <div class="chat-messages">
- <div *ngFor="let message of messages" class="message"
[class.user-message]="message.isUser" [class.bot-message]="!message.isUser">
- <div class="message-content">{{ message.content }}</div>
- <div class="message-time">{{ message.timestamp | date : 'HH:mm'
}}</div>
- </div>
- <div *ngIf="isLoading" class="bot-message loading-message">
- <nz-spin nzSimple></nz-spin>
- </div>
- </div>
-
- <div class="chat-input">
- <input nz-input placeholder="{{ 'ai.bot.input.placeholder' | i18n }}"
[(ngModel)]="currentMessage" (keyup.enter)="sendMessage()" />
- <button nz-button nzType="primary" [disabled]="!currentMessage.trim()"
(click)="sendMessage()"> {{ 'ai.bot.send' | i18n }} </button>
- </div>
- </div>
-</div>
diff --git a/web-app/src/app/shared/components/ai-bot/ai-bot.component.less
b/web-app/src/app/shared/components/ai-bot/ai-bot.component.less
deleted file mode 100644
index 03fe1519e..000000000
--- a/web-app/src/app/shared/components/ai-bot/ai-bot.component.less
+++ /dev/null
@@ -1,147 +0,0 @@
-.ai-bot-container {
- position: fixed;
- bottom: 30px;
- right: 30px;
- z-index: 10000;
-}
-
-.ai-bot-icon {
- width: 60px;
- height: 60px;
- border-radius: 50%;
- background-color: #1890ff;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- transition: all 0.3s;
- color: white;
- font-weight: bold;
- font-size: 18px;
-
- &:hover {
- transform: scale(1.05);
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
- }
-
- &.active {
- background-color: #ff4d4f;
- }
-}
-
-.ai-bot-icon-text {
- width: 60px;
- height: 60px;
- border-radius: 50%;
- background-color: #1890ff;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- transition: all 0.3s;
- color: white;
- font-weight: bold;
- font-size: 14px;
- text-align: center;
-
- &:hover {
- transform: scale(1.05);
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
- }
-}
-
-.ai-bot-chat-window {
- position: absolute;
- bottom: 80px;
- right: 0;
- width: 350px;
- height: 500px;
- background-color: white;
- border-radius: 8px;
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
- display: flex;
- flex-direction: column;
- overflow: hidden;
- z-index: 10000;
-}
-
-.chat-header {
- padding: 12px 16px;
- border-bottom: 1px solid #f0f0f0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- background-color: #1890ff;
- color: white;
-
- .chat-title {
- font-weight: 500;
- font-size: 16px;
- }
-
- .close-btn {
- cursor: pointer;
- font-size: 16px;
- color: white;
- font-weight: bold;
- }
-}
-
-.chat-messages {
- flex: 1;
- padding: 16px;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
- gap: 12px;
- background-color: #f5f5f5;
-}
-
-.message {
- max-width: 80%;
- padding: 10px 12px;
- border-radius: 8px;
- position: relative;
-
- .message-content {
- word-break: break-word;
- }
-
- .message-time {
- font-size: 11px;
- margin-top: 4px;
- opacity: 0.7;
- text-align: right;
- }
-}
-
-.bot-message {
- align-self: flex-start;
- background-color: white;
- border: 1px solid #e8e8e8;
-
- &.loading-message {
- padding: 16px;
- display: flex;
- justify-content: center;
- }
-}
-
-.user-message {
- align-self: flex-end;
- background-color: #e6f7ff;
- color: #0050b3;
-}
-
-.chat-input {
- padding: 12px;
- border-top: 1px solid #f0f0f0;
- display: flex;
- gap: 8px;
-
- input {
- flex: 1;
- }
-}
\ No newline at end of file
diff --git a/web-app/src/app/shared/components/ai-bot/ai-bot.component.scss
b/web-app/src/app/shared/components/ai-bot/ai-bot.component.scss
deleted file mode 100644
index b33c34ecc..000000000
--- a/web-app/src/app/shared/components/ai-bot/ai-bot.component.scss
+++ /dev/null
@@ -1,158 +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.
-
-.ai-bot-container {
- position: fixed;
- bottom: 20px;
- right: 20px;
- z-index: 1000;
-}
-
-.ai-bot-icon {
- width: 50px;
- height: 50px;
- border-radius: 50%;
- background-color: #1890ff;
- color: white;
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- transition: all 0.3s ease;
-}
-
-.ai-bot-icon:hover {
- transform: scale(1.05);
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
-}
-
-.ai-bot-icon.active {
- background-color: #f5222d;
-}
-
-/* 机器人图标样式 */
-.robot-icon {
- width: 30px;
- height: 30px;
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.robot-icon svg {
- width: 100%;
- height: 100%;
- fill: white; /* 确保SVG是白色 */
-}
-
-/* 关闭图标样式 */
-.close-icon {
- font-weight: bold;
- font-size: 18px;
-}
-
-.ai-bot-chat-window {
- position: absolute;
- bottom: 70px;
- right: 0;
- width: 320px;
- height: 450px;
- border-radius: 8px;
- background-color: white;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-.chat-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 12px 16px;
- background-color: #1890ff;
- color: white;
-}
-
-.chat-title {
- font-weight: 500;
- display: flex;
- align-items: center;
-}
-
-.close-btn {
- cursor: pointer;
- font-weight: bold;
-}
-
-.chat-messages {
- flex: 1;
- overflow-y: auto;
- padding: 16px;
- display: flex;
- flex-direction: column;
- gap: 12px;
- background-color: #f5f5f5;
-}
-
-.message {
- max-width: 80%;
- padding: 8px 12px;
- border-radius: 12px;
-}
-
-.user-message {
- align-self: flex-end;
- background-color: #1890ff;
- color: white;
-}
-
-.bot-message {
- align-self: flex-start;
- background-color: white;
- color: #333;
- border: 1px solid #e8e8e8;
-}
-
-.message-content {
- word-break: break-word;
- white-space: pre-wrap;
-}
-
-.message-time {
- font-size: 12px;
- margin-top: 4px;
- opacity: 0.7;
- text-align: right;
-}
-
-.loading-message {
- padding: 8px;
- text-align: center;
-}
-
-.chat-input {
- display: flex;
- padding: 12px;
- border-top: 1px solid #e8e8e8;
-}
-
-.chat-input input {
- flex: 1;
- margin-right: 8px;
-}
diff --git a/web-app/src/app/shared/components/ai-bot/ai-bot.component.ts
b/web-app/src/app/shared/components/ai-bot/ai-bot.component.ts
deleted file mode 100644
index 7975eab9e..000000000
--- a/web-app/src/app/shared/components/ai-bot/ai-bot.component.ts
+++ /dev/null
@@ -1,72 +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 { Component, OnInit } from '@angular/core';
-import { I18NService } from '@core';
-
-import { AiBotService, ChatMessage } from '../../services/ai-bot.service';
-
-@Component({
- selector: 'app-ai-bot',
- templateUrl: './ai-bot.component.html',
- styleUrls: ['./ai-bot.component.less']
-})
-export class AiBotComponent implements OnInit {
- isOpen = false;
- messages: ChatMessage[] = [];
- currentMessage = '';
- isLoading = false;
-
- constructor(private aiBotService: AiBotService, private i18nSvc:
I18NService) {}
-
- ngOnInit(): void {
- // add greeting message
- this.messages.push({
- content: this.i18nSvc.fanyi('ai.bot.greeting'),
- isUser: false,
- timestamp: new Date()
- });
- console.log('The AI assistant component has loaded.');
- }
-
- toggleChat(): void {
- this.isOpen = !this.isOpen;
- }
-
- sendMessage(): void {
- if (!this.currentMessage.trim()) return;
-
- // add user message
- this.messages.push({
- content: this.currentMessage,
- isUser: true,
- timestamp: new Date()
- });
-
- const userMessage = this.currentMessage;
- this.currentMessage = '';
- this.isLoading = true;
-
- // call service to fetch AI response
- this.aiBotService.sendMessage(userMessage).subscribe(response => {
- this.messages.push(response);
- this.isLoading = false;
- });
- }
-}
diff --git a/web-app/src/app/shared/components/ai-chat/ai-chat.module.ts
b/web-app/src/app/shared/components/ai-chat/ai-chat.module.ts
index 0f3ac1a0e..ca16c108b 100644
--- a/web-app/src/app/shared/components/ai-chat/ai-chat.module.ts
+++ b/web-app/src/app/shared/components/ai-chat/ai-chat.module.ts
@@ -28,6 +28,7 @@ import { NzIconModule } from 'ng-zorro-antd/icon';
import { NzInputModule } from 'ng-zorro-antd/input';
import { NzMessageModule } from 'ng-zorro-antd/message';
import { NzModalModule } from 'ng-zorro-antd/modal';
+import { NzSelectModule } from 'ng-zorro-antd/select';
import { NzSpinModule } from 'ng-zorro-antd/spin';
import { MarkdownPipe } from 'ngx-markdown';
@@ -47,6 +48,7 @@ import { ChatComponent } from './chat.component';
NzInputModule,
NzMessageModule,
NzModalModule,
+ NzSelectModule,
NzSpinModule,
MarkdownPipe
],
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..a44748bb7 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,23 +160,73 @@
<!-- 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"
+ nzWidth="700px"
[nzOkLoading]="configLoading"
nzOkText="Validate & Save"
nzCancelText="Cancel"
>
<div *nzModalContent class="-inner-content">
<form nz-form nzLayout="vertical">
+ <!-- Provider Selection -->
<nz-form-item>
- <nz-form-label nzRequired="true">OpenAI API Key</nz-form-label>
+ <nz-form-label nzRequired="true">AI Provider</nz-form-label>
+ <nz-form-control nzErrorTip="Please select an AI provider">
+ <nz-select
+ [(ngModel)]="aiProviderConfig.code"
+ name="provider"
+ nzPlaceHolder="Select AI Provider"
+ (ngModelChange)="onProviderChange($event)"
+ style="width: 100%"
+ >
+ <nz-option *ngFor="let option of providerOptions"
[nzValue]="option.value" [nzLabel]="option.label"></nz-option>
+ </nz-select>
+ <p class="form-help">Choose your AI provider (OpenAI, ZhiPu, or
ZAI)</p>
+ </nz-form-control>
+ </nz-form-item>
+
+ <!-- API Key -->
+ <nz-form-item>
+ <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>
+
+ <!-- Base URL -->
+ <nz-form-item>
+ <nz-form-label>Base URL</nz-form-label>
+ <nz-form-control>
+ <nz-input-group [nzSuffix]="resetBtn">
+ <input nz-input [(ngModel)]="aiProviderConfig.baseUrl"
name="baseUrl" placeholder="https://api.openai.com/v1" />
+ <ng-template #resetBtn>
+ <button
+ nz-button
+ nzType="link"
+ nzSize="small"
+ (click)="resetToDefaults()"
+ nzTooltipTitle="Reset to default values"
+ nz-tooltip
+ >
+ <i nz-icon nzType="reload"></i>
+ </button>
+ </ng-template>
+ </nz-input-group>
+ <p class="form-help">Custom API endpoint URL. Leave empty to use
default for selected provider.</p>
+ </nz-form-control>
+ </nz-form-item>
+
+ <!-- Model -->
+ <nz-form-item>
+ <nz-form-label>Model</nz-form-label>
+ <nz-form-control>
+ <input nz-input [(ngModel)]="aiProviderConfig.model" name="model"
placeholder="gpt-4" />
+ <p class="form-help">Model name to use. Leave empty to use default
for selected provider.</p>
</nz-form-control>
</nz-form-item>
</form>
diff --git a/web-app/src/app/shared/components/ai-chat/chat.component.less
b/web-app/src/app/shared/components/ai-chat/chat.component.less
index 91e6c0005..8edbb3a1c 100644
--- a/web-app/src/app/shared/components/ai-chat/chat.component.less
+++ b/web-app/src/app/shared/components/ai-chat/chat.component.less
@@ -634,6 +634,41 @@ body[data-theme='dark'] .chat-container {
}
}
+// Provider configuration form styles
+.provider-config-form {
+ .provider-select-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ nz-select {
+ flex: 1;
+ }
+ }
+
+ .baseurl-input-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ nz-input-group {
+ flex: 1;
+ }
+
+ .reset-btn {
+ flex-shrink: 0;
+ }
+ }
+
+ .form-item-with-help {
+ margin-bottom: 8px;
+
+ + .form-help {
+ margin-bottom: 16px;
+ }
+ }
+}
+
nz-modal {
.ant-modal-body {
padding: 24px;
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..b47365bcd 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, PROVIDER_OPTIONS, ProviderOption } 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,8 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
isOpenAiConfigured = false;
showConfigModal = false;
configLoading = false;
- openAiConfig: OpenAiConfig = {
- enable: false,
- apiKey: ''
- };
+ aiProviderConfig: ModelProviderConfig = new ModelProviderConfig();
+ providerOptions: ProviderOption[] = PROVIDER_OPTIONS;
constructor(
private aiChatService: AiChatService,
@@ -58,12 +57,12 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
private i18n: I18NService,
private cdr: ChangeDetectorRef,
private themeSvc: ThemeService,
- private openAiConfigService: OpenAiConfigService
+ private generalConfigSvc: GeneralConfigService
) {}
ngOnInit(): void {
this.theme = this.themeSvc.getTheme() || 'default';
- this.checkOpenAiConfiguration();
+ this.checkAiConfiguration();
}
ngAfterViewChecked(): void {
@@ -74,10 +73,8 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
* Load all conversations
*/
loadConversations(): void {
- console.log('Loading conversations...');
this.aiChatService.getConversations().subscribe({
next: response => {
- console.log('Conversations response:', response);
if (response.code === 0 && response.data) {
this.conversations = response.data;
// If no current conversation, create a new one
@@ -107,10 +104,8 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
* Create a new conversation
*/
createNewConversation(): void {
- console.log('Creating new conversation...');
this.aiChatService.createConversation().subscribe({
next: response => {
- console.log('Create conversation response:', response);
if (response.code === 0 && response.data) {
const newConversation = response.data;
this.conversations.unshift(newConversation);
@@ -165,7 +160,6 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
this.aiChatService.getConversation(conversationId).subscribe({
next: response => {
this.isLoading = false;
- console.log('Conversation history response:', response);
if (response.code === 0 && response.data) {
this.messages = response.data.messages || [];
@@ -322,7 +316,6 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
this.scrollToBottom();
},
complete: () => {
- console.log('Chat stream completed');
this.isLoading = false;
this.cdr.detectChanges();
@@ -428,61 +421,68 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
}
/**
- * Check OpenAI configuration status
+ * Check provider configuration status
*/
- checkOpenAiConfiguration(): void {
- this.openAiConfigService.getOpenAiConfigStatus().subscribe({
+ checkAiConfiguration(): void {
+ 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;
+ // Ensure default values are set if not present
+ if (!this.aiProviderConfig.code) {
+ this.aiProviderConfig.code = 'openai';
+ }
+ if (!this.aiProviderConfig.baseUrl) {
+ const defaultProvider = this.providerOptions.find(p => p.value ===
this.aiProviderConfig.code);
+ if (defaultProvider) {
+ this.aiProviderConfig.baseUrl = defaultProvider.defaultBaseUrl;
+ }
+ }
+ if (!this.aiProviderConfig.model) {
+ const defaultProvider = this.providerOptions.find(p => p.value ===
this.aiProviderConfig.code);
+ if (defaultProvider) {
+ this.aiProviderConfig.model = defaultProvider.defaultModel;
+ }
+ }
+
+ 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();
+ // Initialize with default values if no config exists
+ this.aiProviderConfig = new ModelProviderConfig();
+ this.showAiProviderConfigDialog();
}
},
error: error => {
- console.error('Error checking OpenAI configuration:', error);
- this.showOpenAiConfigDialog();
+ console.error('Failed to load model provider config:', error);
+ this.aiProviderConfig = new ModelProviderConfig();
+ 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 +500,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.checkAiConfiguration();
this.showConfigModal = true;
}
@@ -534,23 +518,39 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
/**
* Save OpenAI configuration
*/
- onSaveOpenAiConfig(): void {
- if (!this.openAiConfig.apiKey.trim()) {
- this.message.error('API Key is required');
+ onSaveAiProviderConfig(): void {
+ if (!this.aiProviderConfig.apiKey?.trim()) {
+ this.message.error('Please enter API Key');
+ return;
+ }
+
+ if (!this.aiProviderConfig.code?.trim()) {
+ this.message.error('Please select a provider');
+ return;
+ }
+
+ if (!this.aiProviderConfig.baseUrl?.trim()) {
+ this.message.error('Please enter Base URL');
+ return;
+ }
+
+ if (!this.aiProviderConfig.model?.trim()) {
+ this.message.error('Please enter Model');
return;
}
// Always enable when saving an API key
- this.openAiConfig.enable = true;
+ this.aiProviderConfig.enable = true;
+ this.aiProviderConfig.status = 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();
@@ -569,4 +569,32 @@ export class ChatComponent implements OnInit,
AfterViewChecked {
}
});
}
+
+ /**
+ * Handle provider selection change
+ */
+ onProviderChange(provider: string): void {
+ const selectedProvider = this.providerOptions.find(p => p.value ===
provider);
+ if (selectedProvider) {
+ this.aiProviderConfig.code = provider;
+ // Auto-fill default values if current values are empty
+ if (!this.aiProviderConfig.baseUrl) {
+ this.aiProviderConfig.baseUrl = selectedProvider.defaultBaseUrl;
+ }
+ if (!this.aiProviderConfig.model) {
+ this.aiProviderConfig.model = selectedProvider.defaultModel;
+ }
+ }
+ }
+
+ /**
+ * Reset to default values for selected provider
+ */
+ resetToDefaults(): void {
+ const selectedProvider = this.providerOptions.find(p => p.value ===
this.aiProviderConfig.code);
+ if (selectedProvider) {
+ this.aiProviderConfig.baseUrl = selectedProvider.defaultBaseUrl;
+ this.aiProviderConfig.model = selectedProvider.defaultModel;
+ }
+ }
}
diff --git a/web-app/src/app/shared/shared.module.ts
b/web-app/src/app/shared/shared.module.ts
index ee9e6e62f..0d499cddf 100644
--- a/web-app/src/app/shared/shared.module.ts
+++ b/web-app/src/app/shared/shared.module.ts
@@ -20,7 +20,6 @@ import { NzTagModule } from 'ng-zorro-antd/tag';
// Icon to be used for registration
const icons: IconDefinition[] = [RobotOutline, CloseOutline, SendOutline];
-import { AiBotComponent } from './components/ai-bot/ai-bot.component';
import { AiChatModule } from './components/ai-chat/ai-chat.module';
import { ConfigurableFieldComponent } from
'./components/configurable-field/configurable-field.component';
import { FormFieldComponent } from
'./components/form-field/form-field.component';
@@ -43,7 +42,6 @@ const COMPONENTS: Array<Type<void>> = [
ConfigurableFieldComponent,
FormFieldComponent,
MonitorSelectMenuComponent,
- AiBotComponent,
LabelSelectorComponent
];
const DIRECTIVES: Array<Type<void>> = [TimezonePipe, I18nElsePipe,
ElapsedTimePipe];
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]