Copilot commented on code in PR #3922:
URL: https://github.com/apache/hertzbeat/pull/3922#discussion_r2637779772
##########
hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/controller/ChatController.java:
##########
@@ -148,8 +149,21 @@ public ResponseEntity<Message<ChatConversation>>
getConversation(
@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)));
+ }
Review Comment:
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.
##########
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/ai/ChatConversation.java:
##########
@@ -81,4 +82,6 @@ public class ChatConversation {
@OneToMany
@JoinColumn(name = "conversation_id")
private List<ChatMessage> messages;
+
+ private String securityData;
Review Comment:
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.
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * 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:');
+ }
+ });
+ this.showSecurityFormModal = false;
Review Comment:
The modal is closed immediately after submitting (line 684), even before the
async saveSecurityData operation completes. This creates a race condition where
the modal closes regardless of whether the save operation succeeds or fails.
The modal should only close after a successful save, and should remain open if
there's an error so the user can retry.
```suggestion
.subscribe({
next: (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;
// Close the modal only after a successful save
this.showSecurityFormModal = false;
} else {
console.log('Error saving security data:');
// Keep the modal open so the user can correct and retry
}
},
error: (err: any) => {
console.log('Error saving security data:', err);
// Keep the modal open on error so the user can retry
}
});
```
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * 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:');
+ }
+ });
Review Comment:
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.
```suggestion
.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);
}
);
```
##########
hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java:
##########
@@ -92,9 +94,12 @@ public Flux<String> streamChat(ChatRequestContext context) {
log.info("Starting streaming chat for conversation: {}",
context.getConversationId());
+ Map<String, Object> metadata = new HashMap<>();
+ metadata.put("conversationId", context.getConversationId());
+
log.info(SystemPromptTemplate.builder().resource(systemResource).build().createMessage(metadata).getText());
return chatClient.prompt()
.messages(messages)
-
.system(SystemPromptTemplate.builder().resource(systemResource).build().getTemplate())
+
.system(SystemPromptTemplate.builder().resource(systemResource).build().create(metadata).getContents())
Review Comment:
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.
```suggestion
.system(SystemPromptTemplate.builder().resource(systemResource).build().createMessage(metadata))
```
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * 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:');
Review Comment:
Debug console.log statement should be removed before merging to production.
Error details should be logged properly or displayed to the user through the
message service.
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * 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:');
+ }
+ });
+ 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];
+ return i;
+ });
+
+ console.log('this.securityParamDefine:', this.securityParamDefine);
Review Comment:
Debug console.log statement should be removed before merging to production.
This exposes potentially sensitive parameter definitions in browser console
logs.
```suggestion
```
##########
hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java:
##########
@@ -92,9 +94,12 @@ public Flux<String> streamChat(ChatRequestContext context) {
log.info("Starting streaming chat for conversation: {}",
context.getConversationId());
+ Map<String, Object> metadata = new HashMap<>();
+ metadata.put("conversationId", context.getConversationId());
+
log.info(SystemPromptTemplate.builder().resource(systemResource).build().createMessage(metadata).getText());
Review Comment:
This debug log statement on line 99 will log the entire system message which
could be very large and may contain sensitive information about the system's
capabilities. Consider removing this debug log or moving it behind a more
restrictive log level check before production deployment.
```suggestion
if (log.isDebugEnabled()) {
String systemPromptText = SystemPromptTemplate.builder()
.resource(systemResource)
.build()
.createMessage(metadata)
.getText();
log.debug("Generated system prompt for conversation {}
(length={} characters)",
context.getConversationId(),
systemPromptText != null ? systemPromptText.length()
: 0);
}
```
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * 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:');
+ }
+ });
+ 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];
+ return i;
+ });
Review Comment:
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.
##########
hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java:
##########
@@ -194,44 +210,45 @@ public String queryMonitors(
@Override
@Tool(name = "add_monitor", 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.
- **********
-
- 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"}
- """)
+ 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.
+ **********
+
+ 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 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 = "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) {
Review Comment:
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.
##########
hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ChatClientProviderServiceImpl.java:
##########
@@ -54,13 +56,13 @@ public class ChatClientProviderServiceImpl implements
ChatClientProviderService
private final ApplicationContext applicationContext;
private final GeneralConfigDao generalConfigDao;
-
+
@Autowired
private ToolCallbackProvider toolCallbackProvider;
-
+
private boolean isConfigured = false;
- @Value("classpath:/prompt/system-message.st")
+ @Value("classpath:/prompt/system-message-improve.st")
Review Comment:
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'.
```suggestion
@Value("classpath:/prompt/system-message.st")
```
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * handle security form submit
+ *
+ */
+ onSecurityFormSubmit(): void {
+ this.aiChatService
+ .saveSecurityData({
+ securityData: JSON.stringify(Object.values(this.securityParams)),
Review Comment:
There's no validation to ensure that securityParams contains valid data
before sending. If a required field is empty or invalid, the backend should
validate it, but the frontend should also validate before submission to provide
immediate user feedback. Consider adding form validation before calling
saveSecurityData.
```suggestion
// Basic client-side validation to ensure all security parameters have
values
const paramsArray = this.securityParams ?
Object.values(this.securityParams) : [];
const hasInvalidParam = paramsArray.some((param: any) => {
const value = param && param.paramValue;
return value === null || value === undefined || value === '';
});
if (hasInvalidParam) {
// Provide immediate feedback to the user and prevent submission
alert('Please fill in all required security fields before
submitting.');
return;
}
this.aiChatService
.saveSecurityData({
securityData: JSON.stringify(paramsArray),
```
##########
hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java:
##########
@@ -251,32 +268,43 @@ public String addMonitor(
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
+ 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);
Review Comment:
Missing JSON deserialization error handling. If the decrypted securityData
contains invalid JSON, JsonUtil.fromJson() will fail. Add error handling to
catch deserialization exceptions and provide meaningful error messages.
```suggestion
String decryptedSecurityData =
AesUtil.aesDecode(chatConversation.get().getSecurityData());
try {
List<Param> securityParams = JsonUtil.fromJson(
decryptedSecurityData,
new TypeReference<List<Param>>() {
});
if (CollectionUtils.isNotEmpty(securityParams)) {
paramList.addAll(securityParams);
}
} catch (Exception ex) {
log.error("Failed to deserialize securityData for
conversationId {}: {}", conversationId, ex.getMessage(), ex);
return "Error: Failed to parse stored security
parameters for this conversation. "
+ "Please clear or reconfigure the security data and
try again. Details: " + ex.getMessage();
```
##########
web-app/src/app/shared/components/ai-chat/chat.component.html:
##########
@@ -229,3 +238,25 @@ <h3 class="welcome-title">{{ 'ai.chat.welcome.title' |
i18n }}</h3>
</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>
Review Comment:
The security form modal lacks proper form validation. The template
references form="ngForm" but there's no validation logic to check if required
fields are filled before allowing submission. The submit button should be
disabled when the form is invalid, similar to how Angular forms typically work
with [disabled]="!form.valid".
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * 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:');
+ }
+ });
Review Comment:
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.
```suggestion
.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.');
}
);
```
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * 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;
Review Comment:
There's no validation to check if messages array is empty before accessing
the last message. If the messages array is empty, this will throw an error. Add
a check to ensure the array has elements before accessing
messages[messages.length - 1].
```suggestion
const lastMessage = this.messages.length > 0 ?
this.messages[this.messages.length - 1] : null;
if (lastMessage && lastMessage.securityForm) {
lastMessage.securityForm.complete = true;
}
```
##########
hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/tools/impl/MonitorToolsImpl.java:
##########
@@ -251,32 +268,43 @@ public String addMonitor(
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
+ 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);
Review Comment:
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.
```suggestion
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.";
```
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -253,6 +266,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'];
Review Comment:
This array of hardcoded translated strings is brittle and error-prone for
detecting form completion. If any translation changes or new languages are
added, this will break. Consider using a different mechanism, such as checking
a specific flag in the message metadata or using a consistent message key that
can be detected programmatically.
##########
web-app/src/app/shared/components/ai-chat/chat.component.ts:
##########
@@ -618,4 +658,57 @@ export class ChatComponent implements OnInit, OnDestroy {
this.aiProviderConfig.model = selectedProvider.defaultModel;
}
}
+
+ /**
+ * 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:');
+ }
+ });
+ 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];
Review Comment:
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.
```suggestion
i.name = i.name[this.i18nSvc.defaultLang] || i.name['en-US'] || i.name;
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]