Skip to content

Conversation

@LiuTianyou
Copy link
Contributor

What's changed?

  1. optimize AI's monitoring creation capabilities and provide secure form entry for sensitive information.
image image

Checklist

  • I have read the Contributing Guide
  • I have written the necessary doc or comment.
  • I have added the necessary unit tests and all cases have passed.

Add or update API

  • I have added the necessary e2e tests and all cases have passed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the AI-powered monitoring creation functionality by introducing a secure form mechanism for collecting sensitive parameters (passwords, keys, tokens, etc.) separately from regular conversation flow. The implementation includes frontend UI changes, backend API endpoints for encrypted data storage, and comprehensive AI prompt updates to guide secure parameter collection.

Key Changes:

  • Adds secure form modal for collecting sensitive parameters with AES encryption
  • Updates AI system prompts with detailed security guidelines and secure interaction protocols
  • Integrates encrypted security data storage into the monitor creation workflow

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 20 comments.

Show a summary per file
File Description
web-app/src/assets/i18n/*.json Adds translations for security form UI in 5 languages (en-US, zh-CN, zh-TW, ja-JP, pt-BR)
web-app/src/app/shared/shared.module.ts Removes AiChatModule from SharedModule to resolve circular dependency
web-app/src/app/shared/components/ai-chat/chat.component.ts Implements security form modal logic, parameter parsing, and encrypted data submission
web-app/src/app/shared/components/ai-chat/chat.component.html Adds security form modal UI with parameter input fields
web-app/src/app/shared/components/ai-chat/ai-chat.module.ts Imports SharedModule to access form components
web-app/src/app/service/ai-chat.service.ts Adds SecurityForm interface, DEFAULT_SECURITY_FORM constant, and saveSecurityData API method
hertzbeat-common/.../ChatConversation.java Adds securityData field to persist encrypted sensitive parameters
hertzbeat-ai/.../prompt/system-message.st Extensive prompt updates with security principles, secure interaction protocols, and parameter classification guidelines
hertzbeat-ai/.../MonitorToolsImpl.java Updates addMonitor to retrieve and merge encrypted security parameters from conversation
hertzbeat-ai/.../MonitorTools.java Adds conversationId parameter to addMonitor method signature
hertzbeat-ai/.../ConversationServiceImpl.java Implements saveSecurityData method with AES encryption
hertzbeat-ai/.../ChatClientProviderServiceImpl.java Updates system prompt template loading to pass conversationId metadata
hertzbeat-ai/.../ConversationService.java Adds saveSecurityData method to service interface
hertzbeat-ai/.../SecurityData.java New DTO for security data submission with conversationId and encrypted data
hertzbeat-ai/.../ChatController.java Adds POST /security endpoint for saving encrypted security data

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +672 to +683
.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:');
}
});
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handler for the saveSecurityData observable. If the HTTP request fails due to network issues or server errors, there's no error callback to handle it. This could leave the modal in an inconsistent state. Add an error handler in the subscribe method to handle failures gracefully.

Suggested change
.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:');
}
});
.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);
}
);

Copilot uses AI. Check for mistakes.
@JoinColumn(name = "conversation_id")
private List<ChatMessage> messages;

private String securityData;
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The securityData field is added to ChatConversation entity without database migration scripts or schema updates. Ensure that the database schema is updated appropriately with proper migration scripts (e.g., Flyway or Liquibase) to add this column, and verify that existing conversations can handle null values for this field.

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +166
@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)));
}
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation. The SecurityData object should be validated for null conversationId and empty/null securityData before processing. Add validation annotations like @NotNull on the conversationId field and @notblank on securityData field in the SecurityData class, or add explicit validation in the controller method.

Copilot uses AI. Check for mistakes.
return chatClient.prompt()
.messages(messages)
.system(SystemPromptTemplate.builder().resource(systemResource).build().getTemplate())
.system(SystemPromptTemplate.builder().resource(systemResource).build().create(metadata).getContents())
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change from getTemplate() to create(metadata).getContents() appears to use an API that may not exist or may not be the correct method. The typical Spring AI SystemPromptTemplate API uses createMessage(Map) or render(Map). Verify that this method chain is correct for the version of Spring AI being used.

Suggested change
.system(SystemPromptTemplate.builder().resource(systemResource).build().create(metadata).getContents())
.system(SystemPromptTemplate.builder().resource(systemResource).build().createMessage(metadata))

Copilot uses AI. Check for mistakes.
Comment on lines +672 to +683
.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:');
}
});
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling here only logs to console without showing any user feedback. When security data fails to save, users should be notified through a message service. Consider adding this.message.error() to display an error message to the user, similar to other error handling in this component.

Suggested change
.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:');
}
});
.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.error('Error saving security data:', message);
this.message.error('Failed to save security data.');
}
},
(error: any) => {
console.error('Error saving security data:', error);
this.message.error('Failed to save security data.');
}
);

Copilot uses AI. Check for mistakes.
Comment on lines 277 to 282
List<Param> securityParams = JsonUtil.fromJson(
AesUtil.aesDecode(chatConversation.get().getSecurityData()),
new TypeReference<List<Param>>() {
});
if (CollectionUtils.isNotEmpty(securityParams)) {
paramList.addAll(securityParams);
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No error handling for AES decryption failure. If the securityData is corrupted or was encrypted with a different key, AesUtil.aesDecode() could throw an exception that's not caught here. Add try-catch to handle decryption failures gracefully and provide appropriate error messages.

Suggested change
List<Param> securityParams = JsonUtil.fromJson(
AesUtil.aesDecode(chatConversation.get().getSecurityData()),
new TypeReference<List<Param>>() {
});
if (CollectionUtils.isNotEmpty(securityParams)) {
paramList.addAll(securityParams);
try {
List<Param> securityParams = JsonUtil.fromJson(
AesUtil.aesDecode(chatConversation.get().getSecurityData()),
new TypeReference<List<Param>>() {
});
if (CollectionUtils.isNotEmpty(securityParams)) {
paramList.addAll(securityParams);
}
} catch (Exception e) {
log.error("Failed to decrypt or parse security data for conversationId {}", conversationId, e);
return "Error: Failed to decrypt stored secure parameters. Please reconfigure secure parameters for this conversation.";

Copilot uses AI. Check for mistakes.
Comment on lines 243 to 251
@ToolParam(description = "The id for current conversation", 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) {
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addMonitor method now requires a conversationId parameter but there's no validation to ensure it's not null. If conversationId is null, the code will fail when trying to query the conversation. Add validation to check that conversationId is provided and valid before proceeding with monitor creation.

Copilot uses AI. Check for mistakes.
Comment on lines +700 to +709
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];
return i;
});
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing JSON parse error handling. If securityForm.param contains invalid JSON, JSON.parse() will throw an uncaught exception that crashes the component. Add try-catch error handling to gracefully handle malformed JSON and provide user feedback.

Copilot uses AI. Check for mistakes.
field: i.field,
paramValue: null
};
i.name = i.name[this.i18nSvc.defaultLang];
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes i.name[this.i18nSvc.defaultLang] will always exist, but if the AI response doesn't include a translation for the current language, this will result in undefined. Consider adding a fallback to a default language (e.g., 'en-US') or the original name property to prevent displaying undefined to users.

Suggested change
i.name = i.name[this.i18nSvc.defaultLang];
i.name = i.name[this.i18nSvc.defaultLang] || i.name['en-US'] || i.name;

Copilot uses AI. Check for mistakes.
private boolean isConfigured = false;

@Value("classpath:/prompt/system-message.st")
@Value("classpath:/prompt/system-message-improve.st")
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The system prompt template file path was changed from 'system-message.st' to 'system-message-improve.st', but based on the directory listing, only 'system-message.st' exists. This indicates either the file should be renamed to 'system-message-improve.st' or this line should revert to use 'system-message.st'. The current diff shows changes to system-message.st, suggesting the @value annotation should remain as 'system-message.st'.

Suggested change
@Value("classpath:/prompt/system-message-improve.st")
@Value("classpath:/prompt/system-message.st")

Copilot uses AI. Check for mistakes.
Comment on lines 279 to 300
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?"
User: "192.168.1.10"
AI: "Port number (default 3306)?"
User: "3306"
AI: "What name would you like for this monitor?"
User: "Production Database"
AI: "Check interval in seconds (recommended 60)?"
User: "60"
AI: "[Secure Form Required]
Monitor type: mysql
Public parameters collected:
- host: 192.168.1.10
- port: 3306
- name: Production Database
- interval: 60
Private parameters requiring secure collection:
- username (database username)
- password (database password)
Please complete configuration via the secure form."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, although it looks like the user is being guided step by step to fill in the information, in reality each step is a brand-new LLM call. This unnecessarily consumes tokens and increases repeated context.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suggestion is to shorten the steps. If the current context does not contain the required parameter values, the AI should ask the user for all parameters at once, and let the user provide them in a single input before proceeding. If the user’s prompt already includes some parameters, they can be added directly.

+ "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 = "The id for current conversation", required = true) Long conversationId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi I found a new tool param conversationId was added. When we use it as MCP server in agent like claude code, how it can get the conversationId?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I overlooked the compatibility issue when the tool is called by MCP. I should provide an additional tool through enhancing the existing add_monitor tool, so that it can be invoked in scenarios where monitoring is added using the secure form method, such as add_monitor_protected. The conversationId parameter is only used in this tool.

@tomsun28
Copy link
Member

Hi, i think whether it is provided to the built-in HertzBeat AI or exposed as an MCP tool for external agents, the tool’s capabilities should be atomic, with the AI model deciding how to orchestrate and invoke them. The design should not be driven by traditional user UI interactions, but by agent-centric thinking. The required data may be collected in multiple ways—for example, the AI may gather it from the user’s prompt, or an agent may read it in bulk from text and then add it.

@LiuTianyou
Copy link
Contributor Author

LiuTianyou commented Dec 22, 2025

Hi, i think whether it is provided to the built-in HertzBeat AI or exposed as an MCP tool for external agents, the tool’s capabilities should be atomic, with the AI model deciding how to orchestrate and invoke them. The design should not be driven by traditional user UI interactions, but by agent-centric thinking. The required data may be collected in multiple ways—for example, the AI may gather it from the user’s prompt, or an agent may read it in bulk from text and then add it.

I agree with your point that tools provided for AI calls should have higher versatility. I think we could offer some options to users, when they use ai assistant in hertzbeat, allowing them to choose the mode of AI involvement. For now, let’s refer to these as Protected Mode and Trusted Mode.

In Protected Mode, we could optimize the usage methods and data security based on general-purpose tools, enabling AI to integrate deeply into Hertzbeat.

In Trusted Mode, the process is entirely handed over to AI, allowing it to fully utilize the existing general-purpose tools to operate Hertzbeat.

@tomsun28
Copy link
Member

Hi, i think whether it is provided to the built-in HertzBeat AI or exposed as an MCP tool for external agents, the tool’s capabilities should be atomic, with the AI model deciding how to orchestrate and invoke them. The design should not be driven by traditional user UI interactions, but by agent-centric thinking. The required data may be collected in multiple ways—for example, the AI may gather it from the user’s prompt, or an agent may read it in bulk from text and then add it.

I agree with your point that tools provided for AI calls should have higher versatility. I think we could offer some options to users, when they use ai assistant in hertzbeat, allowing them to choose the mode of AI involvement. For now, let’s refer to these as Protected Mode and Trusted Mode.

In Protected Mode, we could optimize the usage methods and data security based on general-purpose tools, enabling AI to integrate deeply into Hertzbeat.

In Trusted Mode, the process is entirely handed over to AI, allowing it to fully utilize the existing general-purpose tools to operate Hertzbeat.

HI, the two modes seem like a good approach, would this correspond to two sets of Function Tools? Our current AI features are rapidly evolving, so it is recommended to use a single set of tools to achieve the desired consistency.
Regarding security, the secret data will be transmitted to the LLM for input parameter filling, shouldn't we also pay attention to this, considering that other data is self-hosted?

@LiuTianyou
Copy link
Contributor Author

LiuTianyou commented Dec 23, 2025

HI, the two modes seem like a good approach, would this correspond to two sets of Function Tools? Our current AI features are rapidly evolving, so it is recommended to use a single set of tools to achieve the desired consistency.
Regarding security, the secret data will be transmitted to the LLM for input parameter filling, shouldn't we also pay attention to this, considering that other data is self-hosted?

Yes, only one toolset is used. The tools needed for the other mode are simply wrappers and enhancements on the existing tools.

Regarding security, in protected mode, private data is not sent to the LLM. After being collected via forms, parameters are populated when the AI ​​calls the addMonitor tool. In trusted mode, all data is sent to the LLM for processing. I think we should explain the differences between the two modes in the documentation and let users choose for themselves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants