Commit efec36bd authored by ligaowei's avatar ligaowei

Revert "Merge branch 'develop_yxj' into 'main'"

This reverts merge request !2
parent b1367155
...@@ -8,11 +8,6 @@ import org.springframework.ai.chat.prompt.Prompt; ...@@ -8,11 +8,6 @@ import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import pangea.hiagent.agent.sse.UserSseService;
import pangea.hiagent.tool.impl.HisenseTripTool;
import pangea.hiagent.tool.impl.VisitorAppointmentTool;
import pangea.hiagent.web.service.AgentService;
import pangea.hiagent.web.service.InfoCollectorService;
import pangea.hiagent.workpanel.IWorkPanelDataCollector; import pangea.hiagent.workpanel.IWorkPanelDataCollector;
import pangea.hiagent.agent.service.ErrorHandlerService; import pangea.hiagent.agent.service.ErrorHandlerService;
import pangea.hiagent.agent.service.TokenConsumerWithCompletion; import pangea.hiagent.agent.service.TokenConsumerWithCompletion;
...@@ -67,14 +62,7 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -67,14 +62,7 @@ public class DefaultReactExecutor implements ReactExecutor {
@Autowired @Autowired
private ErrorHandlerService errorHandlerService; private ErrorHandlerService errorHandlerService;
@Autowired
private InfoCollectorService infoCollectorService;
@Autowired
private AgentService agentService;
private final AgentToolManager agentToolManager; private final AgentToolManager agentToolManager;
@Autowired
private UserSseService userSseService;
public DefaultReactExecutor(AgentToolManager agentToolManager) { public DefaultReactExecutor(AgentToolManager agentToolManager) {
this.agentToolManager = agentToolManager; this.agentToolManager = agentToolManager;
...@@ -191,7 +179,7 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -191,7 +179,7 @@ public class DefaultReactExecutor implements ReactExecutor {
memoryService.getHistoryMessages(sessionId, historyLength); memoryService.getHistoryMessages(sessionId, historyLength);
// 添加历史消息到Prompt // 添加历史消息到Prompt
// messages.addAll(historyMessages); messages.addAll(historyMessages);
// 将当前用户消息添加到内存中,以便下次对话使用 // 将当前用户消息添加到内存中,以便下次对话使用
memoryService.addUserMessageToMemory(sessionId, userInput); memoryService.addUserMessageToMemory(sessionId, userInput);
...@@ -246,11 +234,10 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -246,11 +234,10 @@ public class DefaultReactExecutor implements ReactExecutor {
// 构建Prompt,包含历史对话记录 // 构建Prompt,包含历史对话记录
Prompt prompt = buildPromptWithHistory(DEFAULT_SYSTEM_PROMPT, userInput, agent); Prompt prompt = buildPromptWithHistory(DEFAULT_SYSTEM_PROMPT, userInput, agent);
VisitorAppointmentTool hisenseTripTool = new VisitorAppointmentTool(agentService,infoCollectorService,userSseService);
hisenseTripTool.initialize();
// 订阅流式响应 // 订阅流式响应
chatClient.prompt(prompt) chatClient.prompt(prompt)
.tools(hisenseTripTool) .tools(agentTools.toArray())
.stream() .stream()
.chatResponse() .chatResponse()
.subscribe( .subscribe(
......
...@@ -121,7 +121,6 @@ public class AgentChatService { ...@@ -121,7 +121,6 @@ public class AgentChatService {
// 创建 SSE emitter // 创建 SSE emitter
SseEmitter emitter = workPanelSseService.createEmitter(); SseEmitter emitter = workPanelSseService.createEmitter();
workPanelSseService.registerEmitter("worker1", emitter);
// 将userId设为final以在Lambda表达式中使用 // 将userId设为final以在Lambda表达式中使用
final String finalUserId = userId; final String finalUserId = userId;
......
...@@ -552,12 +552,4 @@ public class UserSseService { ...@@ -552,12 +552,4 @@ 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);
}
} }
\ 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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
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.tool.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.WaitForSelectorState;
import com.microsoft.playwright.options.WaitUntilState;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import pangea.hiagent.common.utils.Contants;
import pangea.hiagent.model.Agent;
import pangea.hiagent.web.service.AgentService;
import pangea.hiagent.web.service.InfoCollectorService;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Slf4j
@Component
public class HisenseTripTool {
public static final String pageId = "tripApply";
// SSO用户名(从配置文件读取)
private String ssoToken;
// SSO密码(从配置文件读取)
private String ldapToken;
private String tripToken;
// Playwright实例
private Playwright playwright;
// 浏览器实例
private Browser browser;
// 共享的浏览器上下文,用于保持登录状态
private BrowserContext sharedContext;
// 上次登录时间
private long lastLoginTime = 0;
private AgentService agentService;
private InfoCollectorService infoCollectorService;
// 登录状态有效期(毫秒),设置为30分钟
private static final long LOGIN_VALIDITY_PERIOD = 30 * 60 * 1000;
public HisenseTripTool(AgentService agentService, InfoCollectorService infoCollectorService) {
this.agentService = agentService;
this.infoCollectorService = infoCollectorService;
this.ssoToken = "d10bc61aa4e00dcc6f08de64ca42012814fdbcee9b88aa977f7fb07d3a4018f4";
this.ldapToken = "AAECAzY5NDRBNTQ1Njk0NTRFMDV5b3V4aWFvamlaLv+jUGNEEORN24GLIC3OlqcCdw==";
this.tripToken = "c88c2f09a20140e190357ebb68f27e35";
}
@PostConstruct
public void initialize() {
try {
if(StringUtils.isEmpty(tripToken)){
}
log.info("正在初始化海信SSO认证工具的Playwright...");
this.playwright = Playwright.create();
// 使用chromium浏览器,无头模式(headless=true),适合服务器运行
// 可根据需要修改为有头模式(headless=false)用于调试
this.browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(true));
// 初始化共享上下文
this.sharedContext = browser.newContext();
// 检查是否已有有效的登录会话
Cookie ssoTokenCookie = new Cookie("ssoLoginToken", ssoToken);
ssoTokenCookie.setDomain(".hisense.com");
ssoTokenCookie.setPath("/");
Cookie ldapTokenCookie = new Cookie("LtpaToken", ldapToken);
ldapTokenCookie.setDomain(".hisense.com");
ldapTokenCookie.setPath("/");
Cookie tripCookie = new Cookie("FCC_SESSION", tripToken);
tripCookie.setDomain("trip.hisense.com");
tripCookie.setPath("/");
List<Cookie> cookies = new ArrayList<>();
cookies.add(ssoTokenCookie);
cookies.add(ldapTokenCookie);
cookies.add(tripCookie);
sharedContext.addCookies(cookies);
log.info("海信SSO认证工具的Playwright初始化成功");
} catch (Exception e) {
log.error("海信SSO认证工具的Playwright初始化失败: ", e);
}
}
/**
* 销毁Playwright资源
*/
@PreDestroy
public void destroy() {
try {
if (sharedContext != null) {
sharedContext.close();
log.info("海信SSO认证工具的共享浏览器上下文已关闭");
}
if (browser != null) {
browser.close();
log.info("海信SSO认证工具的浏览器实例已关闭");
}
if (playwright != null) {
playwright.close();
log.info("海信SSO认证工具的Playwright实例已关闭");
}
} catch (Exception e) {
log.error("海信SSO认证工具的Playwright资源释放失败: ", e);
}
}
@Tool(description = "提交出差申请")
public String apply(){
String tripApplyUrl = "https://trip.hisense.com/fcc/fcapply/ccsqd/add.html?state=1";
long startTime = System.currentTimeMillis();
Page page = null;
try {
page = sharedContext.newPage();
page.setDefaultTimeout(60*1000);
// 访问业务系统页面
log.info("正在访问业务系统页面: {}", tripApplyUrl);
page.navigate(tripApplyUrl, new Page.NavigateOptions().setWaitUntil(WaitUntilState.NETWORKIDLE));
// 检查是否重定向到了SSO登录页面
String currentUrl = page.url();
log.info("当前页面URL: {}", currentUrl);
// 如果页面尚未导航到业务系统URL,则导航到该URL
if (!page.url().equals(tripApplyUrl) && !page.url().startsWith(tripApplyUrl)) {
log.info("正在访问业务系统页面: {}", tripApplyUrl);
page.navigate(tripApplyUrl, new Page.NavigateOptions().setWaitUntil(WaitUntilState.NETWORKIDLE));
}
sharedContext.tracing().start(new Tracing.StartOptions()
.setScreenshots(true)
.setSnapshots(true)
.setSources(true));
JSONArray jsonArray = infoCollectorService.getInfo(pageId);
Locator.WaitForOptions waitForOptions = new Locator.WaitForOptions();
waitForOptions.setTimeout(5*1000);
Locator.WaitForOptions waitUntilOptions = new Locator.WaitForOptions();
waitUntilOptions.setState(WaitForSelectorState.ATTACHED);
page.locator("[id^='layui-layer-shade']").waitFor(waitUntilOptions);
final Locator btnLocator = page.locator("[class^='jsAgreed btn']");
page.waitForCondition(() ->
(boolean) btnLocator.evaluate("el => !el.classList.contains('active')")
);
btnLocator.click();
for(int i=0;i<jsonArray.size();i++){
log.info("index {} ",i);
JSONObject obj = jsonArray.getJSONObject(i);
log.info("json {}",obj);
String fieldName = obj.getString("field_name");
String fieldValue = infoCollectorService.getValue(fieldName).toString();
String locator = obj.getString("locator");
JSONObject attributes = obj.getJSONObject("attributes");
if(attributes.containsKey("class")){
Locator.FillOptions fillOptions = new Locator.FillOptions();
fillOptions.setForce(true);
List<Locator> list = page.locator("[class*='"+attributes.getString("class")+"']").all();
for(Locator loc : list){
String tagName = (String) loc.evaluate("el => el.tagName");
log.info("标签类型: {}" , tagName);
if(tagName.toLowerCase().contains("div") || tagName.toLowerCase().contains("span") || (tagName.compareToIgnoreCase("a")==0)){
continue;
}
loc.fill(fieldValue,fillOptions);
page.mouse().click(0, 0);
this.saveScreenShot(page.screenshot(),obj.getString("field_name"));
}
//page.locator(locator).and(page.locator("[class*='"+attributes.getString("class")+"']")).fill(fieldValue,fillOptions);
}else{
page.locator(locator).fill(fieldValue);
page.mouse().click(0, 0);
this.saveScreenShot(page.screenshot(),obj.getString("field_name"));
}
}
page.mouse().click(0, 0);
page.locator("[class='jsCostDepart validate[required]']").click();
page.locator(".layui-layer[type='dialog']").waitFor(new Locator.WaitForOptions()
.setState(WaitForSelectorState.DETACHED));
saveScreenShot(page.screenshot(),"list");
List<Locator> itemList = page.locator("[class^='zdyTable-checkItem jsZdyTableChecks']").all();
itemList.get(1).click();
saveScreenShot(page.screenshot(),"choose");
page.locator("[class='btn jsCheckData']").click();
// page.onDialog(dialog -> dialog.accept());
saveScreenShot(page.screenshot(),"filled");
page.locator("[class*='btn theme jsSave']").click(new Locator.ClickOptions().setForce(true));
Locator locator = page.locator("[class*='layui-layer layui-layer-dialog layer-anim']");
locator.waitFor(waitForOptions);
//page.locator("[id^='layui-layer-shade']").click();
saveScreenShot(page.screenshot(),"confirm");
page.locator("[class^='layui-layer-btn0']").click(new Locator.ClickOptions().setForce(true));
saveScreenShot(page.screenshot(),"submit");
//.page.locator("text=操作成功").waitFor();
Locator successMsg = page.locator("text=操作成功");
successMsg.waitFor();
successMsg.waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.HIDDEN).setTimeout(10000));
saveScreenShot(page.screenshot(),"saved");
return "申请已暂存,请进入信息提交";
} catch (Exception e) {
long endTime = System.currentTimeMillis();
String errorMsg = "获取海信出差申请页面内容失败: " + e.getMessage();
log.error("获取海信海信出差申请内容失败,耗时: {} ms", endTime - startTime, e);
return errorMsg;
} finally {
// 释放页面资源
if (page != null) {
try {
saveScreenShot(page.screenshot(),"closed");
page.close();
} catch (Exception e) {
log.warn("关闭页面时发生异常: {}", e.getMessage());
}
}
sharedContext.tracing().stop(new Tracing.StopOptions()
.setPath(Paths.get("trace.zip")));
}
}
private void saveScreenShot(byte[] bytes,String suffix){
// 生成一个唯一的文件名,防止覆盖
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String fileName = "screenshot_" + timestamp +"_"+suffix+ ".png";
try {
// Paths.get() 指定存储路径,默认在项目根目录
Files.write(Paths.get(fileName), bytes);
log.info("截图已保存至: {}" , fileName);
} catch (IOException e) {
log.info("保存截图失败: " + e.getMessage());
e.printStackTrace();
}
}
@Tool(description = "存储用户提交的出差申请信息")
public String applyInfoSave(@ToolParam(required = true) JSONObject infos){
infos.keySet().forEach(key -> {
infoCollectorService.saveValue(key,infos.get(key));
});
Set<String> keys = infoCollectorService.findLackInfo();
StringBuilder sb = new StringBuilder();
if(keys.isEmpty()){
sb.append("用户已提交全部数据,提示用户提交申请");
}else{
sb.append("用户还有以下信息未提交:");
sb.append("\n");
for(String key:keys){
sb.append(key);
sb.append("\n");
}
sb.append("提示用户继续以json格式提交信息");
}
return sb.toString();
}
/**
* 工具方法:获取海信差旅平台出差申请的网页内容
*
* @return 页面内容(HTML文本)
*/
@Tool(description = "获取出差申请必要信息")
public String getTripApplyNecessaryInfo() {
String tripApplyUrl = "https://trip.hisense.com/fcc/fcapply/ccsqd/add.html?state=1";
long startTime = System.currentTimeMillis();
Page page = null;
try {
page = sharedContext.newPage();
page.setDefaultTimeout(60*1000);
// 访问业务系统页面
log.info("正在访问业务系统页面: {}", tripApplyUrl);
page.navigate(tripApplyUrl, new Page.NavigateOptions().setWaitUntil(WaitUntilState.NETWORKIDLE));
// 检查是否重定向到了SSO登录页面
String currentUrl = page.url();
log.info("当前页面URL: {}", currentUrl);
// 如果页面尚未导航到业务系统URL,则导航到该URL
if (!page.url().equals(tripApplyUrl) && !page.url().startsWith(tripApplyUrl)) {
log.info("正在访问业务系统页面: {}", tripApplyUrl);
page.navigate(tripApplyUrl, new Page.NavigateOptions().setWaitUntil(WaitUntilState.NETWORKIDLE));
}
JSONArray jsonArray = null;
if (!infoCollectorService.exists(pageId)) {
JSONArray tmp = getLocators(page.locator("body").innerHTML());
jsonArray = tmp;
infoCollectorService.register(pageId,tmp);
}else{
jsonArray = infoCollectorService.getInfo(pageId);
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("用户需要提供的信息包括:");
stringBuilder.append("\n");
for(int i=0;i<jsonArray.size();i++){
log.info("index {} ",i);
JSONObject obj = jsonArray.getJSONObject(i);
stringBuilder.append(obj.getString("field_name"));
stringBuilder.append("\n");
}
stringBuilder.append("提示用户以json格式提交信息");
// 提取页面内容
String content = stringBuilder.toString();
long endTime = System.currentTimeMillis();
log.info("成功获取海信出差申请页面内容,耗时: {} ms", endTime - startTime);
log.info("用户需要提交的信息包括:{}",content);
return content;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
String errorMsg = "获取海信出差申请页面内容失败: " + e.getMessage();
log.error("获取海信海信出差申请内容失败,耗时: {} ms", endTime - startTime, e);
return errorMsg;
} finally {
// 释放页面资源
if (page != null) {
try {
page.close();
} catch (Exception e) {
log.warn("关闭页面时发生异常: {}", e.getMessage());
}
}
}
}
private JSONArray getLocators(String html) throws MalformedURLException {
System.out.println("----------------------------------------");
System.out.println(html);
System.out.println("----------------------------------------");
Agent agent = agentService.getAgent("agent-7");
ChatClient chatClient = ChatClient.builder(agentService.getChatModelForAgent(agent)).build();
String systemPrompt = "你是一个网页解析助手,你可以将html {htmlData} 中所有的必填项的名称标题和对应的html元素的定位表达式,attributes完整的解析出来;无论元素是否动态生成,都需要解析;以{jsonSchema}格式告诉我;定位表达式可以被playwright直接用来定位元素";
JSONArray response = chatClient.prompt().user(u -> u.text(systemPrompt).param("htmlData", html).param("jsonSchema", Contants.LOCATOR_SCHEMA))
.call()
.entity(JSONArray.class);
// 获取响应文本
// String responseText = response.getResult().getOutput().getText();
log.info(response.toJSONString());
return response;
}
}
package pangea.hiagent.tool.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.WaitUntilState;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import pangea.hiagent.agent.sse.UserSseService;
import pangea.hiagent.common.utils.Contants;
import pangea.hiagent.common.utils.HybridUniqueLongGenerator;
import pangea.hiagent.common.utils.InputCodeGenerator;
import pangea.hiagent.model.Agent;
import pangea.hiagent.web.service.AgentService;
import pangea.hiagent.web.service.InfoCollectorService;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Slf4j
public class VisitorAppointmentTool {
public static final String pageId = "visitorAppointment";
private static final String destUrl = "https://vrms-proxy.hisense.com/ipark/hichat/#/Liteapp";
private String ssoToken;
// SSO密码(从配置文件读取)
private String ldapToken;
private String iparkToken;
// Playwright实例
private Playwright playwright;
// 浏览器实例
private Browser browser;
// 共享的浏览器上下文,用于保持登录状态
private BrowserContext sharedContext;
// 上次登录时间
private long lastLoginTime = 0;
private AgentService agentService;
private InfoCollectorService infoCollectorService;
private UserSseService userSseService;
// 登录状态有效期(毫秒),设置为30分钟
private static final long LOGIN_VALIDITY_PERIOD = 30 * 60 * 1000;
public VisitorAppointmentTool(AgentService agentService, InfoCollectorService infoCollectorService, UserSseService userSseService) {
this.agentService = agentService;
this.infoCollectorService = infoCollectorService;
this.ssoToken = "d10bc61aa4e00dcc6f08de64ca42012814fdbcee9b88aa977f7fb07d3a4018f4";
this.ldapToken = "AAECAzY5NDRBNTQ1Njk0NTRFMDV5b3V4aWFvamlaLv+jUGNEEORN24GLIC3OlqcCdw==";
this.iparkToken = "208a55e4-59bc-400b-971f-a4ed3a5379ca";
this.userSseService = userSseService;
}
@PostConstruct
public void initialize() {
try {
log.info("正在初始化海信SSO认证工具的Playwright...");
this.playwright = Playwright.create();
// 使用chromium浏览器,无头模式(headless=true),适合服务器运行
// 可根据需要修改为有头模式(headless=false)用于调试
this.browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(true));
// 初始化共享上下文
this.sharedContext = browser.newContext();
// 检查是否已有有效的登录会话
Cookie ssoTokenCookie = new Cookie("ssoLoginToken", ssoToken);
ssoTokenCookie.setDomain(".hisense.com");
ssoTokenCookie.setPath("/");
Cookie ldapTokenCookie = new Cookie("LtpaToken", ldapToken);
ldapTokenCookie.setDomain(".hisense.com");
ldapTokenCookie.setPath("/");
Cookie tripCookie = new Cookie("jwtToken", iparkToken);
tripCookie.setDomain("trip.hisense.com");
tripCookie.setPath("/");
// String userName= SecurityContextHolder.getContext().getAuthentication().getName();
List<Cookie> cookies = new ArrayList<>();
cookies.add(ssoTokenCookie);
cookies.add(ldapTokenCookie);
cookies.add(tripCookie);
sharedContext.addCookies(cookies);
log.info("海信SSO认证工具的Playwright初始化成功");
} catch (Exception e) {
log.error("海信SSO认证工具的Playwright初始化失败: ", e);
}
}
/**
* 销毁Playwright资源
*/
@PreDestroy
public void destroy() {
try {
if (sharedContext != null) {
sharedContext.close();
log.info("海信SSO认证工具的共享浏览器上下文已关闭");
}
if (browser != null) {
browser.close();
log.info("海信SSO认证工具的浏览器实例已关闭");
}
if (playwright != null) {
playwright.close();
log.info("海信SSO认证工具的Playwright实例已关闭");
}
} catch (Exception e) {
log.error("海信SSO认证工具的Playwright资源释放失败: ", e);
}
}
@Tool(description = "存储用户提交的访客预约申请信息", returnDirect = true)
public String applyInfoSave(@ToolParam(required = true) JSONObject infos) {
log.info("applyInfoSave(infos={})", infos);
infos.keySet().forEach(key -> {
infoCollectorService.saveValue(key, infos.get(key));
});
Set<String> keys = infoCollectorService.findLackInfo();
StringBuilder sb = new StringBuilder();
if (keys.isEmpty()) {
sb.append("用户已提交全部数据,提示用户提交申请");
} else {
sb.append("用户还有以下信息未提交:");
sb.append("\n");
for (String key : keys) {
sb.append(key);
sb.append("\n");
}
sb.append("提示用户继续以json格式提交信息");
}
return sb.toString();
}
/**
* 工具方法:获取海信访客预约申请的网页内容
*
* @return 页面内容(HTML文本)
*/
@Tool(description = "获取访客预约申请必要信息")
public String getTripApplyNecessaryInfo() {
long startTime = System.currentTimeMillis();
Page page = null;
try {
page = sharedContext.newPage();
page.setDefaultTimeout(2 * 60 * 1000);
// 访问业务系统页面
log.info("正在访问业务系统页面: {}", destUrl);
String faviconUrl = "https://vrms-proxy.hisense.com/favicon.ico";
page.navigate(faviconUrl);
page.evaluate("() => sessionStorage.setItem('Access-Token', '208a55e4-59bc-400b-971f-a4ed3a5379ca')");
page.navigate(destUrl, new Page.NavigateOptions().setWaitUntil(WaitUntilState.NETWORKIDLE));
// 检查是否重定向到了SSO登录页面
String currentUrl = page.url();
log.info("当前页面URL: {}", currentUrl);
// 如果页面尚未导航到业务系统URL,则导航到该URL
if (!page.url().equals(destUrl) && !page.url().startsWith(destUrl)) {
log.info("正在访问业务系统页面: {}", destUrl);
page.navigate(destUrl, new Page.NavigateOptions().setWaitUntil(WaitUntilState.NETWORKIDLE));
}
JSONArray jsonArray = null;
if (!infoCollectorService.exists(pageId)) {
JSONArray tmp = getLocators(page.locator("body").innerHTML());
jsonArray = tmp;
infoCollectorService.register(pageId, tmp);
} else {
jsonArray = infoCollectorService.getInfo(pageId);
}
StringBuilder stringBuilder = new StringBuilder();
// stringBuilder.append("用户需要提供的信息包括:");
// stringBuilder.append("\n");
// for(int i=0;i<jsonArray.size();i++){
// log.info("index {} ",i);
// JSONObject obj = jsonArray.getJSONObject(i);
// stringBuilder.append(obj.getString("field_name"));
// stringBuilder.append("\n");
// }
// stringBuilder.append("提示用户以json格式提交信息");
//stringBuilder.append("从FORMSTART开始到FORMEND结束是要返回给用户的数据,直接展示给用户就可以。FORM START:");
JSONObject jsonObject = new JSONObject();
jsonObject.put("FORM", jsonArray);
stringBuilder.append(jsonArray.toJSONString());
//stringBuilder.append("FORM END");
// 提取页面内容
String content = stringBuilder.toString();
SseEmitter sseEmitter = userSseService.getEmitter("worker1");
JSONObject formMessage = generateJson(jsonArray);
log.info("Send Form Message {}", formMessage);
sseEmitter.send(SseEmitter.event().name("form").data(formMessage));
long endTime = System.currentTimeMillis();
log.info("成功获取海信出差申请页面内容,耗时: {} ms", endTime - startTime);
log.info("用户需要提交的信息包括:{}", content);
return "获取成功";
} catch (Exception e) {
long endTime = System.currentTimeMillis();
String errorMsg = "获取海信出差申请页面内容失败: " + e.getMessage();
log.error("获取海信海信出差申请内容失败,耗时: {} ms", endTime - startTime, e);
return errorMsg;
} finally {
// 释放页面资源
if (page != null) {
try {
page.close();
} catch (Exception e) {
log.warn("关闭页面时发生异常: {}", e.getMessage());
}
}
}
}
public JSONObject generateJson(JSONArray jsonArray) {
JSONArray components = new JSONArray();
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject tmp = jsonArray.getJSONObject(i);
JSONObject obj = buildComponents(tmp);
tmp.put("code", obj.getJSONObject("props").getString("name"));
components.add(obj);
}
JSONObject obj = new JSONObject();
obj.put("coms", components);
return obj;
}
private JSONObject buildComponents(JSONObject source) {
JSONObject obj = new JSONObject();
obj.put("key", HybridUniqueLongGenerator.generateUnique13DigitNumber());
obj.put("name", "输入框");
String name = InputCodeGenerator.generateUniqueInputCode("INPUT_");
if (source.getString("field_name").contains("日期")) {
obj.put("code", "HiDatePicker");
name = InputCodeGenerator.generateUniqueInputCode("DATE_");
} else {
obj.put("code", "HiInput");
}
JSONObject props = new JSONObject();
props.put("title", source.getString("field_name"));
props.put("status", "default");
props.put("name", name);
obj.put("props", props);
return obj;
}
private JSONArray getLocators(String html) throws MalformedURLException {
// System.out.println("----------------------------------------");
// System.out.println(html);
//
// System.out.println("----------------------------------------");
Agent agent = agentService.getAgent("agent-7");
ChatClient chatClient = ChatClient.builder(agentService.getChatModelForAgent(agent)).build();
String systemPrompt = "你是一个网页解析助手,你可以将html {htmlData} 中所有的必填项的名称标题和对应的html元素的唯一的定位表达式,html标签类型,attributes完整的解析出来;无论元素是否动态生成,都需要解析;以{jsonSchema}格式告诉我;定位表达式要求可以直接被playwright使用,准确并且可以定位唯一元素";
JSONArray response = chatClient.prompt().user(u -> u.text(systemPrompt).param("htmlData", html).param("jsonSchema", Contants.LOCATOR_SCHEMA))
.call()
.entity(JSONArray.class);
// 获取响应文本
// String responseText = response.getResult().getOutput().getText();
log.info(response.toJSONString());
return response;
}
}
package pangea.hiagent.web.service;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import pangea.hiagent.tool.impl.HisenseTripTool;
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 saveValue(String key, Object value) {
log.info("key {} value {}",key,value);
values.put(key, value);
}
public Object getValue(String key) {
return values.get(key);
}
public Set<String> findLackInfo() {
Set<String> valueKeys = values.keySet();
Set<String> allKeys = infos.get(HisenseTripTool.pageId).stream().map(t -> ((JSONObject) t).getString("field_name")).collect(Collectors.toSet());
allKeys.removeAll(valueKeys);
log.info("lack keys {}", allKeys);
return allKeys;
}
}
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: false
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,7 +4,7 @@ spring: ...@@ -4,7 +4,7 @@ spring:
# 配置文件激活 # 配置文件激活
profiles: profiles:
active: test active: dev
# 启用懒加载初始化 # 启用懒加载初始化
main: main:
......
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