Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b434649
[improve] use security form collect password
LiuTianyou Dec 20, 2025
d43dd22
[improve] use security form collect password or other sensitive infor…
LiuTianyou Dec 20, 2025
0c673d4
[improve] use security form collect password or other sensitive infor…
LiuTianyou Dec 21, 2025
2f68a6e
Merge branch 'master' into improve-ai
LiuTianyou Dec 21, 2025
3483cae
Merge remote-tracking branch 'origin/improve-ai' into improve-ai
LiuTianyou Dec 21, 2025
fc12017
[improve] use security form collect password or other sensitive infor…
LiuTianyou Dec 21, 2025
c2c670d
[improve] use security form collect password or other sensitive infor…
LiuTianyou Dec 21, 2025
decfc7d
[improve] use security form collect password or other sensitive infor…
LiuTianyou Dec 21, 2025
e946fa4
[improve] use security form collect password or other sensitive infor…
LiuTianyou Dec 23, 2025
153c24a
Merge branch 'master' into improve-ai
lynx009 Dec 24, 2025
f75283d
Merge branch 'master' into improve-ai
Aias00 Dec 28, 2025
3855bb0
Merge branch 'master' into improve-ai
yuluo-yx Dec 28, 2025
c932a64
Merge branch 'master' into improve-ai
Duansg Dec 30, 2025
ecbbd22
Merge branch 'master' into improve-ai
Duansg Dec 31, 2025
e90766c
Merge branch 'master' into improve-ai
Duansg Jan 2, 2026
08f03cf
Update web-app/src/app/shared/components/ai-chat/chat.component.ts
Aias00 Jan 5, 2026
21646ec
Update web-app/src/app/shared/components/ai-chat/chat.component.ts
Aias00 Jan 5, 2026
0a11d49
Merge branch 'master' into improve-ai
Aias00 Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -78,12 +79,12 @@ public Flux<ServerSentEvent<ChatResponseChunk>> streamChat(@Valid @RequestBody C
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());
Expand All @@ -92,12 +93,12 @@ public Flux<ServerSentEvent<ChatResponseChunk>> streamChat(@Valid @RequestBody C
} 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());
}
}

Expand Down Expand Up @@ -134,7 +135,7 @@ public ResponseEntity<Message<List<ChatConversation>>> listConversations() {
@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));
}
Expand All @@ -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)));
}
Comment on lines +162 to +166
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.


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.hertzbeat.ai.pojo.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

/**
* security data
*/
@Data
public class SecurityData {

@NotNull
@Schema(description = "Conversation ID", example = "123")
private Long conversationId;

@NotNull
@Schema(description = "security data", example = "{\"password\":\"xxxxx\"}")
private String securityData;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
*/
Expand Down Expand Up @@ -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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@

package org.apache.hertzbeat.ai.service.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.ai.ChatMessage;
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.event.EventListener;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
Expand All @@ -43,9 +48,8 @@
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
Expand All @@ -54,15 +58,21 @@ public class ChatClientProviderServiceImpl implements ChatClientProviderService
private final ApplicationContext applicationContext;

private final GeneralConfigDao generalConfigDao;


private ModelProviderConfig modelProviderConfig;

@Autowired
private ToolCallbackProvider toolCallbackProvider;

private boolean isConfigured = false;

@Value("classpath:/prompt/system-message.st")
private Resource systemResource;

@Value("classpath:/prompt/system-message-protected.st")
private Resource systemResourceProtected;


@Autowired
public ChatClientProviderServiceImpl(ApplicationContext applicationContext, GeneralConfigDao generalConfigDao) {
this.applicationContext = applicationContext;
Expand All @@ -74,7 +84,7 @@ public Flux<String> streamChat(ChatRequestContext context) {
try {
// Get the current (potentially refreshed) ChatClient instance
ChatClient chatClient = applicationContext.getBean("openAiChatClient", ChatClient.class);

List<Message> messages = new ArrayList<>();

// Add conversation history if available
Expand All @@ -91,28 +101,45 @@ public Flux<String> streamChat(ChatRequestContext context) {
messages.add(new UserMessage(context.getMessage()));

log.info("Starting streaming chat for conversation: {}", context.getConversationId());

return chatClient.prompt()
.messages(messages)
.system(SystemPromptTemplate.builder().resource(systemResource).build().getTemplate())
.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(generateSystemMessage(context))
.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);
return Flux.error(e);
}
}

private String generateSystemMessage(ChatRequestContext context) {
if (Objects.equals(modelProviderConfig.getParticipationModel(), "PROTECTED")) {
Map<String, Object> metadata = new HashMap<>();
metadata.put("conversationId", context.getConversationId());
return SystemPromptTemplate.builder().resource(systemResourceProtected).build().create(metadata)
.getContents();
}
return SystemPromptTemplate.builder().resource(systemResource).build().getTemplate();
}

@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;
}
Expand Down
Loading
Loading