This is an automated email from the ASF dual-hosted git repository.
zhaoqingran 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 2bb4f43494 [improve] optimize the function of creating monitoring
using AI (#3922)
2bb4f43494 is described below
commit 2bb4f43494dfaaf3931b4c605c4816218877c666
Author: liutianyou <[email protected]>
AuthorDate: Fri Mar 27 23:05:14 2026 +0800
[improve] optimize the function of creating monitoring using AI (#3922)
Signed-off-by: aias00 <[email protected]>
Co-authored-by: lynx009 <[email protected]>
Co-authored-by: aias00 <[email protected]>
Co-authored-by: shown <[email protected]>
Co-authored-by: Duansg <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: manshi <[email protected]>
---
.../hertzbeat/ai/controller/ChatController.java | 38 ++--
.../apache/hertzbeat/ai/pojo/dto/SecurityData.java | 38 ++--
.../hertzbeat/ai/service/ConversationService.java | 12 +-
.../impl/ChatClientProviderServiceImpl.java | 85 +++++---
.../ai/service/impl/ConversationServiceImpl.java | 132 ++++++------
.../apache/hertzbeat/ai/tools/MonitorTools.java | 77 ++++---
.../hertzbeat/ai/tools/impl/MonitorToolsImpl.java | 221 ++++++++++++++-------
.../resources/prompt/extra-message-protected.st | 140 +++++++++++++
.../src/main/resources/prompt/system-message.st | 20 +-
.../common/entity/dto/ModelProviderConfig.java | 14 +-
.../common/entity/ai/ChatConversation.java | 3 +
web-app/src/app/pojo/ModelProviderConfig.ts | 1 +
web-app/src/app/service/ai-chat.service.ts | 25 ++-
.../shared/components/ai-chat/ai-chat.module.ts | 4 +-
.../shared/components/ai-chat/chat.component.html | 47 +++++
.../shared/components/ai-chat/chat.component.ts | 109 +++++++++-
web-app/src/app/shared/shared.module.ts | 6 +-
web-app/src/assets/i18n/en-US.json | 9 +-
web-app/src/assets/i18n/ja-JP.json | 12 +-
web-app/src/assets/i18n/pt-BR.json | 8 +
web-app/src/assets/i18n/zh-CN.json | 9 +-
web-app/src/assets/i18n/zh-TW.json | 8 +
22 files changed, 766 insertions(+), 252 deletions(-)
diff --git
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/controller/ChatController.java
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/controller/ChatController.java
index ff2dc19cbd..0f5bfcd9e2 100644
---
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/controller/ChatController.java
+++
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/controller/ChatController.java
@@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.ai.config.McpContextHolder;
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
import org.apache.hertzbeat.ai.pojo.dto.ChatResponseChunk;
+import org.apache.hertzbeat.ai.pojo.dto.SecurityData;
import org.apache.hertzbeat.ai.service.ConversationService;
import org.apache.hertzbeat.common.entity.ai.ChatConversation;
import org.apache.hertzbeat.common.entity.dto.Message;
@@ -78,12 +79,12 @@ public class ChatController {
McpContextHolder.setSubject(subject);
if (context.getMessage() == null ||
context.getMessage().trim().isEmpty()) {
ChatResponseChunk errorResponse = ChatResponseChunk.builder()
- .conversationId(context.getConversationId())
- .response("Error: Message cannot be empty")
- .build();
+ .conversationId(context.getConversationId())
+ .response("Error: Message cannot be empty")
+ .build();
return Flux.just(ServerSentEvent.builder(errorResponse)
- .event("error")
- .build());
+ .event("error")
+ .build());
}
log.info("Received streaming chat request for conversation: {}",
context.getConversationId());
@@ -92,12 +93,12 @@ public class ChatController {
} catch (Exception e) {
log.error("Error in stream chat endpoint: ", e);
ChatResponseChunk errorResponse = ChatResponseChunk.builder()
- .conversationId(context.getConversationId())
- .response("An error occurred: " + e.getMessage())
- .build();
+ .conversationId(context.getConversationId())
+ .response("An error occurred: " + e.getMessage())
+ .build();
return Flux.just(ServerSentEvent.builder(errorResponse)
- .event("error")
- .build());
+ .event("error")
+ .build());
}
}
@@ -134,7 +135,7 @@ public class ChatController {
@GetMapping(path = "/conversations/{conversationId}")
@Operation(summary = "Get conversation history", description = "Get
detailed information and message history for a specific conversation")
public ResponseEntity<Message<ChatConversation>> getConversation(
- @Parameter(description = "Conversation ID", example = "12345678")
@PathVariable(value = "conversationId") Long conversationId) {
+ @Parameter(description = "Conversation ID", example = "12345678")
@PathVariable(value = "conversationId") Long conversationId) {
ChatConversation conversation =
conversationService.getConversation(conversationId);
return ResponseEntity.ok(Message.success(conversation));
}
@@ -148,8 +149,21 @@ public class ChatController {
@DeleteMapping(path = "/conversations/{conversationId}")
@Operation(summary = "Delete conversation", description = "Delete a
specific conversation and all its messages")
public ResponseEntity<Message<Void>> deleteConversation(
- @Parameter(description = "Conversation ID", example = "2345678")
@PathVariable("conversationId") Long conversationId) {
+ @Parameter(description = "Conversation ID", example = "2345678")
@PathVariable("conversationId") Long conversationId) {
conversationService.deleteConversation(conversationId);
return ResponseEntity.ok(Message.success());
}
+
+ /**
+ * Save data submitted by secure form
+ * @param securityData security data
+ * @return save result
+ */
+ @PostMapping(path = "/security")
+ @Operation(summary = "save security data", description = "Save security
data")
+ public ResponseEntity<Message<Boolean>> commitSecurityData(@Valid
@RequestBody SecurityData securityData) {
+ return
ResponseEntity.ok(Message.success(conversationService.saveSecurityData(securityData)));
+ }
+
+
}
diff --git
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/ModelProviderConfig.java
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/pojo/dto/SecurityData.java
similarity index 50%
copy from
hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/ModelProviderConfig.java
copy to
hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/pojo/dto/SecurityData.java
index 527f65da72..fb01d1ea93 100644
---
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/ModelProviderConfig.java
+++
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/pojo/dto/SecurityData.java
@@ -15,36 +15,24 @@
* limitations under the License.
*/
-package org.apache.hertzbeat.common.entity.dto;
+package org.apache.hertzbeat.ai.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
+import jakarta.validation.constraints.NotNull;
import lombok.Data;
-import lombok.NoArgsConstructor;
/**
- * Model Provider Configuration
+ * security data
*/
@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@Schema(description = "LLM Model Provider configuration")
-public class ModelProviderConfig {
-
- @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;
+public class SecurityData {
+
+ @NotNull
+ @Schema(description = "Conversation ID", example = "123")
+ private Long conversationId;
+
+ @NotNull
+ @Schema(description = "security data", example =
"{\"password\":\"xxxxx\"}")
+ private String securityData;
+
}
diff --git
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/ConversationService.java
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/ConversationService.java
index a9eadef052..8a007cf0a0 100644
---
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/ConversationService.java
+++
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/ConversationService.java
@@ -19,6 +19,7 @@
package org.apache.hertzbeat.ai.service;
import org.apache.hertzbeat.ai.pojo.dto.ChatResponseChunk;
+import org.apache.hertzbeat.ai.pojo.dto.SecurityData;
import org.apache.hertzbeat.common.entity.ai.ChatConversation;
import org.springframework.http.codec.ServerSentEvent;
import reactor.core.publisher.Flux;
@@ -33,7 +34,7 @@ public interface ConversationService {
/**
* Send a message and receive a streaming response
*
- * @param message The user's message
+ * @param message The user's message
* @param conversationId Optional conversation ID for continuing a chat
* @return Flux of ServerSentEvent for streaming the response
*/
@@ -67,4 +68,13 @@ public interface ConversationService {
* @param conversationId Conversation ID to delete
*/
void deleteConversation(Long conversationId);
+
+ /**
+ * save security data for a conversation
+ *
+ * @param securityData securityData
+ * @return save result
+ */
+ Boolean saveSecurityData(SecurityData securityData);
+
}
diff --git
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java
index edd8bd4ea8..e744d3e017 100644
---
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java
+++
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java
@@ -18,6 +18,10 @@
package org.apache.hertzbeat.ai.service.impl;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.ai.sop.model.SopDefinition;
import org.apache.hertzbeat.ai.sop.model.SopParameter;
@@ -27,9 +31,12 @@ import
org.apache.hertzbeat.common.entity.dto.ModelProviderConfig;
import org.apache.hertzbeat.ai.service.ChatClientProviderService;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
+import org.apache.hertzbeat.common.support.event.AiProviderConfigChangeEvent;
import org.apache.hertzbeat.common.util.JsonUtil;
+import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
+import org.springframework.context.event.EventListener;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
@@ -44,14 +51,12 @@ import org.springframework.context.ApplicationContext;
import reactor.core.publisher.Flux;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
- * Implementation of the {@link ChatClientProviderService}.
- * Provides functionality to interact with the ChatClient for handling chat
- * messages.
+ * Implementation of the {@link ChatClientProviderService}. Provides
functionality to interact with the ChatClient for
+ * handling chat messages.
*/
@Slf4j
@Service
@@ -64,21 +69,28 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
private final GeneralConfigDao generalConfigDao;
+ private ModelProviderConfig modelProviderConfig;
+
+
private final SkillRegistry skillRegistry;
-
+
@Autowired
@Qualifier("hertzbeatTools")
private ToolCallbackProvider toolCallbackProvider;
-
+
private boolean isConfigured = false;
@Value("classpath:/prompt/system-message.st")
private Resource systemResource;
+ @Value("classpath:/prompt/extra-message-protected.st")
+ private Resource extraResourceProtected;
+
+
@Autowired
- public ChatClientProviderServiceImpl(ApplicationContext
applicationContext,
- GeneralConfigDao generalConfigDao,
- @Lazy SkillRegistry skillRegistry) {
+ public ChatClientProviderServiceImpl(ApplicationContext applicationContext,
+ GeneralConfigDao generalConfigDao,
+ @Lazy SkillRegistry skillRegistry) {
this.applicationContext = applicationContext;
this.generalConfigDao = generalConfigDao;
this.skillRegistry = skillRegistry;
@@ -89,7 +101,7 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
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
@@ -112,13 +124,13 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
String systemPrompt =
buildSystemPrompt(context.getConversationId());
return chatClient.prompt()
- .messages(messages)
- .system(systemPrompt)
- .toolCallbacks(toolCallbackProvider)
- .stream()
- .content()
- .doOnComplete(() -> log.info("Streaming completed for
conversation: {}", context.getConversationId()))
- .doOnError(error -> log.error("Error in streaming chat:
{}", error.getMessage(), error));
+ .messages(messages)
+ .system(systemPrompt)
+ .toolCallbacks(toolCallbackProvider)
+ .stream()
+ .content()
+ .doOnComplete(() -> log.info("Streaming completed for
conversation: {}", context.getConversationId()))
+ .doOnError(error -> log.error("Error in streaming chat: {}",
error.getMessage(), error));
} catch (Exception e) {
log.error("Error setting up streaming chat: {}", e.getMessage(),
e);
@@ -133,30 +145,43 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
try {
String template =
systemResource.getContentAsString(StandardCharsets.UTF_8);
String skillsList = generateSkillsList();
- return template
- .replace(SKILLS_PLACEHOLDER, skillsList)
- .replace(CONVERSATION_ID_PLACEHOLDER,
String.valueOf(conversationId));
+ template = template
+ .replace(SKILLS_PLACEHOLDER, skillsList)
+ .replace(CONVERSATION_ID_PLACEHOLDER,
String.valueOf(conversationId));
+
+ // add extra prompt for protected model to guide it to use
protected tools
+ if (Objects.equals(modelProviderConfig.getParticipationModel(),
"PROTECTED")) {
+ Map<String, Object> metadata = new HashMap<>();
+ metadata.put("conversationId", conversationId);
+ return template +
SystemPromptTemplate.builder().resource(extraResourceProtected).build()
+ .create(metadata)
+ .getContents();
+ } else {
+ return template;
+ }
+
} catch (IOException e) {
log.error("Failed to read system prompt template: {}",
e.getMessage());
return "";
}
}
+
/**
* Generate a formatted list of available skills for the system prompt.
*/
private String generateSkillsList() {
List<SopDefinition> skills = skillRegistry.getAllSkills();
-
+
if (skills.isEmpty()) {
return "No skills currently available. Use listSkills tool to
refresh.";
}
-
+
StringBuilder sb = new StringBuilder();
for (SopDefinition skill : skills) {
sb.append("- **").append(skill.getName()).append("**: ");
sb.append(skill.getDescription());
-
+
// Add parameter hints
if (skill.getParameters() != null &&
!skill.getParameters().isEmpty()) {
sb.append(" (requires: ");
@@ -171,16 +196,24 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
}
sb.append("\n");
}
-
+
return sb.toString();
}
+ @EventListener(AiProviderConfigChangeEvent.class)
+ public void onAiProviderConfigChange(AiProviderConfigChangeEvent event) {
+ GeneralConfig providerConfig = generalConfigDao.findByType("provider");
+ this.modelProviderConfig =
JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class);
+ }
+
@Override
public boolean isConfigured() {
if (!isConfigured) {
GeneralConfig providerConfig =
generalConfigDao.findByType("provider");
- ModelProviderConfig modelProviderConfig =
JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class);
- isConfigured = modelProviderConfig != null &&
modelProviderConfig.getApiKey() != null;
+ ModelProviderConfig modelProviderConfig =
JsonUtil.fromJson(providerConfig.getContent(),
+ ModelProviderConfig.class);
+ isConfigured = modelProviderConfig != null &&
modelProviderConfig.getApiKey() != null;
+ this.modelProviderConfig = modelProviderConfig;
}
return isConfigured;
}
diff --git
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImpl.java
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImpl.java
index 23e745a050..94c760580f 100644
---
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImpl.java
+++
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImpl.java
@@ -17,15 +17,18 @@
package org.apache.hertzbeat.ai.service.impl;
+import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.ai.dao.ChatConversationDao;
import org.apache.hertzbeat.ai.dao.ChatMessageDao;
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
import org.apache.hertzbeat.ai.pojo.dto.ChatResponseChunk;
+import org.apache.hertzbeat.ai.pojo.dto.SecurityData;
import org.apache.hertzbeat.ai.service.ChatClientProviderService;
import org.apache.hertzbeat.ai.service.ConversationService;
import org.apache.hertzbeat.common.entity.ai.ChatConversation;
import org.apache.hertzbeat.common.entity.ai.ChatMessage;
+import org.apache.hertzbeat.common.util.AesUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.http.codec.ServerSentEvent;
@@ -62,17 +65,17 @@ public class ConversationServiceImpl implements
ConversationService {
// Check if provider is properly configured
if (!chatClientProviderService.isConfigured()) {
ChatResponseChunk errorResponse = ChatResponseChunk.builder()
- .conversationId(conversationId)
- .response("Provider is not configured. Please configure
your AI Provider.")
- .build();
+ .conversationId(conversationId)
+ .response("Provider is not configured. Please configure your
AI Provider.")
+ .build();
return Flux.just(ServerSentEvent.builder(errorResponse)
- .event("error")
- .build());
+ .event("error")
+ .build());
}
log.info("Starting streaming conversation: {}", conversationId);
ChatConversation conversation =
conversationDao.findById(conversationId)
- .orElseThrow(() -> new IllegalArgumentException("Conversation
not found: " + conversationId));
+ .orElseThrow(() -> new IllegalArgumentException("Conversation not
found: " + conversationId));
// Manually load messages for conversation history
List<ChatMessage> messages =
messageDao.findByConversationIdOrderByGmtCreateAsc(conversationId);
@@ -94,58 +97,59 @@ public class ConversationServiceImpl implements
ConversationService {
chatMessage = messageDao.save(chatMessage);
ChatRequestContext context = ChatRequestContext.builder()
- .message(message)
- .conversationId(conversationId)
-
.conversationHistory(CollectionUtils.isEmpty(conversation.getMessages()) ? null
- : conversation.getMessages().subList(0,
conversation.getMessages().size() - 1))
- .build();
+ .message(message)
+ .conversationId(conversationId)
+
.conversationHistory(CollectionUtils.isEmpty(conversation.getMessages()) ? null
+ : conversation.getMessages().subList(0,
conversation.getMessages().size() - 1))
+ .build();
// Stream response from AI service
StringBuilder fullResponse = new StringBuilder();
ChatMessage finalChatMessage = chatMessage;
return chatClientProviderService.streamChat(context)
- .map(chunk -> {
- fullResponse.append(chunk);
- ChatResponseChunk responseChunk =
ChatResponseChunk.builder()
- .conversationId(conversationId)
- .userMessageId(finalChatMessage.getId())
- .response(chunk)
- .build();
-
- return ServerSentEvent.builder(responseChunk)
- .event("message")
- .build();
- })
- .concatWith(Flux.defer(() -> {
- // Add the complete AI response to conversation
- ChatMessage assistantMessage = ChatMessage.builder()
- .conversationId(conversationId)
- .content(fullResponse.toString())
- .role("assistant")
- .build();
- assistantMessage = messageDao.save(assistantMessage);
- ChatResponseChunk finalResponse =
ChatResponseChunk.builder()
- .conversationId(conversationId)
- .response("")
- .assistantMessageId(assistantMessage.getId())
- .build();
-
- return Flux.just(ServerSentEvent.builder(finalResponse)
- .event("complete")
- .build());
- }))
- .doOnComplete(() -> log.info("Streaming completed for
conversation: {}", conversationId))
- .doOnError(error -> log.error("Error in streaming chat for
conversation {}: {}", conversationId, error.getMessage(), error))
- .onErrorResume(error -> {
- ChatResponseChunk errorResponse =
ChatResponseChunk.builder()
- .conversationId(conversationId)
- .response("An error occurred: " +
error.getMessage())
- .userMessageId(finalChatMessage.getId())
- .build();
- return Flux.just(ServerSentEvent.builder(errorResponse)
- .event("error")
- .build());
- });
+ .map(chunk -> {
+ fullResponse.append(chunk);
+ ChatResponseChunk responseChunk = ChatResponseChunk.builder()
+ .conversationId(conversationId)
+ .userMessageId(finalChatMessage.getId())
+ .response(chunk)
+ .build();
+
+ return ServerSentEvent.builder(responseChunk)
+ .event("message")
+ .build();
+ })
+ .concatWith(Flux.defer(() -> {
+ // Add the complete AI response to conversation
+ ChatMessage assistantMessage = ChatMessage.builder()
+ .conversationId(conversationId)
+ .content(fullResponse.toString())
+ .role("assistant")
+ .build();
+ assistantMessage = messageDao.save(assistantMessage);
+ ChatResponseChunk finalResponse = ChatResponseChunk.builder()
+ .conversationId(conversationId)
+ .response("")
+ .assistantMessageId(assistantMessage.getId())
+ .build();
+
+ return Flux.just(ServerSentEvent.builder(finalResponse)
+ .event("complete")
+ .build());
+ }))
+ .doOnComplete(() -> log.info("Streaming completed for
conversation: {}", conversationId))
+ .doOnError(error -> log.error("Error in streaming chat for
conversation {}: {}", conversationId,
+ error.getMessage(), error))
+ .onErrorResume(error -> {
+ ChatResponseChunk errorResponse = ChatResponseChunk.builder()
+ .conversationId(conversationId)
+ .response("An error occurred: " + error.getMessage())
+ .userMessageId(finalChatMessage.getId())
+ .build();
+ return Flux.just(ServerSentEvent.builder(errorResponse)
+ .event("error")
+ .build());
+ });
}
@Override
@@ -175,13 +179,14 @@ public class ConversationServiceImpl implements
ConversationService {
return conversations;
}
List<Long> conversationIds = conversations.stream()
- .map(ChatConversation::getId)
- .toList();
+ .map(ChatConversation::getId)
+ .toList();
List<ChatMessage> allMessages =
messageDao.findByConversationIdInOrderByGmtCreateAsc(conversationIds);
Map<Long, List<ChatMessage>> messagesByConversationId =
allMessages.stream()
-
.collect(Collectors.groupingBy(ChatMessage::getConversationId));
+ .collect(Collectors.groupingBy(ChatMessage::getConversationId));
for (ChatConversation conversation : conversations) {
- List<ChatMessage> messages =
messagesByConversationId.getOrDefault(conversation.getId(),
Collections.emptyList());
+ List<ChatMessage> messages =
messagesByConversationId.getOrDefault(conversation.getId(),
+ Collections.emptyList());
conversation.setMessages(messages);
}
return conversations;
@@ -196,4 +201,17 @@ public class ConversationServiceImpl implements
ConversationService {
}
conversationDao.deleteById(conversationId);
}
+
+ @Override
+ public Boolean saveSecurityData(SecurityData securityData) {
+ Optional<ChatConversation> chatConversation =
conversationDao.findById(securityData.getConversationId());
+ if (chatConversation.isPresent()) {
+ ChatConversation conversation = chatConversation.get();
+
conversation.setSecurityData(AesUtil.aesEncode(securityData.getSecurityData()));
+ conversationDao.save(conversation);
+ return true;
+ }
+ return false;
+ }
+
}
diff --git
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/MonitorTools.java
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/MonitorTools.java
index c813f09d92..a821776e05 100644
--- a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/MonitorTools.java
+++ b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/MonitorTools.java
@@ -27,19 +27,39 @@ public interface MonitorTools {
/**
* Add a new monitor with comprehensive configuration
*
- * @param name Monitor name
- * @param app Monitor type/application (e.g., 'linux', 'mysql', 'http')
- * @param intervals Collection interval in seconds (default: 600)
- * @param params Monitor-specific parameters as JSON string (e.g., host,
port, username, password, etc.)
+ * @param name Monitor name
+ * @param app Monitor type/application (e.g., 'linux', 'mysql',
'http')
+ * @param intervals Collection interval in seconds (default: 600)
+ * @param params Monitor-specific parameters as JSON string (e.g.,
host, port, username, password, etc.)
* @param description Monitor description (optional)
* @return Result message with monitor ID if successful
*/
String addMonitor(
- String name,
- String app,
- Integer intervals,
- String params,
- String description
+ String name,
+ String app,
+ Integer intervals,
+ String params,
+ String description
+ );
+
+ /**
+ * Add a new monitor with comprehensive configuration, methods
specifically for hertzbeat ai
+ *
+ * @param conversationId Conversation ID
+ * @param name Monitor name
+ * @param app Monitor type/application (e.g., 'linux', 'mysql',
'http')
+ * @param intervals Collection interval in seconds (default: 600)
+ * @param params Monitor-specific parameters as JSON string (e.g.,
host, port, username, password, etc.)
+ * @param description Monitor description (optional)
+ * @return Result message with monitor ID if successful
+ */
+ String addMonitorProtected(
+ Long conversationId,
+ String name,
+ String app,
+ Integer intervals,
+ String params,
+ String description
);
/**
@@ -52,29 +72,30 @@ public interface MonitorTools {
/**
* Comprehensive monitor querying with flexible filtering, pagination, and
specialized views
- * @param ids Specific monitor IDs to retrieve (optional)
- * @param app Monitor type filter (linux, mysql, http, etc.)
- * @param status Monitor status (1=online, 2=offline, 3=unreachable,
0=paused, 9=all)
- * @param search Search in monitor names or hosts (partial matching)
- * @param labels Label filters, format: 'key1:value1,key2:value2'
- * @param sort Sort field (name, gmtCreate, gmtUpdate, status, app)
- * @param order Sort order (asc, desc)
- * @param pageIndex Page number starting from 0
- * @param pageSize Items per page (1-100 recommended)
+ *
+ * @param ids Specific monitor IDs to retrieve (optional)
+ * @param app Monitor type filter (linux, mysql, http, etc.)
+ * @param status Monitor status (1=online, 2=offline, 3=unreachable,
0=paused, 9=all)
+ * @param search Search in monitor names or hosts (partial matching)
+ * @param labels Label filters, format: 'key1:value1,key2:value2'
+ * @param sort Sort field (name, gmtCreate, gmtUpdate, status, app)
+ * @param order Sort order (asc, desc)
+ * @param pageIndex Page number starting from 0
+ * @param pageSize Items per page (1-100 recommended)
* @param includeStats Include status statistics summary
* @return Comprehensive monitor information with optional statistics
*/
String queryMonitors(
- List<Long> ids,
- String app,
- Byte status,
- String search,
- String labels,
- String sort,
- String order,
- Integer pageIndex,
- Integer pageSize,
- Boolean includeStats);
+ List<Long> ids,
+ String app,
+ Byte status,
+ String search,
+ String labels,
+ String sort,
+ String order,
+ Integer pageIndex,
+ Integer pageSize,
+ Boolean includeStats);
/**
* Get parameter definitions required for a specific monitor type
diff --git
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java
index 41080c7985..7886cf97b6 100644
---
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java
+++
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java
@@ -18,8 +18,15 @@
package org.apache.hertzbeat.ai.tools.impl;
import com.usthe.sureness.subject.SubjectSum;
+import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.ai.config.McpContextHolder;
+import org.apache.hertzbeat.ai.dao.ChatConversationDao;
+import org.apache.hertzbeat.common.entity.ai.ChatConversation;
+import org.apache.hertzbeat.common.util.AesUtil;
+import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
import org.apache.hertzbeat.manager.service.MonitorService;
@@ -33,6 +40,7 @@ import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.Param;
+import tools.jackson.core.type.TypeReference;
import java.util.ArrayList;
import java.util.List;
@@ -44,76 +52,79 @@ import java.util.Map;
@Slf4j
@Service
public class MonitorToolsImpl implements MonitorTools {
+
@Autowired
private MonitorService monitorService;
@Autowired
private AppService appService;
+ @Autowired
+ private ChatConversationDao conversationDao;
+
/**
- * Tool to query monitor information with flexible filtering and
pagination.
- * Supports filtering by monitor IDs, type, status, host, labels, sorting,
and
- * pagination.
- * Returns detailed monitor information including ID, name, type, host,
and status.
+ * Tool to query monitor information with flexible filtering and
pagination. Supports filtering by monitor IDs,
+ * type, status, host, labels, sorting, and pagination. Returns detailed
monitor information including ID, name,
+ * type, host, and status.
*/
@Override
@Tool(name = "query_monitors", description = """
- HertzBeat: Query Existing/configured monitors in HertzBeat.
- This tool retrieves monitors based on various filters and
parameters.
- Comprehensive monitor querying with flexible filtering,
pagination, and specialized views.
-
- MONITOR STATUSES:
- - status=1: Online/Active monitors (healthy, responding normally)
- - status=2: Offline monitors (not responding, connection failed)
- - status=3: Unreachable monitors (network/connectivity issues)
- - status=0: Paused monitors (manually disabled/suspended)
- - status=9 or null: All monitors regardless of status (default)
-
- COMMON USE CASES & PARAMETER COMBINATIONS:
-
- 1. BASIC MONITOR LISTING:
- - Default: No parameters (shows all monitors, 8 per page)
- - By type: app='linux' (show only Linux monitors)
- - Search: search='web' (find monitors with 'web' in name/host)
-
- 2. STATUS-BASED QUERIES:
- - Healthy monitors: status=1, pageSize=50
- - Problem monitors: status=2 or status=3, pageSize=50
- - Offline monitors only: status=2
- - Unreachable monitors only: status=3
- - Paused monitors: status=0
-
- 3. MONITORING HEALTH OVERVIEW:
- - All statuses with statistics: status=9, includeStats=true,
pageSize=100
- - Unhealthy monitors: Pass both status=2 AND status=3 (make 2
separate calls)
-
- 4. ADVANCED FILTERING:
- - Specific monitor types: app='mysql', status=1 (healthy MySQL
monitors)
- - Label-based: labels='env:prod,critical:true'
- - Host search: search='192.168' (find by IP pattern)
- - Monitor IDs: ids=[1,2,3] (specific monitors by ID)
-
- 5. SORTING & PAGINATION:
- - Recently updated: sort='gmtUpdate', order='desc'
- - Alphabetical: sort='name', order='asc'
- - By creation: sort='gmtCreate', order='desc' (newest first)
- - Large datasets: pageSize=50-100 for bulk operations
-
- RESPONSE FORMAT:
- - includeStats=true: Adds status distribution summary at top
- - Default: Simple list with ID, name, type, host, status
- - Shows total count and pagination info
- """)
+ HertzBeat: Query Existing/configured monitors in HertzBeat.
+ This tool retrieves monitors based on various filters and parameters.
+ Comprehensive monitor querying with flexible filtering, pagination,
and specialized views.
+
+ MONITOR STATUSES:
+ - status=1: Online/Active monitors (healthy, responding normally)
+ - status=2: Offline monitors (not responding, connection failed)
+ - status=3: Unreachable monitors (network/connectivity issues)
+ - status=0: Paused monitors (manually disabled/suspended)
+ - status=9 or null: All monitors regardless of status (default)
+
+ COMMON USE CASES & PARAMETER COMBINATIONS:
+
+ 1. BASIC MONITOR LISTING:
+ - Default: No parameters (shows all monitors, 8 per page)
+ - By type: app='linux' (show only Linux monitors)
+ - Search: search='web' (find monitors with 'web' in name/host)
+
+ 2. STATUS-BASED QUERIES:
+ - Healthy monitors: status=1, pageSize=50
+ - Problem monitors: status=2 or status=3, pageSize=50
+ - Offline monitors only: status=2
+ - Unreachable monitors only: status=3
+ - Paused monitors: status=0
+
+ 3. MONITORING HEALTH OVERVIEW:
+ - All statuses with statistics: status=9, includeStats=true,
pageSize=100
+ - Unhealthy monitors: Pass both status=2 AND status=3 (make 2
separate calls)
+
+ 4. ADVANCED FILTERING:
+ - Specific monitor types: app='mysql', status=1 (healthy MySQL
monitors)
+ - Label-based: labels='env:prod,critical:true'
+ - Host search: search='192.168' (find by IP pattern)
+ - Monitor IDs: ids=[1,2,3] (specific monitors by ID)
+
+ 5. SORTING & PAGINATION:
+ - Recently updated: sort='gmtUpdate', order='desc'
+ - Alphabetical: sort='name', order='asc'
+ - By creation: sort='gmtCreate', order='desc' (newest first)
+ - Large datasets: pageSize=50-100 for bulk operations
+
+ RESPONSE FORMAT:
+ - includeStats=true: Adds status distribution summary at top
+ - Default: Simple list with ID, name, type, host, status
+ - Shows total count and pagination info
+ """)
public String queryMonitors(
- @ToolParam(description = "Specific monitor IDs to retrieve
(optional)", required = false) List<Long> ids,
- @ToolParam(description = "Monitor type filter: 'linux', 'mysql',
'http', 'redis', etc. (optional)", required = false) String app,
- @ToolParam(description = "Monitor status: 1=online, 2=offline,
3=unreachable, 0=paused, 9=all (default: 9)", required = false) Byte status,
- @ToolParam(description = "Search in monitor names or hosts
(partial matching)", required = false) String search,
- @ToolParam(description = "Label filters, format:
'key1:value1,key2:value2'", required = false) String labels,
- @ToolParam(description = "Sort field: 'name', 'gmtCreate',
'gmtUpdate', 'status', 'app' (default: gmtCreate)", required = false) String
sort,
- @ToolParam(description = "Sort order: 'asc' (ascending) or 'desc'
(descending, default)", required = false) String order,
- @ToolParam(description = "Page number starting from 0 (default:
0)", required = false) Integer pageIndex,
- @ToolParam(description = "Items per page: 1-100 recommended
(default: 20)", required = false) Integer pageSize,
- @ToolParam(description = "Include status statistics summary
(default: false)", required = false) Boolean includeStats) {
+ @ToolParam(description = "Specific monitor IDs to retrieve
(optional)", required = false) List<Long> ids,
+ @ToolParam(description = "Monitor type filter: 'linux', 'mysql',
'http', 'redis', etc. (optional)", required = false) String app,
+ @ToolParam(description = "Monitor status: 1=online, 2=offline,
3=unreachable, 0=paused, 9=all (default: 9)", required = false) Byte status,
+ @ToolParam(description = "Search in monitor names or hosts (partial
matching)", required = false) String search,
+ @ToolParam(description = "Label filters, format:
'key1:value1,key2:value2'", required = false) String labels,
+ @ToolParam(description = "Sort field: 'name', 'gmtCreate',
'gmtUpdate', 'status', 'app' (default: gmtCreate)", required = false) String
sort,
+ @ToolParam(description = "Sort order: 'asc' (ascending) or 'desc'
(descending, default)", required = false) String order,
+ @ToolParam(description = "Page number starting from 0 (default: 0)",
required = false) Integer pageIndex,
+ @ToolParam(description = "Items per page: 1-100 recommended (default:
20)", required = false) Integer pageSize,
+ @ToolParam(description = "Include status statistics summary (default:
false)", required = false) Boolean includeStats) {
try {
// Set defaults
if (pageSize == null || pageSize <= 0) {
@@ -130,7 +141,7 @@ public class MonitorToolsImpl implements MonitorTools {
log.debug("Current security subject: {}", subjectSum);
Page<Monitor> result = monitorService.getMonitors(
- ids, app, search, status, sort, order, pageIndex,
pageSize, labels);
+ ids, app, search, status, sort, order, pageIndex, pageSize,
labels);
log.debug("MonitorService.getMonitors result: {}", result);
StringBuilder response = new StringBuilder();
@@ -160,19 +171,20 @@ public class MonitorToolsImpl implements MonitorTools {
}
response.append("Query Results:
").append(result.getContent().size())
- .append(" monitors (Total:
").append(result.getTotalElements()).append(")\n");
+ .append(" monitors (Total:
").append(result.getTotalElements()).append(")\n");
if (result.getTotalPages() > 1) {
- response.append("Page ").append(pageIndex + 1).append(" of
").append(result.getTotalPages()).append("\n");
+ response.append("Page ").append(pageIndex + 1).append(" of
").append(result.getTotalPages())
+ .append("\n");
}
response.append("\n");
for (Monitor monitor : result.getContent()) {
response.append("ID: ").append(monitor.getId())
- .append(" | Name: ").append(monitor.getName())
- .append(" | Type: ").append(monitor.getApp())
- .append(" | Instance: ").append(monitor.getInstance())
- .append(" | Status:
").append(UtilityClass.getStatusText(monitor.getStatus()));
+ .append(" | Name: ").append(monitor.getName())
+ .append(" | Type: ").append(monitor.getApp())
+ .append(" | Instance: ").append(monitor.getInstance())
+ .append(" | Status:
").append(UtilityClass.getStatusText(monitor.getStatus()));
// Add creation date for better context
if (monitor.getGmtCreate() != null) {
@@ -224,15 +236,59 @@ public class MonitorToolsImpl implements MonitorTools {
- Redis: {"host":"redis.server.com", "port":"6379",
"password":"xxx"}
""")
public String addMonitor(
- @ToolParam(description = "Monitor name (required)", required =
true) String name,
- @ToolParam(description = "Monitor type: website, mysql,
postgresql, redis, linux, windows, etc.", required = true) String app,
- @ToolParam(description = "Collection interval in seconds (default:
600)", required = false) Integer intervals,
- @ToolParam(description = "Monitor-specific parameters as JSON
string. "
- + "Use get_monitor_additional_params to see required
fields. "
- + "Example: {\"host\":\"192.168.1.1\", \"port\":\"22\",
\"username\":\"root\"}",
- required = true) String params,
- @ToolParam(description = "Monitor description (optional)",
required = false) String description) {
+ @ToolParam(description = "Monitor name (required)", required = true)
String name,
+ @ToolParam(description = "Monitor type: website, mysql, postgresql,
redis, linux, windows, etc.", required = true) String app,
+ @ToolParam(description = "Collection interval in seconds (default:
600)", required = false) Integer intervals,
+ @ToolParam(description = "Monitor-specific parameters as JSON string. "
+ + "Use get_monitor_additional_params to see required fields. "
+ + "Example: {\"host\":\"192.168.1.1\", \"port\":\"22\",
\"username\":\"root\"}",
+ required = true) String params,
+ @ToolParam(description = "Monitor description (optional)", required =
false) String description) {
+ return addMonitorProtected(null, name, app, intervals, params,
description);
+ }
+ @Override
+ @Tool(name = "add_monitor_protected", description = """
+ HertzBeat: Add a new monitoring target to HertzBeat with comprehensive
configuration.
+ This tool dynamically handles different parameter requirements for
each monitor type.
+
+ This tool creates monitors with proper app-specific parameters.
+
+ *********
+ VERY IMPORTANT:
+ ALWAYS use get_monitor_additional_params to check the additional
required parameters for the chosen type before adding a monitor or even
mentioning it.
+ Use list_monitor_types tool to see available monitor type names to use
here in the app parameter.
+ Use the information obtained from this to query user for parameters.
+ If the User has not given any parameters, ask them to provide the
necessary parameters, until all the necessary parameters are provided.
+ If the conversation ID is unknown, please call the `add_monitor`
method.
+ **********
+
+ Examples of natural language requests this tool handles:
+ - "Monitor website example.com with HTTPS on port 443"
+ - "Add MySQL monitoring for database server at 192.168.1.10 with user
admin"
+ - "Monitor Linux server health on host server.company.com via SSH"
+ - "Set up Redis monitoring on localhost port 6379 with password"
+
+ PARAMETER MAPPING: Use the 'params' parameter to pass all
monitor-specific configuration.
+ The params should be a JSON string containing key-value pairs for the
monitor type.
+ Use get_monitor_additional_params tool to see what parameters are
required for each monitor type.
+
+ PARAMS EXAMPLES:
+ - Website: {"host":"example.com", "port":"443", "uri":"/api/health",
"ssl":"true", "method":"GET"}
+ - Linux: {"host":"192.168.1.10", "port":"22", "username":"root",
"password":"xxx"}
+ - MySQL: {"host":"db.server.com", "port":"3306", "username":"admin",
"password":"xxx", "database":"mydb"}
+ - Redis: {"host":"redis.server.com", "port":"6379", "password":"xxx"}
+ """)
+ public String addMonitorProtected(
+ @ToolParam(description = "The id for current conversation (required)",
required = true) Long conversationId,
+ @ToolParam(description = "Monitor name (required)", required = true)
String name,
+ @ToolParam(description = "Monitor type: website, mysql, postgresql,
redis, linux, windows, etc.", required = true) String app,
+ @ToolParam(description = "Collection interval in seconds (default:
600)", required = false) Integer intervals,
+ @ToolParam(description = "Monitor-specific parameters as JSON string. "
+ + "Use get_monitor_additional_params to see required fields. "
+ + "Example: {\"host\":\"192.168.1.1\", \"port\":\"22\",
\"username\":\"root\"}",
+ required = true) String params,
+ @ToolParam(description = "Monitor description (optional)", required =
false) String description) {
try {
log.info("Adding monitor: name={}, app={}", name, app);
@@ -251,9 +307,22 @@ public class MonitorToolsImpl implements MonitorTools {
if (intervals == null || intervals < 10) {
intervals = 600;
}
-
// Parse params to extract host and port for instance
List<Param> paramList = parseParams(params);
+
+ // Query and add sensitive parameters
+ if (conversationId != null) {
+ Optional<ChatConversation> chatConversation =
conversationDao.findById(conversationId);
+ if (chatConversation.isPresent() &&
StringUtils.isNotEmpty(chatConversation.get().getSecurityData())) {
+ List<Param> securityParams = JsonUtil.fromJson(
+
AesUtil.aesDecode(chatConversation.get().getSecurityData()),
+ new TypeReference<List<Param>>() {
+ });
+ if (CollectionUtils.isNotEmpty(securityParams)) {
+ paramList.addAll(securityParams);
+ }
+ }
+ }
String host = paramList.stream()
.filter(p -> "host".equals(p.getField()))
.map(Param::getParamValue)
diff --git a/hertzbeat-ai/src/main/resources/prompt/extra-message-protected.st
b/hertzbeat-ai/src/main/resources/prompt/extra-message-protected.st
new file mode 100644
index 0000000000..96bd0f26c0
--- /dev/null
+++ b/hertzbeat-ai/src/main/resources/prompt/extra-message-protected.st
@@ -0,0 +1,140 @@
+# IMPORTANT
+The following information requires special attention. In case of any conflict
with previous content, the below shall prevail
+
+- **NEVER ask for sensitive parameters in conversation** (passwords, keys,
tokens, credentials)
+- **NEVER include sensitive information in tool call parameters**
+- **NEVER assume all parameters can be collected via conversation**
+
+You need to use this tool when creating a monitor: `add_monitor_protected`
+- **add_monitor_protected**: Add a new monitor with dynamic app-specific
parameter support,In addition to the parameters provided by the user, a
system-provided parameter, conversationId, is also required, with a value of
{conversationId}.
+
+## Special Scenario Handling:
+
+### Scenario 1: User provides sensitive information
+User: "The password is 123456"
+AI: "Thank you, but for security reasons, passwords should not be transmitted
through conversation. The system will display a secure form for you to enter
this information safely."
+
+### Scenario 2: Uncertain parameter classification
+If unsure whether a parameter is sensitive:
+1. Assume it's sensitive
+2. Guide to secure form
+3. Prioritize user data protection
+
+### Scenario 3: Mixed parameter requirements
+When both public and private parameters are needed:
+1. Collect all public parameters first
+2. Then guide to secure form for private parameters
+3. Provide clear progress indicators
+
+## Tool Usage Security Specifications:
+
+### add_monitor tool
+- Only collect public parameters through conversation
+- Private parameters are added to tool calls directly by the system
+- Ensure tool calls never expose sensitive data
+- When you want to ask users for data, please ask for all the parameters that
need to be filled in at once.
+
+### Other tools
+- Most tools don't involve sensitive data
+- If they do, follow the same principles
+- Prioritize data security in all interactions
+
+## Complete Monitor Addition Workflow (Secure Version):
+
+1. **Identify requirement**: What does the user want to monitor?
+2. **Determine type**: Use `list_monitor_types` to find matching monitor type
+3. **Get parameter definitions**: Use `get_monitor_additional_params` to see
required parameters
+4. **Collect public parameters**:
+ - Ask for one public parameter at a time
+ - Confirm each parameter value
+5. **Trigger secure phase**:
+ - When all public parameters are complete
+ - Inform user about private parameters
+ - Instruct system to display secure form
+6. **Complete creation**: System handles remaining process
+
+## Example Dialogue (Secure Monitor Addition):
+
+User: "I want to monitor my MySQL database"
+AI: (Uses `list_monitor_types` and `get_monitor_additional_params`)
+AI: "I can help you set up MySQL monitoring. First, I need some information:
+ 1. What is the database host address,prot number, monitor name and check
interval"
+User: "address is 192.168.1.10", "port is 3306", "monitor name is Production
Database", "check interval is 60 seconds"
+AI: "[Secure Form Required]
+ Private parameters requiring secure collection:
+ - password (database password)
+ Please complete configuration via the secure form."
+---
+
+## Security and Privacy Principles
+1. **Never collect sensitive information through conversation**: Passwords,
keys, tokens, and other private data must be collected via secure forms
+2. **Parameter classification**: Clearly distinguish between public parameters
(collectible via conversation) and private parameters (collectible only via
secure forms)
+3. **Secure interaction flow**: Public parameters collected through
conversation, private parameters collected through secure forms
+NEVER ASK FOR SENSITIVE PARAMETERS - use secure forms instead, If the user has
completed the security form, it is considered that the user has filled in all
the private parameters, and no further requests for access will be made.
+
+## Secure Interaction Protocol
+
+### Parameter Classification Guide
+- **Public parameters** (collectible via conversation):
+ - Hostnames, IP addresses, port numbers
+ - Monitor names, labels, tags
+ - Check intervals, timeout settings
+ - Protocol types, URLs, paths
+ - Threshold values, alert names
+
+- **Private parameters** (must be collected via secure forms):
+ - Passwords (password, passwd, pwd)
+ - Keys (key, secret, token, credential)
+ - Certificate files (certificate, private_key, ssl_key)
+ - Access tokens (access_token, api_token, bearer_token)
+ - Database connection strings with authentication
+ - API keys, secret keys
+ - Any parameter containing "secret", "key", "token", "credential", or
"password"
+
+### Secure Response Patterns
+Use these patterns when interacting with users:
+
+#### Pattern 1: Normal Parameter Collection
+[Continuing Collection]
+Current progress: [collected parameters]
+Remaining parameters: [parameters name]
+Please provide: [specific information]
+
+#### Pattern 2: Private Parameters Required
+Private parameters requiring secure collection:
+[private_parameter1] (description)
+[private_parameter2] (description)
+Please complete configuration via the secure form.
+(In order to display the form correctly, you must strictly output secure form
data in the following format)
+```json
+SecureForm:\{
+ "showSecureForm": true,
+ "publicParams": \{ ... \},
+ "privateParams": [
+ \{
+ "id": ...,
+ "app":...,
+ "name": \{
+ "zh-CN": "...",
+ "en-US": "...",
+ "ja-JP": "...",
+ "pt-BR": "...",
+ "zh-TW": "..."
+ \},
+ "field": "...",
+ "type": "password",
+ "required": ...,
+ "defaultValue": ...,
+ "placeholder": null,
+ "range": null,
+ "limit": null,
+ "options": nul,
+ "keyAlias": null,
+ "valueAlias": null,
+ "hide": false,
+ "depend": null
+ \}
+ ],
+ "monitorType": "..."
+\}
+```
diff --git a/hertzbeat-ai/src/main/resources/prompt/system-message.st
b/hertzbeat-ai/src/main/resources/prompt/system-message.st
index 782e1aa042..891f6b22f5 100644
--- a/hertzbeat-ai/src/main/resources/prompt/system-message.st
+++ b/hertzbeat-ai/src/main/resources/prompt/system-message.st
@@ -95,7 +95,7 @@ Common conversions:
- "List all Redis monitors with their connection status"
### Alert Configuration:
-- ALERT RULE means when to alert a user
+- ALERT RULE means when to alert a user
- "Create an alert for Kafka JVM when VmName equals 'vm-w2'"
- "Alert when OpenAI credit grants exceed 1000"
- "Set up HBase Master alert when heap memory usage is over 80%"
@@ -126,7 +126,7 @@ Common conversions:
- Collect all required parameters from the list_monitor_types tool and ask
user to give them all, before calling add_monitor
- Example: "To monitor MySQL, I need host, port, username, password, and
database name"
-2. **Creating Alert Rules or Alerts**:
+### 2. Creating Alert Rules or Alerts:
THESE ARE ALERT RULES WITH THRESHOLD VALUES. USERS CAN SPECIFY THE THRESHOLD
VALUES FOR EXAMPLE,
IF THE USER SAYS "ALERT ME WHEN MY COST EXCEEDS 700, THE EXPRESSION SHOULD BE
'cost > 700' NOT 'cost < 700'.
APPLY THE SAME LOGIC FOR LESS THAN OPERATOR.
@@ -162,16 +162,16 @@ CRITICAL WORKFLOW Do all of this iteratively with user
interaction at each step:
- Priority levels: 0=critical, 1=warning, 2=info
-3. **Analyzing Performance**:
- - Use get_realtime_metrics for current status
- - Use get_historical_metrics for trends
- - Use get_high_usage_monitors to find problems
+### 3. Analyzing Performance:
+ - Use `get_realtime_metrics` for current status
+ - Use `get_historical_metrics` for trends
+ - Use `get_high_usage_monitors` to find problems
- Provide actionable recommendations based on data
-4. **Troubleshooting Alerts**:
- - Use query_alerts to find current issues
- - Use get_monitor_alerts for specific monitor problems
- - Use get_frequent_alerts to identify recurring issues
+### 4. Troubleshooting Alerts:
+ - Use `query_alerts` to find current issues
+ - Use `get_monitor_alerts` for specific monitor problems
+ - Use `get_frequent_alerts` to identify recurring issues
- Suggest root cause analysis steps
## Parameter Guidelines:
diff --git
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/ModelProviderConfig.java
b/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/ModelProviderConfig.java
index 527f65da72..5aaa2d7507 100644
---
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/ModelProviderConfig.java
+++
b/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/ModelProviderConfig.java
@@ -31,20 +31,24 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
@Schema(description = "LLM Model Provider configuration")
public class ModelProviderConfig {
-
+
@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;
+
+ @Schema(title = "Participation Model", description = "model for
participation", example = "PROTECTED")
+ @NotBlank(message = "API Key cannot be empty when enabled")
+ private String participationModel;
}
diff --git
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/ai/ChatConversation.java
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/ai/ChatConversation.java
index 62218b3fb3..22e2f94639 100644
---
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/ai/ChatConversation.java
+++
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/ai/ChatConversation.java
@@ -18,6 +18,7 @@
package org.apache.hertzbeat.common.entity.ai;
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
@@ -79,4 +80,6 @@ public class ChatConversation {
*/
@OneToMany(mappedBy = "conversation")
private List<ChatMessage> messages;
+
+ private String securityData;
}
diff --git a/web-app/src/app/pojo/ModelProviderConfig.ts
b/web-app/src/app/pojo/ModelProviderConfig.ts
index 7a3d155257..76d0df6ffd 100644
--- a/web-app/src/app/pojo/ModelProviderConfig.ts
+++ b/web-app/src/app/pojo/ModelProviderConfig.ts
@@ -24,6 +24,7 @@ export class ModelProviderConfig {
baseUrl: string = '';
model: string = '';
apiKey!: string;
+ participationModel: string = 'PROTECTED';
}
export interface ProviderOption {
diff --git a/web-app/src/app/service/ai-chat.service.ts
b/web-app/src/app/service/ai-chat.service.ts
index 08e14baa7b..f5cc20c41d 100644
--- a/web-app/src/app/service/ai-chat.service.ts
+++ b/web-app/src/app/service/ai-chat.service.ts
@@ -28,10 +28,25 @@ export interface ChatMessage {
content: string;
role: 'user' | 'assistant' | 'system_push';
gmtCreate: Date;
+ securityForm: SecurityForm;
/** Flag indicating this message is a skill report that should be displayed
directly */
isSkillReport?: boolean;
}
+export interface SecurityForm {
+ show: Boolean;
+ param: string;
+ content: string;
+ complete: boolean;
+}
+
+export const DEFAULT_SECURITY_FORM: SecurityForm = {
+ show: false,
+ param: '',
+ content: '',
+ complete: false
+};
+
export interface ChatConversation {
id: number;
title: string;
@@ -142,7 +157,7 @@ export class AiChatService {
const decoder = new TextDecoder();
let buffer = '';
- function readStream(): Promise<void> {
+ const readStream = (): Promise<void> => {
if (!reader) {
return Promise.resolve();
}
@@ -169,6 +184,7 @@ export class AiChatService {
responseSubject.next({
content: data.response || '',
role: 'assistant',
+ securityForm: DEFAULT_SECURITY_FORM,
gmtCreate: data.timestamp ? new Date(data.timestamp) :
new Date()
});
}
@@ -178,6 +194,7 @@ export class AiChatService {
responseSubject.next({
content: jsonStr,
role: 'assistant',
+ securityForm: DEFAULT_SECURITY_FORM,
gmtCreate: new Date()
});
}
@@ -188,7 +205,7 @@ export class AiChatService {
return readStream();
});
- }
+ };
return readStream();
})
@@ -200,6 +217,10 @@ export class AiChatService {
return responseSubject.asObservable();
}
+ saveSecurityData(body: any): Observable<Message<any>> {
+ return this.http.post<Message<any>>(`${chat_uri}/security`, body);
+ }
+
// ===== Schedule API Methods =====
/**
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 737754bd9b..378c906e3f 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
@@ -22,6 +22,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { DelonFormModule } from '@delon/form';
import { AlainThemeModule } from '@delon/theme';
+import { SharedModule } from '@shared';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzDividerModule } from 'ng-zorro-antd/divider';
import { NzFormModule } from 'ng-zorro-antd/form';
@@ -58,7 +59,8 @@ import { ChatComponent } from './chat.component';
NzSwitchModule,
NzTableModule,
MarkdownComponent,
- NzTooltipDirective
+ NzTooltipDirective,
+ SharedModule
],
exports: [ChatComponent]
})
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 fd21295ca7..45ddd289ea 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
@@ -146,6 +146,15 @@
<div class="message-text">
<div *ngIf="message.role === 'user'"
[innerHTML]="message.content"></div>
<markdown *ngIf="message.role === 'assistant' || message.role ===
'system_push'" [data]="message.content"> </markdown>
+ <button
+ *ngIf="message.securityForm && message.securityForm.show"
+ [disabled]="message.securityForm.complete"
+ (click)="openSecurityForm(message.securityForm)"
+ nz-button
+ nzType="primary"
+ >
+ {{ (message.securityForm.complete ?
'ai.chat.security.form.complete' : 'ai.chat.security.form.button') | i18n }}
+ </button>
</div>
<div *ngIf="message.role === 'assistant' && message.content === ''
&& isSendingMessage" class="typing-indicator">
<span>{{ 'ai.chat.typing' | i18n }}</span>
@@ -255,6 +264,44 @@
<p class="form-help">{{ 'ai.chat.config.model.help' | i18n }}</p>
</nz-form-control>
</nz-form-item>
+ <!-- AI Participation Mode -->
+ <nz-form-item>
+ <nz-form-label>{{ 'ai.chat.config.participation.model' | i18n
}}</nz-form-label>
+ <nz-form-control>
+ <nz-select
+ class="mobile-hide"
+ name="participationModel"
+ [nzPlaceHolder]="'ai.chat.config.participation.model' | i18n"
+ [(ngModel)]="aiProviderConfig.participationModel"
+ >
+ <nz-option
[nzLabel]="'ai.chat.config.participation.model.protected' | i18n"
[nzValue]="'PROTECTED'"></nz-option>
+ <nz-option [nzLabel]="'ai.chat.config.participation.model.trusted'
| i18n" [nzValue]="'TRUSTED'"></nz-option>
+ </nz-select>
+ <p class="form-help">{{ 'ai.chat.config.participation.model.help' |
i18n }}</p>
+ </nz-form-control>
+ </nz-form-item>
+ </form>
+ </div>
+</nz-modal>
+
+<!-- Security Form Modal -->
+<nz-modal
+ [(nzVisible)]="showSecurityFormModal"
+ [nzTitle]="'ai.chat.security.form.title' | i18n"
+ (nzOnCancel)="onSecurityFormCancel()"
+ (nzOnOk)="onSecurityFormSubmit()"
+ nzMaskClosable="false"
+>
+ <div *nzModalContent class="-inner-content">
+ <form nz-form #form="ngForm">
+ <ng-container *ngFor="let paramDefine of securityParamDefine">
+ <nz-form-item>
+ <nz-form-label nzSpan="7" [nzRequired]="paramDefine.required"
[nzFor]="paramDefine.field">{{ paramDefine.name }}</nz-form-label>
+ <nz-form-control nzSpan="8" [nzErrorTip]="'validation.required' |
i18n">
+ <app-form-field [item]="paramDefine" [name]="paramDefine.field"
[(ngModel)]="securityParams[paramDefine.field].paramValue" />
+ </nz-form-control>
+ </nz-form-item>
+ </ng-container>
</form>
</div>
</nz-modal>
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 de958b1379..f6d37eef28 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
@@ -24,7 +24,16 @@ 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, ChatConversation, SopSchedule, SkillInfo
} from '../../../service/ai-chat.service';
+import { ParamDefine } from '../../../pojo/ParamDefine';
+import {
+ AiChatService,
+ ChatMessage,
+ ChatConversation,
+ SecurityForm,
+ DEFAULT_SECURITY_FORM,
+ SopSchedule,
+ SkillInfo
+} from '../../../service/ai-chat.service';
import { GeneralConfigService } from '../../../service/general-config.service';
import { ThemeService } from '../../../service/theme.service';
@@ -57,9 +66,13 @@ export class ChatComponent implements OnInit, OnDestroy {
isAiProviderConfigured = false;
showConfigModal = false;
configLoading = false;
+ showSecurityFormModal = false;
aiProviderConfig: ModelProviderConfig = new ModelProviderConfig();
providerOptions: ProviderOption[] = PROVIDER_OPTIONS;
+ securityParamDefine: ParamDefine[] = [];
+ securityParams: any = {};
+
// Schedule Configuration
showScheduleModal = false;
scheduleLoading = false;
@@ -210,6 +223,14 @@ export class ChatComponent implements OnInit, OnDestroy {
if (response.code === 0 && response.data) {
this.messages = response.data.messages || [];
+ //calculate security form for each message
+ for (let i = 0; i < this.messages.length; i++) {
+ const message = this.messages[i];
+ const next = i < this.messages.length - 1 ? this.messages[i +
1].content : '';
+ message.securityForm =
this.calculateShowSecurityForm(message.content, next);
+ message.content = message.securityForm.content;
+ }
+
this.cdr.detectChanges();
this.scrollToBottom();
} else {
@@ -271,6 +292,26 @@ export class ChatComponent implements OnInit, OnDestroy {
});
}
+ /**
+ * calculate if the security form should be shown for the given message
content
+ *
+ * @param content current message content
+ * @param next next message content
+ */
+ calculateShowSecurityForm(content: string, next: string): SecurityForm {
+ const completeMessage: String[] = ['表单填写完成', '表單已完成', 'Formulário
concluído', 'フォームが完了しました', 'Form completed'];
+ const complete = completeMessage.includes(next);
+ const regex = /```json\s*SecureForm:((?:.+\s)+)```/gm;
+ if (!content) return { show: false, param: '', content: content, complete:
complete };
+ const match = content.match(regex);
+ if (!match || match.length === 0) {
+ return { show: false, param: '', content: content, complete: complete };
+ }
+ // @ts-ignore
+ const result = match[0].replace(/```json\s*SecureForm:/gm,
'').replace(/```/, '');
+ return { show: true, param: result, content: content.replace(regex, ''),
complete: complete };
+ }
+
/**
* Send a message
*/
@@ -282,6 +323,7 @@ export class ChatComponent implements OnInit, OnDestroy {
const userMessage: ChatMessage = {
content: this.newMessage.trim(),
role: 'user',
+ securityForm: DEFAULT_SECURITY_FORM,
gmtCreate: new Date()
};
@@ -300,6 +342,7 @@ export class ChatComponent implements OnInit, OnDestroy {
const offlineMessage: ChatMessage = {
content: this.i18nSvc.fanyi('ai.chat.offline.response'),
role: 'assistant',
+ securityForm: DEFAULT_SECURITY_FORM,
gmtCreate: new Date()
};
this.messages.push(offlineMessage);
@@ -314,6 +357,7 @@ export class ChatComponent implements OnInit, OnDestroy {
const assistantMessage: ChatMessage = {
content: '',
role: 'assistant',
+ securityForm: { show: false, param: '', content: '', complete: false },
gmtCreate: new Date()
};
this.messages.push(assistantMessage);
@@ -356,6 +400,7 @@ export class ChatComponent implements OnInit, OnDestroy {
const errorMessage: ChatMessage = {
content: this.i18nSvc.fanyi('ai.chat.error.processing'),
role: 'assistant',
+ securityForm: DEFAULT_SECURITY_FORM,
gmtCreate: new Date()
};
this.messages.push(errorMessage);
@@ -367,6 +412,10 @@ export class ChatComponent implements OnInit, OnDestroy {
this.isSendingMessage = false;
this.cdr.detectChanges();
+ const lastMessage = this.messages[this.messages.length - 1];
+ lastMessage.securityForm =
this.calculateShowSecurityForm(lastMessage.content, '');
+ lastMessage.content = lastMessage.securityForm.content;
+
// Refresh current conversation to get updated data (only if not
fallback)
if (this.currentConversation && this.currentConversation.id !== 0) {
this.aiChatService.getConversation(this.currentConversation.id).subscribe({
@@ -660,6 +709,64 @@ export class ChatComponent implements OnInit, OnDestroy {
}
}
+ /**
+ * handle security form submit
+ *
+ */
+ onSecurityFormSubmit(): void {
+ this.aiChatService
+ .saveSecurityData({
+ securityData: JSON.stringify(Object.values(this.securityParams)),
+ conversationId: this.currentConversation?.id
+ })
+ .subscribe(
+ (message: any) => {
+ if (message.code === 0) {
+ const lastMessage = this.messages[this.messages.length - 1];
+ lastMessage.securityForm.complete = true;
+ const tmpMessage = this.newMessage;
+ this.newMessage =
this.i18nSvc.fanyi('ai.chat.security.form.default.callback');
+ this.sendMessage();
+ this.newMessage = tmpMessage;
+ } else {
+ console.log('Error saving security data:');
+ }
+ },
+ (error: any) => {
+ console.error('Error saving security data:', error);
+ }
+ );
+ this.showSecurityFormModal = false;
+ }
+
+ /**
+ * handle security form cancel
+ */
+ onSecurityFormCancel(): void {
+ this.showSecurityFormModal = false;
+ }
+
+ /**
+ * handle open security form
+ *
+ * @param securityForm
+ */
+ openSecurityForm(securityForm: SecurityForm): void {
+ this.securityParamDefine =
JSON.parse(securityForm.param).privateParams.map((i: any) => {
+ this.securityParams[i.field] = {
+ // Parameter type 0: number 1: string 2: encrypted string 3: json
string mapped by map
+ type: i.type === 'number' ? 0 : i.type === 'text' || i.type ===
'string' ? 1 : i.type === 'json' ? 3 : 2,
+ field: i.field,
+ paramValue: null
+ };
+ i.name = i.name[this.i18nSvc.defaultLang] || i.name['en-US'] || i.name;
+ return i;
+ });
+
+ console.log('this.securityParamDefine:', this.securityParamDefine);
+ this.showSecurityFormModal = true;
+ }
+
// ===== Schedule Management Methods =====
/**
diff --git a/web-app/src/app/shared/shared.module.ts
b/web-app/src/app/shared/shared.module.ts
index 1c2735e285..72971c6a86 100644
--- a/web-app/src/app/shared/shared.module.ts
+++ b/web-app/src/app/shared/shared.module.ts
@@ -73,8 +73,7 @@ const DIRECTIVES: Array<Type<void>> = [TimezonePipe,
I18nElsePipe, ElapsedTimePi
NzInputModule,
NzIconModule.forChild(icons),
NzSpinModule,
- NzCodeEditorModule,
- AiChatModule
+ NzCodeEditorModule
],
declarations: [...COMPONENTS, ...DIRECTIVES, HelpMessageShowComponent],
exports: [
@@ -89,8 +88,7 @@ const DIRECTIVES: Array<Type<void>> = [TimezonePipe,
I18nElsePipe, ElapsedTimePi
...SHARED_ZORRO_MODULES,
...ThirdModules,
...COMPONENTS,
- ...DIRECTIVES,
- AiChatModule
+ ...DIRECTIVES
]
})
export class SharedModule {}
diff --git a/web-app/src/assets/i18n/en-US.json
b/web-app/src/assets/i18n/en-US.json
index a2407bf068..0f82ca7be5 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -1177,6 +1177,14 @@
"ai.chat.error.processing": "Sorry, there was an error processing your
request. Please check if the AI Agent service is running and try again.",
"ai.chat.offline.mode": "AI Chat service unavailable. Running in offline
mode.",
"ai.chat.offline.response": "I apologize, but the AI Chat service is
currently unavailable. Please ensure the HertzBeat AI module is running and try
again later.",
+ "ai.chat.security.form.title": "Security Form",
+ "ai.chat.security.form.button": "Enter private parameters",
+ "ai.chat.security.form.complete": "Complete",
+ "ai.chat.security.form.default.callback": "Form completed",
+ "ai.chat.config.participation.model": "AI Participation Mode",
+ "ai.chat.config.participation.model.help": "Trusted Mode: All data is sent
to the LLM for processing. Protected Mode: Restricts some AI capabilities, such
as preventing private data from being sent to the LLM for processing.",
+ "ai.chat.config.participation.model.protected": "Protected",
+ "ai.chat.config.participation.model.trusted": "Trusted",
"ai.chat.schedule.title": "Schedule Configuration",
"ai.chat.schedule.create": "Create Schedule",
"ai.chat.schedule.add": "Add New Task",
@@ -1203,7 +1211,6 @@
"ai.chat.schedule.button": "Schedule",
"ai.chat.schedule.tooltip": "Configure scheduled push tasks",
"ai.chat.schedule.edit": "Edit Schedule",
- "ai.chat.schedule.skill": "Skill",
"ai.chat.schedule.update.success": "Schedule updated successfully",
"ai.chat.schedule.update.failed": "Failed to update schedule",
"setting.drawer.theme.color": "Theme Color",
diff --git a/web-app/src/assets/i18n/ja-JP.json
b/web-app/src/assets/i18n/ja-JP.json
index 66ec1addfe..352f40db66 100644
--- a/web-app/src/assets/i18n/ja-JP.json
+++ b/web-app/src/assets/i18n/ja-JP.json
@@ -1095,7 +1095,6 @@
"ai.chat.error.chat.response": "AI の応答の取得に失敗しました:",
"ai.chat.error.processing": "申し訳ございませんが、リクエストの処理中にエラーが発生しました。AI
エージェントサービスが実行されているかご確認の上、再度お試しください。",
"ai.chat.offline.mode": "AI チャットサービスが利用できません。オフラインモードで実行中です。",
- "ai.chat.offline.response": "申し訳ございませんが、AI チャットサービスは現在利用できません。HertzBeat AI
モジュールが実行されていることを確認して、後でもう一度お試しください。",
"setting.drawer.theme.color": "テーマカラー",
"setting.drawer.settings": "設定",
"setting.drawer.top": "上部",
@@ -1111,5 +1110,14 @@
"setting.drawer.preview": "プレビュー",
"setting.drawer.reset": "リセット",
"setting.drawer.copy": "コピー",
- "setting.drawer.info.message":
"設定パネルは開発環境でのプレビューのみに使用され、本番環境では表示されません。コピーして、パラメータ設定ファイル src/styles/theme.less
を手動で変更してください"
+ "setting.drawer.info.message":
"設定パネルは開発環境でのプレビューのみに使用され、本番環境では表示されません。コピーして、パラメータ設定ファイル src/styles/theme.less
を手動で変更してください",
+ "ai.chat.offline.response": "申し訳ございませんが、AI チャットサービスは現在利用できません。HertzBeat AI
モジュールが実行されていることを確認して、後でもう一度お試しください。",
+ "ai.chat.security.form.title": "セキュアフォーム",
+ "ai.chat.security.form.button": "プライベートパラメータを入力してください",
+ "ai.chat.security.form.complete": "完了",
+ "ai.chat.security.form.default.callback": "フォームが完了しました",
+ "ai.chat.config.participation.model": "AI参加モード",
+ "ai.chat.config.participation.model.help":
"信頼モード:すべてのデータが大規模モデルに送信され、処理されます。保護モード:プライベートデータが大規模モデルに送信され、処理されるのを防ぐなど、一部のAI機能を制限します。",
+ "ai.chat.config.participation.model.protected": "保護モード",
+ "ai.chat.config.participation.model.trusted": "信頼モード"
}
diff --git a/web-app/src/assets/i18n/pt-BR.json
b/web-app/src/assets/i18n/pt-BR.json
index 7453e3e0d1..0cb486e1ef 100644
--- a/web-app/src/assets/i18n/pt-BR.json
+++ b/web-app/src/assets/i18n/pt-BR.json
@@ -1228,6 +1228,14 @@
"common.notify.import-progress": "Progresso da Importação",
"common.notify.query-fail": "Falha na Consulta",
"define.disable": "Desabilitar",
+ "ai.chat.security.form.title": "Formulários seguros",
+ "ai.chat.security.form.button": "Inserir parâmetros privados",
+ "ai.chat.security.form.complete": "Concluir",
+ "ai.chat.security.form.default.callback": "Formulário concluído",
+ "ai.chat.config.participation.model": "Modo de Participação da IA",
+ "ai.chat.config.participation.model.help": "Modo Confiável: Todos os dados
são enviados para o modelo principal para processamento. Modo Protegido:
Restringe algumas funcionalidades da IA, como impedir que dados privados sejam
enviados para o modelo principal para processamento.",
+ "ai.chat.config.participation.model.protected": "Modo protegido",
+ "ai.chat.config.participation.model.trusted": "Modo confiável",
"define.enable": "Habilitar",
"setting.drawer.theme.color": "Cor do Tema",
"setting.drawer.settings": "Configurações",
diff --git a/web-app/src/assets/i18n/zh-CN.json
b/web-app/src/assets/i18n/zh-CN.json
index 6f585e092f..d4c74654eb 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -1180,11 +1180,18 @@
"ai.chat.error.processing": "处理您的请求时出错。请检查 AI 智能体服务是否正在运行,然后重试。",
"ai.chat.offline.mode": "AI 聊天服务不可用。正在离线模式下运行。",
"ai.chat.offline.response": "抱歉,AI 聊天服务当前不可用。请确保 HertzBeat AI 模块正在运行,稍后再试。",
+ "ai.chat.security.form.title": "安全表单",
+ "ai.chat.security.form.button": "填写私密参数",
+ "ai.chat.security.form.complete": "已填写",
+ "ai.chat.security.form.default.callback": "表单填写完成",
+ "ai.chat.config.participation.model": "AI参与模式",
+ "ai.chat.config.participation.model.help":
"信任模式:所有的数据都会发送到大模型处理,受保护模式:限制一些AI能力,如私密数据不发送给大模型处理",
+ "ai.chat.config.participation.model.protected": "受保护模式",
+ "ai.chat.config.participation.model.trusted": "信任模式",
"ai.chat.schedule.title": "定时任务设置",
"ai.chat.schedule.create": "创建定时任务",
"ai.chat.schedule.add": "添加新任务",
"ai.chat.schedule.configured": "已配置的定时任务",
- "ai.chat.schedule.skill": "技能",
"ai.chat.schedule.skill.select": "选择技能",
"ai.chat.schedule.skill.placeholder": "选择要执行的技能",
"ai.chat.schedule.cron": "执行时间",
diff --git a/web-app/src/assets/i18n/zh-TW.json
b/web-app/src/assets/i18n/zh-TW.json
index 2b4fbc57cb..ba46a82bb6 100644
--- a/web-app/src/assets/i18n/zh-TW.json
+++ b/web-app/src/assets/i18n/zh-TW.json
@@ -1110,6 +1110,14 @@
"ai.chat.error.processing": "處理您的請求時發生錯誤。請檢查 AI 智能體服務是否正在執行,然後重試。",
"ai.chat.offline.mode": "AI 聊天服務不可用。正在離線模式下執行。",
"ai.chat.offline.response": "抱歉,AI 聊天服務目前不可用。請確保 HertzBeat AI 模組正在執行,稍後再試。",
+ "ai.chat.security.form.title": "安全表單",
+ "ai.chat.security.form.button": "輸入私有參數",
+ "ai.chat.security.form.complete": "完成",
+ "ai.chat.security.form.default.callback": "表單已完成",
+ "ai.chat.config.participation.model": "AI參與模式",
+ "ai.chat.config.participation.model.help":
"信任模式:所有的資料都會傳送到大模型處理,受保護模式:限制一些AI能力,如私密資料不傳送給大模型處理",
+ "ai.chat.config.participation.model.protected": "受保護模式",
+ "ai.chat.config.participation.model.trusted": "信任模式",
"setting.drawer.theme.color": "主題色",
"setting.drawer.settings": "設置",
"setting.drawer.top": "頂部",
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]