Commit a2b3a8c8 authored by 高如斌's avatar 高如斌

Merge branch 'develop_tmp' of...

Merge branch 'develop_tmp' of https://gitlab-cloud.hisense.com/gavin-group/pangea-agent into feature/chat-form
parents 50254708 1dc90ff8
This diff is collapsed.
...@@ -44,16 +44,16 @@ ...@@ -44,16 +44,16 @@
</dependencyManagement> </dependencyManagement>
<!-- Repositories for Spring AI milestone versions --> <!-- Repositories for Spring AI milestone versions -->
<repositories> <!-- <repositories>-->
<repository> <!-- <repository>-->
<id>spring-milestones</id> <!-- <id>spring-milestones</id>-->
<name>Spring Milestones</name> <!-- <name>Spring Milestones</name>-->
<url>https://repo.spring.io/milestone</url> <!-- <url>https://repo.spring.io/milestone</url>-->
<snapshots> <!-- <snapshots>-->
<enabled>false</enabled> <!-- <enabled>false</enabled>-->
</snapshots> <!-- </snapshots>-->
</repository> <!-- </repository>-->
</repositories> <!-- </repositories>-->
<dependencies> <dependencies>
<!-- Spring Boot Web with Undertow --> <!-- Spring Boot Web with Undertow -->
...@@ -288,7 +288,7 @@ ...@@ -288,7 +288,7 @@
<dependency> <dependency>
<groupId>com.microsoft.playwright</groupId> <groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId> <artifactId>playwright</artifactId>
<version>1.46.0</version> <version>1.49.0</version>
</dependency> </dependency>
<!-- FastJSON2 for JSON processing --> <!-- FastJSON2 for JSON processing -->
......
...@@ -5,16 +5,24 @@ import org.springframework.ai.chat.client.ChatClient; ...@@ -5,16 +5,24 @@ import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.*; import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import pangea.hiagent.agent.service.ErrorHandlerService; import pangea.hiagent.agent.service.ErrorHandlerService;
import pangea.hiagent.agent.service.SseTokenEmitter;
import pangea.hiagent.agent.service.TokenConsumerWithCompletion; import pangea.hiagent.agent.service.TokenConsumerWithCompletion;
import pangea.hiagent.agent.service.UserSseService;
import pangea.hiagent.memory.MemoryService; import pangea.hiagent.memory.MemoryService;
import pangea.hiagent.model.Agent; import pangea.hiagent.model.Agent;
import pangea.hiagent.model.UserToken;
import pangea.hiagent.tool.AgentToolManager; import pangea.hiagent.tool.AgentToolManager;
import pangea.hiagent.common.utils.UserUtils; import pangea.hiagent.common.utils.UserUtils;
import pangea.hiagent.web.service.ChatService;
import pangea.hiagent.web.service.UserTokenService;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
...@@ -24,6 +32,8 @@ import java.util.function.Consumer; ...@@ -24,6 +32,8 @@ import java.util.function.Consumer;
@Service @Service
public class DefaultReactExecutor implements ReactExecutor { public class DefaultReactExecutor implements ReactExecutor {
private final UserSseService userSseService;
private final ChatService chatService;
@Value("${hiagent.react.system-prompt}") @Value("${hiagent.react.system-prompt}")
private String defaultSystemPrompt; private String defaultSystemPrompt;
...@@ -38,12 +48,15 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -38,12 +48,15 @@ public class DefaultReactExecutor implements ReactExecutor {
private final AgentToolManager agentToolManager; private final AgentToolManager agentToolManager;
public DefaultReactExecutor(EventSplitter eventSplitter, AgentToolManager agentToolManager ,
MemoryService memoryService, ErrorHandlerService errorHandlerService) { public DefaultReactExecutor(EventSplitter eventSplitter, AgentToolManager agentToolManager,
MemoryService memoryService, ErrorHandlerService errorHandlerService, UserSseService userSseService, ChatService chatService) {
this.eventSplitter = eventSplitter; this.eventSplitter = eventSplitter;
this.agentToolManager = agentToolManager; this.agentToolManager = agentToolManager;
this.memoryService = memoryService; this.memoryService = memoryService;
this.errorHandlerService = errorHandlerService; this.errorHandlerService = errorHandlerService;
this.userSseService = userSseService;
this.chatService = chatService;
} }
@Override @Override
...@@ -67,7 +80,7 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -67,7 +80,7 @@ public class DefaultReactExecutor implements ReactExecutor {
List<Object> agentTools = getAgentTools(agent); List<Object> agentTools = getAgentTools(agent);
try { try {
Prompt prompt = buildPromptWithHistory(defaultSystemPrompt, userInput, agent, userId); Prompt prompt = buildPromptWithHistory(defaultSystemPrompt, userInput, agent, userId, false);
ChatResponse response = chatClient.prompt(prompt) ChatResponse response = chatClient.prompt(prompt)
.tools(agentTools.toArray()) .tools(agentTools.toArray())
...@@ -108,7 +121,7 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -108,7 +121,7 @@ public class DefaultReactExecutor implements ReactExecutor {
* @param userId 用户ID(可选,如果为null则自动获取) * @param userId 用户ID(可选,如果为null则自动获取)
* @return 构建好的提示词对象 * @return 构建好的提示词对象
*/ */
private Prompt buildPromptWithHistory(String systemPrompt, String userInput, Agent agent, String userId) { private Prompt buildPromptWithHistory(String systemPrompt, String userInput, Agent agent, String userId, boolean newChat) {
List<org.springframework.ai.chat.messages.Message> messages = new ArrayList<>(); List<org.springframework.ai.chat.messages.Message> messages = new ArrayList<>();
messages.add(new SystemMessage(systemPrompt)); messages.add(new SystemMessage(systemPrompt));
...@@ -125,8 +138,9 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -125,8 +138,9 @@ public class DefaultReactExecutor implements ReactExecutor {
List<org.springframework.ai.chat.messages.Message> historyMessages = List<org.springframework.ai.chat.messages.Message> historyMessages =
memoryService.getHistoryMessages(sessionId, historyLength); memoryService.getHistoryMessages(sessionId, historyLength);
if (!newChat) {
messages.addAll(historyMessages); messages.addAll(historyMessages);
}
memoryService.addUserMessageToMemory(sessionId, userInput); memoryService.addUserMessageToMemory(sessionId, userInput);
} catch (Exception e) { } catch (Exception e) {
...@@ -135,7 +149,9 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -135,7 +149,9 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
messages.add(new UserMessage(userInput)); messages.add(new UserMessage(userInput));
for (Message message : messages) {
log.info("message is {}", message);
}
return new Prompt(messages); return new Prompt(messages);
} }
...@@ -155,17 +171,40 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -155,17 +171,40 @@ public class DefaultReactExecutor implements ReactExecutor {
StringBuilder fullResponse = new StringBuilder(); StringBuilder fullResponse = new StringBuilder();
try { try {
Prompt prompt = buildPromptWithHistory(defaultSystemPrompt, userInput, agent, userId); SseTokenEmitter sseTokenEmitter = (SseTokenEmitter) tokenConsumer;
String emitterId = sseTokenEmitter.getEmitterId();
String tmpUserId = sseTokenEmitter.getUserId();
Prompt prompt = buildPromptWithHistory(defaultSystemPrompt, userInput, agent, tmpUserId, false);
log.info("agent id {}", agent.getId());
log.info("agentTools {}", agentTools);
if (agent.getId().compareToIgnoreCase("agent-8") == 0) {
if (!chatService.chatExists(tmpUserId, agent.getId())) {
log.info("new chat for {} {} ", userId, agent.getId());
prompt = buildPromptWithHistory(defaultSystemPrompt, userInput, agent, tmpUserId, true);
}
chatClient.prompt(prompt) chatClient.prompt(prompt)
.tools(agentTools.toArray()) .tools(agentTools.toArray())
.toolContext(Map.of("emitterId", emitterId, "userId", sseTokenEmitter.getUserId(),"agentId",agent.getId()))
.stream() .stream()
.chatResponse() .chatResponse()
.subscribe( .subscribe(
chatResponse -> handleTokenResponse(chatResponse, tokenConsumer, fullResponse), chatResponse -> handleTokenResponse(chatResponse, tokenConsumer, fullResponse),
throwable -> handleStreamError(throwable, tokenConsumer), throwable -> handleStreamError(throwable, tokenConsumer, emitterId),
() -> handleStreamCompletion(tokenConsumer, fullResponse, agent, userId) () -> handleStreamCompletion(tokenConsumer, fullResponse, agent, userId, emitterId)
); );
} else {
chatClient.prompt(prompt)
.tools(agentTools.toArray())
.stream()
.chatResponse()
.subscribe(
chatResponse -> handleTokenResponse(chatResponse, tokenConsumer, fullResponse),
throwable -> handleStreamError(throwable, tokenConsumer, emitterId),
() -> handleStreamCompletion(tokenConsumer, fullResponse, agent, userId, emitterId)
);
}
} catch (Exception e) { } catch (Exception e) {
log.error("流式执行ReAct流程时发生错误", e); log.error("流式执行ReAct流程时发生错误", e);
...@@ -207,12 +246,14 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -207,12 +246,14 @@ public class DefaultReactExecutor implements ReactExecutor {
* @param agent 智能体对象 * @param agent 智能体对象
* @param userId 用户ID * @param userId 用户ID
*/ */
private void handleStreamCompletion(Consumer<String> tokenConsumer, StringBuilder fullResponse, Agent agent, String userId) { private void handleStreamCompletion(Consumer<String> tokenConsumer, StringBuilder fullResponse, Agent agent, String userId, String emitterId) {
try { try {
log.info("流式处理完成"); log.info("流式处理完成");
String responseStr = fullResponse.toString(); String responseStr = fullResponse.toString();
saveAssistantResponseToMemory(agent, responseStr, userId); saveAssistantResponseToMemory(agent, responseStr, userId);
log.info("complete, remove emitterId {}", emitterId);
userSseService.removeEmitter(emitterId);
sendCompletionEvent(tokenConsumer, responseStr); sendCompletionEvent(tokenConsumer, responseStr);
} catch (Exception e) { } catch (Exception e) {
log.error("处理流式完成回调时发生错误", e); log.error("处理流式完成回调时发生错误", e);
...@@ -274,7 +315,9 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -274,7 +315,9 @@ public class DefaultReactExecutor implements ReactExecutor {
* @param throwable 异常对象 * @param throwable 异常对象
* @param tokenConsumer token消费者 * @param tokenConsumer token消费者
*/ */
private void handleStreamError(Throwable throwable, Consumer<String> tokenConsumer) { private void handleStreamError(Throwable throwable, Consumer<String> tokenConsumer, String emitterId) {
log.info("error,remove emitterId:{}", emitterId);
userSseService.removeEmitter(emitterId);
errorHandlerService.handleStreamError(throwable, tokenConsumer, "ReAct流式处理"); errorHandlerService.handleStreamError(throwable, tokenConsumer, "ReAct流式处理");
} }
......
...@@ -15,6 +15,8 @@ import pangea.hiagent.tool.AgentToolManager; ...@@ -15,6 +15,8 @@ import pangea.hiagent.tool.AgentToolManager;
import pangea.hiagent.web.dto.AgentRequest; import pangea.hiagent.web.dto.AgentRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.util.UUID;
/** /**
* Agent 对话服务 * Agent 对话服务
* 职责:协调整个AI对话流程,作为流式处理的统一入口和协调者 * 职责:协调整个AI对话流程,作为流式处理的统一入口和协调者
...@@ -125,9 +127,11 @@ public class AgentChatService { ...@@ -125,9 +127,11 @@ public class AgentChatService {
// 创建 SSE emitter // 创建 SSE emitter
SseEmitter emitter = userSseService.createAndRegisterConnection(userId); SseEmitter emitter = userSseService.createAndRegisterConnection(userId);
String emitterId = UUID.randomUUID().toString();
log.info("emitterId: {}", emitterId);
userSseService.registerEmitter(emitterId, emitter);
// 异步处理对话,避免阻塞HTTP连接 // 异步处理对话,避免阻塞HTTP连接
processChatStreamAsync(emitter, agent, chatRequest, userId); processChatStreamAsync(emitter, agent, chatRequest, userId,emitterId);
return emitter; return emitter;
} }
...@@ -136,14 +140,14 @@ public class AgentChatService { ...@@ -136,14 +140,14 @@ public class AgentChatService {
* 异步处理流式对话 * 异步处理流式对话
*/ */
@Async @Async
private void processChatStreamAsync(SseEmitter emitter, Agent agent, ChatRequest chatRequest, String userId) { private void processChatStreamAsync(SseEmitter emitter, Agent agent, ChatRequest chatRequest, String userId,String emitterId) {
try { try {
// 首先检查连接状态 // 首先检查连接状态
if (emitter != null && userSseService.isEmitterCompleted(emitter)) { if (emitter != null && userSseService.isEmitterCompleted(emitter)) {
log.debug("SSE连接已关闭,跳过异步处理"); log.debug("SSE连接已关闭,跳过异步处理");
return; return;
} }
processChatRequest(emitter, agent, chatRequest, userId); processChatRequest(emitter, agent, chatRequest, userId,emitterId);
} catch (Exception e) { } catch (Exception e) {
log.error("处理聊天请求时发生异常", e); log.error("处理聊天请求时发生异常", e);
...@@ -163,7 +167,7 @@ public class AgentChatService { ...@@ -163,7 +167,7 @@ public class AgentChatService {
* @param chatRequest 聊天请求 * @param chatRequest 聊天请求
* @param userId 用户ID * @param userId 用户ID
*/ */
private void processChatRequest(SseEmitter emitter, Agent agent, ChatRequest chatRequest, String userId) { private void processChatRequest(SseEmitter emitter, Agent agent, ChatRequest chatRequest, String userId,String emitterId) {
try { try {
// 参数验证 // 参数验证
if (!validateParameters(emitter, agent, chatRequest, userId)) { if (!validateParameters(emitter, agent, chatRequest, userId)) {
...@@ -197,7 +201,7 @@ public class AgentChatService { ...@@ -197,7 +201,7 @@ public class AgentChatService {
// 创建新的SseTokenEmitter实例 // 创建新的SseTokenEmitter实例
SseTokenEmitter tokenEmitter = new SseTokenEmitter(userSseService, emitter, agent, request, userId, this::handleCompletion); SseTokenEmitter tokenEmitter = new SseTokenEmitter(userSseService, emitter, agent, request, userId, this::handleCompletion);
tokenEmitter.setEmitterId(emitterId);
// 处理流式请求前再次检查连接状态 // 处理流式请求前再次检查连接状态
if (!userSseService.isEmitterCompleted(emitter)) { if (!userSseService.isEmitterCompleted(emitter)) {
processor.processStreamRequest(request, agent, userId, tokenEmitter); processor.processStreamRequest(request, agent, userId, tokenEmitter);
......
...@@ -25,6 +25,7 @@ public class SseTokenEmitter implements TokenConsumerWithCompletion { ...@@ -25,6 +25,7 @@ public class SseTokenEmitter implements TokenConsumerWithCompletion {
private final AgentRequest request; private final AgentRequest request;
private final String userId; private final String userId;
private final CompletionCallback completionCallback; private final CompletionCallback completionCallback;
private String emitterId;
/** /**
* 构造函数 * 构造函数
...@@ -160,4 +161,13 @@ public class SseTokenEmitter implements TokenConsumerWithCompletion { ...@@ -160,4 +161,13 @@ public class SseTokenEmitter implements TokenConsumerWithCompletion {
public interface CompletionCallback { public interface CompletionCallback {
void onComplete(SseEmitter emitter, Agent agent, AgentRequest request, String userId, String fullContent); void onComplete(SseEmitter emitter, Agent agent, AgentRequest request, String userId, String fullContent);
} }
public void setEmitterId(String emitterId) {
this.emitterId = emitterId;
}
public String getEmitterId() {
return emitterId;
}
public String getUserId() {
return userId;
}
} }
\ No newline at end of file
...@@ -719,4 +719,17 @@ public class UserSseService { ...@@ -719,4 +719,17 @@ public class UserSseService {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
public void registerEmitter(String id,SseEmitter emitter) {
this.userEmitters.put(id, emitter);
}
public SseEmitter getEmitter(String id) {
return userEmitters.get(id);
}
public boolean removeEmitter(String id) {
userEmitters.remove(id);
return true;
}
} }
\ No newline at end of file
package pangea.hiagent.common.utils;
public class Contants {
public static final String LOCATOR_SCHEMA = "{\n" +
" \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"field_name\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"locator\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"label_tag\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"attributes\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"type\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"maxlength\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"class\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"name\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"value\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"autocomplete\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"placeholder\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"readonly\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"id\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"droptreeids\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"vetitle\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"contenteditable\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"style\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"tipstext\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"fylx\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" },\n" +
" \"additionalProperties\": false,\n" +
" \"required\": [\n" +
" \"class\",\n" +
" \"value\"\n" +
" ]\n" +
" }\n" +
" },\n" +
" \"additionalProperties\": false,\n" +
" \"required\": [\n" +
" \"field_name\",\n" +
" \"locator\",\n" +
" \"attributes\"\n" +
" ]\n" +
" }\n" +
"}";
}
package pangea.hiagent.common.utils;
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicLong;
public class HybridUniqueLongGenerator {
private static final SecureRandom random = new SecureRandom();
private static final AtomicLong counter = new AtomicLong(0);
public static long generateUnique13DigitNumber() {
long timestamp = System.currentTimeMillis();
long count = counter.incrementAndGet();
// 使用时间戳的前10位 + 计数器的后3位
long timestampPart = (timestamp / 1000) * 1000;
long counterPart = count % 1000;
return timestampPart + counterPart;
}
// 更随机的版本,但仍保证唯一
public static synchronized long generateRandomUnique() {
long timestamp = System.currentTimeMillis();
// 在时间戳基础上加上一个小的随机偏移
int randomOffset = random.nextInt(100);
long result = timestamp * 100 + randomOffset;
// 确保是13位
while (result >= 10000000000000L) {
result /= 10;
}
while (result < 1000000000000L) {
result *= 10;
result += random.nextInt(10);
}
return result;
}
}
\ No newline at end of file
package pangea.hiagent.common.utils;
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicLong;
public class InputCodeGenerator {
private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static final SecureRandom random = new SecureRandom();
private static final AtomicLong sequence = new AtomicLong(0);
public static String generateUniqueInputCode(String prefix) {
// 当前时间戳(毫秒)
long timestamp = System.currentTimeMillis();
// 序列号
long seq = sequence.incrementAndGet();
// 组合时间戳和序列号
long combined = (timestamp << 10) | (seq & 0x3FF); // 取序列号后10位
// 转为36进制
String code = Long.toString(Math.abs(combined), 36).toUpperCase();
// 确保8位长度
if (code.length() > 8) {
code = code.substring(code.length() - 8);
} else if (code.length() < 8) {
// 前面补随机字符
StringBuilder sb = new StringBuilder();
for (int i = code.length(); i < 8; i++) {
sb.append(CHARS.charAt(random.nextInt(CHARS.length())));
}
code = sb.toString() + code;
}
return prefix + code;
}
}
package pangea.hiagent.model;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user_token")
public class UserToken extends BaseEntity{
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
private String username;
private String userId;
private String tokenType;
private String tokenValue;
}
...@@ -299,7 +299,7 @@ public class HisenseLbpmApprovalTool extends BaseTool { ...@@ -299,7 +299,7 @@ public class HisenseLbpmApprovalTool extends BaseTool {
} }
// 定位并点击提交按钮 - 尝试新的选择器 // 定位并点击提交按钮 - 尝试新的选择器
String submitButtonSelector = "input[id='process_review_button'][class='process_review_button'][type='button'][value='提交']"; String submitButtonSelector = "input[id='process_review_button']";
log.debug("正在定位提交按钮: {}", submitButtonSelector); log.debug("正在定位提交按钮: {}", submitButtonSelector);
Locator submitButton = page.locator(submitButtonSelector); Locator submitButton = page.locator(submitButtonSelector);
if (submitButton.count() > 0) { if (submitButton.count() > 0) {
...@@ -307,7 +307,7 @@ public class HisenseLbpmApprovalTool extends BaseTool { ...@@ -307,7 +307,7 @@ public class HisenseLbpmApprovalTool extends BaseTool {
log.info("提交按钮点击完成 (使用新选择器)"); log.info("提交按钮点击完成 (使用新选择器)");
} else { } else {
// 如果新选择器未找到元素,尝试原选择器 // 如果新选择器未找到元素,尝试原选择器
submitButtonSelector = "input[id='process_review_button'][class='process_review_button'][type='button'][value='提交']"; submitButtonSelector = "input[id='process_review_button']";
log.debug("新选择器未找到元素,尝试原选择器: {}", submitButtonSelector); log.debug("新选择器未找到元素,尝试原选择器: {}", submitButtonSelector);
submitButton = page.locator(submitButtonSelector); submitButton = page.locator(submitButtonSelector);
if (submitButton.count() > 0) { if (submitButton.count() > 0) {
......
...@@ -49,10 +49,10 @@ public class HisenseSsoLoginTool extends BaseTool { ...@@ -49,10 +49,10 @@ public class HisenseSsoLoginTool extends BaseTool {
private static final int MFA_WAIT_FOR_URL_TIMEOUT = 45000; private static final int MFA_WAIT_FOR_URL_TIMEOUT = 45000;
// 用户名输入框选择器 // 用户名输入框选择器
private static final String USERNAME_INPUT_SELECTOR = "input[placeholder='账号名/海信邮箱/手机号']"; private static final String USERNAME_INPUT_SELECTOR = "#username .emailSelect-account input[type='text']";
// 密码输入框选择器 // 密码输入框选择器
private static final String PASSWORD_INPUT_SELECTOR = "input[placeholder='密码'][type='password']"; private static final String PASSWORD_INPUT_SELECTOR = "#username .emailSelect-account input[type='password']";
// 登录按钮选择器 // 登录按钮选择器
private static final String LOGIN_BUTTON_SELECTOR = "#login-button"; private static final String LOGIN_BUTTON_SELECTOR = "#login-button";
......
package pangea.hiagent.web.repository;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import pangea.hiagent.model.Tool;
import pangea.hiagent.model.User;
import pangea.hiagent.model.UserToken;
import java.util.List;
@Mapper
public interface UserTokenRepository extends BaseMapper<User> {
@Select("SELECT * FROM sys_user_token WHERE user_id = #{userName} AND token_type=#{tokenType} ORDER BY created_at DESC")
UserToken getTokenByUserIdAndTokenType(String userName, String tokenType);
}
...@@ -82,6 +82,9 @@ public class AuthService { ...@@ -82,6 +82,9 @@ public class AuthService {
// 检查是否为开发环境,如果是则允许任意密码 // 检查是否为开发环境,如果是则允许任意密码
boolean isDevEnvironment = Arrays.asList(environment.getActiveProfiles()).contains("dev") || boolean isDevEnvironment = Arrays.asList(environment.getActiveProfiles()).contains("dev") ||
Arrays.asList(environment.getDefaultProfiles()).contains("default"); Arrays.asList(environment.getDefaultProfiles()).contains("default");
log.info("active profile {}",environment.getActiveProfiles());
log.info("default profile {}",environment.getDefaultProfiles());
if (isDevEnvironment) { if (isDevEnvironment) {
log.info("开发环境: 跳过密码验证"); log.info("开发环境: 跳过密码验证");
......
package pangea.hiagent.web.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Slf4j
@Service
public class ChatService {
private final ConcurrentMap<String,String> chatList = new ConcurrentHashMap<>(1024);
public boolean chatExists(String userId,String agentId) {
String chatId = buildChatId(userId,agentId);
boolean exists = chatList.containsKey(chatId);
if(!exists){
log.info("put chatId:{}",chatId);
chatList.put(chatId,"exists");
}
return exists;
}
public void removeChat(String userId,String agentId) {
String chatId = buildChatId(userId,agentId);
log.info("remove chatId:{}",chatId);
chatList.remove(chatId);
}
private String buildChatId(String userId,String agentId) {
return userId+"-"+agentId;
}
}
package pangea.hiagent.web.service;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import pangea.hiagent.tool.impl.HisenseTripTool;
import pangea.hiagent.tool.impl.VisitorAppointmentTool;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Service
@Slf4j
public class InfoCollectorService {
private static final ConcurrentHashMap<String, JSONArray> infos = new ConcurrentHashMap<>(16);
private static final ConcurrentHashMap<String, Object> values = new ConcurrentHashMap<>(16);
public void register(String pageId, JSONArray info) {
infos.put(pageId, info);
}
public boolean exists(String pageId) {
return infos.containsKey(pageId);
}
public JSONArray getInfo(String pageId) {
return infos.get(pageId);
}
public void clearInfo(String pageId){
infos.remove(pageId);
}
public void saveValue(String key, Object value) {
log.info("key {} value {}", key, value);
if(value == null || StringUtils.isEmpty(value.toString())
|| value.toString().compareToIgnoreCase("待补充") == 0 || value.toString().contains("海信园区")) {
return;
}
values.put(key, value);
}
public void clearValue() {
values.clear();
}
public void saveDefaultValue(String pageId){
JSONArray jsonArray = infos.get(pageId);
for(int i = 0; i < jsonArray.size(); i++){
JSONObject object = jsonArray.getJSONObject(i);
if(object.getString("field_name").compareToIgnoreCase("接访员工手机号") == 0){
String code = object.getString("code");
saveValue(code, "15841169015");
}
if(object.getString("field_name").compareToIgnoreCase("接访员工姓名") == 0){
String code = object.getString("code");
saveValue(code, "杜艺");
}
if(object.getString("field_name").compareToIgnoreCase("证件类型") == 0){
String code = object.getString("code");
saveValue(code, "居民身份证");
}
}
}
public Object getValue(String key) {
return values.get(key);
}
public Map<String,String> findLackInfo(String pageId) {
Set<String> valueKeys = values.keySet();
log.info("value keys {}", valueKeys);
Set<String> allKeys = infos.get(pageId).stream().map(t -> ((JSONObject) t).getString("code")).collect(Collectors.toSet());
log.info("all keys {}", allKeys);
allKeys.removeAll(valueKeys);
log.info("lack keys {}", allKeys);
// Set<String> lackInfos = new HashSet<>();
Map<String,String> lackInfos = new HashMap<>();
if(allKeys.isEmpty()){
log.info("info is good enough");
return lackInfos;
}
infos.get(pageId).stream().forEach(t ->
{
JSONObject info = (JSONObject) t;
String code = info.getString("code");
if (allKeys.contains(code)) {
lackInfos.put(code,info.getString("field_name"));
}
});
log.info("lack infos {}", lackInfos);
return lackInfos;
}
}
package pangea.hiagent.web.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import pangea.hiagent.model.UserToken;
import pangea.hiagent.web.repository.UserTokenRepository;
@Service
public class UserTokenService {
@Autowired
private UserTokenRepository userTokenRepository;
public UserToken getUserToken(String userId,String tokenType) {
return userTokenRepository.getTokenByUserIdAndTokenType(userId,tokenType);
}
}
spring:
application:
name: hiagent
# 数据源配置
datasource:
url: jdbc:mysql://${DB_HOST:127.0.0.1}:3306/hiagent2?allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver}
username: ${DB_NAME:root}
password: ${DB_PASSWORD:123456Aa?}
hikari:
maximum-pool-size: 10
minimum-idle: 2
connection-timeout: 30000
# 禁用Milvus自动配置
autoconfigure:
exclude:
- org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration
# SQL初始化配置
sql:
init:
schema-locations: classpath:schema.sql
mode: never
# mode: always
# JPA/Hibernate配置
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
# hibernate:
# ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
# H2 Console配置(仅开发环境)
# h2:
# console:
# enabled: true
# path: /h2-console
# Redis配置
data:
redis:
host: localhost
port: 6379
password:
timeout: 2000
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1
# RabbitMQ配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
connection-timeout: 15000
# Jackson配置
jackson:
serialization:
write-dates-as-timestamps: false
deserialization:
fail-on-unknown-properties: false
default-property-inclusion: non_null
# Web配置
web:
resources:
add-mappings: true
# servlet配置
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
# 默认性异步请求配置
mvc:
async:
request-timeout: 300000 # 5分钟,与SSE保持一致
# Spring AI配置
ai:
openai:
enabled: false
ollama:
enabled: false
# MyBatis Plus配置
mybatis-plus:
type-aliases-package: pangea.hiagent.model
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
cache-enabled: true
use-generated-keys: true
global-config:
db-config:
id-type: assign_uuid
table-underline: true
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# Logging配置
logging:
level:
root: INFO
pangea.hiagent: DEBUG
org.springframework: INFO
org.springframework.security: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/hiagent.log
max-size: 10MB
max-history: 30
charset:
console: UTF-8
file: UTF-8
# Server配置
server:
port: 8080
servlet:
context-path: /
compression:
enabled: true
min-response-size: 1024
# SSE和异步请求超时配置
request-timeout: 300000 # 5分钟(毫秒)
# Undertow配置
undertow:
# IO线程数,默认为处理器数量
io-threads: 4
# 工作线程数
worker-threads: 50
# 缓冲区配置
buffer-size: 65536
# 是否直接分配缓冲区
direct-buffers: true
# HTTP/2支持
enable-http2: true
# 最大HTTP头大小
max-http-header-size: 8192
# 最大参数数量
max-parameters: 1000
# 最大请求头数量
max-headers: 200
# 最大cookies数量
max-cookies: 100
# URL编码字符集
url-charset: UTF-8
# 访问日志配置
accesslog:
enabled: false
pattern: common
dir: logs
prefix: access_log.
# SSL配置
ssl:
# SSL引擎
engine:
# 密码套件
enabled-protocols: TLSv1.2,TLSv1.3
# WebSocket配置
websocket:
# WebSocket消息缓冲区大小
buffer-size: 1048576
# 最大WebSocket帧大小
max-frame-size: 10485760
# 应用自定义配置
hiagent:
# JWT配置
jwt:
secret: ${JWT_SECRET:hiagent-secret-key-for-production-change-this}
expiration: 7200000 # 2小时
refresh-expiration: 604800000 # 7天
# Agent配置
agent:
default-model: deepseek-chat
default-temperature: 0.7
default-max-tokens: 4096
history-length: 10
# LLM配置
llm:
providers:
deepseek:
default-api-key: ${DEEPSEEK_API_KEY:sk-e8ef4359d20b413696512db21c09db87}
default-model: deepseek-chat
base-url: https://api.deepseek.com
openai:
default-api-key: ${OPENAI_API_KEY:}
default-model: gpt-3.5-turbo
base-url: https://api.openai.com/v1
ollama:
default-model: llama2
base-url: http://localhost:11434
# RAG配置
rag:
chunk-size: 512
chunk-overlap: 50
top-k: 5
score-threshold: 0.8
# Milvus Lite配置
milvus:
data-dir: ./milvus_data
db-name: hiagent
collection-name: document_embeddings
# ChatMemory配置
app:
chat-memory:
# 实现类型: caffeine, redis, hybrid
implementation: caffeine
caffeine:
# 是否启用Caffeine缓存
enabled: true
redis:
# 是否启用Redis缓存
enabled: false
\ No newline at end of file
...@@ -4,24 +4,12 @@ spring: ...@@ -4,24 +4,12 @@ spring:
# 配置文件激活 # 配置文件激活
profiles: profiles:
active: dev active: test
# 启用懒加载初始化 # 启用懒加载初始化
main: main:
lazy-initialization: true lazy-initialization: true
# 数据源配置
datasource:
url: jdbc:h2:mem:hiagent;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driver-class-name: org.h2.Driver
username: sa
password: sa
hikari:
maximum-pool-size: 10
minimum-idle: 1
connection-timeout: 20000
initialization-fail-timeout: 0
# 禁用Milvus自动配置 # 禁用Milvus自动配置
autoconfigure: autoconfigure:
exclude: exclude:
...@@ -32,22 +20,6 @@ spring: ...@@ -32,22 +20,6 @@ spring:
init: init:
mode: never # 生产环境禁用SQL脚本自动初始化 mode: never # 生产环境禁用SQL脚本自动初始化
# JPA/Hibernate配置
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create # 生产环境仅验证表结构,不修改数据库
show-sql: false
properties:
hibernate:
format_sql: true
# H2 Console配置(仅开发环境)
h2:
console:
enabled: true
path: /h2-console
# Redis配置 # Redis配置
data: data:
redis: redis:
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
"dompurify": "^3.3.1", "dompurify": "^3.3.1",
"element-plus": "^2.4.0", "element-plus": "^2.4.0",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"lodash-es": "^4.17.21",
"marked": "^17.0.1", "marked": "^17.0.1",
"pako": "^2.1.0", "pako": "^2.1.0",
"pangea-ui": "1.0.1-beta.2", "pangea-ui": "1.0.1-beta.2",
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
"dompurify": "^3.3.1", "dompurify": "^3.3.1",
"element-plus": "^2.4.0", "element-plus": "^2.4.0",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"lodash-es": "^4.17.21",
"marked": "^17.0.1", "marked": "^17.0.1",
"pako": "^2.1.0", "pako": "^2.1.0",
"pangea-ui": "1.0.1-beta.2", "pangea-ui": "1.0.1-beta.2",
......
...@@ -22,7 +22,6 @@ const formStore = useFormStore(); ...@@ -22,7 +22,6 @@ const formStore = useFormStore();
// 表单组件ref // 表单组件ref
const templateRef = ref(); const templateRef = ref();
// 表单提交回调
const submit = () => { const submit = () => {
templateRef.value?.ctx.validate(1, (isValid: boolean, formData: any) => { templateRef.value?.ctx.validate(1, (isValid: boolean, formData: any) => {
if (isValid) { if (isValid) {
......
...@@ -30,14 +30,14 @@ export default defineConfig({ ...@@ -30,14 +30,14 @@ export default defineConfig({
}, },
proxy: { proxy: {
"/api": { "/api": {
// target: "http://localhost:8080", target: "http://localhost:8080",
target: "http://agent-backend.clouddev.hisense.com", // target: "http://agent-backend.clouddev.hisense.com",
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, "/api"), rewrite: (path) => path.replace(/^\/api/, "/api"),
}, },
"/ws": { "/ws": {
// target: "http://localhost:8080", target: "http://localhost:8080",
target: "http://agent-backend.clouddev.hisense.com", // target: "http://agent-backend.clouddev.hisense.com",
ws: true, // 启用WebSocket代理 ws: true, // 启用WebSocket代理
changeOrigin: true, changeOrigin: true,
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment