diff --git a/modules/.DS_Store b/modules/.DS_Store
index ec17aff0..99b5b4c9 100644
Binary files a/modules/.DS_Store and b/modules/.DS_Store differ
diff --git a/modules/ai/.DS_Store b/modules/ai/.DS_Store
index 051cdf09..5c39d0b6 100644
Binary files a/modules/ai/.DS_Store and b/modules/ai/.DS_Store differ
diff --git a/modules/ai/pom.xml b/modules/ai/pom.xml
index 161a588e..7bbf2d2a 100644
--- a/modules/ai/pom.xml
+++ b/modules/ai/pom.xml
@@ -79,14 +79,6 @@
release-V4-2.3.0
-
-
- com.google.protobuf
- protobuf-java
- 3.25.2
- provided
-
-
org.springframework.boot
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/moonshot/MoonshotController.java b/modules/ai/src/main/java/com/bytedesk/ai/moonshot/MoonshotController.java
index b6fe135c..ef668ee9 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/moonshot/MoonshotController.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/moonshot/MoonshotController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-19 21:16:26
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-28 09:29:01
+ * @LastEditTime: 2024-08-28 09:29:23
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/Robot.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/Robot.java
index 09073e43..22b8e10b 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/Robot.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/Robot.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-03-22 16:16:26
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:10:25
+ * @LastEditTime: 2024-08-30 08:34:31
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,15 +14,10 @@
*/
package com.bytedesk.ai.robot;
-import org.hibernate.annotations.JdbcTypeCode;
-import org.hibernate.type.SqlTypes;
-
import com.bytedesk.ai.settings.RobotServiceSettings;
import com.bytedesk.core.base.BaseEntity;
import com.bytedesk.core.constant.AvatarConsts;
-import com.bytedesk.core.constant.BdConstants;
import com.bytedesk.core.constant.I18Consts;
-import com.bytedesk.core.constant.TypeConsts;
import com.bytedesk.core.enums.LevelEnum;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
@@ -53,7 +48,8 @@ public class Robot extends BaseEntity {
private static final long serialVersionUID = 1L;
- private String nickname;
+ @Builder.Default
+ private String nickname = I18Consts.I18N_ROBOT_NICKNAME;
@Builder.Default
private String avatar = AvatarConsts.DEFAULT_AVATAR_URL;
@@ -69,6 +65,10 @@ public class Robot extends BaseEntity {
@Builder.Default
private RobotLlm llm = new RobotLlm();
+ @Embedded
+ @Builder.Default
+ private RobotFlow flow = new RobotFlow();
+
// 如果未匹配到关键词,默认回复内容
@Builder.Default
private String defaultReply = I18Consts.I18N_ROBOT_REPLY;
@@ -86,10 +86,10 @@ public class Robot extends BaseEntity {
// private LevelEnum level = LevelEnum.ORGNIZATION;
private String level = LevelEnum.ORGNIZATION.name();
- @Builder.Default
- @Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
- @JdbcTypeCode(SqlTypes.JSON)
- private String flow = BdConstants.EMPTY_JSON_STRING;
+ // @Builder.Default
+ // @Column(columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ // @JdbcTypeCode(SqlTypes.JSON)
+ // private String flow = BdConstants.EMPTY_JSON_STRING;
@Builder.Default
private boolean published = false;
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotEventListener.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotEventListener.java
index 3e71ce14..041c9d2e 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotEventListener.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-12 07:17:13
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-19 16:51:10
+ * @LastEditTime: 2024-09-07 18:49:09
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -15,9 +15,7 @@
package com.bytedesk.ai.robot;
import java.io.IOException;
-import java.util.Arrays;
import java.util.Date;
-import java.util.List;
import java.util.Optional;
import org.springframework.context.event.EventListener;
@@ -30,19 +28,22 @@
import com.alibaba.fastjson2.JSONObject;
import com.bytedesk.ai.utils.ConvertAiUtils;
import com.bytedesk.ai.zhipuai.ZhipuaiService;
-import com.bytedesk.core.config.BytedeskEventPublisher;
+import com.bytedesk.core.config.BytedeskProperties;
import com.bytedesk.core.constant.BdConstants;
-import com.bytedesk.core.constant.I18Consts;
import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.message.MessageCache;
import com.bytedesk.core.message.MessageExtra;
import com.bytedesk.core.message.MessageJsonEvent;
import com.bytedesk.core.message.MessageProtoEvent;
import com.bytedesk.core.message.MessageProtobuf;
import com.bytedesk.core.message.MessageStatusEnum;
import com.bytedesk.core.message.MessageTypeEnum;
+import com.bytedesk.core.message.MessageUtils;
import com.bytedesk.core.rbac.organization.Organization;
import com.bytedesk.core.rbac.organization.OrganizationCreateEvent;
import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.rbac.user.UserTypeEnum;
+import com.bytedesk.core.redis.pubsub.RedisPubsubService;
import com.bytedesk.core.socket.protobuf.model.MessageProto;
import com.bytedesk.core.thread.ThreadCreateEvent;
import com.bytedesk.core.thread.ThreadProtobuf;
@@ -65,41 +66,57 @@ public class RobotEventListener {
private final ZhipuaiService zhipuaiService;
- private final BytedeskEventPublisher bytedeskEventPublisher;
+ // private final BytedeskEventPublisher bytedeskEventPublisher;
private final UidUtils uidUtils;
private final ThreadService threadService;
+ private final RedisPubsubService redisPubsubService;
+
+ private final BytedeskProperties bytedeskProperties;
+
+ private final MessageCache messageCache;
+
@Order(5)
@EventListener
public void onOrganizationCreateEvent(OrganizationCreateEvent event) {
Organization organization = (Organization) event.getSource();
- // User user = organization.getUser();
String orgUid = organization.getUid();
log.info("robot - organization created: {}", organization.getName());
//
- List faqUids = Arrays.asList(
- orgUid + I18Consts.I18N_FAQ_DEMO_TITLE_1,
- orgUid + I18Consts.I18N_FAQ_DEMO_TITLE_2);
- //
- // List quickButtonUids = Arrays.asList(
- // orgUid + I18Consts.I18N_QUICK_BUTTON_DEMO_TITLE_1,
- // orgUid + I18Consts.I18N_QUICK_BUTTON_DEMO_TITLE_2);
- //
- RobotRequest robotRequest = RobotRequest.builder()
- .nickname(I18Consts.I18N_ROBOT_NICKNAME)
- .description(I18Consts.I18N_ROBOT_DESCRIPTION)
- // .kb(kb)
- // .llm(llm)
- .build();
- robotRequest.setType(RobotTypeEnum.SERVICE.name());
- robotRequest.setOrgUid(orgUid);
- //
- robotRequest.getServiceSettings().setFaqUids(faqUids);
- robotRequest.getServiceSettings().setQuickFaqUids(faqUids);
+ robotService.createDefaultRobot(orgUid, uidUtils.getCacheSerialUid());
+ robotService.createDefaultAgentAsistantRobot(orgUid);
+ }
+
+ //
+ @EventListener
+ public void onThreadCreateEvent(ThreadCreateEvent event) {
+ Thread thread = event.getThread();
+ log.info("robot ThreadCreateEvent: {}", thread.getUid());
//
- robotService.create(robotRequest);
+ if (thread.getType().equals(ThreadTypeEnum.ROBOT.name())
+ && thread.getAgent().equals(BdConstants.EMPTY_JSON_STRING)) {
+ // 机器人会话:org/robot/{robot_uid}/{visitor_uid}
+ String topic = thread.getTopic();
+ //
+ String[] splits = topic.split("/");
+ if (splits.length < 4) {
+ throw new RuntimeException("robot topic format error");
+ }
+ String robotUid = splits[2];
+ Optional robotOptional = robotService.findByUid(robotUid);
+ if (robotOptional.isPresent()) {
+ Robot robot = robotOptional.get();
+ // 更新机器人配置+大模型相关信息
+ thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ robot.getServiceSettings())));
+ thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ //
+ threadService.save(thread);
+ }
+ }
+
}
@EventListener
@@ -132,11 +149,15 @@ public void onMessageProtoEvent(MessageProtoEvent event) {
private void processMessage(String messageJson) {
MessageProtobuf messageProtobuf = JSON.parseObject(messageJson, MessageProtobuf.class);
MessageTypeEnum messageType = messageProtobuf.getType();
+ if (messageType.equals(MessageTypeEnum.STREAM)) {
+ // ai回答暂不处理
+ return;
+ }
String query = messageProtobuf.getContent();
log.info("robot processMessage {}", query);
//
- ThreadProtobuf thread = messageProtobuf.getThread();
- if (thread == null) {
+ ThreadProtobuf threadProtobuf = messageProtobuf.getThread();
+ if (threadProtobuf == null) {
throw new RuntimeException("thread is null");
}
// 仅针对文本类型自动回复
@@ -144,12 +165,11 @@ private void processMessage(String messageJson) {
return;
}
//
- String threadTopic = thread.getTopic();
- log.info("robot threadTopic {}, thread.type {}", threadTopic, thread.getType());
- if (thread.getType().equals(ThreadTypeEnum.ROBOT)) {
+ String threadTopic = threadProtobuf.getTopic();
+ if (threadProtobuf.getType().equals(ThreadTypeEnum.ROBOT)) {
+ log.info("robot threadTopic {}, thread.type {}", threadTopic, threadProtobuf.getType());
// 机器人回复
- log.info("robot thread reply");
- // 机器人客服消息 org/robot/default_robot_uid/1420995827073219
+ // 机器人客服消息 org/robot/df_robot_uid/1420995827073219
String[] splits = threadTopic.split("/");
if (splits.length < 4) {
throw new RuntimeException("robot topic format error");
@@ -167,64 +187,63 @@ private void processMessage(String messageJson) {
user.setNickname(robot.getNickname());
user.setAvatar(robot.getAvatar());
//
- MessageExtra extra = MessageExtra.builder()
- // .isAutoReply(true)
- // .autoReplyType(autoReplySettings.getAutoReplyType().name())
- .orgUid(robot.getOrgUid())
- .build();
- //
- MessageProtobuf message = MessageProtobuf.builder()
- .uid(uidUtils.getCacheSerialUid())
- .status(MessageStatusEnum.SUCCESS)
- .thread(thread)
- .user(user)
- .client(ClientEnum.SYSTEM_AUTO)
- .extra(JSONObject.toJSONString(extra))
- .createdAt(new Date())
- .build();
- // 返回一个输入中消息,让访客端显示输入中
- MessageProtobuf clonedMessage = SerializationUtils.clone(message);
- clonedMessage.setUid(uidUtils.getCacheSerialUid());
- clonedMessage.setType(MessageTypeEnum.PROCESSING);
- String json = JSON.toJSONString(clonedMessage);
- bytedeskEventPublisher.publishMessageJsonEvent(json);
- // 绑定知识库
- zhipuaiService.sendWsRobotMessage(query, robot.getKbUid(), robot, message);
+ sendRobotReply(threadProtobuf, user, query, robot);
} else {
log.error("robot not found");
}
+ } else if (threadProtobuf.getType().equals(ThreadTypeEnum.AGENT)
+ || threadProtobuf.getType().equals(ThreadTypeEnum.WORKGROUP)) {
+ log.info("robot threadTopic {}, thread.type {}", threadTopic, threadProtobuf.getType());
+ Thread thread = threadService.findByTopic(threadTopic)
+ .orElseThrow(() -> new RuntimeException("thread with topic " + threadTopic + " not found"));
+ UserProtobuf agent = JSON.parseObject(thread.getAgent(), UserProtobuf.class);
+ // 当前会话为机器人接待,而且是访客发送的消息
+ if (agent.getType().equals(UserTypeEnum.ROBOT.name())
+ && messageProtobuf.getUser().getType().equals(UserTypeEnum.VISITOR.name())) {
+ // 机器人回复
+ log.info("robot thread reply");
+ Robot robot = robotService.findByUid(agent.getUid())
+ .orElseThrow(() -> new RuntimeException("robot " + agent.getUid() + " not found"));
+ //
+ sendRobotReply(threadProtobuf, agent, query, robot);
+ }
}
-
}
+
- //
- @EventListener
- public void onThreadCreateEvent(ThreadCreateEvent event) {
- Thread thread = event.getThread();
- log.info("robot ThreadCreateEvent: {}", thread.getUid());
+ private void sendRobotReply(ThreadProtobuf threadProtobuf, UserProtobuf user, String query, Robot robot) {
//
- if (thread.getType().equals(ThreadTypeEnum.ROBOT)
- && thread.getAgent().equals(BdConstants.EMPTY_JSON_STRING)) {
- // 机器人会话:org/robot/{robot_uid}/{visitor_uid}
- String topic = thread.getTopic();
- //
- String[] splits = topic.split("/");
- if (splits.length < 4) {
- throw new RuntimeException("robot topic format error");
- }
- String robotUid = splits[2];
- Optional robotOptional = robotService.findByUid(robotUid);
- if (robotOptional.isPresent()) {
- Robot robot = robotOptional.get();
- // 更新机器人配置+大模型相关信息
- thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
- robot.getServiceSettings())));
- thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
- //
- threadService.save(thread);
- }
- }
+ String threadTopic = threadProtobuf.getTopic();
+ MessageExtra extra = MessageUtils.getMessageExtra(robot.getOrgUid());
+ //
+ String messageUid = uidUtils.getCacheSerialUid();
+ MessageProtobuf message = MessageProtobuf.builder()
+ .uid(messageUid)
+ .status(MessageStatusEnum.SUCCESS)
+ .thread(threadProtobuf)
+ .user(user)
+ .client(ClientEnum.SYSTEM_AUTO)
+ .extra(JSONObject.toJSONString(extra))
+ .createdAt(new Date())
+ .build();
+ // 返回一个输入中消息,让访客端显示输入中
+ MessageProtobuf clonedMessage = SerializationUtils.clone(message);
+ clonedMessage.setUid(uidUtils.getCacheSerialUid());
+ clonedMessage.setType(MessageTypeEnum.PROCESSING);
+ //
+ MessageUtils.notifyUser(clonedMessage);
+ // 知识库
+ if (bytedeskProperties.getJavaai()) {
+ zhipuaiService.sendWsRobotMessage(query, robot.getKbUid(), robot, message);
+ }
+ // 通知python ai模块处理回答
+ if (bytedeskProperties.getPythonai()) {
+ messageCache.put(messageUid, message);
+ redisPubsubService.sendQuestionMessage(messageUid, threadTopic, robot.getKbUid(),
+ query);
+ }
}
+
}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotFlow.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotFlow.java
new file mode 100644
index 00000000..bc992ffc
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotFlow.java
@@ -0,0 +1,67 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 15:13:07
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 15:59:48
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot;
+
+import org.hibernate.annotations.JdbcTypeCode;
+import org.hibernate.type.SqlTypes;
+
+import com.bytedesk.core.constant.BdConstants;
+import com.bytedesk.core.constant.TypeConsts;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Embeddable
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class RobotFlow {
+
+ @Builder.Default
+ @Column(name = "flow_groups", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String groups = BdConstants.EMPTY_JSON_STRING;
+
+ @Builder.Default
+ @Column(name = "flow_events", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String events = BdConstants.EMPTY_JSON_STRING;
+
+ @Builder.Default
+ @Column(name = "flow_variables", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String variables = BdConstants.EMPTY_JSON_STRING;
+
+ @Builder.Default
+ @Column(name = "flow_edges", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String edges = BdConstants.EMPTY_JSON_STRING;
+
+ @Builder.Default
+ @Column(name = "flow_themes", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String themes = BdConstants.EMPTY_JSON_STRING;
+
+ @Builder.Default
+ @Column(name = "flow_settings", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
+ @JdbcTypeCode(SqlTypes.JSON)
+ private String settings = BdConstants.EMPTY_JSON_STRING;
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotProtobuf.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotProtobuf.java
index 57083214..10d61fb6 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotProtobuf.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotProtobuf.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-06 11:28:30
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-05 18:15:57
+ * @LastEditTime: 2024-09-07 08:29:21
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRequest.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRequest.java
index 2adcd34e..2d6ef0f7 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRequest.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotRequest.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-03-22 16:45:07
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-18 13:15:05
+ * @LastEditTime: 2024-08-30 09:20:16
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -32,7 +32,8 @@
@EqualsAndHashCode(callSuper = false)
public class RobotRequest extends BaseRequest {
- private String nickname;
+ @Builder.Default
+ private String nickname = I18Consts.I18N_ROBOT_NICKNAME;
@Builder.Default
private String avatar = AvatarConsts.DEFAULT_AVATAR_URL;
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotService.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotService.java
index ffb78af1..4c781c70 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotService.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-03-22 16:44:41
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:42:14
+ * @LastEditTime: 2024-08-30 08:43:36
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -336,36 +336,60 @@ private Boolean existsByNicknameAndOrgUidAndDeleted(String name, String orgUid)
return robotRepository.existsByNicknameAndOrgUidAndDeleted(name, orgUid, false);
}
- public void initData() {
-
- if (robotRepository.count() > 0) {
- return;
- }
- //
- String orgUid = BdConstants.DEFAULT_ORGANIZATION_UID;
+ public RobotResponse createDefaultRobot(String orgUid, String uid) {
List faqUids = Arrays.asList(
orgUid + I18Consts.I18N_FAQ_DEMO_TITLE_1,
orgUid + I18Consts.I18N_FAQ_DEMO_TITLE_2);
//
- // Kb kb = kbService.getKb(I18Consts.I18N_ROBOT_NICKNAME,
- // BdConstants.DEFAULT_ORGANIZATION_UID);
- // RobotLlm llm = RobotLlm.builder().build();
RobotRequest robotRequest = RobotRequest.builder()
.nickname(I18Consts.I18N_ROBOT_NICKNAME)
- // .description(I18Consts.I18N_ROBOT_DESCRIPTION)
- // .kb(kb)
- // .llm(llm)
.build();
- robotRequest.setUid(BdConstants.DEFAULT_ROBOT_UID);
+ robotRequest.setUid(uid);
robotRequest.setType(RobotTypeEnum.SERVICE.name());
- robotRequest.setOrgUid(BdConstants.DEFAULT_ORGANIZATION_UID);
+ robotRequest.setOrgUid(orgUid);
//
robotRequest.getServiceSettings().setFaqUids(faqUids);
robotRequest.getServiceSettings().setQuickFaqUids(faqUids);
//
- create(robotRequest);
- // save(robotRequest);
+ return create(robotRequest);
+ }
+ public RobotResponse createDefaultAgentAsistantRobot(String orgUid) {
+ //
+ RobotRequest robotRequest = RobotRequest.builder()
+ .nickname(I18Consts.I18N_ROBOT_AGENT_ASISTANT_NICKNAME)
+ .build();
+ robotRequest.setType(RobotTypeEnum.AGENT_ASSISTANT.name());
+ robotRequest.setOrgUid(orgUid);
+ //
+ return create(robotRequest);
+ }
+
+ public void initData() {
+
+ if (robotRepository.count() > 0) {
+ return;
+ }
+ //
+ String orgUid = BdConstants.DEFAULT_ORGANIZATION_UID;
+ createDefaultRobot(orgUid, BdConstants.DEFAULT_ROBOT_UID);
+ createDefaultAgentAsistantRobot(orgUid);
+ // //
+ // List faqUids = Arrays.asList(
+ // orgUid + I18Consts.I18N_FAQ_DEMO_TITLE_1,
+ // orgUid + I18Consts.I18N_FAQ_DEMO_TITLE_2);
+ // //
+ // RobotRequest robotRequest = RobotRequest.builder()
+ // .nickname(I18Consts.I18N_ROBOT_NICKNAME)
+ // .build();
+ // robotRequest.setUid(BdConstants.DEFAULT_ROBOT_UID);
+ // robotRequest.setType(RobotTypeEnum.SERVICE.name());
+ // robotRequest.setOrgUid(orgUid);
+ // //
+ // robotRequest.getServiceSettings().setFaqUids(faqUids);
+ // robotRequest.getServiceSettings().setQuickFaqUids(faqUids);
+ // //
+ // create(robotRequest);
}
}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotSpecification.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotSpecification.java
index fc5efb76..63098be7 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotSpecification.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotSpecification.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-12 09:07:53
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-12 09:08:56
+ * @LastEditTime: 2024-08-30 09:25:30
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -18,6 +18,7 @@
import java.util.List;
import org.springframework.data.jpa.domain.Specification;
+import org.springframework.util.StringUtils;
import com.bytedesk.core.base.BaseSpecification;
@@ -32,7 +33,15 @@ public static Specification search(RobotRequest request) {
return (root, query, criteriaBuilder) -> {
List predicates = new ArrayList<>();
predicates.addAll(getBasicPredicates(root, criteriaBuilder, request.getOrgUid()));
+ //
+ if (StringUtils.hasText(request.getType())) {
+ predicates.add(criteriaBuilder.equal(root.get("type"), request.getType()));
+ }
//
+ // if (request.getPublished()) {
+ // predicates.add(criteriaBuilder.equal(root.get("published"), request.getPublished()));
+ // }
+ //
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotTypeEnum.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotTypeEnum.java
index 78fd8f61..1a89dd9d 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotTypeEnum.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/RobotTypeEnum.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-05 11:10:06
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-24 14:11:39
+ * @LastEditTime: 2024-08-30 11:07:42
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -18,7 +18,10 @@ public enum RobotTypeEnum {
SERVICE, // 客服机器人
MARKETING, // 营销机器人
KNOWLEDGEBASE, // 知识库机器人
- QA; // 问答机器人
+ QA, // 问答机器人
+ AGENT_ASSISTANT, // 客服助理机器人
+ TICKET_ASSISTANT, // 工单助手机器人
+ ;
// 根据整型值查找对应的枚举常量
public static RobotTypeEnum fromValue(String value) {
@@ -27,17 +30,7 @@ public static RobotTypeEnum fromValue(String value) {
return type;
}
}
- throw new IllegalArgumentException("No enum constant with value " + value);
- }
-
- public static RobotTypeEnum fromString(String typeStr) {
- // 使用try-catch处理可能的异常
- try {
- return RobotTypeEnum.valueOf(typeStr);
- } catch (IllegalArgumentException e) {
- // 处理错误,例如记录日志或抛出更具体的异常
- throw new IllegalArgumentException("Invalid robot type: " + typeStr, e);
- }
+ throw new IllegalArgumentException("No RobotTypeEnum constant with value " + value);
}
}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/Flow.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/Flow.java
new file mode 100644
index 00000000..9b67c149
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/Flow.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 17:15:26
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 17:15:29
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class Flow {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowAnswer.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowAnswer.java
new file mode 100644
index 00000000..18769e18
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowAnswer.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 15:26:10
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 15:26:13
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowAnswer {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowBlock.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowBlock.java
new file mode 100644
index 00000000..b9f76b8e
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowBlock.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 16:14:02
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 16:14:05
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowBlock {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowCoordinate.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowCoordinate.java
new file mode 100644
index 00000000..c275600a
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowCoordinate.java
@@ -0,0 +1,29 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 16:08:40
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 16:08:42
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class FlowCoordinate {
+ private int x;
+ private int y;
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowEdge.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowEdge.java
new file mode 100644
index 00000000..3e349c54
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowEdge.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 16:07:09
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 16:07:12
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowEdge {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowEvent.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowEvent.java
new file mode 100644
index 00000000..3d23a198
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowEvent.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 16:06:44
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 16:06:46
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowEvent {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowGroup.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowGroup.java
new file mode 100644
index 00000000..712affff
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowGroup.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 15:25:34
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 15:25:37
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowGroup {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowResult.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowResult.java
new file mode 100644
index 00000000..b4189431
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowResult.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 15:28:51
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 15:28:54
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowResult {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowSetting.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowSetting.java
new file mode 100644
index 00000000..2c6a9b11
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowSetting.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 16:07:35
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 16:07:38
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowSetting {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowTheme.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowTheme.java
new file mode 100644
index 00000000..4a153401
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowTheme.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 16:07:23
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 16:07:26
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowTheme {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowVariable.java b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowVariable.java
new file mode 100644
index 00000000..4f3e691f
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot/flow/FlowVariable.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 16:06:57
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 16:07:00
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot.flow;
+
+public class FlowVariable {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot_message/RobotMessageEventListener.java b/modules/ai/src/main/java/com/bytedesk/ai/robot_message/RobotMessageEventListener.java
index e5b58944..50e15943 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/robot_message/RobotMessageEventListener.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot_message/RobotMessageEventListener.java
@@ -1,26 +1,36 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-07-05 11:06:26
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 16:15:58
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
package com.bytedesk.ai.robot_message;
-import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.message.MessageCreateEvent;
-import com.bytedesk.core.message.MessageUpdateEvent;
-
// import lombok.extern.slf4j.Slf4j;
// @Slf4j
@Component
public class RobotMessageEventListener {
- @EventListener
- public void onMessageCreateEvent(MessageCreateEvent event) {
- // log.info("robot message unread create event: " + event);
+ // @EventListener
+ // public void onMessageCreateEvent(MessageCreateEvent event) {
+ // // log.info("robot message unread create event: " + event);
- }
+ // }
- @EventListener
- public void onMessageUpdateEvent(MessageUpdateEvent event) {
- // log.info("robot message unread update event: " + event);
- }
+ // @EventListener
+ // public void onMessageUpdateEvent(MessageUpdateEvent event) {
+ // // log.info("robot message unread update event: " + event);
+ // }
}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/robot_published/RobotPublished.java b/modules/ai/src/main/java/com/bytedesk/ai/robot_published/RobotPublished.java
new file mode 100644
index 00000000..b0d18502
--- /dev/null
+++ b/modules/ai/src/main/java/com/bytedesk/ai/robot_published/RobotPublished.java
@@ -0,0 +1,21 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-06 17:33:55
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 17:35:08
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.ai.robot_published;
+
+// 线上已发布机器人,与Robot表的区别是RobotPublished表,机器人发布后,机器人信息不会变动
+// Robot内主要用于本地测试,此表用于存储线上Robot信息
+public class RobotPublished {
+
+}
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/utils/ConvertAiUtils.java b/modules/ai/src/main/java/com/bytedesk/ai/utils/ConvertAiUtils.java
index d5c15be9..7ca95ecb 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/utils/ConvertAiUtils.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/utils/ConvertAiUtils.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-06 11:28:01
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-05 18:16:49
+ * @LastEditTime: 2024-09-07 10:25:24
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -20,6 +20,8 @@
import com.bytedesk.ai.robot.RobotResponse;
import com.bytedesk.ai.robot.RobotProtobuf;
import com.bytedesk.ai.settings.RobotServiceSettings;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.rbac.user.UserTypeEnum;
import com.bytedesk.kbase.service_settings.ServiceSettingsResponseVisitor;
public class ConvertAiUtils {
@@ -35,6 +37,12 @@ public static RobotProtobuf convertToRobotProtobuf(Robot entity) {
return new ModelMapper().map(entity, RobotProtobuf.class);
}
+ public static UserProtobuf convertToUserProtobuf(Robot entity) {
+ UserProtobuf userProtobuf = new ModelMapper().map(entity, UserProtobuf.class);
+ userProtobuf.setType(UserTypeEnum.ROBOT.name());
+ return userProtobuf;
+ }
+
public static ServiceSettingsResponseVisitor convertToServiceSettingsResponseVisitor(
RobotServiceSettings serviceSettings) {
return new ModelMapper().map(serviceSettings, ServiceSettingsResponseVisitor.class);
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/zhipuai/ZhipuAiConfig.java b/modules/ai/src/main/java/com/bytedesk/ai/zhipuai/ZhipuAiConfig.java
index d6e31d4c..9bc2c10e 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/zhipuai/ZhipuAiConfig.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/zhipuai/ZhipuAiConfig.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-31 10:53:11
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 11:17:19
+ * @LastEditTime: 2024-08-30 14:40:31
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -41,6 +41,9 @@ public class ZhipuaiConfig {
@Value("${spring.ai.zhipuai.api-key}")
String zhiPuAiApiKey;
+ @Value("${spring.ai.zhipuai.chat.options.model}")
+ String zhiPuAiApiModel;
+
@Bean
ZhiPuAiApi zhipuaiApi() {
return new ZhiPuAiApi(zhiPuAiApiKey);
@@ -65,7 +68,8 @@ ZhiPuAiEmbeddingModel zhipuaiEmbeddingModel() {
@Primary
ZhiPuAiChatModel zhipuaiChatModel() {
return new ZhiPuAiChatModel(zhipuaiApi(), ZhiPuAiChatOptions.builder()
- .withModel(ZhiPuAiApi.ChatModel.GLM_3_Turbo.getValue())
+ // .withModel(ZhiPuAiApi.ChatModel.GLM_3_Turbo.getValue())
+ .withModel(zhiPuAiApiModel)
.withTemperature(0.4f)
.withMaxTokens(200)
.build());
diff --git a/modules/ai/src/main/java/com/bytedesk/ai/zhipuai/ZhipuAiService.java b/modules/ai/src/main/java/com/bytedesk/ai/zhipuai/ZhipuAiService.java
index 8b34cc57..3f4fa392 100644
--- a/modules/ai/src/main/java/com/bytedesk/ai/zhipuai/ZhipuAiService.java
+++ b/modules/ai/src/main/java/com/bytedesk/ai/zhipuai/ZhipuAiService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-05 15:39:22
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:42:25
+ * @LastEditTime: 2024-08-30 14:41:52
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -29,7 +29,6 @@
import com.bytedesk.ai.robot.RobotMessage;
import com.bytedesk.ai.robot.RobotProtobuf;
import com.bytedesk.ai.robot.RobotTypeEnum;
-import com.bytedesk.core.config.BytedeskEventPublisher;
import com.bytedesk.core.enums.ClientEnum;
import com.bytedesk.core.thread.Thread;
import com.bytedesk.core.message.Message;
@@ -37,6 +36,7 @@
import com.bytedesk.core.message.MessageService;
import com.bytedesk.core.message.MessageStatusEnum;
import com.bytedesk.core.message.MessageTypeEnum;
+import com.bytedesk.core.message.MessageUtils;
import com.bytedesk.core.rbac.user.UserProtobuf;
import com.bytedesk.core.thread.ThreadService;
import com.bytedesk.core.uid.UidUtils;
@@ -44,7 +44,7 @@
import com.bytedesk.core.utils.JsonResultCodeEnum;
import com.bytedesk.kbase.upload.UploadVectorStore;
import com.zhipu.oapi.ClientV4;
-import com.zhipu.oapi.Constants;
+// import com.zhipu.oapi.Constants;
import com.zhipu.oapi.service.v4.model.ChatCompletionRequest;
import com.zhipu.oapi.service.v4.model.ChatMessage;
import com.zhipu.oapi.service.v4.model.ChatMessageAccumulator;
@@ -68,6 +68,8 @@
@AllArgsConstructor
public class ZhipuaiService {
+ private final ZhipuaiConfig zhipuaiConfig;
+
private final ClientV4 client;
private final UidUtils uidUtils;
@@ -80,7 +82,7 @@ public class ZhipuaiService {
private final UploadVectorStore uploadVectorStore;
- private final BytedeskEventPublisher bytedeskEventPublisher;
+ // private final BytedeskEventPublisher bytedeskEventPublisher;
private final String PROMPT_BLUEPRINT = """
根据提供的文档信息回答问题,文档信息如下:
@@ -226,8 +228,8 @@ public void getSseAnswer(String uid, String sid, String question, SseEmitter emi
public void sendWsRobotMessage(String query, String kbUid, Robot robot, MessageProtobuf messageProtobuf) {
//
String prompt = robot.getLlm().getPrompt();
- if (robot.getType().equals(RobotTypeEnum.SERVICE)
- || robot.getType().equals(RobotTypeEnum.KNOWLEDGEBASE)) {
+ if (robot.getType().equals(RobotTypeEnum.SERVICE.name())
+ || robot.getType().equals(RobotTypeEnum.KNOWLEDGEBASE.name())) {
List contentList = uploadVectorStore.searchText(query, kbUid);
String context = String.join("\n", contentList);
prompt = PROMPT_BLUEPRINT.replace("{context}", context).replace("{query}", query);
@@ -242,7 +244,8 @@ public void sendWsRobotMessage(String query, String kbUid, Robot robot, MessageP
//
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
// 模型名称
- .model(Constants.ModelChatGLM3TURBO)
+ .model(zhipuaiConfig.zhiPuAiApiModel)
+ // .model(Constants.ModelChatGLM3TURBO)
// .model(robotSimple.getLlm().getModel())
// .temperature(robotSimple.getLlm().getTemperature())
// .topP(robotSimple.getLlm().getTopP())
@@ -272,8 +275,9 @@ public void sendWsRobotMessage(String query, String kbUid, Robot robot, MessageP
messageProtobuf.setType(MessageTypeEnum.STREAM);
messageProtobuf.setContent(answerContent);
//
- String json = JSON.toJSONString(messageProtobuf);
- bytedeskEventPublisher.publishMessageJsonEvent(json);
+ MessageUtils.notifyUser(messageProtobuf);
+ // String json = JSON.toJSONString(messageProtobuf);
+ // bytedeskEventPublisher.publishMessageJsonEvent(json);
}
}
}
@@ -315,7 +319,8 @@ public void sendWsAutoReply(String query, String kbUid, MessageProtobuf messageP
//
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
// 模型名称
- .model(Constants.ModelChatGLM3TURBO)
+ .model(zhipuaiConfig.zhiPuAiApiModel)
+ // .model(Constants.ModelChatGLM3TURBO)
// .model(robotSimple.getLlm().getModel())
// .temperature(robotSimple.getLlm().getTemperature())
// .topP(robotSimple.getLlm().getTopP())
@@ -345,8 +350,9 @@ public void sendWsAutoReply(String query, String kbUid, MessageProtobuf messageP
messageProtobuf.setType(MessageTypeEnum.STREAM);
messageProtobuf.setContent(answerContent);
//
- String json = JSON.toJSONString(messageProtobuf);
- bytedeskEventPublisher.publishMessageJsonEvent(json);
+ MessageUtils.notifyUser(messageProtobuf);
+ // String json = JSON.toJSONString(messageProtobuf);
+ // bytedeskEventPublisher.publishMessageJsonEvent(json);
}
}
}
diff --git a/modules/core/.DS_Store b/modules/core/.DS_Store
index 1ca22e5a..025dee03 100644
Binary files a/modules/core/.DS_Store and b/modules/core/.DS_Store differ
diff --git a/modules/core/pom.xml b/modules/core/pom.xml
index 196afc46..79b1abb9 100644
--- a/modules/core/pom.xml
+++ b/modules/core/pom.xml
@@ -59,14 +59,6 @@
provided
-
-
- com.google.protobuf
- protobuf-java
- 3.25.2
- provided
-
-
com.google.protobuf
diff --git a/modules/core/src/main/java/com/bytedesk/core/action/ActionEventListener.java b/modules/core/src/main/java/com/bytedesk/core/action/ActionEventListener.java
index a0db585e..3fd45208 100644
--- a/modules/core/src/main/java/com/bytedesk/core/action/ActionEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/action/ActionEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-23 10:00:44
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-23 21:39:23
+ * @LastEditTime: 2024-08-30 18:01:24
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,7 +14,7 @@
*/
package com.bytedesk.core.action;
-import org.springframework.context.event.EventListener;
+// import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import lombok.AllArgsConstructor;
@@ -25,11 +25,11 @@
@AllArgsConstructor
public class ActionEventListener {
- @EventListener
- public void onActionCreateEvent(ActionCreateEvent event) {
- log.info("onActionCreateEvent Received event: {}", event.getAction().getTitle());
- // do something
- // Action action = event.getAction();
- }
+ // @EventListener
+ // public void onActionCreateEvent(ActionCreateEvent event) {
+ // log.info("onActionCreateEvent Received event: {}", event.getAction().getTitle());
+ // // do something
+ // // Action action = event.getAction();
+ // }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/base/BaseResponse.java b/modules/core/src/main/java/com/bytedesk/core/base/BaseResponse.java
index 1cf463c4..cbbf30e7 100644
--- a/modules/core/src/main/java/com/bytedesk/core/base/BaseResponse.java
+++ b/modules/core/src/main/java/com/bytedesk/core/base/BaseResponse.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-24 23:17:29
+ * @LastEditTime: 2024-09-07 12:03:50
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -23,5 +23,9 @@ public abstract class BaseResponse implements Serializable {
private String uid;
+ // 导致报错: com.google.protobuf.InvalidProtocolBufferException: Cannot find field: orgUid
+ // in message Thread
+ // at com.bytedesk.core.utils.MessageConvertUtils.toProtoBean(MessageConvertUtils.java:19)
+ // at com.bytedesk.core.message.MessageEventListener.onMessageJsonEvent(MessageEventListener.java:55)
// private String orgUid;
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/config/BytedeskEventPublisher.java b/modules/core/src/main/java/com/bytedesk/core/config/BytedeskEventPublisher.java
index 2f250df0..7dfe3615 100644
--- a/modules/core/src/main/java/com/bytedesk/core/config/BytedeskEventPublisher.java
+++ b/modules/core/src/main/java/com/bytedesk/core/config/BytedeskEventPublisher.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-23 14:42:58
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-27 12:58:18
+ * @LastEditTime: 2024-09-09 16:26:19
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -24,10 +24,12 @@
// import com.bytedesk.core.cache.CaffeineCacheGroupEvent;
import com.bytedesk.core.message.MessageProtoEvent;
import com.bytedesk.core.message.MessageUpdateEvent;
+import com.bytedesk.core.quartz.event.QuartzFiveMinEvent;
+import com.bytedesk.core.quartz.event.QuartzFiveSecondEvent;
+import com.bytedesk.core.quartz.event.QuartzOneMinEvent;
import com.bytedesk.core.message.Message;
import com.bytedesk.core.message.MessageCreateEvent;
import com.bytedesk.core.message.MessageJsonEvent;
-import com.bytedesk.core.quartz.QuartzFiveSecondEvent;
import com.bytedesk.core.rbac.organization.Organization;
import com.bytedesk.core.rbac.organization.OrganizationCreateEvent;
import com.bytedesk.core.rbac.user.User;
@@ -96,6 +98,14 @@ public void publishQuartzFiveSecondEvent() {
applicationEventPublisher.publishEvent(new QuartzFiveSecondEvent(this));
}
+ public void publishQuartzFiveMinEvent() {
+ applicationEventPublisher.publishEvent(new QuartzFiveMinEvent(this));
+ }
+
+ public void publishQuartzOneMinEvent() {
+ applicationEventPublisher.publishEvent(new QuartzOneMinEvent(this));
+ }
+
public void publishMqttConnectedEvent(String clientId) {
applicationEventPublisher.publishEvent(new MqttConnectedEvent(this, clientId));
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/config/BytedeskProperties.java b/modules/core/src/main/java/com/bytedesk/core/config/BytedeskProperties.java
index 4f13b0d4..8d9974dc 100644
--- a/modules/core/src/main/java/com/bytedesk/core/config/BytedeskProperties.java
+++ b/modules/core/src/main/java/com/bytedesk/core/config/BytedeskProperties.java
@@ -44,13 +44,14 @@ public class BytedeskProperties {
private List mobileWhitelist = new ArrayList<>();
private String mobileCode;
-
private String organizationName;
-
private String organizationCode;
-
// private String timezone;
+ // ai
+ private Boolean javaai;
+ private Boolean pythonai;
+
// cors
private String corsAllowedOrigins;
diff --git a/modules/core/src/main/java/com/bytedesk/core/constant/BdConstants.java b/modules/core/src/main/java/com/bytedesk/core/constant/BdConstants.java
index cf45b316..a1a827d0 100644
--- a/modules/core/src/main/java/com/bytedesk/core/constant/BdConstants.java
+++ b/modules/core/src/main/java/com/bytedesk/core/constant/BdConstants.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-02 21:48:19
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-27 14:52:26
+ * @LastEditTime: 2024-09-08 08:43:47
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -44,6 +44,7 @@ private BdConstants() {
public static final String ACTION_LOGIN_USERNAME = "loginWithUsernamePassword";
public static final String ACTION_LOGIN_MOBILE = "loginWithMobileCode";
public static final String ACTION_LOGIN_EMAIL = "loginWithEmailCode";
+ public static final String ACTION_LOGIN_SCAN = "loginWithScan";
//
diff --git a/modules/core/src/main/java/com/bytedesk/core/constant/I18Consts.java b/modules/core/src/main/java/com/bytedesk/core/constant/I18Consts.java
index dc86e517..7dd164a5 100644
--- a/modules/core/src/main/java/com/bytedesk/core/constant/I18Consts.java
+++ b/modules/core/src/main/java/com/bytedesk/core/constant/I18Consts.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-26 22:25:47
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-23 21:37:48
+ * @LastEditTime: 2024-08-31 21:57:58
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -63,7 +63,9 @@ private I18Consts() {
//
public static final String I18N_ROBOT_NICKNAME = I18N_PREFIX + "robot.nickname";
public static final String I18N_ROBOT_DESCRIPTION = I18N_PREFIX + "robot.description";
- // public static final String I18N_ROBOT_LLM_PROMPT = I18N_PREFIX + "llm.prompt";
+ public static final String I18N_ROBOT_AGENT_ASISTANT_NICKNAME = I18N_PREFIX + "robot.agent.asistant.nickname";
+ // public static final String I18N_ROBOT_LLM_PROMPT = I18N_PREFIX +
+ // "llm.prompt";
public static final String I18N_ROBOT_LLM_PROMPT = "角色:资深客服专家; 背景:有专业客服经验,对教育、电商、金融领域有深刻理解; 任务:根据上下文中提到的内容,对提出的问题给出有用、详细、礼貌的回答; 要求:1. 解决客户提出的问题,2. 安抚客户情绪,3. 提升客户满意度";
public static final String I18N_ROBOT_REPLY = I18N_PREFIX + "robot.reply";
public static final String I18N_ROBOT_NO_REPLY = I18N_PREFIX + "robot.noreply";
@@ -104,10 +106,10 @@ private I18Consts() {
public static final String I18N_QUICK_BUTTON_DEMO_CONTENT_1 = I18N_PREFIX + "quick.button.demo.content.1";
public static final String I18N_QUICK_BUTTON_DEMO_TITLE_2 = I18N_PREFIX + "quick.button.demo.title.2";
public static final String I18N_QUICK_BUTTON_DEMO_CONTENT_2 = I18N_PREFIX + "quick.button.demo.content.2";
- //
+ //
public static final String I18N_GROUP_NAME = I18N_PREFIX + "group.name";
public static final String I18N_GROUP_DESCRIPTION = I18N_PREFIX + "group.description";
- //
+ //
public static final String I18N_NOTICE_TITLE = I18N_PREFIX + "notice.title";
public static final String I18N_NOTICE_TYPE = I18N_PREFIX + "notice.type";
public static final String I18N_NOTICE_CONTENT = I18N_PREFIX + "notice.content";
@@ -115,6 +117,11 @@ private I18Consts() {
public static final String I18N_NOTICE_IPLOCATION = I18N_PREFIX + "notice.ipLocation";
public static final String I18N_NOTICE_URL = I18N_PREFIX + "notice.url";
public static final String I18N_NOTICE_EXTRA = I18N_PREFIX + "notice.extra";
- //
+ //
+ public static final String I18N_NOTICE_PARSE_FILE_SUCCESS = I18N_PREFIX + "notice.parse.file.success";
+ public static final String I18N_NOTICE_PARSE_FILE_ERROR = I18N_PREFIX + "notice.parse.file.error";
+ //
+ public static final String I18N_AUTO_CLOSED = I18N_PREFIX + "auto.closed";
+ public static final String I18N_AGENT_CLOSED = I18N_PREFIX + "agent.closed";
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/constant/TypeConsts.java b/modules/core/src/main/java/com/bytedesk/core/constant/TypeConsts.java
index b474cdf0..a3709dfe 100644
--- a/modules/core/src/main/java/com/bytedesk/core/constant/TypeConsts.java
+++ b/modules/core/src/main/java/com/bytedesk/core/constant/TypeConsts.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-06 11:14:06
+ * @LastEditTime: 2024-09-09 14:39:12
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -22,6 +22,7 @@ private TypeConsts() {
public static final String TYPE_SYSTEM = "system";
public static final String TYPE_MOBILE = "mobile";
public static final String TYPE_EMAIL = "email";
+ public static final String TYPE_SCAN = "scan";
//
public static final String SUPER = "SUPER";
@@ -92,12 +93,6 @@ private TypeConsts() {
//
public static final String COLUMN_TYPE_TEXT = "TEXT"; // length = 65534
public static final String COLUMN_TYPE_JSON = "json"; //
- // public static final String COLUMN_NAME_TYPE = "by_type";
- // public static final String COLUMN_NAME_USER = "by_user";
-
- //
- // public static final String ROBOT_TYPE_SERVICE = "service";
-
//
// public static final String ACTION_TYPE_FAILED = "failed";
// public static final String ACTION_TYPE_LOG = "log";
diff --git a/modules/core/src/main/java/com/bytedesk/core/enums/ClientEnum.java b/modules/core/src/main/java/com/bytedesk/core/enums/ClientEnum.java
index a22369d8..87d7ec9c 100644
--- a/modules/core/src/main/java/com/bytedesk/core/enums/ClientEnum.java
+++ b/modules/core/src/main/java/com/bytedesk/core/enums/ClientEnum.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-25 13:07:20
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-27 21:59:49
+ * @LastEditTime: 2024-08-28 11:12:16
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -19,6 +19,8 @@ public enum ClientEnum {
SYSTEM_AUTO, // auto reply
SYSTEM_BOT, // robot reply
WEB,
+ WEB_VISITOR, // 访客端
+ WEB_ADMIN, // 管理端
H5,
IOS,
ANDROID,
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/Message.java b/modules/core/src/main/java/com/bytedesk/core/message/Message.java
index aa4d6247..daa65b87 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/Message.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/Message.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:14:34
+ * @LastEditTime: 2024-08-30 16:33:09
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -47,13 +47,13 @@ public class Message extends BaseEntity {
// @Enumerated(EnumType.STRING) // 默认使用int类型表示,如果为了可读性,可以转换为使用字符串存储
@Column(name = "message_type", nullable = false)
// private MessageTypeEnum type = MessageTypeEnum.TEXT;
- private String type = MessageTypeEnum.TEXT.name();
+ private String type = MessageTypeEnum.TEXT.name();
// 仅对一对一/客服/技能组聊天有效,表示对方是否已读。群聊无效
@Builder.Default
// @Enumerated(EnumType.STRING)
// private MessageStatusEnum status = MessageStatusEnum.SUCCESS;
- private String status = MessageStatusEnum.SUCCESS.name();
+ private String status = MessageStatusEnum.SUCCESS.name();
// 复杂类型可以使用json存储在此,通过type字段区分
@Column(columnDefinition = TypeConsts.COLUMN_TYPE_TEXT)
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageCache.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageCache.java
new file mode 100644
index 00000000..1f08004c
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageCache.java
@@ -0,0 +1,50 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-31 10:01:05
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-31 10:05:47
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.message;
+
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.stereotype.Component;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+
+import jakarta.annotation.PostConstruct;
+
+@Component
+public class MessageCache {
+
+ // 创建一个caffeinecache实例
+ private Cache messageCache;
+
+ @PostConstruct
+ public void init() {
+ // 初始化caffeinecache,设置缓存的最大大小、过期时间等参数
+ messageCache = Caffeine.newBuilder()
+ .maximumSize(10000) // 设置缓存的最大条目数
+ .expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存条目的过期时间
+ .build();
+ }
+
+ public void put(String key, MessageProtobuf message) {
+ messageCache.put(key, message);
+ }
+
+ public MessageProtobuf get(String key) {
+ return messageCache.getIfPresent(key);
+ }
+
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageCacheService.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageCacheService.java
deleted file mode 100644
index 3f43af62..00000000
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageCacheService.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-07-16 11:09:19
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-16 11:11:04
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.core.message;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import org.springframework.stereotype.Service;
-
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.CacheLoader;
-import com.github.benmanes.caffeine.cache.Caffeine;
-
-@Service
-public class MessageCacheService {
-
- // 假设我们使用"myList"作为缓存中的键
- String defaultPersistKey = "messageList";
-
- // 创建一个缓存实例,设置过期时间为5天
- Cache> messageCache = Caffeine.newBuilder()
- .expireAfterWrite(5, TimeUnit.DAYS)
- .build(new CacheLoader>() {
- @Override
- public List load(String key) throws Exception {
- // 当缓存中没有找到对应的键时,使用load方法初始化
- return new ArrayList<>();
- }
- });
-
- // 模拟 push 操作:向列表中添加元素
- public void pushForPersist(String messageJSON) {
- push(defaultPersistKey, messageJSON);
- }
-
- // 模拟 pop 操作:从列表中移除元素
- public List getListForPersist() {
- return getList(defaultPersistKey);
- }
-
- //
- // 模拟 push 操作:向列表中添加元素
- public void push(String listKey, String messageJSON) {
- List cachedList = messageCache.getIfPresent(listKey);
- if (cachedList == null) {
- // 如果缓存中没有找到对应的键,则使用load方法初始化
- cachedList = new ArrayList<>();
- }
- cachedList.add(messageJSON);
- messageCache.put(listKey, cachedList);
- }
-
- public void pushGroup(String groupUid, String messageJSON) {
- // bytedeskEventPublisher.publishCaffeineCacheGroupEvent(groupUid, messageJSON);
- }
-
- // 模拟 pop 操作:从列表中移除元素
- // public String getFirst(String listKey) {
- // List cachedList = cache.getIfPresent(listKey);
- // if (cachedList != null && !cachedList.isEmpty()) {
- // String messageJSON = cachedList.remove(0);
- // // log.info("Popped element: " + messageJSON);
- // return messageJSON;
- // }
- // return null;
- // }
-
- // public String removeMessage(String listKey, MessageProtobuf messageObject) {
- // List cachedList = messageCache.getIfPresent(listKey);
- // if (cachedList != null && !cachedList.isEmpty()) {
- // for (int i = 0; i < cachedList.size(); i++) {
- // String element = cachedList.get(i);
- // MessageProtobuf messageElement = JSON.parseObject(element, MessageProtobuf.class);
- // // 回执消息内容中存储被回执消息的uid
- // if (messageElement.getUid().equals(messageObject.getContent())) {
- // cachedList.remove(i);
- // }
- // }
- // messageCache.put(listKey, cachedList);
- // }
- // return null;
- // }
-
- public List getList(String listKey) {
- List cachedList = messageCache.getIfPresent(listKey);
- if (cachedList != null && !cachedList.isEmpty()) {
- // 只需要返回一次即可
- remove(listKey);
- return cachedList;
- }
- return null;
- }
-
- public void remove(String listKey) {
- messageCache.invalidate(listKey);
- }
-
- // public List getAllKeyList() {
- // List keys = new ArrayList<>();
- // messageCache.asMap().keySet().forEach(key -> {
- // if (key != defaultPersistKey) {
- // keys.add(key);
- // }
- // });
- // return keys;
- // }
-
- public void clear() {
- messageCache.invalidateAll();
- }
-
-}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageEntityListener.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageEntityListener.java
index 64771b4e..80df2cb2 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageEntityListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageEntityListener.java
@@ -30,7 +30,7 @@ public class MessageEntityListener {
@PostPersist
public void onPostPersist(Message message) {
- log.info("message MessageEntityListener: onPostPersist");
+ // log.info("message MessageEntityListener: onPostPersist");
Message clonedMessage = SerializationUtils.clone(message);
//
BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class);
@@ -39,7 +39,7 @@ public void onPostPersist(Message message) {
@PostUpdate
public void onPostUpdate(Message message) {
- log.info("message MessageEntityListener: onPostUpdate");
+ // log.info("message MessageEntityListener: onPostUpdate");
Message clonedMessage = SerializationUtils.clone(message);
//
BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class);
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageEventListener.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageEventListener.java
index 98fdc205..cf4b2e7d 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-27 16:02:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-01 07:26:46
+ * @LastEditTime: 2024-08-31 10:02:59
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -22,7 +22,7 @@
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
-import com.bytedesk.core.quartz.QuartzFiveSecondEvent;
+import com.bytedesk.core.quartz.event.QuartzFiveSecondEvent;
import com.bytedesk.core.socket.protobuf.model.MessageProto;
import com.bytedesk.core.thread.ThreadProtobuf;
import com.bytedesk.core.utils.MessageConvertUtils;
@@ -36,7 +36,7 @@
@AllArgsConstructor
public class MessageEventListener {
- private final MessageCacheService messageCacheService;
+ private final MessagePersistCache messagePersistCache;
private final MessagePersistService messagePersistService;
@@ -108,7 +108,7 @@ private String processMessage(String messageJson) {
String msgJson = JSON.toJSONString(messageProtobuf);
// 缓存消息,用于定期持久化到数据库
- messageCacheService.pushForPersist(msgJson);
+ messagePersistCache.pushForPersist(msgJson);
//
return msgJson;
}
@@ -116,7 +116,7 @@ private String processMessage(String messageJson) {
@EventListener
public void onQuartzFiveSecondEvent(QuartzFiveSecondEvent event) {
// log.info("message quartz five second event: " + event);
- List messageJsonList = messageCacheService.getListForPersist();
+ List messageJsonList = messagePersistCache.getListForPersist();
if (messageJsonList == null || messageJsonList.isEmpty()) {
return;
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageExtra.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageExtra.java
index cd1f1965..e24f570f 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageExtra.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageExtra.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-02 11:55:53
+ * @LastEditTime: 2024-09-10 14:14:09
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -25,10 +25,18 @@
@NoArgsConstructor
@Builder
public class MessageExtra {
-
+ // 自动回复
private Boolean isAutoReply;
-
private String autoReplyType;
+
+ // 机器人回复
+
+ // 翻译
+ private String translation;
+
+ // 引用
+ private String quotation;
+ // 企业id
private String orgUid;
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistCache.java b/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistCache.java
new file mode 100644
index 00000000..18f1347f
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistCache.java
@@ -0,0 +1,133 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-07-16 11:09:19
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-31 10:45:29
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.message;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.stereotype.Component;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.CacheLoader;
+import com.github.benmanes.caffeine.cache.Caffeine;
+
+import jakarta.annotation.PostConstruct;
+
+@Component
+public class MessagePersistCache {
+
+ // 假设我们使用"myList"作为缓存中的键
+ String defaultPersistKey = "messageList";
+
+ // 创建一个缓存实例,设置过期时间为5天
+ private Cache> messageCache;
+
+ @PostConstruct
+ public void init() {
+ // 初始化caffeinecache,设置缓存的最大大小、过期时间等参数
+ messageCache = Caffeine.newBuilder()
+ .expireAfterWrite(1, TimeUnit.DAYS)
+ .build(new CacheLoader>() {
+ @Override
+ public List load(String key) throws Exception {
+ // 当缓存中没有找到对应的键时,使用load方法初始化
+ return new ArrayList<>();
+ }
+ });
+ }
+
+
+ // 模拟 push 操作:向列表中添加元素
+ public void pushForPersist(String messageJSON) {
+ push(defaultPersistKey, messageJSON);
+ }
+
+ // 模拟 pop 操作:从列表中移除元素
+ public List getListForPersist() {
+ return getList(defaultPersistKey);
+ }
+
+ // 模拟 push 操作:向列表中添加元素
+ public void push(String listKey, String messageJSON) {
+ List cachedList = messageCache.getIfPresent(listKey);
+ if (cachedList == null) {
+ // 如果缓存中没有找到对应的键,则使用load方法初始化
+ cachedList = new ArrayList<>();
+ }
+ cachedList.add(messageJSON);
+ messageCache.put(listKey, cachedList);
+ }
+
+ public void pushGroup(String groupUid, String messageJSON) {
+ // bytedeskEventPublisher.publishCaffeineCacheGroupEvent(groupUid, messageJSON);
+ }
+
+ public List getList(String listKey) {
+ List cachedList = messageCache.getIfPresent(listKey);
+ if (cachedList != null && !cachedList.isEmpty()) {
+ // 只需要返回一次即可
+ remove(listKey);
+ return cachedList;
+ }
+ return null;
+ }
+
+ public void remove(String listKey) {
+ messageCache.invalidate(listKey);
+ }
+
+ // public List getAllKeyList() {
+ // List keys = new ArrayList<>();
+ // messageCache.asMap().keySet().forEach(key -> {
+ // if (key != defaultPersistKey) {
+ // keys.add(key);
+ // }
+ // });
+ // return keys;
+ // }
+
+ public void clear() {
+ messageCache.invalidateAll();
+ }
+
+ // 模拟 pop 操作:从列表中移除元素
+ // public String getFirst(String listKey) {
+ // List cachedList = messageCache.getIfPresent(listKey);
+ // if (cachedList != null && !cachedList.isEmpty()) {
+ // String messageJSON = cachedList.remove(0);
+ // // log.info("Popped element: " + messageJSON);
+ // return messageJSON;
+ // }
+ // return null;
+ // }
+
+ // public String removeMessage(String listKey, MessageProtobuf messageObject) {
+ // List cachedList = messageCache.getIfPresent(listKey);
+ // if (cachedList != null && !cachedList.isEmpty()) {
+ // for (int i = 0; i < cachedList.size(); i++) {
+ // String element = cachedList.get(i);
+ // MessageProtobuf messageElement = JSON.parseObject(element,
+ // MessageProtobuf.class);
+ // // 回执消息内容中存储被回执消息的uid
+ // if (messageElement.getUid().equals(messageObject.getContent())) {
+ // cachedList.remove(i);
+ // }
+ // }
+ // messageCache.put(listKey, cachedList);
+ // }
+ // return null;
+ // }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistService.java b/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistService.java
index 47414b6e..07515dae 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessagePersistService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-16 18:04:37
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:43:05
+ * @LastEditTime: 2024-09-07 16:23:29
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -37,17 +37,16 @@ public class MessagePersistService {
private final ModelMapper modelMapper;
public void persist(String messageJSON) {
- log.info("persist: {}", messageJSON);
+ // log.info("persist: {}", messageJSON);
MessageProtobuf messageProtobuf = JSON.parseObject(messageJSON, MessageProtobuf.class);
//
MessageTypeEnum type = messageProtobuf.getType();
String threadTopic = messageProtobuf.getThread().getTopic();
-
// log.info("orgUid: {}", orgUid);
// 返回true表示该消息是系统通知,不应该保存到数据库
if (dealWithMessageNotification(type, messageProtobuf)) {
- log.info("message should not be saved uid {}, type {}", messageProtobuf.getUid(), type);
+ // log.info("message should not be saved uid {}, type {}", messageProtobuf.getUid(), type);
return;
}
//
@@ -191,12 +190,12 @@ private void dealWithMessageReceipt(MessageTypeEnum type, @Nonnull MessageProtob
// 消息撤回,从数据库中删除消息
private void dealWithMessageRecall(MessageProtobuf message) {
- log.info("dealWithMessageRecall");
+ // log.info("dealWithMessageRecall");
messageService.deleteByUid(message.getUid());
}
private void dealWithRateMessage(MessageTypeEnum type, MessageProtobuf message) {
- log.info("dealWithMessageRateSubmit");
+ // log.info("dealWithMessageRateSubmit");
// 如果是客服邀请评价,则content为邀请评价消息的uid,否则为空
Optional messageOpt = messageService.findByUid(message.getContent());
if (messageOpt.isPresent()) {
@@ -212,7 +211,7 @@ private void dealWithRateMessage(MessageTypeEnum type, MessageProtobuf message)
}
private void dealWithLeaveMsg(MessageTypeEnum type, MessageProtobuf message) {
- log.info("dealWithLeaveMsg");
+ // log.info("dealWithLeaveMsg");
Optional messageOpt = messageService.findByUid(message.getContent());
if (messageOpt.isPresent()) {
Message messageEntity = messageOpt.get();
@@ -225,8 +224,7 @@ private void dealWithLeaveMsg(MessageTypeEnum type, MessageProtobuf message) {
}
private void dealWithFaqRateMessage(MessageTypeEnum type, MessageProtobuf message) {
- log.info("dealWithFaqRateMessage");
- //
+ // log.info("dealWithFaqRateMessage");
Optional messageOpt = messageService.findByUid(message.getContent());
if (messageOpt.isPresent()) {
Message messageEntity = messageOpt.get();
@@ -240,7 +238,7 @@ private void dealWithFaqRateMessage(MessageTypeEnum type, MessageProtobuf messag
}
private void dealWithRobotRateMessage(MessageTypeEnum type, MessageProtobuf message) {
- log.info("dealWithRobotRateMessage");
+ // log.info("dealWithRobotRateMessage");
//
Optional messageOpt = messageService.findByUid(message.getContent());
if (messageOpt.isPresent()) {
@@ -255,7 +253,7 @@ private void dealWithRobotRateMessage(MessageTypeEnum type, MessageProtobuf mess
}
private void dealWithTransferMessage(MessageTypeEnum type, MessageProtobuf message) {
- log.info("dealWithTransferMessage");
+ // log.info("dealWithTransferMessage");
MessageTransferContent transferContentObject = JSONObject.parseObject(message.getContent(),
MessageTransferContent.class);
//
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageService.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageService.java
index 8ba7b248..dd9b249a 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-24 09:18:11
+ * @LastEditTime: 2024-09-07 17:01:20
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,12 +14,10 @@
*/
package com.bytedesk.core.message;
-import java.util.Date;
import java.util.Optional;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.annotation.CachePut;
-import org.modelmapper.ModelMapper;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -30,16 +28,9 @@
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
-import com.alibaba.fastjson2.JSON;
+// import com.alibaba.fastjson2.JSON;
import com.bytedesk.core.base.BaseService;
-import com.bytedesk.core.config.BytedeskEventPublisher;
-import com.bytedesk.core.enums.ClientEnum;
-import com.bytedesk.core.rbac.user.User;
-import com.bytedesk.core.rbac.user.UserProtobuf;
-import com.bytedesk.core.thread.ThreadProtobuf;
-import com.bytedesk.core.thread.ThreadResponse;
-import com.bytedesk.core.thread.ThreadService;
-import com.bytedesk.core.uid.UidUtils;
+// import com.bytedesk.core.config.BytedeskEventPublisher;
import com.bytedesk.core.utils.ConvertUtils;
import lombok.AllArgsConstructor;
@@ -51,13 +42,7 @@ public class MessageService extends BaseService queryByOrg(MessageRequest request) {
@@ -101,16 +86,6 @@ public Optional findByUid(String uid) {
return messageRepository.findByUid(uid);
}
- /**
- * find the last message in the thread
- * 找到当前会话中最新一条聊天记录
- */
- // @Cacheable(value = "message", key = "#threadUid", unless = "#result == null")
- // public Optional findByThreadsUidInOrderByCreatedAtDesc(String
- // threadUid) {
- // return messageRepository.findFirstByThreadsUidInOrderByCreatedAtDesc(new
- // String[] { threadUid });
- // }
@Caching(put = {
@CachePut(value = "message", key = "#message.uid"),
@@ -147,12 +122,6 @@ public boolean existsByUid(String uid) {
return messageRepository.existsByUid(uid);
}
- // public int ping() {
- // User user = authService.getCurrentUser();
- // int count = messageCacheService.getUnreadCount(user.getUid());
- // return count;
- // }
-
@Override
public Page queryByUser(MessageRequest request) {
// TODO Auto-generated method stub
@@ -184,58 +153,80 @@ public void handleOptimisticLockingFailureException(ObjectOptimisticLockingFailu
}
//
- public void notifyUser(MessageProtobuf messageProtobuf) {
- String json = JSON.toJSONString(messageProtobuf);
- bytedeskEventPublisher.publishMessageJsonEvent(json);
- }
+ // public void notifyUser(MessageProtobuf messageProtobuf) {
+ // String json = JSON.toJSONString(messageProtobuf);
+ // bytedeskEventPublisher.publishMessageJsonEvent(json);
+ // }
+
+ // public MessageProtobuf createNoticeMessage(String userUid, String orgUid, String content) {
+ // //
+ // UserProtobuf sender = UserUtils.getSystemChannelUser();
+ // //
+ // String topic = TopicUtils.getSystemTopic(userUid);
+ // ThreadProtobuf thread = ThreadUtils.getThreadProtobuf(topic, ThreadTypeEnum.CHANNEL, sender);
+ // //
+ // MessageExtra extra = MessageUtils.getMessageExtra(orgUid);
+ // //
+ // MessageProtobuf message = MessageProtobuf.builder()
+ // .uid(uidUtils.getCacheSerialUid())
+ // .type(MessageTypeEnum.NOTICE)
+ // .content(content)
+ // .status(MessageStatusEnum.SUCCESS)
+ // .createdAt(new Date())
+ // .client(ClientEnum.SYSTEM)
+ // .thread(thread)
+ // .user(sender)
+ // .extra(JSON.toJSONString(extra))
+ // .build();
+ // return message;
+ // }
// 通知消息:登录
- public MessageProtobuf createNoticeMessage(User user, String content) {
- //
- ThreadResponse noticeThread = threadService.createSystemChannelThread(user);
- ThreadProtobuf thread = modelMapper.map(noticeThread, ThreadProtobuf.class);
- UserProtobuf sender = thread.getUser();
- //
- MessageExtra extra = MessageExtra.builder().orgUid(user.getOrgUid()).build();
- //
- MessageProtobuf message = MessageProtobuf.builder()
- .uid(uidUtils.getCacheSerialUid())
- .type(MessageTypeEnum.NOTICE)
- .content(content)
- .status(MessageStatusEnum.SUCCESS)
- .createdAt(new Date())
- .client(ClientEnum.SYSTEM)
- .thread(thread)
- .user(sender)
- .extra(JSON.toJSONString(extra))
- .build();
-
- return message;
- }
+ // public MessageProtobuf createNoticeMessage(User user, String content) {
+ // //
+ // ThreadResponse noticeThread = threadService.createSystemChannelThread(user);
+ // ThreadProtobuf thread = modelMapper.map(noticeThread, ThreadProtobuf.class);
+ // UserProtobuf sender = thread.getUser();
+ // //
+ // MessageExtra extra = MessageExtra.builder().orgUid(user.getOrgUid()).build();
+ // //
+ // MessageProtobuf message = MessageProtobuf.builder()
+ // .uid(uidUtils.getCacheSerialUid())
+ // .type(MessageTypeEnum.NOTICE)
+ // .content(content)
+ // .status(MessageStatusEnum.SUCCESS)
+ // .createdAt(new Date())
+ // .client(ClientEnum.SYSTEM)
+ // .thread(thread)
+ // .user(sender)
+ // .extra(JSON.toJSONString(extra))
+ // .build();
+ // return message;
+ // }
// TODO: 事件消息:访客离线、访客上线
- public MessageProtobuf createEventMessage(User user, String content) {
- //
- ThreadResponse noticeThread = threadService.createSystemChannelThread(user);
- ThreadProtobuf thread = modelMapper.map(noticeThread, ThreadProtobuf.class);
- UserProtobuf sender = thread.getUser();
- //
- MessageExtra extra = MessageExtra.builder().orgUid(user.getOrgUid()).build();
- //
- MessageProtobuf message = MessageProtobuf.builder()
- .uid(uidUtils.getCacheSerialUid())
- .type(MessageTypeEnum.EVENT)
- .content(content)
- .status(MessageStatusEnum.SUCCESS)
- .createdAt(new Date())
- .client(ClientEnum.SYSTEM)
- .thread(thread)
- .user(sender)
- .extra(JSON.toJSONString(extra))
- .build();
-
- return message;
- }
+ // public MessageProtobuf createEventMessage(User user, String content) {
+ // //
+ // ThreadResponse noticeThread = threadService.createSystemChannelThread(user);
+ // ThreadProtobuf thread = modelMapper.map(noticeThread, ThreadProtobuf.class);
+ // UserProtobuf sender = thread.getUser();
+ // //
+ // MessageExtra extra = MessageExtra.builder().orgUid(user.getOrgUid()).build();
+ // //
+ // MessageProtobuf message = MessageProtobuf.builder()
+ // .uid(uidUtils.getCacheSerialUid())
+ // .type(MessageTypeEnum.EVENT)
+ // .content(content)
+ // .status(MessageStatusEnum.SUCCESS)
+ // .createdAt(new Date())
+ // .client(ClientEnum.SYSTEM)
+ // .thread(thread)
+ // .user(sender)
+ // .extra(JSON.toJSONString(extra))
+ // .build();
+
+ // return message;
+ // }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageSocketService.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageSocketService.java
index 0d5020f0..aeb09e8b 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageSocketService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageSocketService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-26 10:36:50
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-01 18:54:05
+ * @LastEditTime: 2024-08-31 16:46:01
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -147,28 +147,5 @@ private void doSendMessage(String topic, @NonNull MessageProto.Message messagePr
}
}
- // private void doSendToSenderClients(MessageProto.Message messageProto) {
- // log.debug("doSendToSenderClients");
- // // String uid, String topic, byte[] messageBytes
- // // String uid = messageProto.getUser().getUid();
- // String topic = messageProto.getThread().getTopic();
- // byte[] messageBytes = messageProto.toByteArray();
- // //
- // // String sid = topic.split("/")[0];
- // // List topicList = topicService.findByTopic(sid);
- // Set topicSet = topicService.findByTopic(topic);
- // topicSet.forEach(topicElement -> {
- // Set clientIdList = topicElement.getClientIds();
- // clientIdList.forEach(clientId -> {
- // doSendMessage(topic, messageBytes, clientId);
- // });
- // });
- // //
- // if (topicSet.size() == 0) {
- // log.debug("doSendToSenderClients: no topic");
- // // TODO: 数据库中为空,尝试匹配内存
- // // doSendMessage(topic, messageBytes, topic);
- // }
- // }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageTypeEnum.java
index 3793d687..b4141cd7 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message/MessageTypeEnum.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageTypeEnum.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-05 21:50:54
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-24 06:18:04
+ * @LastEditTime: 2024-09-07 18:15:48
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -62,6 +62,8 @@ public enum MessageTypeEnum {
RATE_INVITE, // 客服邀请评价
RATE_SUBMIT, // 访客提交评价
RATE_CANCEL, // 访客取消评价
+ AUTO_CLOSED, // 自动关闭
+ AGENT_CLOSED, // 客服关闭
TRANSFER,
TRANSFER_REJECT,
TRANSFER_ACCEPT,
diff --git a/modules/core/src/main/java/com/bytedesk/core/message/MessageUtils.java b/modules/core/src/main/java/com/bytedesk/core/message/MessageUtils.java
new file mode 100644
index 00000000..85bf20b5
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/message/MessageUtils.java
@@ -0,0 +1,88 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-31 16:23:54
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 18:13:54
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.message;
+
+import java.util.Date;
+
+import com.alibaba.fastjson2.JSON;
+import com.bytedesk.core.config.BytedeskEventPublisher;
+import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.rbac.user.UserUtils;
+import com.bytedesk.core.thread.Thread;
+import com.bytedesk.core.thread.ThreadProtobuf;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.thread.ThreadUtils;
+import com.bytedesk.core.topic.TopicUtils;
+import com.bytedesk.core.utils.ApplicationContextHolder;
+
+public class MessageUtils {
+
+ public static MessageExtra getMessageExtra(String orgUid) {
+ return MessageExtra.builder().orgUid(orgUid).build();
+ }
+
+ public static MessageProtobuf createNoticeMessage(String messageUid, String userUid, String orgUid,
+ String content) {
+ //
+ UserProtobuf sender = UserUtils.getSystemChannelUser();
+ //
+ String topic = TopicUtils.getSystemTopic(userUid);
+ ThreadProtobuf thread = ThreadUtils.getThreadProtobuf(topic, ThreadTypeEnum.CHANNEL, sender);
+ //
+ MessageExtra extra = MessageUtils.getMessageExtra(orgUid);
+ //
+ MessageProtobuf message = MessageProtobuf.builder()
+ .uid(messageUid)
+ .type(MessageTypeEnum.NOTICE)
+ .content(content)
+ .status(MessageStatusEnum.SUCCESS)
+ .createdAt(new Date())
+ .client(ClientEnum.SYSTEM)
+ .thread(thread)
+ .user(sender)
+ .extra(JSON.toJSONString(extra))
+ .build();
+ return message;
+ }
+
+ public static MessageProtobuf createThreadMessage(String messageUid, Thread thread, MessageTypeEnum type, String content) {
+ //
+ UserProtobuf sender = UserUtils.getSystemChannelUser();
+ ThreadProtobuf threadProtobuf = thread.toProtobuf();
+ MessageExtra extra = MessageUtils.getMessageExtra(thread.getOrgUid());
+ //
+ MessageProtobuf message = MessageProtobuf.builder()
+ .uid(messageUid)
+ .type(type)
+ .content(content)
+ .status(MessageStatusEnum.SUCCESS)
+ .createdAt(new Date())
+ .client(ClientEnum.SYSTEM)
+ .thread(threadProtobuf)
+ .user(sender)
+ .extra(JSON.toJSONString(extra))
+ .build();
+ return message;
+ }
+
+ public static void notifyUser(MessageProtobuf messageProtobuf) {
+ String json = JSON.toJSONString(messageProtobuf);
+ //
+ BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class);
+ bytedeskEventPublisher.publishMessageJsonEvent(json);
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/message_unread/MessageUnreadEventListener.java b/modules/core/src/main/java/com/bytedesk/core/message_unread/MessageUnreadEventListener.java
index e71d94ea..6c4bb438 100644
--- a/modules/core/src/main/java/com/bytedesk/core/message_unread/MessageUnreadEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/message_unread/MessageUnreadEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-01 12:37:41
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-05 12:24:45
+ * @LastEditTime: 2024-09-10 17:06:01
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -21,7 +21,9 @@
import com.bytedesk.core.message.Message;
import com.bytedesk.core.message.MessageCreateEvent;
import com.bytedesk.core.message.MessageStatusEnum;
+import com.bytedesk.core.message.MessageTypeEnum;
import com.bytedesk.core.message.MessageUpdateEvent;
+import com.bytedesk.core.quartz.event.QuartzFiveSecondEvent;
import com.bytedesk.core.rbac.user.UserProtobuf;
import com.bytedesk.core.socket.mqtt.MqttConnectedEvent;
import com.bytedesk.core.socket.stomp.StompConnectedEvent;
@@ -42,7 +44,10 @@ public class MessageUnreadEventListener {
@EventListener
public void onMessageCreateEvent(MessageCreateEvent event) {
Message message = event.getMessage();
- log.info("message unread create event: {}", message.getContent());
+ if (message.getType().equals(MessageTypeEnum.STREAM.name())) {
+ return;
+ }
+ // log.info("message unread create event: {}", message.getContent());
// 缓存未读消息
String threadTopic = message.getThreadTopic();
String userString = message.getUser();
@@ -67,23 +72,23 @@ public void onMessageCreateEvent(MessageCreateEvent event) {
}
//
} else if (TopicUtils.isOrgWorkgroupTopic(threadTopic)) {
- // 技能组客服 topic格式:org/workgroup/{workgroup_uid}/{agent_uid}/{visitor_uid}
+ // 技能组客服 topic格式:org/workgroup/{workgroup_uid}/{visitor_uid}
String[] splits = threadTopic.split("/");
- if (splits.length < 5) {
+ if (splits.length < 4) {
throw new RuntimeException("workgroup topic format error");
}
String workgroupUid = splits[2];
- String agentUid = splits[3];
- String visitorUid = splits[4];
- log.info("workgroupUid {}, agentUid {}, visitorUid {}", workgroupUid, agentUid, visitorUid);
+ // String agentUid = splits[3];
+ String visitorUid = splits[3];
+ log.info("workgroupUid {}, visitorUid {}", workgroupUid, visitorUid);
//
// 仅缓存接受者未读消息
- if (userUid.equals(agentUid)) {
- messageUnreadService.create(message, visitorUid);
- }
- if (userUid.equals(visitorUid)) {
- messageUnreadService.create(message, agentUid);
- }
+ // if (userUid.equals(agentUid)) {
+ // messageUnreadService.create(message, visitorUid);
+ // }
+ // if (userUid.equals(visitorUid)) {
+ // messageUnreadService.create(message, agentUid);
+ // }
} else if (TopicUtils.isOrgGroupTopic(threadTopic)) {
// 群组消息 topic格式:org/group/{group_uid}
@@ -122,7 +127,10 @@ public void onMessageCreateEvent(MessageCreateEvent event) {
@EventListener
public void onMessageUpdateEvent(MessageUpdateEvent event) {
Message message = event.getMessage();
- log.info("message unread update event: {}", message.getContent());
+ // log.info("message unread update event: {}", message.getContent());
+ if (message.getType().equals(MessageTypeEnum.STREAM.name())) {
+ return;
+ }
//
String threadTopic = message.getThreadTopic();
MessageStatusEnum messageStatus = MessageStatusEnum.fromValue(message.getStatus());
@@ -145,17 +153,17 @@ public void onMessageUpdateEvent(MessageUpdateEvent event) {
messageUnreadService.delete(visitorUid);
//
} else if (TopicUtils.isOrgWorkgroupTopic(threadTopic)) {
- // 技能组客服 topic格式:org/workgroup/{workgroup_uid}/{agent_uid}/{visitor_uid}
+ // 技能组客服 topic格式:org/workgroup/{workgroup_uid}/{visitor_uid}
String[] splits = threadTopic.split("/");
- if (splits.length < 5) {
+ if (splits.length < 4) {
throw new RuntimeException("workgroup topic format error");
}
String workgroupUid = splits[2];
- String agentUid = splits[3];
- String visitorUid = splits[4];
- log.info("workgroupUid {}, agentUid {}, visitorUid {}", workgroupUid, agentUid, visitorUid);
+ // String agentUid = splits[3];
+ String visitorUid = splits[3];
+ log.info("workgroupUid {}, visitorUid {}", workgroupUid, visitorUid);
//
- messageUnreadService.delete(agentUid);
+ // messageUnreadService.delete(agentUid);
messageUnreadService.delete(visitorUid);
} else if (TopicUtils.isOrgGroupTopic(threadTopic)) {
@@ -183,10 +191,11 @@ public void onMessageUpdateEvent(MessageUpdateEvent event) {
}
}
- // @EventListener
- // public void onQuartzFiveSecondEvent(QuartzFiveSecondEvent event) {
- // // log.info("message quartz five second event: " + event);
- // }
+ @EventListener
+ public void onQuartzFiveSecondEvent(QuartzFiveSecondEvent event) {
+ // log.info("message quartz five second event: " + event);
+
+ }
@EventListener
public void onMqttConnectEvent(MqttConnectedEvent event) {
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/Notice.java b/modules/core/src/main/java/com/bytedesk/core/notice/Notice.java
new file mode 100644
index 00000000..fbe1a738
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notice/Notice.java
@@ -0,0 +1,48 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-01 09:27:49
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 10:00:03
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notice;
+
+import com.bytedesk.core.base.BaseEntity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * TODO: 通知
+ * 1. 一条消息通知-每个用户写一条
+ * 2. 定期清理一段时间以前的通知
+ */
+@Data
+@Entity
+@Builder
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@Table(name = "core_notice")
+public class Notice extends BaseEntity {
+
+ private String title;
+
+ private String content;
+
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeController.java b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeController.java
new file mode 100644
index 00000000..9a268889
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeController.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-01 09:28:15
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-01 09:28:17
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notice;
+
+public class NoticeController {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRepository.java b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRepository.java
new file mode 100644
index 00000000..a09c8277
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRepository.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-01 09:29:07
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-01 09:29:11
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notice;
+
+public class NoticeRepository {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRequest.java b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRequest.java
new file mode 100644
index 00000000..9578c624
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeRequest.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-01 09:28:40
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-01 09:28:43
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notice;
+
+public class NoticeRequest {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeResponse.java b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeResponse.java
new file mode 100644
index 00000000..e873abaf
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeResponse.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-01 09:28:51
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-01 09:28:54
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notice;
+
+public class NoticeResponse {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeService.java b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeService.java
new file mode 100644
index 00000000..cc8a7102
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeService.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-01 09:28:27
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-01 09:28:30
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notice;
+
+public class NoticeService {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/notice/NoticeSpecification.java b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeSpecification.java
new file mode 100644
index 00000000..331e6570
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/notice/NoticeSpecification.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-01 09:29:23
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-01 09:29:26
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.notice;
+
+public class NoticeSpecification {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/Notifier.java b/modules/core/src/main/java/com/bytedesk/core/push/Notifier.java
deleted file mode 100644
index f5b1a5c5..00000000
--- a/modules/core/src/main/java/com/bytedesk/core/push/Notifier.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-03-29 15:49:55
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-01 16:14:54
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.core.push;
-
-import com.bytedesk.core.message.Message;
-
-import jakarta.servlet.http.HttpServletRequest;
-
-public abstract class Notifier {
- abstract void notify(Message e);
- abstract void send(String to, String content, HttpServletRequest request);
-}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/Push.java b/modules/core/src/main/java/com/bytedesk/core/push/Push.java
index bdd911ba..68d80933 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/Push.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/Push.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:30:11
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:18:13
+ * @LastEditTime: 2024-09-09 16:16:20
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -57,6 +57,8 @@ public class Push extends BaseEntity {
// according to ip address
private String ipLocation;
+ private String deviceUid; // 设备唯一标识
+
// @Enumerated(EnumType.STRING)
@Builder.Default
// private String status = StatusConsts.CODE_STATUS_PENDING;
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushEventListener.java b/modules/core/src/main/java/com/bytedesk/core/push/PushEventListener.java
index 31b0df26..22b54cca 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-27 16:24:13
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-27 16:38:55
+ * @LastEditTime: 2024-09-10 10:22:57
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -17,12 +17,12 @@
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.quartz.QuartzFiveSecondEvent;
+import com.bytedesk.core.quartz.event.QuartzOneMinEvent;
import lombok.AllArgsConstructor;
-// import lombok.extern.slf4j.Slf4j;
+import lombok.extern.slf4j.Slf4j;
-// @Slf4j
+@Slf4j
@Component
@AllArgsConstructor
public class PushEventListener {
@@ -30,9 +30,8 @@ public class PushEventListener {
private final PushService pushService;
@EventListener
- public void onQuartzFiveSecondEvent(QuartzFiveSecondEvent event) {
- // log.info("push quartz five second event: " + event);
-
+ public void onQuartzOneMinEvent(QuartzOneMinEvent event) {
+ // log.info("push quartz one min event");
// auto outdate code
pushService.autoOutdateCode();
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushNotifier.java b/modules/core/src/main/java/com/bytedesk/core/push/PushNotifier.java
new file mode 100644
index 00000000..877e3452
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushNotifier.java
@@ -0,0 +1,26 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-03-29 15:49:55
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 09:55:47
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.push;
+
+import com.bytedesk.core.message.Message;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+public abstract class PushNotifier {
+
+ abstract void notify(Message e);
+
+ abstract void send(String to, String content, HttpServletRequest request);
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushRepository.java b/modules/core/src/main/java/com/bytedesk/core/push/PushRepository.java
index 42553065..e2118bcf 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushRepository.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushRepository.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:42:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:55:00
+ * @LastEditTime: 2024-09-09 20:12:09
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -25,7 +25,11 @@ public interface PushRepository extends JpaRepository, JpaSpecificat
List findByStatus(String status);
Optional findByStatusAndTypeAndReceiverAndContent(String status, String type, String receiver,
- String content);
+ String content);
+
+ Optional findByDeviceUid(String deviceUid);
+
+ Optional findByDeviceUidAndContent(String deviceUid, String code);
Boolean existsByStatusAndTypeAndReceiver(String status, String type, String receiver);
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushRequest.java b/modules/core/src/main/java/com/bytedesk/core/push/PushRequest.java
index 13732ee4..bc6d3577 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushRequest.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushRequest.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:42:01
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-20 18:12:10
+ * @LastEditTime: 2024-09-09 16:14:14
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -46,6 +46,10 @@ public class PushRequest extends BaseRequest {
// according to ip address
private String ipLocation;
+ // 用于扫码登录
+ private Boolean forceRefresh; // 强制刷新
+ private String deviceUid; // 设备唯一标识
+
@Builder.Default
// private PushStatusEnum status = PushStatusEnum.PENDING;
private String status = PushStatusEnum.PENDING.name();
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushResponse.java b/modules/core/src/main/java/com/bytedesk/core/push/PushResponse.java
index a16448ea..0c7c8099 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushResponse.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushResponse.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:42:11
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-09 22:23:27
+ * @LastEditTime: 2024-09-09 17:00:32
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -48,6 +48,8 @@ public class PushResponse extends BaseResponse {
private String ipLocation;
+ private String deviceUid; // 设备唯一标识
+
private PushStatusEnum status;
private String client;
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushService.java b/modules/core/src/main/java/com/bytedesk/core/push/PushService.java
index 3fba9214..d954e2ac 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-25 15:41:33
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:55:08
+ * @LastEditTime: 2024-09-09 20:35:37
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -21,7 +21,6 @@
import org.modelmapper.ModelMapper;
import org.springframework.cache.annotation.CachePut;
-import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -113,7 +112,7 @@ public Boolean sendCode(String receiver, String type, String client, String auth
code = bytedeskProperties.getMobileCode();
log.info("test code: {}", code);
} else {
- code = Utils.getRandomCode(receiver);
+ code = Utils.getRandomCode();
}
if (type.equals(TypeConsts.TYPE_EMAIL)) {
@@ -138,6 +137,67 @@ public Boolean sendCode(String receiver, String type, String client, String auth
return true;
}
+ public PushResponse scanQuery(PushRequest pushRequest, HttpServletRequest request) {
+
+ Optional pushOptional = findByDeviceUid(pushRequest.getDeviceUid());
+ if (pushOptional.isPresent()) {
+ Push push = pushOptional.get();
+ //
+ if (pushRequest.getForceRefresh().booleanValue()) {
+ push.setStatus(PushStatusEnum.PENDING.name());
+ save(push);
+ }
+ //
+ return convertToResponse(push);
+ }
+
+ String ip = ipService.getIp(request);
+ String ipLocation = ipService.getIpLocation(ip);
+ //
+ Push push = modelMapper.map(pushRequest, Push.class);
+ push.setUid(uidUtils.getCacheSerialUid());
+ push.setType(TypeConsts.TYPE_SCAN);
+ push.setSender(TypeConsts.TYPE_SYSTEM);
+ push.setContent(Utils.getRandomCode());
+ push.setIp(ip);
+ push.setIpLocation(ipLocation);
+ //
+ Push savedPush = save(push);
+ if (savedPush == null) {
+ throw new RuntimeException("scan query failed");
+ }
+ return convertToResponse(savedPush);
+ }
+
+ public PushResponse scan(PushRequest pushRequest, HttpServletRequest request) {
+
+ Push push = findByDeviceUid(pushRequest.getDeviceUid())
+ .orElseThrow(() -> new RuntimeException("scan deviceUid " + pushRequest.getDeviceUid() + " not found"));
+
+ push.setStatus(PushStatusEnum.SCANED.name());
+ //
+ Push savedPush = save(push);
+ if (savedPush == null) {
+ throw new RuntimeException("scan save failed");
+ }
+ return convertToResponse(savedPush);
+ }
+
+ public PushResponse scanConfirm(PushRequest pushRequest, HttpServletRequest request) {
+
+ Push push = findByDeviceUid(pushRequest.getDeviceUid())
+ .orElseThrow(() -> new RuntimeException(
+ "scanConfirm deviceUid " + pushRequest.getDeviceUid() + " not found"));
+ push.setReceiver(pushRequest.getReceiver());
+ push.setStatus(PushStatusEnum.CONFIRMED.name());
+ //
+ Push savedPush = save(push);
+ if (savedPush == null) {
+ throw new RuntimeException("scanConfirm save failed");
+ }
+ return convertToResponse(savedPush);
+ }
+
public PushResponse create(PushRequest pushRequest) {
log.info("pushRequest {}", pushRequest.toString());
@@ -149,6 +209,7 @@ public PushResponse create(PushRequest pushRequest) {
if (savedPush == null) {
throw new RuntimeException("create push failed");
}
+ //
return convertToResponse(savedPush);
}
@@ -162,15 +223,6 @@ public Boolean validateSmsCode(String mobile, String code) {
public Boolean validateCode(String receiver, String type, String code) {
// check if has already send validate code within 15min
-
- // Boolean result =
- // pushRepository.existsByStatusAndTypeAndReceiverAndContent(StatusConsts.CODE_STATUS_PENDING,
- // type, receiver, code);
- // if (result) {
- // // TODO: 更新状态
- // }
- // return result;
- //
Optional pushOptional = findByStatusAndTypeAndReceiverAndContent(PushStatusEnum.PENDING, type, receiver,
code);
if (pushOptional.isPresent()) {
@@ -189,6 +241,10 @@ public Optional findByStatusAndTypeAndReceiverAndContent(PushStatusEnum st
return pushRepository.findByStatusAndTypeAndReceiverAndContent(status.name(), type, receiver, content);
}
+ public Optional findByDeviceUid(String deviceUid) {
+ return pushRepository.findByDeviceUid(deviceUid);
+ }
+
public Boolean existsByStatusAndTypeAndReceiver(PushStatusEnum status, String type, String receiver) {
return pushRepository.existsByStatusAndTypeAndReceiver(status.name(), type, receiver);
}
@@ -201,32 +257,38 @@ public Push save(Push push) {
try {
return pushRepository.save(push);
} catch (Exception e) {
- // TODO: handle exception
+ e.printStackTrace();
}
return null;
}
// TODO: 更新缓存
- @Cacheable(value = "pushPending")
+ // @Cacheable(value = "pushPending")
public List findStatusPending() {
- // return pushRepository.findByStatus(StatusConsts.CODE_STATUS_PENDING);
return pushRepository.findByStatus(PushStatusEnum.PENDING.name());
}
// 自动过期
- // TODO: 频繁查库,待优化
@Async
public void autoOutdateCode() {
List pendingPushes = findStatusPending();
+ // log.info("autoOutdateCode pendingPushes {}", pendingPushes.size());
pendingPushes.forEach(push -> {
// 计算两个日期之间的毫秒差
long diffInMilliseconds = Math.abs(new Date().getTime() - push.getUpdatedAt().getTime());
// 转换为分钟
long diffInMinutes = TimeUnit.MILLISECONDS.toMinutes(diffInMilliseconds);
- // 验证码有效时间15分钟
- if (diffInMinutes > 15) {
- // TODO: 过期,清空缓存
- // push.setStatus(StatusConsts.CODE_STATUS_EXPIRED);
+ // log.info("autoOutdateCode diffInMinutes {} {}", push.getContent(), diffInMinutes);
+ //
+ if (push.getType().equals(TypeConsts.TYPE_SCAN)) {
+ // log.info("autoOutdateCode scan TypeConsts.TYPE_SCAN");
+ // 扫码有效时间3分钟
+ if (diffInMinutes > 3) {
+ push.setStatus(PushStatusEnum.EXPIRED.name());
+ save(push);
+ }
+ } else if (diffInMinutes > 15) {
+ // 手机验证码有效时间15分钟
push.setStatus(PushStatusEnum.EXPIRED.name());
save(push);
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplAndroid.java b/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplAndroid.java
index 7e750c6c..327b8086 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplAndroid.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplAndroid.java
@@ -22,8 +22,7 @@
import jakarta.servlet.http.HttpServletRequest;
@Service
-public class PushServiceImplAndroid extends Notifier {
-
+public class PushServiceImplAndroid extends PushNotifier {
@Async
@Override
@@ -37,9 +36,8 @@ void send(String to, String content, HttpServletRequest request) {
// TODO: 检测同一个ip是否短时间内有发送过验证码,如果短时间内发送过,则不发送
-
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'send'");
}
-
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplEmail.java b/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplEmail.java
index 04ba8df4..34111bb6 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplEmail.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplEmail.java
@@ -38,7 +38,7 @@
*/
@Slf4j
@Service
-public class PushServiceImplEmail extends Notifier {
+public class PushServiceImplEmail extends PushNotifier {
@Async
@Override
@@ -51,10 +51,10 @@ void notify(Message e) {
@Override
void send(String email, String content, HttpServletRequest request) {
log.info("send email to {}, content {}", email, content);
- //
+ //
sendValidateCode(email, content);
}
-
+
@Value("${aliyun.access.key.id}")
private String accessKeyId;
@@ -80,7 +80,6 @@ public void sendValidateCode(String email, String code) {
// TODO: 检测同一个ip是否短时间内有发送过验证码,如果短时间内发送过,则不发送
-
// 如果是除杭州region外的其它region(如新加坡、澳洲Region),需要将下面的"cn-hangzhou"替换为"ap-southeast-1"、或"ap-southeast-2"。
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
// 如果是除杭州region外的其它region(如新加坡region), 需要做如下处理
@@ -121,5 +120,4 @@ public void sendValidateCode(String email, String code) {
}
}
-
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplIos.java b/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplIos.java
index 446d0f79..2318e467 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplIos.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplIos.java
@@ -22,7 +22,7 @@
import jakarta.servlet.http.HttpServletRequest;
@Service
-public class PushServiceImplIos extends Notifier {
+public class PushServiceImplIos extends PushNotifier {
@Async
@Override
@@ -35,9 +35,9 @@ void notify(Message e) {
void send(String to, String content, HttpServletRequest request) {
// TODO: 检测同一个ip是否短时间内有发送过验证码,如果短时间内发送过,则不发送
-
+
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'send'");
}
-
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplSms.java b/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplSms.java
index 91c8ca3f..de64e63b 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplSms.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushServiceImplSms.java
@@ -35,7 +35,7 @@
@Slf4j
@Service
-public class PushServiceImplSms extends Notifier {
+public class PushServiceImplSms extends PushNotifier {
// @Value("${bytedesk.debug}")
// private Boolean debug;
@@ -68,7 +68,7 @@ void send(String mobile, String content, HttpServletRequest request) {
log.info("send sms to {}, content: {}", mobile, content);
// TODO: 检测同一个ip是否短时间内有发送过验证码,如果短时间内发送过,则不发送
-
+
// not test mobile, send sms
if (Utils.isTestMobile(mobile)) {
return;
@@ -82,12 +82,10 @@ void send(String mobile, String content, HttpServletRequest request) {
sendValidateCode(mobile, content);
}
-
-
public void sendValidateCode(String phone, String code) {
// if (debug) {
- // return;
+ // return;
// }
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushSmsTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/push/PushSmsTypeEnum.java
new file mode 100644
index 00000000..821c0b75
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushSmsTypeEnum.java
@@ -0,0 +1,29 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-09 14:26:20
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-09 14:27:30
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.push;
+
+public enum PushSmsTypeEnum {
+ LOGIN, REGISTER, SCAN, FORGET, VERIFY;
+
+ // 根据字符串查找对应的枚举常量
+ public static PushSmsTypeEnum fromValue(String value) {
+ for (PushSmsTypeEnum type : PushSmsTypeEnum.values()) {
+ if (type.name().equalsIgnoreCase(value)) {
+ return type;
+ }
+ }
+ throw new IllegalArgumentException("No PushSmsTypeEnum constant with value: " + value);
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushStatusEnum.java b/modules/core/src/main/java/com/bytedesk/core/push/PushStatusEnum.java
index 2307eeb1..7dbb9739 100644
--- a/modules/core/src/main/java/com/bytedesk/core/push/PushStatusEnum.java
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushStatusEnum.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-25 10:40:08
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:58:25
+ * @LastEditTime: 2024-09-09 16:41:01
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -16,6 +16,7 @@
public enum PushStatusEnum {
PENDING,
+ SCANED,
CONFIRMED,
EXPIRED,
SUCCESS,
diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushToken.java b/modules/core/src/main/java/com/bytedesk/core/push/PushToken.java
new file mode 100644
index 00000000..277be951
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/push/PushToken.java
@@ -0,0 +1,53 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-11 09:21:23
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-11 09:21:26
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.push;
+
+import com.bytedesk.core.base.BaseEntity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * 离线推送token
+ */
+@Data
+@Entity
+@Builder
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@Table(name = "core_push_token")
+public class PushToken extends BaseEntity {
+
+
+ private String token;
+
+ // dev or prod
+ @Column(name = "token_type")
+ private String type;
+
+ // ios or android
+ private String device;
+
+ private String userUid;
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/quartz/QuartzConfig.java b/modules/core/src/main/java/com/bytedesk/core/quartz/QuartzConfig.java
index 625a14e3..bc8ae630 100644
--- a/modules/core/src/main/java/com/bytedesk/core/quartz/QuartzConfig.java
+++ b/modules/core/src/main/java/com/bytedesk/core/quartz/QuartzConfig.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-28 13:05:47
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-05-17 11:39:54
+ * @LastEditTime: 2024-09-09 16:28:17
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -22,7 +22,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import com.bytedesk.core.quartz.job.FiveMinJob;
import com.bytedesk.core.quartz.job.FiveSecondJob;
+import com.bytedesk.core.quartz.job.OneMinJob;
/**
* Cron使用方法:
@@ -78,6 +80,58 @@ public Trigger fiveSecondJobTrigger() {
.withSchedule(scheduleBuilder)
.build();
}
+
+ /**
+ * 每5分钟运行一次
+ */
+ @Bean
+ public JobDetail fiveMinJobJobDetail() {
+ return JobBuilder.newJob(FiveMinJob.class)
+ .withIdentity("FiveMinJob", "bytedesk")
+ .withDescription("每5分钟运行一次")
+ .storeDurably()
+ .build();
+ }
+
+ @Bean
+ public Trigger fiveMinJobTrigger() {
+ SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
+ .simpleSchedule()
+ .withIntervalInMinutes(5)
+ .repeatForever();
+ return TriggerBuilder.newTrigger()
+ .forJob(fiveMinJobJobDetail())
+ .withIdentity("fiveMinJobTrigger", "bytedesk")
+ .withDescription("每隔5分钟检查一次")
+ .withSchedule(scheduleBuilder)
+ .build();
+ }
+
+ /**
+ * 每1分钟运行一次
+ */
+ @Bean
+ public JobDetail oneMinJobJobDetail() {
+ return JobBuilder.newJob(OneMinJob.class)
+ .withIdentity("OneMinJob", "bytedesk")
+ .withDescription("每1分钟运行一次")
+ .storeDurably()
+ .build();
+ }
+
+ @Bean
+ public Trigger oneMinJobTrigger() {
+ SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
+ .simpleSchedule()
+ .withIntervalInMinutes(1)
+ .repeatForever();
+ return TriggerBuilder.newTrigger()
+ .forJob(oneMinJobJobDetail())
+ .withIdentity("oneMinJobTrigger", "bytedesk")
+ .withDescription("每隔1分钟检查一次")
+ .withSchedule(scheduleBuilder)
+ .build();
+ }
diff --git a/modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzFiveMinEvent.java b/modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzFiveMinEvent.java
new file mode 100644
index 00000000..aa0119fb
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzFiveMinEvent.java
@@ -0,0 +1,31 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-02-28 14:37:33
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-09 16:23:41
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.quartz.event;
+
+import org.springframework.context.ApplicationEvent;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class QuartzFiveMinEvent extends ApplicationEvent {
+
+ public QuartzFiveMinEvent(Object source) {
+ super(source);
+ //TODO Auto-generated constructor stub
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/quartz/QuartzFiveSecondEvent.java b/modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzFiveSecondEvent.java
similarity index 96%
rename from modules/core/src/main/java/com/bytedesk/core/quartz/QuartzFiveSecondEvent.java
rename to modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzFiveSecondEvent.java
index f2f8ff20..2507970b 100644
--- a/modules/core/src/main/java/com/bytedesk/core/quartz/QuartzFiveSecondEvent.java
+++ b/modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzFiveSecondEvent.java
@@ -12,7 +12,7 @@
* 联系:270580156@qq.com
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
*/
-package com.bytedesk.core.quartz;
+package com.bytedesk.core.quartz.event;
import org.springframework.context.ApplicationEvent;
diff --git a/modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzOneMinEvent.java b/modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzOneMinEvent.java
new file mode 100644
index 00000000..78e0bd5f
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/quartz/event/QuartzOneMinEvent.java
@@ -0,0 +1,31 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-02-28 14:37:33
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-09 16:25:03
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.quartz.event;
+
+import org.springframework.context.ApplicationEvent;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class QuartzOneMinEvent extends ApplicationEvent {
+
+ public QuartzOneMinEvent(Object source) {
+ super(source);
+ //TODO Auto-generated constructor stub
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/quartz/job/FiveMinJob.java b/modules/core/src/main/java/com/bytedesk/core/quartz/job/FiveMinJob.java
new file mode 100644
index 00000000..a4d3a2b5
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/quartz/job/FiveMinJob.java
@@ -0,0 +1,51 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-02-28 13:07:58
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-09 20:29:08
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.quartz.job;
+
+import java.io.Serializable;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.lang.NonNull;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+
+import com.bytedesk.core.config.BytedeskEventPublisher;
+
+import lombok.AllArgsConstructor;
+
+// import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 5 min job
+ * 5分钟定时任务
+ *
+ */
+// @Slf4j
+@AllArgsConstructor
+@DisallowConcurrentExecution
+public class FiveMinJob extends QuartzJobBean implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final BytedeskEventPublisher bytedeskEventPublisher;
+
+ @Override
+ protected void executeInternal(@NonNull JobExecutionContext context) throws JobExecutionException {
+ // log.info("FiveMinJob");
+ bytedeskEventPublisher.publishQuartzFiveMinEvent();
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/quartz/job/OneMinJob.java b/modules/core/src/main/java/com/bytedesk/core/quartz/job/OneMinJob.java
new file mode 100644
index 00000000..f0ac1baa
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/quartz/job/OneMinJob.java
@@ -0,0 +1,51 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-02-28 13:07:58
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-09 20:28:34
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.quartz.job;
+
+import java.io.Serializable;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.lang.NonNull;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+
+import com.bytedesk.core.config.BytedeskEventPublisher;
+
+import lombok.AllArgsConstructor;
+
+// import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 1 min job
+ * 1分钟定时任务
+ *
+ */
+// @Slf4j
+@AllArgsConstructor
+@DisallowConcurrentExecution
+public class OneMinJob extends QuartzJobBean implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final BytedeskEventPublisher bytedeskEventPublisher;
+
+ @Override
+ protected void executeInternal(@NonNull JobExecutionContext context) throws JobExecutionException {
+ // log.info("OneMinJob");
+ bytedeskEventPublisher.publishQuartzOneMinEvent();
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthController.java b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthController.java
index a9dd74ad..bb594612 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-12 16:56:29
+ * @LastEditTime: 2024-09-08 15:50:13
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -52,17 +52,12 @@ public class AuthController {
private AuthService authService;
- private AuthenticationManager authenticationManager;
-
private PushService pushService;
private KaptchaCacheService kaptchaCacheService;
- /**
- *
- * @param userRequest
- * @return
- */
+ private AuthenticationManager authenticationManager;
+
@ActionAnnotation(title = "auth", action = "register", description = "register")
@PostMapping(value = "/register")
public ResponseEntity> register(@RequestBody UserRequest userRequest) {
@@ -198,19 +193,9 @@ public ResponseEntity> loginWithEmailCode(@RequestBody AuthRequest authRequest
return ResponseEntity.ok(JsonResult.success(authResponse));
}
+ // TODO: 刷新token
// @PostMapping("/refreshToken")
- // public JwtResponse refreshToken(@RequestBody RefreshTokenRequest
- // refreshTokenRequestDTO) {
- // return refreshTokenService.findByToken(refreshTokenRequestDTO.getToken())
- // .map(refreshTokenService::verifyExpiration)
- // .map(RefreshToken::getUser)
- // .map(userInfo -> {
- // String accessToken = jwtService.generateToken(userInfo.getUsername());
- // // String accessToken = jwtUtils.generateJwtToken(userInfo.getUsername());
- // return JwtResponse.builder()
- // .access_token(accessToken)
- // .refresh_token(refreshTokenRequestDTO.getToken()).build();
- // }).orElseThrow(() -> new RuntimeException("Refresh Token is not in DB..!!"));
+ // public JwtResponse refreshToken(@RequestBody RefreshTokenRequest refreshTokenRequestDTO) {
// }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthEventListener.java b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthEventListener.java
index 481df921..94fe067c 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/auth/AuthEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-19 11:36:50
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-23 21:39:15
+ * @LastEditTime: 2024-09-07 16:56:31
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -24,8 +24,9 @@
import com.bytedesk.core.constant.BdConstants;
import com.bytedesk.core.constant.I18Consts;
import com.bytedesk.core.message.MessageProtobuf;
-import com.bytedesk.core.message.MessageService;
+import com.bytedesk.core.message.MessageUtils;
import com.bytedesk.core.rbac.user.User;
+import com.bytedesk.core.uid.UidUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -35,11 +36,13 @@
@AllArgsConstructor
public class AuthEventListener {
- private final MessageService messageService;
+ // private final MessageService messageService;
+
+ private final UidUtils uidUtils;
@EventListener
public void onActionCreateEvent(ActionCreateEvent event) {
- log.info("onActionCreateEvent Received event: {}", event.getAction().getTitle());
+ log.info("onActionCreateEvent Received event: {}", event.toString());
// do something
Action action = event.getAction();
// 监听登录action,发送登录系统消息,提醒相关用户
@@ -53,9 +56,11 @@ public void onActionCreateEvent(ActionCreateEvent event) {
contentObject.put(I18Consts.I18N_NOTICE_CONTENT, action.getAction());
contentObject.put(I18Consts.I18N_NOTICE_IP, action.getIp());
contentObject.put(I18Consts.I18N_NOTICE_IPLOCATION, action.getIpLocation());
- MessageProtobuf messsage = messageService.createNoticeMessage(user, JSON.toJSONString(contentObject));
- //
- messageService.notifyUser(messsage);
+ //
+ // MessageProtobuf messsage = messageService.createNoticeMessage(user, JSON.toJSONString(contentObject));
+ MessageProtobuf messsage = MessageUtils.createNoticeMessage(uidUtils.getCacheSerialUid(), user.getUid(), user.getOrgUid(),
+ JSON.toJSONString(contentObject));
+ MessageUtils.notifyUser(messsage);
}
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserExtra.java b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserExtra.java
index 9c9bfc72..09c3bd0a 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserExtra.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserExtra.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-26 10:01:53
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-26 10:01:55
+ * @LastEditTime: 2024-09-07 09:39:26
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserProtobuf.java b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserProtobuf.java
index 48bd25cf..694b91a2 100644
--- a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserProtobuf.java
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserProtobuf.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-29 16:22:46
+ * @LastEditTime: 2024-09-07 10:15:18
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -34,11 +34,15 @@
@EqualsAndHashCode(callSuper = true)
public class UserProtobuf extends BaseResponse {
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 1L;
private String nickname;
-
+
private String avatar;
+ // ROBOT/AGENT/SYSTEM/USER/VISITOR
+ @Builder.Default
+ private String type = UserTypeEnum.VISITOR.name();
+
private String extra;
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserTypeEnum.java
new file mode 100644
index 00000000..f764e978
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserTypeEnum.java
@@ -0,0 +1,26 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-03 12:37:26
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 11:46:22
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.rbac.user;
+
+public enum UserTypeEnum {
+ AGENT,
+ SYSTEM,
+ VISITOR,
+ ROBOT,
+ MEMBER,
+ ASISTANT,
+ CHANNEL,
+ USER
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserUtils.java b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserUtils.java
new file mode 100644
index 00000000..55e872f8
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/rbac/user/UserUtils.java
@@ -0,0 +1,41 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-31 16:20:44
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-31 16:20:47
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.rbac.user;
+
+import com.bytedesk.core.constant.AvatarConsts;
+import com.bytedesk.core.constant.BdConstants;
+import com.bytedesk.core.constant.I18Consts;
+
+public class UserUtils {
+
+ public static UserProtobuf getFileAsistantUser() {
+ UserProtobuf user = UserProtobuf.builder()
+ .nickname(I18Consts.I18N_FILE_ASISTANT_NAME)
+ .avatar(AvatarConsts.DEFAULT_FILE_ASISTANT_AVATAR_URL)
+ .build();
+ user.setUid(BdConstants.DEFAULT_FILE_ASISTANT_UID);
+ return user;
+ }
+
+ public static UserProtobuf getSystemChannelUser() {
+ UserProtobuf user = UserProtobuf.builder()
+ .nickname(I18Consts.I18N_SYSTEM_NOTIFICATION_NAME)
+ .avatar(AvatarConsts.DEFAULT_SYSTEM_NOTIFICATION_AVATAR_URL)
+ .build();
+ user.setUid(BdConstants.DEFAULT_SYSTEM_UID);
+ return user;
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubController.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubController.java
index b2c63c2d..4f4de9c5 100644
--- a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubController.java
@@ -15,7 +15,7 @@
package com.bytedesk.core.redis.pubsub;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.RedisTemplate;
+// import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -39,8 +39,8 @@ public class RedisPubsubController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
- @Autowired
- private RedisTemplate redisTemplate;
+ // @Autowired
+ // private RedisTemplate redisTemplate;
/**
* http://127.0.0.1:9003/redis/pubsub/send?message=hello
@@ -64,17 +64,17 @@ public JsonResult publishText(@RequestParam String message) {
* @param message
* @return
*/
- @GetMapping("/send/object")
- public JsonResult publishObject(@RequestParam String message) {
- log.info("redisPubsub send: {}", message);
- //
- RedisPubsubMessage messageDto = RedisPubsubMessage.builder()
- .type("text")
- .content("pubsub")
- .build();
- redisTemplate.convertAndSend(RedisPubsubConst.BYTEDESK_PUBSUB_CHANNEL_OBJECT, messageDto);
+ // @GetMapping("/send/object")
+ // public JsonResult publishObject(@RequestParam String message) {
+ // log.info("redisPubsub send: {}", message);
+ // //
+ // RedisPubsubMessage messageDto = RedisPubsubMessage.builder()
+ // .type("text")
+ // .fileUrl("pubsub").fileUid("").kbUid("")
+ // .build();
+ // redisTemplate.convertAndSend(RedisPubsubConst.BYTEDESK_PUBSUB_CHANNEL_OBJECT, messageDto);
- return new JsonResult<>("send object", 200, message);
- }
+ // return new JsonResult<>("send object", 200, message);
+ // }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubMessage.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubMessage.java
index a693c9db..97e638a0 100644
--- a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubMessage.java
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubMessage.java
@@ -1,3 +1,17 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-04-15 17:13:01
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-30 16:05:07
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
package com.bytedesk.core.redis.pubsub;
import java.io.Serializable;
@@ -8,6 +22,7 @@
import lombok.NoArgsConstructor;
import lombok.Data;
+// java发送,python接收
@Data
@Builder
@AllArgsConstructor
@@ -19,4 +34,5 @@ public class RedisPubsubMessage implements Serializable {
private String type;
private String content;
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubMessageType.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubMessageType.java
new file mode 100644
index 00000000..c7a87581
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubMessageType.java
@@ -0,0 +1,42 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-28 14:45:30
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-30 16:42:34
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.redis.pubsub;
+
+public enum RedisPubsubMessageType {
+ PARSE_FILE,
+ PARSE_FILE_SUCCESS,
+ PARSE_FILE_ERROR,
+ //
+ DELETE_FILE,
+ DELETE_FILE_SUCCESS,
+ DELETE_FILE_ERROR,
+ //
+ QUESTION,
+ ANSWER,
+ ANSWER_FINISHED,
+
+ ;
+
+ // 根据字符串查找对应的枚举常量
+ public static RedisPubsubMessageType fromValue(String value) {
+ for (RedisPubsubMessageType type : RedisPubsubMessageType.values()) {
+ if (type.name().equalsIgnoreCase(value)) {
+ return type;
+ }
+ }
+ throw new IllegalArgumentException("No RedisPubsubMessageType constant with value: " + value);
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubObjectListener.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubObjectListener.java
index 4e18a6cd..a85a6e41 100644
--- a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubObjectListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubObjectListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-15 17:13:01
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-23 17:36:13
+ * @LastEditTime: 2024-08-30 16:15:25
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -26,7 +26,8 @@ public void onMessage(RedisPubsubMessage message, String pattern) {
log.info("redisPubsub receiveObject: topic: {} type {}, content: {} ",
pattern,
message.getType(),
- message.getContent());
+ message.getContent()
+ );
}
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubParseFileErrorEvent.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubParseFileErrorEvent.java
new file mode 100644
index 00000000..c5c6da66
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubParseFileErrorEvent.java
@@ -0,0 +1,37 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-30 17:38:05
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-30 17:39:46
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.redis.pubsub;
+
+import org.springframework.context.ApplicationEvent;
+
+import com.bytedesk.core.redis.pubsub.message.RedisPubsubMessageFile;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class RedisPubsubParseFileErrorEvent extends ApplicationEvent {
+
+ private static final long serialVersionUID = 1L;
+
+ private RedisPubsubMessageFile messageFile;
+
+ public RedisPubsubParseFileErrorEvent(Object source, RedisPubsubMessageFile messageFile) {
+ super(source);
+ this.messageFile = messageFile;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubParseFileSuccessEvent.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubParseFileSuccessEvent.java
new file mode 100644
index 00000000..e37974a5
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubParseFileSuccessEvent.java
@@ -0,0 +1,37 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-30 17:27:47
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-30 17:29:53
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.redis.pubsub;
+
+import org.springframework.context.ApplicationEvent;
+
+import com.bytedesk.core.redis.pubsub.message.RedisPubsubMessageFile;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class RedisPubsubParseFileSuccessEvent extends ApplicationEvent {
+
+ private static final long serialVersionUID = 1L;
+
+ private RedisPubsubMessageFile messageFile;
+
+ public RedisPubsubParseFileSuccessEvent(Object source, RedisPubsubMessageFile messageFile) {
+ super(source);
+ this.messageFile = messageFile;
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubService.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubService.java
index ba0ef976..faa86599 100644
--- a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-23 19:25:35
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-23 19:25:39
+ * @LastEditTime: 2024-08-31 08:06:12
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,11 +14,77 @@
*/
package com.bytedesk.core.redis.pubsub;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
+import com.alibaba.fastjson2.JSON;
+import com.bytedesk.core.redis.pubsub.message.RedisPubsubMessageFile;
+import com.bytedesk.core.redis.pubsub.message.RedisPubsubMessageQa;
+
@Service
public class RedisPubsubService {
+ @Autowired
+ private StringRedisTemplate stringRedisTemplate;
+
+ public void sendParseFileMessage(String fileUid, String fileUrl, String kbUid) {
+ //
+ RedisPubsubMessageFile messageFile = RedisPubsubMessageFile.builder()
+ .fileUid(fileUid)
+ .fileUrl(fileUrl)
+ .kbUid(kbUid)
+ .build();
+ //
+ RedisPubsubMessage messageObject = RedisPubsubMessage.builder()
+ .type(RedisPubsubMessageType.PARSE_FILE.name())
+ .content(JSON.toJSONString(messageFile))
+ .build();
+ //
+ stringRedisTemplate.convertAndSend(
+ RedisPubsubConst.BYTEDESK_PUBSUB_CHANNEL_STRING,
+ JSON.toJSONString(messageObject));
+ }
-
+ public void sendDeleteFileMessage(String fileUid, List docIds) {
+ //
+ RedisPubsubMessageFile messageFile = RedisPubsubMessageFile.builder()
+ .fileUid(fileUid)
+ .docIds(docIds)
+ .build();
+ //
+ RedisPubsubMessage messageObject = RedisPubsubMessage.builder()
+ .type(RedisPubsubMessageType.DELETE_FILE.name())
+ .content(JSON.toJSONString(messageFile))
+ .build();
+ //
+ stringRedisTemplate.convertAndSend(
+ RedisPubsubConst.BYTEDESK_PUBSUB_CHANNEL_STRING,
+ JSON.toJSONString(messageObject));
+ }
+
+ public void sendQuestionMessage(String uid, String threadTopic, String kbUid, String question) {
+ //
+ RedisPubsubMessageQa messageQa = RedisPubsubMessageQa.builder()
+ .uid(uid)
+ .threadTopic(threadTopic)
+ .kbUid(kbUid)
+ .question(question)
+ .build();
+ //
+ RedisPubsubMessage messageObject = RedisPubsubMessage.builder()
+ .type(RedisPubsubMessageType.QUESTION.name())
+ .content(JSON.toJSONString(messageQa))
+ .build();
+ //
+ stringRedisTemplate.convertAndSend(
+ RedisPubsubConst.BYTEDESK_PUBSUB_CHANNEL_STRING,
+ JSON.toJSONString(messageObject));
+ }
+
+
+
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubStringListener.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubStringListener.java
index e639bb97..3d0d7c8f 100644
--- a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubStringListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/RedisPubsubStringListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-15 17:13:01
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-23 17:36:36
+ * @LastEditTime: 2024-09-07 17:11:46
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,21 +14,121 @@
*/
package com.bytedesk.core.redis.pubsub;
+import java.util.LinkedList;
+import java.util.Queue;
+
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Service;
+
+import com.alibaba.fastjson2.JSON;
+import com.bytedesk.core.config.BytedeskEventPublisher;
+import com.bytedesk.core.event.GenericApplicationEvent;
+import com.bytedesk.core.message.MessageCache;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.message.MessageTypeEnum;
+import com.bytedesk.core.message.MessageUtils;
+import com.bytedesk.core.redis.pubsub.message.RedisPubsubMessageFile;
+import com.bytedesk.core.redis.pubsub.message.RedisPubsubMessageQa;
+import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
+@AllArgsConstructor
public class RedisPubsubStringListener implements MessageListener {
+
+ private final BytedeskEventPublisher eventPublisher;
+
+ private final MessageCache messageCache;
+
+ // private final BytedeskEventPublisher bytedeskEventPublisher;
+
+ private final Queue messageQueue = new LinkedList<>();
+
@Override
public void onMessage(Message message, byte[] pattern) {
// log.info("onMessage,{}", message.toString());
String channel = new String(message.getChannel());
- // log.info("channel:" + channel);
String messageContext = new String(message.getBody());
+ //
+ RedisPubsubMessage redisPubsubMessage = JSON.parseObject(messageContext, RedisPubsubMessage.class);
+ log.info("redisPubsub receiveString: {}, type {}", channel, redisPubsubMessage.getType());
+ //
+ if (redisPubsubMessage.getType().equals(RedisPubsubMessageType.PARSE_FILE_SUCCESS.name())) {
+ // 解析成功
+ log.info("parse file success, content {}", redisPubsubMessage.getContent());
+ RedisPubsubMessageFile messageFile = JSON.parseObject(redisPubsubMessage.getContent(),
+ RedisPubsubMessageFile.class);
+ log.info("fileUid {}, docIds {}", messageFile.getFileUid(), messageFile.getDocIds());
+ //
+ eventPublisher.publishGenericApplicationEvent(new GenericApplicationEvent(
+ this, new RedisPubsubParseFileSuccessEvent(this, messageFile)));
+
+ } else if (redisPubsubMessage.getType().equals(RedisPubsubMessageType.PARSE_FILE_ERROR.name())) {
+ // 解析失败
+ log.info("parse file error, content {}", redisPubsubMessage.getContent());
+ RedisPubsubMessageFile messageFile = JSON.parseObject(redisPubsubMessage.getContent(),
+ RedisPubsubMessageFile.class);
+ log.info("fileUid {}", messageFile.getFileUid());
+ //
+ eventPublisher.publishGenericApplicationEvent(new GenericApplicationEvent(
+ this, new RedisPubsubParseFileErrorEvent(this, messageFile)));
+
+ } else if (redisPubsubMessage.getType().equals(RedisPubsubMessageType.DELETE_FILE_SUCCESS.name())) {
+ // TODO: 删除成功
+ log.info("delete file success, content {}", redisPubsubMessage.getContent());
+
+ } else if (redisPubsubMessage.getType().equals(RedisPubsubMessageType.DELETE_FILE_ERROR.name())) {
+ // TODO: 删除失败
+ log.info("delete file error, content {}", redisPubsubMessage.getContent());
+
+ } else if (redisPubsubMessage.getType().equals(RedisPubsubMessageType.ANSWER.name())) {
+ // 回答
+ // answer {"threadTopic": "org/robot/df_rt_uid/1463055175142405", "kbUid":
+ // "1461090177253570", "question": "\u4f60\u597d", "answer":
+ // "\u53ef\u4ee5\u5e2e\u52a9", "model": "glm-4-flash", "created": 1725063232}
+ log.info("answer {}", redisPubsubMessage.getContent());
+ messageQueue.add(redisPubsubMessage); // 添加消息到队列
+ // sendMessage(redisPubsubMessage);
+ processMessageQueue();
+ //
+ } else if (redisPubsubMessage.getType().equals(RedisPubsubMessageType.ANSWER_FINISHED.name())) {
+ // TODO: 回答结束
+ // answer finished {"threadTopic": "org/robot/df_rt_uid/1463055175142405",
+ // "kbUid": "1461090177253570", "question": "\u4f60\u597d", "answer": "",
+ // "model": "glm-4-flash", "created": 1725063232, "promptTokens": 525,
+ // "completionTokens": 9, "totalTokens": 534}
+ log.info("answer finished {}", redisPubsubMessage.getContent());
+ messageQueue.add(redisPubsubMessage); // 添加消息到队列
+ // sendMessage(redisPubsubMessage);
+ processMessageQueue();
+ }
+ }
+
+ // FIXME: 直接调用sendMessage会导致消息乱序,增加messageQueue,还是消息乱序,未解决
+ private void processMessageQueue() {
+ while (!messageQueue.isEmpty()) {
+ RedisPubsubMessage redisPubsubMessage = messageQueue.poll();
+ sendMessage(redisPubsubMessage);
+ }
+ }
+
+ private void sendMessage(RedisPubsubMessage redisPubsubMessage) {
+ log.info("sendMessage, messageQa content {}", redisPubsubMessage.getContent());
+ RedisPubsubMessageQa messageQa = JSON.parseObject(redisPubsubMessage.getContent(),
+ RedisPubsubMessageQa.class);
+
+ log.info("sendMessage, messageQa Id {}", messageQa.getId());
+ MessageProtobuf messageProtobuf = messageCache.get(messageQa.getUid());
+ if (messageProtobuf == null) {
+ log.error("message not found, uid {}", messageQa.getUid());
+ return;
+ }
+ //
+ messageProtobuf.setType(MessageTypeEnum.STREAM);
+ messageProtobuf.setContent(messageQa.getAnswer());
//
- log.info("redisPubsub receiveString: {} content: {}", channel, messageContext);
+ MessageUtils.notifyUser(messageProtobuf);
}
-}
\ No newline at end of file
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/message/RedisPubsubMessageFile.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/message/RedisPubsubMessageFile.java
new file mode 100644
index 00000000..780f86d5
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/message/RedisPubsubMessageFile.java
@@ -0,0 +1,37 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-30 16:07:09
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-30 18:38:52
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.redis.pubsub.message;
+
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+@AllArgsConstructor
+public class RedisPubsubMessageFile {
+
+ private String fileUid;
+
+ private String fileUrl;
+
+ private String kbUid;
+
+ private List docIds;
+
+ private String errorMsg;
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/message/RedisPubsubMessageQa.java b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/message/RedisPubsubMessageQa.java
new file mode 100644
index 00000000..5059848e
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/pubsub/message/RedisPubsubMessageQa.java
@@ -0,0 +1,57 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-30 16:09:19
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-31 10:19:59
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.redis.pubsub.message;
+
+import jakarta.validation.constraints.NotEmpty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+@AllArgsConstructor
+public class RedisPubsubMessageQa {
+
+ private Integer id;
+
+ @NotEmpty
+ private String uid;
+
+ private String threadTopic;
+
+ //
+ private String kbUid;
+
+ private String fileUid;
+
+ //
+ private String question;
+
+ private String answer;
+
+ private String model;
+
+ private Integer created;
+
+ private String finishReason;
+
+ //
+ private String promptTokens;
+
+ private String completionTokens;
+
+ private String totalTokens;
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/redis/stream/RedisStreamConfig.java b/modules/core/src/main/java/com/bytedesk/core/redis/stream/RedisStreamConfig.java
index 8ad8cd71..647abdf1 100644
--- a/modules/core/src/main/java/com/bytedesk/core/redis/stream/RedisStreamConfig.java
+++ b/modules/core/src/main/java/com/bytedesk/core/redis/stream/RedisStreamConfig.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-23 10:53:47
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-23 16:38:40
+ * @LastEditTime: 2024-08-30 15:08:54
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,25 +14,8 @@
*/
package com.bytedesk.core.redis.stream;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.time.Duration;
-
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.connection.stream.Consumer;
-import org.springframework.data.redis.connection.stream.ObjectRecord;
-import org.springframework.data.redis.connection.stream.ReadOffset;
-import org.springframework.data.redis.connection.stream.StreamOffset;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.stream.StreamMessageListenerContainer;
-import org.springframework.data.redis.stream.Subscription;
-
-import com.bytedesk.core.redis.RedisEvent;
-
import lombok.extern.slf4j.Slf4j;
// https://howtodoinjava.com/spring-data/redis-streams-processing/
@@ -43,54 +26,54 @@ public class RedisStreamConfig {
@Value("${bytedesk.redis-stream-key}")
private String streamKey;
- @Autowired
- private RedisStreamListener redisStreamListener;
-
- @Autowired
- private RedisTemplate redisTemplate;
-
- @Bean
- public Subscription subscription(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
-
- String groupName = streamKey;
-
- try {
- // redisConnectionFactory.getConnection().streamCommands()
- // .xGroupCreate(streamKey.getBytes(), groupName, ReadOffset.from("0-0"), true);
- redisTemplate.opsForStream().createGroup(streamKey, groupName);
- } catch (Exception e) {
- // 如果异常信息表明组已存在,则可以选择忽略或进行其他处理
- if (e.getMessage().contains("BUSYGROUP Consumer Group name already exists")) {
- // 组已存在,根据业务需求选择是否要抛出异常或进行其他处理
- log.info("Consumer group '{}' already exists for stream '{}'", groupName, streamKey);
- } else {
- // 如果是其他异常,则重新抛出
- throw e;
- }
- }
-
- StreamOffset streamOffset = StreamOffset.create(streamKey, ReadOffset.lastConsumed());
-
- StreamMessageListenerContainer.StreamMessageListenerContainerOptions> options =
- StreamMessageListenerContainer.StreamMessageListenerContainerOptions
- .builder()
- .pollTimeout(Duration.ofMillis(100))
- .targetType(RedisEvent.class)
- .build();
-
- StreamMessageListenerContainer> container = StreamMessageListenerContainer
- .create(redisConnectionFactory, options);
-
- String hostName = InetAddress.getLocalHost().getHostName();
- Subscription subscription = container.receive(
- Consumer.from(streamKey, hostName),
- streamOffset, redisStreamListener);
-
- log.info("container start, hostName: {}", hostName);
- container.start();
-
- return subscription;
- }
+ // @Autowired
+ // private RedisStreamListener redisStreamListener;
+
+ // @Autowired
+ // private RedisTemplate redisTemplate;
+
+ // @Bean
+ // public Subscription subscription(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
+
+ // String groupName = streamKey;
+
+ // try {
+ // // redisConnectionFactory.getConnection().streamCommands()
+ // // .xGroupCreate(streamKey.getBytes(), groupName, ReadOffset.from("0-0"), true);
+ // redisTemplate.opsForStream().createGroup(streamKey, groupName);
+ // } catch (Exception e) {
+ // // 如果异常信息表明组已存在,则可以选择忽略或进行其他处理
+ // if (e.getMessage().contains("BUSYGROUP Consumer Group name already exists")) {
+ // // 组已存在,根据业务需求选择是否要抛出异常或进行其他处理
+ // log.info("Consumer group '{}' already exists for stream '{}'", groupName, streamKey);
+ // } else {
+ // // 如果是其他异常,则重新抛出
+ // throw e;
+ // }
+ // }
+
+ // StreamOffset streamOffset = StreamOffset.create(streamKey, ReadOffset.lastConsumed());
+
+ // StreamMessageListenerContainer.StreamMessageListenerContainerOptions> options =
+ // StreamMessageListenerContainer.StreamMessageListenerContainerOptions
+ // .builder()
+ // .pollTimeout(Duration.ofMillis(100))
+ // .targetType(RedisEvent.class)
+ // .build();
+
+ // StreamMessageListenerContainer> container = StreamMessageListenerContainer
+ // .create(redisConnectionFactory, options);
+
+ // String hostName = InetAddress.getLocalHost().getHostName();
+ // Subscription subscription = container.receive(
+ // Consumer.from(streamKey, hostName),
+ // streamOffset, redisStreamListener);
+
+ // log.info("container start, hostName: {}", hostName);
+ // container.start();
+
+ // return subscription;
+ // }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/connection/Connection.java b/modules/core/src/main/java/com/bytedesk/core/socket/connection/Connection.java
new file mode 100644
index 00000000..2e8498c4
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/connection/Connection.java
@@ -0,0 +1,50 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-10 09:52:44
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 12:24:44
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.socket.connection;
+
+import com.bytedesk.core.base.BaseEntity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * TODO: 持久化,当前客户端保持的长链接,
+ * 1. 便于在手机端查看pc客户端登录情况,
+ * 2. 在pc客户端查看手机端登录情况
+ * http://127.0.0.1:9003/mqtt/api/v1/clientIds
+ *
+ * @{MqttController}
+ */
+@Data
+@Entity
+@Builder
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@Table(name = "core_connection")
+public class Connection extends BaseEntity {
+
+ //
+
+
+ private String userUid;
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionController.java b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionController.java
new file mode 100644
index 00000000..bfdc2eb9
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionController.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-10 09:53:13
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 09:53:16
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.socket.connection;
+
+public class ConnectionController {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionRepository.java b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionRepository.java
new file mode 100644
index 00000000..48d73f98
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionRepository.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-10 09:54:05
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 09:54:08
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.socket.connection;
+
+public interface ConnectionRepository {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionRequest.java b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionRequest.java
new file mode 100644
index 00000000..967f2e1a
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionRequest.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-10 09:53:21
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 09:53:24
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.socket.connection;
+
+public class ConnectionRequest {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionResponse.java b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionResponse.java
new file mode 100644
index 00000000..4a942711
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionResponse.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-10 09:53:30
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 09:53:33
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.socket.connection;
+
+public class ConnectionResponse {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionService.java b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionService.java
new file mode 100644
index 00000000..cca47096
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionService.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-10 09:53:37
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 09:53:40
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.socket.connection;
+
+public class ConnectionService {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionSpecification.java b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionSpecification.java
new file mode 100644
index 00000000..aabf8615
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/connection/ConnectionSpecification.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-10 09:53:47
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-10 09:53:50
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.socket.connection;
+
+public class ConnectionSpecification {
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttController.java b/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttController.java
index cf2f9ebf..cd2a8839 100644
--- a/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/MqttController.java
@@ -29,13 +29,13 @@
@RestController
@AllArgsConstructor
-@RequestMapping("/visitor/api/v1/mqtt")
+@RequestMapping("/mqtt/api/v1")
public class MqttController {
private MqttSessionService mqttSessionService;
/**
- * http://127.0.0.1:9003/visitor/api/v1/mqtt/clientIds
+ * http://127.0.0.1:9003/mqtt/api/v1/clientIds
*
* @return
*/
diff --git a/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/protocol/PubAck.java b/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/protocol/PubAck.java
index b7fea37a..fa72eadf 100755
--- a/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/protocol/PubAck.java
+++ b/modules/core/src/main/java/com/bytedesk/core/socket/mqtt/protocol/PubAck.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:46
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-08 18:46:07
+ * @LastEditTime: 2024-09-11 11:16:44
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -28,7 +28,6 @@
@AllArgsConstructor
public class PubAck {
-
public void processPubAck(Channel channel, MqttMessageIdVariableHeader variableHeader) {
//
String clientId = ChannelUtils.getClientId(channel);
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/Thread.java b/modules/core/src/main/java/com/bytedesk/core/thread/Thread.java
index 3afadde0..784e42d7 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/Thread.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/Thread.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-27 14:34:30
+ * @LastEditTime: 2024-09-11 08:50:25
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -17,11 +17,14 @@
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
+import com.alibaba.fastjson2.JSON;
import com.bytedesk.core.base.BaseEntity;
import com.bytedesk.core.constant.BdConstants;
import com.bytedesk.core.constant.TypeConsts;
import com.bytedesk.core.enums.ClientEnum;
import com.bytedesk.core.rbac.user.User;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.utils.ConvertUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
@@ -153,10 +156,9 @@ public class Thread extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
private User owner;
- public Boolean isInit() {
- return this.status == ThreadStatusEnum.NORMAL.name();
- // return !StringUtils.hasText(this.content);
- }
+ // public Boolean isInit() {
+ // return this.status == ThreadStatusEnum.NORMAL.name();
+ // }
//
public Boolean isClosed() {
@@ -167,4 +169,16 @@ public Boolean isCustomerService() {
return this.type == ThreadTypeEnum.AGENT.name() || this.type == ThreadTypeEnum.WORKGROUP.name();
}
+ public ThreadProtobuf toProtobuf() {
+ return ConvertUtils.convertToThreadProtobuf(this);
+ }
+
+ public UserProtobuf getAgentProtobuf() {
+ return JSON.parseObject(this.agent, UserProtobuf.class);
+ }
+
+ public UserProtobuf getUserProtobuf() {
+ return JSON.parseObject(this.user, UserProtobuf.class);
+ }
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadController.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadController.java
index dafe1150..a61cad30 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadController.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-04 15:37:39
+ * @LastEditTime: 2024-09-07 16:41:04
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -92,6 +92,8 @@ public ResponseEntity> update(@RequestBody ThreadRequest request) {
@PostMapping("/close")
public ResponseEntity> close(@RequestBody ThreadRequest request) {
+ request.setStatus(ThreadStatusEnum.AGENT_CLOSED);
+
ThreadResponse threadResponse = threadService.close(request);
return ResponseEntity.ok(JsonResult.success(threadResponse));
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEventListener.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEventListener.java
index 5e7c4a8a..e9680205 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-28 13:32:23
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-27 14:39:41
+ * @LastEditTime: 2024-09-11 11:19:50
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,6 +14,7 @@
*/
package com.bytedesk.core.thread;
+import java.util.List;
import java.util.Optional;
import org.springframework.context.event.EventListener;
@@ -21,6 +22,8 @@
import com.bytedesk.core.message.Message;
import com.bytedesk.core.message.MessageCreateEvent;
+import com.bytedesk.core.message.MessageTypeEnum;
+import com.bytedesk.core.quartz.event.QuartzOneMinEvent;
import com.bytedesk.core.rbac.user.User;
import com.bytedesk.core.topic.TopicCacheService;
import com.bytedesk.core.topic.TopicRequest;
@@ -40,16 +43,19 @@ public class ThreadEventListener {
private final ThreadService threadService;
+ private final ThreadPersistCache threadPersistCache;
+
@EventListener
public void onThreadCreateEvent(ThreadCreateEvent event) {
Thread thread = event.getThread();
User user = thread.getOwner();
log.info("thread ThreadCreateEvent: {}", thread.getUid());
-
+
// // 机器人会话不需要订阅topic
// if (event.getThread().getType().equals(ThreadTypeEnum.ROBOT)) {
// return;
// }
+
// 创建客服会话之后,需要订阅topic
if (thread.getType().equals(ThreadTypeEnum.AGENT.name())
|| thread.getType().equals(ThreadTypeEnum.WORKGROUP.name())) {
@@ -60,9 +66,9 @@ public void onThreadCreateEvent(ThreadCreateEvent event) {
.build();
topicService.create(request);
} else if (thread.getType().equals(ThreadTypeEnum.MEMBER.name())
- || thread.getType().equals(ThreadTypeEnum.ASISTANT.name())
+ || thread.getType().equals(ThreadTypeEnum.ASISTANT.name())
|| thread.getType().equals(ThreadTypeEnum.CHANNEL.name())) {
- // 文件助手、系统通知会话延迟订阅topic
+ // 文件助手、系统通知会话延迟订阅topic
TopicRequest request = TopicRequest.builder()
.topic(thread.getTopic())
.userUid(user.getUid())
@@ -77,8 +83,7 @@ public void onThreadUpdateEvent(ThreadUpdateEvent event) {
User user = thread.getOwner();
log.info("topic onThreadUpdateEvent: {}", thread.getUid());
// TODO: 会话关闭之后,需要取消订阅
- //
- // 文件助手、系统通知会话延迟订阅topic
+
if (thread.getType().equals(ThreadTypeEnum.AGENT.name())
|| thread.getType().equals(ThreadTypeEnum.WORKGROUP.name())) {
// 防止首次消息延迟,立即订阅
@@ -90,6 +95,7 @@ public void onThreadUpdateEvent(ThreadUpdateEvent event) {
} else if (thread.getType().equals(ThreadTypeEnum.MEMBER.name())
|| thread.getType().equals(ThreadTypeEnum.ASISTANT.name())
|| thread.getType().equals(ThreadTypeEnum.CHANNEL.name())) {
+ // 文件助手、系统通知会话延迟订阅topic
TopicRequest request = TopicRequest.builder()
.topic(thread.getTopic())
.userUid(user.getUid())
@@ -105,18 +111,31 @@ public void onThreadUpdateEvent(ThreadUpdateEvent event) {
}
}
-
@EventListener
public void onMessageCreateEvent(MessageCreateEvent event) {
Message message = event.getMessage();
+ if (message.getType().equals(MessageTypeEnum.STREAM.name())) {
+ return;
+ }
// log.info("robot message unread create event: " + event);
Optional threadOptional = threadService.findByTopic(message.getThreadTopic());
if (threadOptional.isPresent()) {
Thread thread = threadOptional.get();
thread.setHide(false);
thread.setContent(message.getContent());
- threadService.save(thread);
+ // threadService.save(thread);
+ threadPersistCache.pushForPersist(thread);
}
}
+ @EventListener
+ public void onQuartzOneMinEvent(QuartzOneMinEvent event) {
+ List threadList = threadPersistCache.getListForPersist();
+ if (threadList != null) {
+ threadList.forEach(thread -> {
+ threadService.save(thread);
+ });
+ }
+
+ }
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadExtra.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadExtra.java
new file mode 100644
index 00000000..9391f4e4
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadExtra.java
@@ -0,0 +1,23 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 08:11:16
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 08:11:19
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.thread;
+
+import java.io.Serializable;
+
+public class ThreadExtra implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadPersistCache.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadPersistCache.java
new file mode 100644
index 00000000..49818bbf
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadPersistCache.java
@@ -0,0 +1,111 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-31 10:43:14
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-31 11:03:26
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.thread;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.stereotype.Component;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.CacheLoader;
+import com.github.benmanes.caffeine.cache.Caffeine;
+
+import jakarta.annotation.PostConstruct;
+
+@Component
+public class ThreadPersistCache {
+
+ // 假设我们使用"myList"作为缓存中的键
+ String defaultPersistKey = "threadList";
+
+ // 创建一个缓存实例,设置过期时间为5天
+ private Cache> threadCache;
+
+ @PostConstruct
+ public void init() {
+ // 初始化caffeinecache,设置缓存的最大大小、过期时间等参数
+ threadCache = Caffeine.newBuilder()
+ .expireAfterWrite(1, TimeUnit.DAYS)
+ .build(new CacheLoader>() {
+ @Override
+ public List load(String key) throws Exception {
+ // 当缓存中没有找到对应的键时,使用load方法初始化
+ return new ArrayList<>();
+ }
+ });
+ }
+
+ // 模拟 push 操作:向列表中添加元素
+ public void pushForPersist(Thread thread) {
+ // 通过thread.uid判断defaultPersistKey中是否已经存在 则替换掉,不存在,则插入
+ String uid = thread.getUid();
+ List cachedList = threadCache.getIfPresent(defaultPersistKey);
+ if (cachedList == null) {
+ cachedList = new ArrayList<>();
+ }
+
+ boolean found = false;
+ for (int i = 0; i < cachedList.size(); i++) {
+ if (cachedList.get(i).getUid().equals(uid)) {
+ found = true;
+ cachedList.set(i, thread); // 替换已存在的Thread对象
+ break;
+ }
+ }
+
+ if (!found) {
+ cachedList.add(thread);
+ }
+ threadCache.put(defaultPersistKey, cachedList);
+ // push(defaultPersistKey, thread);
+ }
+
+ // 模拟 pop 操作:从列表中移除元素
+ public List getListForPersist() {
+ return getList(defaultPersistKey);
+ }
+
+ // 模拟 push 操作:向列表中添加元素
+ public void push(String listKey, Thread thread) {
+ List cachedList = threadCache.getIfPresent(listKey);
+ if (cachedList == null) {
+ // 如果缓存中没有找到对应的键,则使用load方法初始化
+ cachedList = new ArrayList<>();
+ }
+ cachedList.add(thread);
+ threadCache.put(listKey, cachedList);
+ }
+
+ public List getList(String listKey) {
+ List cachedList = threadCache.getIfPresent(listKey);
+ if (cachedList != null && !cachedList.isEmpty()) {
+ // 只需要返回一次即可
+ remove(listKey);
+ return cachedList;
+ }
+ return null;
+ }
+
+ public void remove(String listKey) {
+ threadCache.invalidate(listKey);
+ }
+
+ public void clear() {
+ threadCache.invalidateAll();
+ }
+
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRepository.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRepository.java
index 61db02f4..fe647d5d 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRepository.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadRepository.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:56:56
+ * @LastEditTime: 2024-09-04 09:24:54
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -45,16 +45,28 @@ public interface ThreadRepository extends JpaRepository, JpaSpecif
Optional findFirstByTopicAndDeleted(String topic, Boolean deleted);
+ Optional findFirstByTopicAndStatusNotContainingAndDeleted(String topic, String status, Boolean deleted);
+
+ // @Query(value = "select * from core_thread t where t.topic like ?1 and t.status not in ?2 and t.is_deleted = ?3", nativeQuery = true)
+ @Query(value = "select * from core_thread t where t.topic = ?1 and t.status not in ?2 and t.is_deleted = ?3", nativeQuery = true)
+ Optional findFirstTopicAndStatusesNotInAndDeleted(String topicWithWildcard,
+ List statuses,
+ Boolean deleted);
+
Page findByOwnerAndHideAndDeleted(User owner, Boolean hide, Boolean deleted, Pageable pageable);
List findByTopic(String topic);
+
// FIXME: h2不兼容 JSON_EXTRACT
// FIXME: PostgreSQL ERROR: function json_extract(json, unknown) does not exist
- // @Query(value = "SELECT * FROM core_thread WHERE JSON_EXTRACT(extra,'$.closed') = false", nativeQuery = true)
+ // @Query(value = "SELECT * FROM core_thread WHERE
+ // JSON_EXTRACT(extra,'$.closed') = false", nativeQuery = true)
List findByStatusAndDeleted(String status, Boolean deleted);
@Query("SELECT t FROM Thread t WHERE t.status IN :statuses AND t.deleted = :deleted")
List findByStatusesAndDeleted(@Param("statuses") List statuses, Boolean deleted);
- // Page findByOrgUidAndDeleted(String orgUid, Boolean deleted, Pageable pageable);
+ @Query("SELECT t FROM Thread t WHERE t.type IN :types AND t.status not IN :statuses AND t.deleted = :deleted")
+ List findByTypesInAndStatusesNotInAndDeleted(@Param("types") List types, @Param("statuses") List statuses, Boolean deleted);
+
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadService.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadService.java
index fd31cd1b..b740acbd 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:57:09
+ * @LastEditTime: 2024-09-07 19:00:26
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -35,13 +35,16 @@
import com.alibaba.fastjson2.JSON;
import com.bytedesk.core.base.BaseService;
-import com.bytedesk.core.constant.AvatarConsts;
-import com.bytedesk.core.constant.I18Consts;
import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.message.MessageTypeEnum;
+import com.bytedesk.core.message.MessageUtils;
import com.bytedesk.core.rbac.auth.AuthService;
import com.bytedesk.core.rbac.user.User;
import com.bytedesk.core.constant.BdConstants;
+import com.bytedesk.core.constant.I18Consts;
import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.rbac.user.UserUtils;
import com.bytedesk.core.topic.TopicUtils;
import com.bytedesk.core.uid.UidUtils;
@@ -118,7 +121,7 @@ public ThreadResponse create(ThreadRequest request) {
thread.setClient(ClientEnum.fromValue(request.getClient()).name());
thread.setOwner(owner);
thread.setOrgUid(owner.getOrgUid());
-
+
//
Thread savedThread = save(thread);
if (savedThread == null) {
@@ -127,7 +130,7 @@ public ThreadResponse create(ThreadRequest request) {
//
return convertToResponse(savedThread);
}
-
+
// 在group会话创建之后,自动为group成员members创建会话
// 同事群组会话:org/group/{group_uid}
public ThreadResponse createGroupMemberThread(Thread thread, User owner) {
@@ -159,7 +162,7 @@ public ThreadResponse createGroupMemberThread(Thread thread, User owner) {
/** 文件助手会话:file/{user_uid} */
public ThreadResponse createFileAsistantThread(User user) {
- //
+ //
String topic = TopicUtils.TOPIC_FILE_PREFIX + user.getUid();
//
Optional threadOptional = findByTopicAndOwner(topic, user);
@@ -167,11 +170,7 @@ public ThreadResponse createFileAsistantThread(User user) {
return convertToResponse(threadOptional.get());
}
- UserProtobuf userSimple = UserProtobuf.builder()
- .nickname(I18Consts.I18N_FILE_ASISTANT_NAME)
- .avatar(AvatarConsts.DEFAULT_FILE_ASISTANT_AVATAR_URL)
- .build();
- userSimple.setUid(BdConstants.DEFAULT_FILE_ASISTANT_UID);
+ UserProtobuf userSimple = UserUtils.getFileAsistantUser();
//
Thread asistantThread = Thread.builder()
.type(ThreadTypeEnum.ASISTANT.name())
@@ -199,19 +198,16 @@ public ThreadResponse createFileAsistantThread(User user) {
// 系统通知会话:system/{user_uid}
public ThreadResponse createSystemChannelThread(User user) {
- //
- String topic = TopicUtils.TOPIC_SYSTEM_PREFIX + user.getUid();
+ //
+ // String topic = TopicUtils.TOPIC_SYSTEM_PREFIX + user.getUid();
+ String topic = TopicUtils.getSystemTopic(user.getUid());
//
Optional threadOptional = findByTopicAndOwner(topic, user);
if (threadOptional.isPresent()) {
return convertToResponse(threadOptional.get());
}
- UserProtobuf userSimple = UserProtobuf.builder()
- .nickname(I18Consts.I18N_SYSTEM_NOTIFICATION_NAME)
- .avatar(AvatarConsts.DEFAULT_SYSTEM_NOTIFICATION_AVATAR_URL)
- .build();
- userSimple.setUid(BdConstants.DEFAULT_SYSTEM_UID);
+ UserProtobuf userSimple = UserUtils.getSystemChannelUser();
//
Thread noticeThread = Thread.builder()
.type(ThreadTypeEnum.CHANNEL.name())
@@ -228,7 +224,7 @@ public ThreadResponse createSystemChannelThread(User user) {
} else {
noticeThread.setOrgUid(BdConstants.DEFAULT_ORGANIZATION_UID);
}
- //
+ //
Thread updateThread = save(noticeThread);
if (updateThread == null) {
throw new RuntimeException("thread save failed");
@@ -238,9 +234,10 @@ public ThreadResponse createSystemChannelThread(User user) {
}
public ThreadResponse update(ThreadRequest threadRequest) {
- Optional threadOptional = findByUid(threadRequest.getUid());
+ // Optional threadOptional = findByUid(threadRequest.getUid());
+ Optional threadOptional = findByTopic(threadRequest.getTopic());
if (!threadOptional.isPresent()) {
- throw new RuntimeException("thread not found");
+ throw new RuntimeException("update thread " + threadRequest.getTopic() + " not found");
}
//
Thread thread = threadOptional.get();
@@ -260,25 +257,44 @@ public ThreadResponse update(ThreadRequest threadRequest) {
return convertToResponse(updateThread);
}
+ public ThreadResponse autoClose(Thread thread) {
+ // log.info(thread.getUid() + "自动关闭");
+ // thread.setStatus(ThreadStatusEnum.AUTO_CLOSED.name());
+ // return save(thread);
+ ThreadRequest threadRequest = ThreadRequest.builder()
+ .topic(thread.getTopic())
+ .status(ThreadStatusEnum.AUTO_CLOSED)
+ .build();
+ return close(threadRequest);
+ }
+
public ThreadResponse close(ThreadRequest threadRequest) {
- Optional threadOptional = findByUid(threadRequest.getUid());
+ // Optional threadOptional = findByUid(threadRequest.getUid());
+ Optional threadOptional = findByTopic(threadRequest.getTopic());
if (!threadOptional.isPresent()) {
- throw new RuntimeException("thread not found");
+ throw new RuntimeException("close thread " + threadRequest.getTopic() + " not found");
}
//
Thread thread = threadOptional.get();
//
- if (ThreadStatusEnum.AGENT_CLOSED.equals(thread.getStatus())
- || ThreadStatusEnum.AUTO_CLOSED.equals(thread.getStatus())) {
+ if (ThreadStatusEnum.AGENT_CLOSED.name().equals(thread.getStatus())
+ || ThreadStatusEnum.AUTO_CLOSED.name().equals(thread.getStatus())) {
// log.info("thread {} is already closed", uid);
throw new RuntimeException("thread is already closed");
}
- thread.setStatus(ThreadStatusEnum.AGENT_CLOSED.name());
+ // thread.setStatus(ThreadStatusEnum.AGENT_CLOSED.name());
+ thread.setStatus(threadRequest.getStatus().name());
//
Thread updateThread = save(thread);
if (updateThread == null) {
throw new RuntimeException("thread save failed");
}
+ // 发布关闭消息, 通知用户
+ String content = threadRequest.getStatus().equals(ThreadStatusEnum.AUTO_CLOSED) ? I18Consts.I18N_AUTO_CLOSED : I18Consts.I18N_AGENT_CLOSED;
+ MessageProtobuf messageProtobuf = MessageUtils.createThreadMessage(uidUtils.getCacheSerialUid(), updateThread,
+ MessageTypeEnum.fromValue(threadRequest.getStatus().name()),
+ content);
+ MessageUtils.notifyUser(messageProtobuf);
//
return convertToResponse(updateThread);
}
@@ -318,6 +334,21 @@ public Optional findByTopic(String topic) {
return threadRepository.findFirstByTopicAndDeleted(topic, false);
}
+ // 找到某个访客对应某个一对一客服未关闭会话
+ @Cacheable(value = "thread", key = "#topic", unless = "#result == null")
+ public Optional findByTopicNotClosed(String topic, String status) {
+ return threadRepository.findFirstByTopicAndStatusNotContainingAndDeleted(topic, "CLOSED", false);
+ }
+
+ // 找到某个访客当前对应某技能组未关闭会话
+ @Cacheable(value = "thread", key = "#workgroupUid + '-' + #visitorUid", unless = "#result == null")
+ public Optional findByWgTopicNotClosed(String topic) {
+ // String likeTopic = TopicUtils.TOPIC_ORG_WORKGROUP_PREFIX + workgroupUid + "/%/" + visitorUid;
+ List statuses = Arrays
+ .asList(new String[] { ThreadStatusEnum.AGENT_CLOSED.name(), ThreadStatusEnum.AUTO_CLOSED.name() });
+ return threadRepository.findFirstTopicAndStatusesNotInAndDeleted(topic, statuses, false);
+ }
+
// TODO: how to cacheput or cacheevict?
@Cacheable(value = "thread", key = "#user.uid-#pageable.getPageNumber()", unless = "#result == null")
public Page findByOwner(User user, Pageable pageable) {
@@ -327,14 +358,15 @@ public Page findByOwner(User user, Pageable pageable) {
// TODO: 更新缓存
// @Cacheable(value = "threadOpen")
public List findStatusOpen() {
+ List types = Arrays.asList(new String[] { ThreadTypeEnum.AGENT.name(), ThreadTypeEnum.WORKGROUP.name(), ThreadTypeEnum.ROBOT.name() });
List statuses = Arrays
- .asList(new String[] { ThreadStatusEnum.NORMAL.name(), ThreadStatusEnum.CONTINUE.name() });
- return threadRepository.findByStatusesAndDeleted(statuses, false);
+ .asList(new String[] { ThreadStatusEnum.AUTO_CLOSED.name(), ThreadStatusEnum.AGENT_CLOSED.name() });
+ return threadRepository.findByTypesInAndStatusesNotInAndDeleted(types, statuses, false);
}
public Boolean isClosed(Thread thread) {
- return ThreadStatusEnum.AGENT_CLOSED.equals(thread.getStatus())
- || ThreadStatusEnum.AUTO_CLOSED.equals(thread.getStatus());
+ return ThreadStatusEnum.AGENT_CLOSED.name().equals(thread.getStatus())
+ || ThreadStatusEnum.AUTO_CLOSED.name().equals(thread.getStatus());
}
// public Thread reenter(Thread thread) {
@@ -346,10 +378,7 @@ public Boolean isClosed(Thread thread) {
// return save(thread);
// }
- public Thread autoClose(Thread thread) {
- thread.setStatus(ThreadStatusEnum.AUTO_CLOSED.name());
- return save(thread);
- }
+
// public Thread agentClose(Thread thread) {
// thread.setStatus(ThreadStatusEnum.AGENT_CLOSED);
@@ -376,7 +405,8 @@ public Thread save(@NonNull Thread thread) {
})
public void delete(@NonNull Thread entity) {
// threadRepository.delete(thread);
- Optional threadOptional = findByUid(entity.getUid());
+ // Optional threadOptional = findByUid(entity.getUid());
+ Optional threadOptional = findByTopic(entity.getTopic());
threadOptional.ifPresent(thread -> {
thread.setDeleted(true);
save(thread);
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadTypeEnum.java
index 0f9d714d..93d25c46 100644
--- a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadTypeEnum.java
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadTypeEnum.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-29 16:32:15
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-01 18:33:38
+ * @LastEditTime: 2024-08-30 11:13:05
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -26,7 +26,11 @@ public enum ThreadTypeEnum {
CHANNEL(8),
LOCAL(9),
FRIEND(10),
- TICKET(11);
+ TICKET(11),
+ KB(12),
+ KBDOC(13),
+ AGENTASISTANT(14),
+ ;
private final int value;
diff --git a/modules/core/src/main/java/com/bytedesk/core/thread/ThreadUtils.java b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadUtils.java
new file mode 100644
index 00000000..3408859a
--- /dev/null
+++ b/modules/core/src/main/java/com/bytedesk/core/thread/ThreadUtils.java
@@ -0,0 +1,28 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-07-28 06:48:10
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-31 17:01:00
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.core.thread;
+
+import com.bytedesk.core.rbac.user.UserProtobuf;
+
+public class ThreadUtils {
+
+ public static ThreadProtobuf getThreadProtobuf(String topic, ThreadTypeEnum type, UserProtobuf user) {
+ return ThreadProtobuf.builder()
+ .topic(topic)
+ .type(type)
+ .user(user)
+ .build();
+ }
+}
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/Topic.java b/modules/core/src/main/java/com/bytedesk/core/topic/Topic.java
index fcb68737..4a32acfe 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/Topic.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/Topic.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-13 16:03:44
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-03 07:22:42
+ * @LastEditTime: 2024-09-07 12:51:48
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -54,6 +54,12 @@ public class Topic extends BaseEntityNoOrg {
private Set topics = new HashSet<>();
// private String topic;
+ // 管理员监控的topic
+ @Builder.Default
+ @Column(columnDefinition = TypeConsts.COLUMN_TYPE_TEXT)
+ @Convert(converter = StringSetConverter.class)
+ private Set monitorTopics = new HashSet<>();
+
// 每个用户仅存在一条记录
// user, no need map, just uid
@NotBlank
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/TopicCacheService.java b/modules/core/src/main/java/com/bytedesk/core/topic/TopicCacheService.java
index 0afb8625..789d208e 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/TopicCacheService.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/TopicCacheService.java
@@ -25,12 +25,22 @@
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
+import jakarta.annotation.PostConstruct;
+
@Service
public class TopicCacheService {
+ // 假设我们使用"myList"作为缓存中的键
+ String defaultCacheKey = "topicList";
+
// 创建一个缓存实例,设置过期时间为5天
- Cache> topicCache = Caffeine.newBuilder()
- .expireAfterWrite(5, TimeUnit.DAYS)
+ private Cache> topicCache;
+
+ @PostConstruct
+ public void init() {
+ // 初始化caffeinecache,设置缓存的最大大小、过期时间等参数
+ topicCache = Caffeine.newBuilder()
+ .expireAfterWrite(1, TimeUnit.DAYS)
.build(new CacheLoader>() {
@Override
public List load(String key) throws Exception {
@@ -38,9 +48,7 @@ public List load(String key) throws Exception {
return new ArrayList<>();
}
});
-
- // 假设我们使用"myList"作为缓存中的键
- String defaultCacheKey = "topicList";
+ }
// 模拟 push 操作:向列表中添加元素
public void push(String messageJSON) {
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/TopicEventListener.java b/modules/core/src/main/java/com/bytedesk/core/topic/TopicEventListener.java
index be513214..29adf286 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/TopicEventListener.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/TopicEventListener.java
@@ -19,7 +19,8 @@
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
-import com.bytedesk.core.quartz.QuartzFiveSecondEvent;
+import com.bytedesk.core.quartz.event.QuartzFiveSecondEvent;
+
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/TopicResponse.java b/modules/core/src/main/java/com/bytedesk/core/topic/TopicResponse.java
index 839181cc..a00d3e29 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/TopicResponse.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/TopicResponse.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-13 16:15:22
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-05-04 10:42:57
+ * @LastEditTime: 2024-09-07 12:52:48
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -40,8 +40,10 @@ public class TopicResponse extends BaseResponse {
// private String uid;
@Builder.Default
- // private String topic;
private Set topics = new HashSet<>();
+
+ @Builder.Default
+ private Set monitorTopics = new HashSet<>();
private String userUid;
diff --git a/modules/core/src/main/java/com/bytedesk/core/topic/TopicUtils.java b/modules/core/src/main/java/com/bytedesk/core/topic/TopicUtils.java
index 6216c815..c88fc301 100644
--- a/modules/core/src/main/java/com/bytedesk/core/topic/TopicUtils.java
+++ b/modules/core/src/main/java/com/bytedesk/core/topic/TopicUtils.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-26 21:51:31
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-05 13:33:35
+ * @LastEditTime: 2024-09-06 23:07:16
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -38,6 +38,8 @@ private TopicUtils() {
public static final String TOPIC_ORG_ROBOT_PREFIX = "org/robot/";
public static final String TOPIC_ORG_AGENT_PREFIX = "org/agent/";
public static final String TOPIC_ORG_WORKGROUP_PREFIX = "org/workgroup/";
+ public static final String TOPIC_ORG_KB_PREFIX = "org/kb/";
+ public static final String TOPIC_ORG_KBDOC_PREFIX = "org/kbdoc/";
// topic格式定义:
// 注意:开头没有 '/' ,防止stomp主题中将 '/' 替换为 '.'之后,在最前面多余一个 '.'
@@ -69,16 +71,20 @@ private TopicUtils() {
private static final String TOPIC_ORG_DEPARTMENT_PATTERN = TOPIC_ORG_DEPARTMENT_PREFIX + "%s"; // "org/department/%s";
private static final String TOPIC_ORG_GROUP_PATTERN = TOPIC_ORG_GROUP_PREFIX + "%s"; // "org/group/%s";
private static final String TOPIC_ORG_ROBOT_THREAD_PATTERN = TOPIC_ORG_ROBOT_PREFIX + "%s/%s"; // "org/robot/%s/%s";
+ private static final String TOPIC_ORG_KB_THREAD_PATTERN = TOPIC_ORG_KB_PREFIX + "%s/%s"; // "org/kb/%s/%s";
+ private static final String TOPIC_ORG_KBDOC_THREAD_PATTERN = TOPIC_ORG_KBDOC_PREFIX + "%s/%s"; // "org/kbdoc/%s/%s";
// 客服:
// 用户默认订阅客服uid:org/agent/{agent_uid}
// 一对一客服会话:org/agent/{agent_uid}/{visitor_uid}
// 用户默认订阅技能组uid:org/workgroup/{workgroup_uid}
- // 技能组客服会话:org/workgroup/{workgroup_uid}/{agent_uid}/{visitor_uid}
+ // 废弃:技能组客服会话:org/workgroup/{workgroup_uid}/{agent_uid}/{visitor_uid}
+ // 技能组客服会话:org/workgroup/{workgroup_uid}/{visitor_uid}
private static final String TOPIC_ORG_AGENT_PATTERN = TOPIC_ORG_AGENT_PREFIX + "%s"; // "org/agent/%s";
private static final String TOPIC_ORG_AGENT_THREAD_PATTERN = TOPIC_ORG_AGENT_PREFIX + "%s/%s"; // "org/agent/%s/%s";
private static final String TOPIC_ORG_WORKGROUP_PATTERN = TOPIC_ORG_WORKGROUP_PREFIX + "%s"; // "org/workgroup/%s";
- private static final String TOPIC_ORG_WORKGROUP_THREAD_PATTERN = TOPIC_ORG_WORKGROUP_PREFIX + "%s/%s/%s"; // "org/workgroup/%s/%s/%s";
+ // private static final String TOPIC_ORG_WORKGROUP_THREAD_PATTERN = TOPIC_ORG_WORKGROUP_PREFIX + "%s/%s/%s"; // "org/workgroup/%s/%s/%s";
+ private static final String TOPIC_ORG_WORKGROUP_THREAD_PATTERN = TOPIC_ORG_WORKGROUP_PREFIX + "%s/%s"; // "org/workgroup/%s/%s";
//
public static String getUserTopic(String userUid) {
@@ -120,6 +126,7 @@ public static String formatOrgDepartmentTopic(String departmentUid) {
public static Boolean isOrgMemberTopic(String topic) {
return topic.startsWith(TOPIC_ORG_MEMBER_PATTERN);
}
+
public static String formatOrgMemberTopic(String memberUid) {
return String.format(TOPIC_ORG_MEMBER_PATTERN, memberUid);
}
@@ -145,10 +152,9 @@ public static Boolean isOrgGroupTopic(String topic) {
public static String getOrgGroupTopic(String groupUid) {
return String.format(TOPIC_ORG_GROUP_PATTERN, groupUid);
}
-
+
//////////////////////////////////////////////////////////////////////////
-
//////////////////////////////////////////////////////////////////////////
public static Boolean isOrgRobotTopic(String topic) {
@@ -161,6 +167,26 @@ public static String formatOrgRobotThreadTopic(String robotUid, String visitorUi
//////////////////////////////////////////////////////////////////////////
+ public static Boolean isOrgKbTopic(String topic) {
+ return topic.startsWith(TOPIC_ORG_KB_PREFIX);
+ }
+
+ public static String formatOrgKbThreadTopic(String kbUid, String visitorUid) {
+ return String.format(TOPIC_ORG_KB_THREAD_PATTERN, kbUid, visitorUid);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+
+ public static Boolean isOrgKbdocTopic(String topic) {
+ return topic.startsWith(TOPIC_ORG_KBDOC_PREFIX);
+ }
+
+ public static String formatOrgKbdocThreadTopic(String kbdocUid, String visitorUid) {
+ return String.format(TOPIC_ORG_KBDOC_THREAD_PATTERN, kbdocUid, visitorUid);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+
public static Boolean isOrgAgentTopic(String topic) {
return topic.startsWith(TOPIC_ORG_AGENT_PREFIX);
}
@@ -183,8 +209,10 @@ public static String getOrgWorkgroupTopic(String workgroupUid) {
return String.format(TOPIC_ORG_WORKGROUP_PATTERN, workgroupUid);
}
- public static String formatOrgWorkgroupThreadTopic(String workgroupUid, String agentUid, String visitorUid) {
- return String.format(TOPIC_ORG_WORKGROUP_THREAD_PATTERN, workgroupUid, agentUid, visitorUid);
+ // public static String formatOrgWorkgroupThreadTopic(String workgroupUid, String agentUid, String visitorUid) {
+ // return String.format(TOPIC_ORG_WORKGROUP_THREAD_PATTERN, workgroupUid, agentUid, visitorUid);
+ // }
+ public static String formatOrgWorkgroupThreadTopic(String workgroupUid, String visitorUid) {
+ return String.format(TOPIC_ORG_WORKGROUP_THREAD_PATTERN, workgroupUid, visitorUid);
}
-
}
diff --git a/modules/core/src/main/java/com/bytedesk/core/uid/UidUtils.java b/modules/core/src/main/java/com/bytedesk/core/uid/UidUtils.java
index c4caf0b1..701a132c 100644
--- a/modules/core/src/main/java/com/bytedesk/core/uid/UidUtils.java
+++ b/modules/core/src/main/java/com/bytedesk/core/uid/UidUtils.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-07 15:39:15
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-04-09 17:49:09
+ * @LastEditTime: 2024-08-30 10:55:04
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -20,6 +20,8 @@
import com.bytedesk.core.uid.impl.CachedUidGenerator;
import com.bytedesk.core.uid.impl.DefaultUidGenerator;
+import jakarta.annotation.PostConstruct;
+
// import lombok.extern.slf4j.Slf4j;
/**
@@ -36,6 +38,18 @@ public class UidUtils {
@Autowired
private CachedUidGenerator cachedUidGenerator;
+ private static UidUtils instance;
+
+ @PostConstruct
+ public void init() {
+ instance = this;
+ }
+
+ // 使用方法:String uid = UidUtils.getInstance().getDefaultSerialUid();
+ public static UidUtils getInstance() {
+ return instance;
+ }
+
/**
* 实时生成
* 性能有损耗
diff --git a/modules/core/src/main/java/com/bytedesk/core/utils/Utils.java b/modules/core/src/main/java/com/bytedesk/core/utils/Utils.java
index 8644cda8..90545ccd 100644
--- a/modules/core/src/main/java/com/bytedesk/core/utils/Utils.java
+++ b/modules/core/src/main/java/com/bytedesk/core/utils/Utils.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-01 10:22:19
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-23 18:08:54
+ * @LastEditTime: 2024-09-09 16:21:46
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -79,7 +79,7 @@ public static boolean isTestEmail(String email) {
*
* @return
*/
- public static String getRandomCode(String key) {
+ public static String getRandomCode() {
int min = 100001;
int max = 999998;
int code = new Random().nextInt(max) % (max - min + 1) + min;
diff --git a/modules/core/src/main/proto/user.proto b/modules/core/src/main/proto/user.proto
index 88c47556..8563150d 100644
--- a/modules/core/src/main/proto/user.proto
+++ b/modules/core/src/main/proto/user.proto
@@ -26,6 +26,8 @@ message User {
string nickname = 2;
// 头像
string avatar = 3;
+ // 类型
+ string type = 4;
// 自定义扩展/附加信息
- string extra = 4;
+ string extra = 5;
}
diff --git a/modules/kbase/pom.xml b/modules/kbase/pom.xml
index 06546981..0d56007c 100644
--- a/modules/kbase/pom.xml
+++ b/modules/kbase/pom.xml
@@ -70,6 +70,8 @@
+
+
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/auto_reply/AutoReplyEventListener.java b/modules/kbase/src/main/java/com/bytedesk/kbase/auto_reply/AutoReplyEventListener.java
new file mode 100644
index 00000000..de0c652c
--- /dev/null
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/auto_reply/AutoReplyEventListener.java
@@ -0,0 +1,23 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 16:17:49
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 16:17:53
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.kbase.auto_reply;
+
+/**
+ * 迁移至 AutoReplyVipEventListener
+ * 此处不做实现
+ */
+public class AutoReplyEventListener {
+
+}
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/faq/FaqEventListener.java b/modules/kbase/src/main/java/com/bytedesk/kbase/faq/FaqEventListener.java
new file mode 100644
index 00000000..ed2019fb
--- /dev/null
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/faq/FaqEventListener.java
@@ -0,0 +1,23 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 15:42:23
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 16:16:54
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.kbase.faq;
+
+/**
+ * 迁移至 FaqVipEventListener
+ * 此处不做实现
+ */
+public class FaqEventListener {
+
+}
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/keyword/KeywordEventListener.java b/modules/kbase/src/main/java/com/bytedesk/kbase/keyword/KeywordEventListener.java
new file mode 100644
index 00000000..535c2295
--- /dev/null
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/keyword/KeywordEventListener.java
@@ -0,0 +1,23 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 16:19:24
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 16:20:35
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.kbase.keyword;
+
+/**
+ * 迁移至 KeywordVipEventListener
+ * 此处不做实现
+ */
+public class KeywordEventListener {
+
+}
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/knowledge_base/KnowledgebaseEventListener.java b/modules/kbase/src/main/java/com/bytedesk/kbase/knowledge_base/KnowledgebaseEventListener.java
index 0e9a8c91..e0d21ceb 100644
--- a/modules/kbase/src/main/java/com/bytedesk/kbase/knowledge_base/KnowledgebaseEventListener.java
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/knowledge_base/KnowledgebaseEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-08-27 13:53:22
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-27 15:09:54
+ * @LastEditTime: 2024-09-07 17:11:09
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,12 +14,38 @@
*/
package com.bytedesk.kbase.knowledge_base;
+import java.io.IOException;
+import java.util.Date;
+import java.util.Optional;
+
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
+import org.springframework.util.SerializationUtils;
+import org.springframework.util.StringUtils;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.bytedesk.core.config.BytedeskProperties;
+import com.bytedesk.core.enums.ClientEnum;
import com.bytedesk.core.enums.LanguageEnum;
+import com.bytedesk.core.message.MessageCache;
+import com.bytedesk.core.message.MessageExtra;
+import com.bytedesk.core.message.MessageJsonEvent;
+import com.bytedesk.core.message.MessageProtoEvent;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.message.MessageStatusEnum;
+import com.bytedesk.core.message.MessageTypeEnum;
+import com.bytedesk.core.message.MessageUtils;
import com.bytedesk.core.rbac.organization.Organization;
import com.bytedesk.core.rbac.organization.OrganizationCreateEvent;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.redis.pubsub.RedisPubsubService;
+import com.bytedesk.core.socket.protobuf.model.MessageProto;
+import com.bytedesk.core.thread.ThreadProtobuf;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.core.utils.MessageConvertUtils;
+import com.google.protobuf.InvalidProtocolBufferException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -29,85 +55,199 @@
@AllArgsConstructor
public class KnowledgebaseEventListener {
- private final KnowledgebaseService knowledgebaseService;
-
- // BdConstants.DEFAULT_ORGANIZATION_UID
- @EventListener
- public void onOrganizationCreateEvent(OrganizationCreateEvent event) {
- Organization organization = (Organization) event.getSource();
- String orgUid = organization.getUid();
- log.info("onOrganizationCreateEvent: orgUid {}", orgUid);
- //
- KnowledgebaseRequest kownledgebaseRequestHelpdoc = KnowledgebaseRequest.builder()
- .name(KnowledgebaseConsts.KB_HELPDOC_NAME)
- .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
- .language(LanguageEnum.ZH_CN.name())
- .build();
- kownledgebaseRequestHelpdoc.setType(KnowledgebaseTypeEnum.HELPDOC.name());
- kownledgebaseRequestHelpdoc.setOrgUid(orgUid);
- knowledgebaseService.create(kownledgebaseRequestHelpdoc);
- //
- KnowledgebaseRequest kownledgebaseRequestLlm = KnowledgebaseRequest.builder()
- .name(KnowledgebaseConsts.KB_LLM_NAME)
- .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
- .language(LanguageEnum.ZH_CN.name())
- .build();
- kownledgebaseRequestLlm.setType(KnowledgebaseTypeEnum.LLM.name());
- kownledgebaseRequestLlm.setOrgUid(orgUid);
- knowledgebaseService.create(kownledgebaseRequestLlm);
- //
- KnowledgebaseRequest kownledgebaseRequestKeyword = KnowledgebaseRequest.builder()
- .name(KnowledgebaseConsts.KB_KEYWORD_NAME)
- .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
- .language(LanguageEnum.ZH_CN.name())
- .build();
- kownledgebaseRequestKeyword.setType(KnowledgebaseTypeEnum.KEYWORD.name());
- kownledgebaseRequestKeyword.setOrgUid(orgUid);
- knowledgebaseService.create(kownledgebaseRequestKeyword);
- //
- KnowledgebaseRequest kownledgebaseRequeqstFaq = KnowledgebaseRequest.builder()
- .name(KnowledgebaseConsts.KB_FAQ_NAME)
- .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
- .language(LanguageEnum.ZH_CN.name())
- .build();
- kownledgebaseRequeqstFaq.setType(KnowledgebaseTypeEnum.FAQ.name());
- kownledgebaseRequeqstFaq.setOrgUid(orgUid);
- knowledgebaseService.create(kownledgebaseRequeqstFaq);
- //
- KnowledgebaseRequest kownledgebaseRequeqstAutoReply = KnowledgebaseRequest.builder()
- .name(KnowledgebaseConsts.KB_AUTOREPLY_NAME)
- .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
- .language(LanguageEnum.ZH_CN.name())
- .build();
- kownledgebaseRequeqstAutoReply.setType(KnowledgebaseTypeEnum.AUTOREPLY.name());
- kownledgebaseRequeqstAutoReply.setOrgUid(orgUid);
- knowledgebaseService.create(kownledgebaseRequeqstAutoReply);
- //
- KnowledgebaseRequest kownledgebaseRequeqstQuickReply = KnowledgebaseRequest.builder()
- .name(KnowledgebaseConsts.KB_QUICKREPLY_NAME)
- .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
- .language(LanguageEnum.ZH_CN.name())
- .build();
- kownledgebaseRequeqstQuickReply.setType(KnowledgebaseTypeEnum.QUICKREPLY.name());
- kownledgebaseRequeqstQuickReply.setOrgUid(orgUid);
- knowledgebaseService.create(kownledgebaseRequeqstQuickReply);
- //
- KnowledgebaseRequest kownledgebaseRequestTaboo = KnowledgebaseRequest.builder()
- .name(KnowledgebaseConsts.KB_TABOO_NAME)
- .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
- .language(LanguageEnum.ZH_CN.name())
- .build();
- kownledgebaseRequestTaboo.setType(KnowledgebaseTypeEnum.TABOO.name());
- kownledgebaseRequestTaboo.setOrgUid(orgUid);
- knowledgebaseService.create(kownledgebaseRequestTaboo);
- //
- //
-
-
-
-
- }
-
+ private final KnowledgebaseService knowledgebaseService;
+
+ // private final ZhipuaiService zhipuaiService;
+
+ // private final BytedeskEventPublisher bytedeskEventPublisher;
+
+ private final UidUtils uidUtils;
+
+ private final RedisPubsubService redisPubsubService;
+
+ private final BytedeskProperties bytedeskProperties;
+
+ private final MessageCache messageCache;
+
+ // BdConstants.DEFAULT_ORGANIZATION_UID
+ @EventListener
+ public void onOrganizationCreateEvent(OrganizationCreateEvent event) {
+ Organization organization = (Organization) event.getSource();
+ String orgUid = organization.getUid();
+ log.info("onOrganizationCreateEvent: orgUid {}", orgUid);
+ //
+ KnowledgebaseRequest kownledgebaseRequestHelpdoc = KnowledgebaseRequest.builder()
+ .name(KnowledgebaseConsts.KB_HELPDOC_NAME)
+ .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
+ .language(LanguageEnum.ZH_CN.name())
+ .build();
+ kownledgebaseRequestHelpdoc.setType(KnowledgebaseTypeEnum.HELPDOC.name());
+ kownledgebaseRequestHelpdoc.setOrgUid(orgUid);
+ knowledgebaseService.create(kownledgebaseRequestHelpdoc);
+ //
+ KnowledgebaseRequest kownledgebaseRequestLlm = KnowledgebaseRequest.builder()
+ .name(KnowledgebaseConsts.KB_LLM_NAME)
+ .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
+ .language(LanguageEnum.ZH_CN.name())
+ .build();
+ kownledgebaseRequestLlm.setType(KnowledgebaseTypeEnum.LLM.name());
+ kownledgebaseRequestLlm.setOrgUid(orgUid);
+ knowledgebaseService.create(kownledgebaseRequestLlm);
+ //
+ KnowledgebaseRequest kownledgebaseRequestKeyword = KnowledgebaseRequest.builder()
+ .name(KnowledgebaseConsts.KB_KEYWORD_NAME)
+ .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
+ .language(LanguageEnum.ZH_CN.name())
+ .build();
+ kownledgebaseRequestKeyword.setType(KnowledgebaseTypeEnum.KEYWORD.name());
+ kownledgebaseRequestKeyword.setOrgUid(orgUid);
+ knowledgebaseService.create(kownledgebaseRequestKeyword);
+ //
+ KnowledgebaseRequest kownledgebaseRequeqstFaq = KnowledgebaseRequest.builder()
+ .name(KnowledgebaseConsts.KB_FAQ_NAME)
+ .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
+ .language(LanguageEnum.ZH_CN.name())
+ .build();
+ kownledgebaseRequeqstFaq.setType(KnowledgebaseTypeEnum.FAQ.name());
+ kownledgebaseRequeqstFaq.setOrgUid(orgUid);
+ knowledgebaseService.create(kownledgebaseRequeqstFaq);
+ //
+ KnowledgebaseRequest kownledgebaseRequeqstAutoReply = KnowledgebaseRequest.builder()
+ .name(KnowledgebaseConsts.KB_AUTOREPLY_NAME)
+ .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
+ .language(LanguageEnum.ZH_CN.name())
+ .build();
+ kownledgebaseRequeqstAutoReply.setType(KnowledgebaseTypeEnum.AUTOREPLY.name());
+ kownledgebaseRequeqstAutoReply.setOrgUid(orgUid);
+ knowledgebaseService.create(kownledgebaseRequeqstAutoReply);
+ //
+ KnowledgebaseRequest kownledgebaseRequeqstQuickReply = KnowledgebaseRequest.builder()
+ .name(KnowledgebaseConsts.KB_QUICKREPLY_NAME)
+ .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
+ .language(LanguageEnum.ZH_CN.name())
+ .build();
+ kownledgebaseRequeqstQuickReply.setType(KnowledgebaseTypeEnum.QUICKREPLY.name());
+ kownledgebaseRequeqstQuickReply.setOrgUid(orgUid);
+ knowledgebaseService.create(kownledgebaseRequeqstQuickReply);
+ //
+ KnowledgebaseRequest kownledgebaseRequestTaboo = KnowledgebaseRequest.builder()
+ .name(KnowledgebaseConsts.KB_TABOO_NAME)
+ .descriptionHtml(KnowledgebaseConsts.KB_DESCRIPTION)
+ .language(LanguageEnum.ZH_CN.name())
+ .build();
+ kownledgebaseRequestTaboo.setType(KnowledgebaseTypeEnum.TABOO.name());
+ kownledgebaseRequestTaboo.setOrgUid(orgUid);
+ knowledgebaseService.create(kownledgebaseRequestTaboo);
+ //
+ //
+
+ }
+
+ @EventListener
+ public void onMessageJsonEvent(MessageJsonEvent event) {
+ // log.info("MessageJsonEvent {}", event.getJson());
+ String messageJson = event.getJson();
+ //
+ processMessage(messageJson);
+ }
+
+ @EventListener
+ public void onMessageProtoEvent(MessageProtoEvent event) {
+ // log.info("MessageProtoEvent");
+ try {
+ MessageProto.Message messageProto = MessageProto.Message.parseFrom(event.getMessageBytes());
+ //
+ try {
+ String messageJson = MessageConvertUtils.toJson(messageProto);
+ //
+ processMessage(messageJson);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } catch (InvalidProtocolBufferException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void processMessage(String messageJson) {
+ MessageProtobuf messageProtobuf = JSON.parseObject(messageJson, MessageProtobuf.class);
+ MessageTypeEnum messageType = messageProtobuf.getType();
+ if (messageType.equals(MessageTypeEnum.STREAM)) {
+ // ai回答暂不处理
+ return;
+ }
+ String query = messageProtobuf.getContent();
+ log.info("kb processMessage {}", query);
+ //
+ ThreadProtobuf thread = messageProtobuf.getThread();
+ if (thread == null) {
+ throw new RuntimeException("thread is null");
+ }
+ // 仅针对文本类型自动回复
+ if (!messageType.equals(MessageTypeEnum.TEXT)) {
+ return;
+ }
+ //
+ String threadTopic = thread.getTopic();
+ if (thread.getType().equals(ThreadTypeEnum.KB)) {
+ log.info("knowledge_base threadTopic {}, thread.type {}", threadTopic, thread.getType());
+ // 机器人回复
+ log.info("knowledge_base thread reply");
+ // 机器人客服消息 org/kb/default_kb_uid/1420995827073219
+ String[] splits = threadTopic.split("/");
+ if (splits.length < 4) {
+ throw new RuntimeException("kb topic format error");
+ }
+ String kbUid = splits[2];
+ if (!StringUtils.hasText(kbUid)) {
+ throw new RuntimeException("kbUid is null");
+ }
+ Optional kbOptional = knowledgebaseService.findByUid(kbUid);
+ if (kbOptional.isPresent()) {
+ Knowledgebase kb = kbOptional.get();
+ //
+ UserProtobuf user = UserProtobuf.builder().build();
+ user.setUid(kbUid);
+ user.setNickname(kb.getName());
+ user.setAvatar(kb.getLogoUrl());
+ //
+ MessageExtra extra = MessageUtils.getMessageExtra(kb.getOrgUid());
+ //
+ String messageUid = uidUtils.getCacheSerialUid();
+ MessageProtobuf message = MessageProtobuf.builder()
+ .uid(messageUid)
+ .status(MessageStatusEnum.SUCCESS)
+ .thread(thread)
+ .user(user)
+ .client(ClientEnum.SYSTEM_AUTO)
+ .extra(JSONObject.toJSONString(extra))
+ .createdAt(new Date())
+ .build();
+ // 返回一个输入中消息,让访客端显示输入中
+ MessageProtobuf clonedMessage = SerializationUtils.clone(message);
+ clonedMessage.setUid(uidUtils.getCacheSerialUid());
+ clonedMessage.setType(MessageTypeEnum.PROCESSING);
+ //
+ MessageUtils.notifyUser(clonedMessage);
+ // String json = JSON.toJSONString(clonedMessage);
+ // bytedeskEventPublisher.publishMessageJsonEvent(json);
+ // 知识库
+ // if (bytedeskProperties.getJavaai()) {
+ // zhipuaiService.sendWsRobotMessage(query, kb.getKbUid(), kb, message);
+ // }
+ // 通知python ai模块处理回答
+ if (bytedeskProperties.getPythonai()) {
+ messageCache.put(messageUid, message);
+ redisPubsubService.sendQuestionMessage(messageUid, threadTopic,
+ kb.getUid(),
+ query);
+ }
+ } else {
+ log.error("kb not found");
+ }
+ }
+ }
}
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/quick_reply/QuickReplyEventListener.java b/modules/kbase/src/main/java/com/bytedesk/kbase/quick_reply/QuickReplyEventListener.java
new file mode 100644
index 00000000..d6fcc124
--- /dev/null
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/quick_reply/QuickReplyEventListener.java
@@ -0,0 +1,23 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 16:19:48
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 16:19:51
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.kbase.quick_reply;
+
+/**
+ * 迁移至 QuickReplyVipEventListener
+ * 此处不做实现
+ */
+public class QuickReplyEventListener {
+
+}
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/taboo/TabooEventListener.java b/modules/kbase/src/main/java/com/bytedesk/kbase/taboo/TabooEventListener.java
new file mode 100644
index 00000000..0d30822b
--- /dev/null
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/taboo/TabooEventListener.java
@@ -0,0 +1,23 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 16:20:02
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 16:20:05
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.kbase.taboo;
+
+/**
+ * 迁移至 TabooVipEventListener
+ * 此处不做实现
+ */
+public class TabooEventListener {
+
+}
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/Upload.java b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/Upload.java
index ee6d71ef..4567f63b 100644
--- a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/Upload.java
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/Upload.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-03-16 10:46:55
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:30:23
+ * @LastEditTime: 2024-08-31 15:47:14
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -83,7 +83,7 @@ public class Upload extends BaseEntity {
private String kbUid; // 所属知识库
- // 存储用户信息:包括客服、访客、群聊、同事等
+ // 上传用户
@Builder.Default
@Column(name = "upload_user", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
@JdbcTypeCode(SqlTypes.JSON)
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadController.java b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadController.java
index 7237a804..612416c2 100644
--- a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadController.java
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-03-15 11:35:53
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-31 15:21:10
+ * @LastEditTime: 2024-08-30 18:02:23
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -138,7 +138,7 @@ public ResponseEntity> update(UploadRequest request) {
throw new UnsupportedOperationException("Unimplemented method 'update'");
}
- @ActionAnnotation(title = "upload", action = "delete", description = "create upload")
+ @ActionAnnotation(title = "upload", action = "delete", description = "delete upload")
@Override
public ResponseEntity> delete(UploadRequest request) {
// 更新数据库
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadControllerVisitor.java b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadControllerVisitor.java
index 315c89a2..cdc27555 100644
--- a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadControllerVisitor.java
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadControllerVisitor.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-18 19:21:06
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-30 22:54:38
+ * @LastEditTime: 2024-08-28 11:09:18
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -22,7 +22,9 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
+import com.alibaba.fastjson2.JSON;
import com.bytedesk.core.config.BytedeskProperties;
+import com.bytedesk.core.rbac.user.UserProtobuf;
import com.bytedesk.core.utils.JsonResult;
import lombok.AllArgsConstructor;
@@ -46,17 +48,41 @@ public class UploadControllerVisitor {
public ResponseEntity> upload(
@RequestParam("file") MultipartFile file,
@RequestParam("file_name") String fileName,
- @RequestParam("file_type") String type) {
- log.info("fileName {}, type {}", fileName, type);
+ @RequestParam("file_type") String fileType,
+ @RequestParam(name = "kb_type", required = false) String kbType,
+ @RequestParam(name = "visitor_uid", required = false) String visitorUid,
+ @RequestParam(name = "nickname", required = false) String visitorNickname,
+ @RequestParam(name = "avatar", required = false) String visitorAvatar,
+ @RequestParam(name = "org_uid", required = false) String orgUid,
+ @RequestParam(name = "client", required = false) String client) {
+ log.info("fileName {}, fileType {}", fileName, fileType);
// TODO: image/avatar/file/video/voice
// http://localhost:9003/file/20240319162820_img-service2.png
String uploadPath = uploadService.store(file, fileName);
// http://localhost:9003
- String url = String.format("%s/file/%s", bytedeskProperties.getUploadUrl(), uploadPath);
+ String fileUrl = String.format("%s/file/%s", bytedeskProperties.getUploadUrl(), uploadPath);
+ //
+ UserProtobuf visitorProtobuf = UserProtobuf.builder()
+ .nickname(visitorNickname)
+ .avatar(visitorAvatar)
+ .build();
+ visitorProtobuf.setUid(visitorUid);
+ //
+ UploadRequest uploadRequest = UploadRequest.builder()
+ .fileName(fileName)
+ .fileSize(String.valueOf(file.getSize()))
+ .fileUrl(fileUrl)
+ .fileType(fileType)
+ .client(client)
+ .user(JSON.toJSONString(visitorProtobuf))
+ .build();
+ uploadRequest.setType(kbType);
+ uploadRequest.setOrgUid(orgUid);
+ uploadService.create(uploadRequest);
- return ResponseEntity.ok(JsonResult.success("upload success", url));
+ return ResponseEntity.ok(JsonResult.success("upload success", fileUrl));
}
}
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadEventListener.java b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadEventListener.java
index 4a72e259..3a3fdae6 100644
--- a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadEventListener.java
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-28 06:48:10
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-24 08:00:43
+ * @LastEditTime: 2024-09-07 16:56:14
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -15,13 +15,23 @@
package com.bytedesk.kbase.upload;
import java.io.IOException;
-
import org.springframework.context.event.EventListener;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import com.alibaba.excel.EasyExcel;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.bytedesk.core.constant.I18Consts;
import com.bytedesk.core.event.GenericApplicationEvent;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.message.MessageUtils;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.redis.pubsub.RedisPubsubParseFileErrorEvent;
+import com.bytedesk.core.redis.pubsub.RedisPubsubParseFileSuccessEvent;
+import com.bytedesk.core.redis.pubsub.RedisPubsubService;
+import com.bytedesk.core.redis.pubsub.message.RedisPubsubMessageFile;
+import com.bytedesk.core.uid.UidUtils;
import com.bytedesk.kbase.auto_reply.AutoReplyExcel;
import com.bytedesk.kbase.auto_reply.AutoReplyExcelListener;
import com.bytedesk.kbase.auto_reply.AutoReplyService;
@@ -58,7 +68,11 @@ public class UploadEventListener {
private final TabooService tabooService;
- private final UploadVectorStore uploadVectorStore;
+ private final RedisPubsubService redisPubsubService;
+
+ // private final MessageService messageService;
+
+ private final UidUtils uidUtils;
@EventListener
public void onUploadCreateEvent(GenericApplicationEvent event) throws IOException {
@@ -66,7 +80,12 @@ public void onUploadCreateEvent(GenericApplicationEvent event
log.info("UploadEventListener create: {}", upload.toString());
// etl分块处理
if (upload.getType().equals(UploadTypeEnum.LLM.name())) {
- uploadVectorStore.readSplitWriteToVectorStore(upload);
+ // 通知python ai模块处理
+ // uploadVectorStore.readSplitWriteToVectorStore(upload);
+ redisPubsubService.sendParseFileMessage(
+ upload.getUid(),
+ upload.getFileUrl(),
+ upload.getKbUid());
return;
}
// 导入Excel文件
@@ -126,7 +145,6 @@ public void onUploadCreateEvent(GenericApplicationEvent event
// MemberExcelListener(memberService)).sheet().doRead();
}
}
-
}
@EventListener
@@ -135,9 +153,54 @@ public void onUploadUpdateEvent(GenericApplicationEvent event
log.info("UploadEventListener update: {}", upload.toString());
// 后台删除文件记录
if (upload.isDeleted()) {
+ // 通知python ai模块处理
+ redisPubsubService.sendDeleteFileMessage(upload.getUid(), upload.getDocIdList());
// 删除redis中缓存的document
- uploadVectorStore.deleteDoc(upload.getDocIdList());
+ // uploadVectorStore.deleteDoc(upload.getDocIdList());
}
}
+ @EventListener
+ public void onRedisPubsubParseFileSuccessEvent(GenericApplicationEvent event) {
+ RedisPubsubMessageFile messageFile = event.getObject().getMessageFile();
+ log.info("UploadEventListener RedisPubsubParseFileSuccessEvent: {}", messageFile.toString());
+ //
+ Upload upload = uploadService.findByUid(messageFile.getFileUid())
+ .orElseThrow(() -> new RuntimeException("upload not found by uid: " + messageFile.getFileUid()));
+ upload.setDocIdList(messageFile.getDocIds());
+ upload.setStatus(UploadStatusEnum.PARSE_FILE_SUCCESS.name());
+ uploadService.save(upload);
+ //
+ String user = upload.getUser();
+ UserProtobuf uploadUser = JSON.parseObject(user, UserProtobuf.class);
+
+ // 通知前端
+ JSONObject contentObject = new JSONObject();
+ contentObject.put(I18Consts.I18N_NOTICE_TITLE, I18Consts.I18N_NOTICE_PARSE_FILE_SUCCESS);
+ //
+ MessageProtobuf message = MessageUtils.createNoticeMessage(uidUtils.getCacheSerialUid(), uploadUser.getUid(), upload.getOrgUid(),
+ JSON.toJSONString(contentObject));
+ MessageUtils.notifyUser(message);
+ }
+
+ @EventListener
+ public void onRedisPubsubParseFileErrorEvent(GenericApplicationEvent event) {
+ RedisPubsubMessageFile messageFile = event.getObject().getMessageFile();
+ log.info("UploadEventListener RedisPubsubParseFileErrorEvent: {}", messageFile.toString());
+ Upload upload = uploadService.findByUid(messageFile.getFileUid())
+ .orElseThrow(() -> new RuntimeException("upload not found by uid: " + messageFile.getFileUid()));
+ upload.setStatus(UploadStatusEnum.PARSE_FILE_ERROR.name());
+ uploadService.save(upload);
+ //
+ String user = upload.getUser();
+ UserProtobuf uploadUser = JSON.parseObject(user, UserProtobuf.class);
+ // 通知前端
+ JSONObject contentObject = new JSONObject();
+ contentObject.put(I18Consts.I18N_NOTICE_TITLE, I18Consts.I18N_NOTICE_PARSE_FILE_ERROR);
+ //
+ MessageProtobuf message = MessageUtils.createNoticeMessage(uidUtils.getCacheSerialUid(), uploadUser.getUid(), upload.getOrgUid(),
+ JSON.toJSONString(contentObject));
+ MessageUtils.notifyUser(message);
+ }
+
}
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadService.java b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadService.java
index b944c59f..2318eeb6 100755
--- a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadService.java
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-03-15 11:35:53
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:47:14
+ * @LastEditTime: 2024-08-31 16:03:26
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -62,7 +62,11 @@ public UploadResponse create(UploadRequest request) {
upload.setUid(uidUtils.getCacheSerialUid());
upload.setClient(ClientEnum.fromValue(request.getClient()).name());
upload.setType(UploadTypeEnum.fromValue(request.getType()).name());
- upload.setStatus(UploadStatusEnum.UPLOADED.name());
+ if (upload.getType().equals(UploadTypeEnum.LLM.name())) {
+ upload.setStatus(UploadStatusEnum.PARSING.name());
+ } else {
+ upload.setStatus(UploadStatusEnum.UPLOADED.name());
+ }
//
Upload savedUpload = save(upload);
if (savedUpload == null) {
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadStatusEnum.java b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadStatusEnum.java
index 0b9f05f9..e7616b73 100644
--- a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadStatusEnum.java
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadStatusEnum.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-26 14:40:46
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-28 06:36:46
+ * @LastEditTime: 2024-08-31 15:56:42
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -18,14 +18,10 @@
// https://docs.spring.io/spring-ai/reference/api/etl-pipeline.html
public enum UploadStatusEnum {
UPLOADED,
- EXTRACTING,
- EXTRATED,
- TRANSFORMING,
- TRANSFORMED,
- LOADING,
- LOADED,
- FAILED,
- ABORTED;
+ PARSING,
+ PARSE_FILE_SUCCESS,
+ PARSE_FILE_ERROR
+ ;
// 根据字符串查找对应的枚举常量
public static UploadStatusEnum fromValue(String value) {
diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadVectorStore.java b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadVectorStore.java
index 7dd3c6fc..eaacdaf0 100644
--- a/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadVectorStore.java
+++ b/modules/kbase/src/main/java/com/bytedesk/kbase/upload/UploadVectorStore.java
@@ -198,7 +198,7 @@ private void storeDocuments(List docList, Upload upload) {
doc.getMetadata().put(KbaseConst.KBASE_KB_UID, upload.getKbUid());
}
upload.setDocIdList(docIdList);
- upload.setStatus(UploadStatusEnum.EXTRATED.name());
+ upload.setStatus(UploadStatusEnum.PARSE_FILE_SUCCESS.name());
// FIXME: ObjectOptimisticLockingFailureException: Row was updated or deleted by
// another transaction (or unsaved-value mapping was incorrect) :
// [com.bytedesk.kbase.upload.Upload#52]
@@ -233,9 +233,9 @@ public List searchText(String query, String kbUid) {
//
SearchRequest searchRequest = SearchRequest.query(query)
.withFilterExpression(expression);
- // .withTopK(2);
- // .withSimilarityThreshold(0.5)
- // .withFilterExpression(expression);
+ // .withTopK(2);
+ // .withSimilarityThreshold(0.5)
+ // .withFilterExpression(expression);
List similarDocuments = vectorStore.similaritySearch(searchRequest);
List contentList = similarDocuments.stream().map(Document::getContent).toList();
log.info("kbUid {}, query: {} , contentList.size: {}", kbUid, query, contentList.size());
diff --git a/modules/pom.xml b/modules/pom.xml
index 059ac4b9..f83afc60 100644
--- a/modules/pom.xml
+++ b/modules/pom.xml
@@ -186,6 +186,14 @@
provided
+
+
+ com.google.protobuf
+ protobuf-java
+ 3.25.2
+ provided
+
+
diff --git a/modules/service/.DS_Store b/modules/service/.DS_Store
index d41c78b9..3ba66e08 100644
Binary files a/modules/service/.DS_Store and b/modules/service/.DS_Store differ
diff --git a/modules/service/pom.xml b/modules/service/pom.xml
index 833195be..b498a5c3 100644
--- a/modules/service/pom.xml
+++ b/modules/service/pom.xml
@@ -55,14 +55,6 @@
-
-
- com.google.protobuf
- protobuf-java
- 3.25.2
- provided
-
-
diff --git a/modules/service/src/main/java/com/bytedesk/service/agent/AgentEventListener.java b/modules/service/src/main/java/com/bytedesk/service/agent/AgentEventListener.java
index 52d683bc..6fa82755 100644
--- a/modules/service/src/main/java/com/bytedesk/service/agent/AgentEventListener.java
+++ b/modules/service/src/main/java/com/bytedesk/service/agent/AgentEventListener.java
@@ -27,7 +27,7 @@
import com.bytedesk.core.enums.LanguageEnum;
import com.bytedesk.core.enums.LevelEnum;
import com.bytedesk.core.event.GenericApplicationEvent;
-import com.bytedesk.core.quartz.QuartzFiveSecondEvent;
+import com.bytedesk.core.quartz.event.QuartzFiveSecondEvent;
import com.bytedesk.core.rbac.organization.Organization;
import com.bytedesk.core.rbac.organization.OrganizationCreateEvent;
import com.bytedesk.core.rbac.user.User;
diff --git a/modules/service/src/main/java/com/bytedesk/service/agent/AgentQuartzListener.java b/modules/service/src/main/java/com/bytedesk/service/agent/AgentQuartzListener.java
index 04566830..0c5a32d0 100644
--- a/modules/service/src/main/java/com/bytedesk/service/agent/AgentQuartzListener.java
+++ b/modules/service/src/main/java/com/bytedesk/service/agent/AgentQuartzListener.java
@@ -17,7 +17,7 @@
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.quartz.QuartzFiveSecondEvent;
+import com.bytedesk.core.quartz.event.QuartzFiveSecondEvent;
import lombok.AllArgsConstructor;
// import lombok.extern.slf4j.Slf4j;
diff --git a/modules/service/src/main/java/com/bytedesk/service/agent/AgentResponseSimple.java b/modules/service/src/main/java/com/bytedesk/service/agent/AgentResponseSimple.java
deleted file mode 100644
index 90963f6e..00000000
--- a/modules/service/src/main/java/com/bytedesk/service/agent/AgentResponseSimple.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-02-06 10:17:01
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-05-31 14:06:43
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.service.agent;
-
-import com.bytedesk.core.base.BaseResponse;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-@Data
-@Builder
-@Accessors(chain = true)
-@AllArgsConstructor
-@NoArgsConstructor
-@EqualsAndHashCode(callSuper = true)
-public class AgentResponseSimple extends BaseResponse {
-
- private static final long serialVersionUID = 1L;
-
- private String nickname;
-
- private String avatar;
-}
diff --git a/modules/service/src/main/java/com/bytedesk/service/agent_message/AgentMessageEventListener.java b/modules/service/src/main/java/com/bytedesk/service/agent_message/AgentMessageEventListener.java
index eabffcc4..c64c70ce 100644
--- a/modules/service/src/main/java/com/bytedesk/service/agent_message/AgentMessageEventListener.java
+++ b/modules/service/src/main/java/com/bytedesk/service/agent_message/AgentMessageEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-05 11:07:05
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-05 11:09:53
+ * @LastEditTime: 2024-09-07 16:15:08
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,27 +14,23 @@
*/
package com.bytedesk.service.agent_message;
-import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.message.MessageCreateEvent;
-import com.bytedesk.core.message.MessageUpdateEvent;
-
// import lombok.extern.slf4j.Slf4j;
// @Slf4j
@Component
public class AgentMessageEventListener {
- @EventListener
- public void onMessageCreateEvent(MessageCreateEvent event) {
- // log.info("visitor message unread create event: " + event);
+ // @EventListener
+ // public void onMessageCreateEvent(MessageCreateEvent event) {
+ // // log.info("visitor message unread create event: " + event);
- }
+ // }
- @EventListener
- public void onMessageUpdateEvent(MessageUpdateEvent event) {
- // log.info("visitor message unread update event: " + event);
- }
+ // @EventListener
+ // public void onMessageUpdateEvent(MessageUpdateEvent event) {
+ // // log.info("visitor message unread update event: " + event);
+ // }
}
diff --git a/modules/service/src/main/java/com/bytedesk/service/complaint/Complaint.java b/modules/service/src/main/java/com/bytedesk/service/complaint/Complaint.java
new file mode 100644
index 00000000..e1953fc7
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/complaint/Complaint.java
@@ -0,0 +1,20 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-03 09:50:20
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-03 09:50:23
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.complaint;
+
+// 投诉,如:对在线人工客服进行投诉
+public class Complaint {
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintController.java b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintController.java
new file mode 100644
index 00000000..3d1c548a
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintController.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-03 09:50:58
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-03 09:51:01
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.complaint;
+
+public class ComplaintController {
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintRepository.java b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintRepository.java
new file mode 100644
index 00000000..ae2013bb
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintRepository.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-03 09:51:46
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-03 09:51:49
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.complaint;
+
+public class ComplaintRepository {
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintRequest.java b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintRequest.java
new file mode 100644
index 00000000..42fb9a03
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintRequest.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-03 09:51:19
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-03 09:51:22
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.complaint;
+
+public class ComplaintRequest {
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintResponse.java b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintResponse.java
new file mode 100644
index 00000000..3d74d041
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintResponse.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-03 09:51:28
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-03 09:51:31
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.complaint;
+
+public class ComplaintResponse {
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintService.java b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintService.java
new file mode 100644
index 00000000..a270abe8
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintService.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-03 09:51:08
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-03 09:51:11
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.complaint;
+
+public class ComplaintService {
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintSpecification.java b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintSpecification.java
new file mode 100644
index 00000000..07a24c86
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/complaint/ComplaintSpecification.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-03 09:52:03
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-03 09:52:06
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.complaint;
+
+public class ComplaintSpecification {
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/leave_msg/LeaveMsgEventListener.java b/modules/service/src/main/java/com/bytedesk/service/leave_msg/LeaveMsgEventListener.java
index 1c535b4a..8f1c1888 100644
--- a/modules/service/src/main/java/com/bytedesk/service/leave_msg/LeaveMsgEventListener.java
+++ b/modules/service/src/main/java/com/bytedesk/service/leave_msg/LeaveMsgEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-18 11:45:43
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-05 21:30:50
+ * @LastEditTime: 2024-08-31 10:22:20
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -35,9 +35,9 @@ public class LeaveMsgEventListener {
@EventListener
public void onMessageUpdateEvent(MessageUpdateEvent event) {
Message message = event.getMessage();
- log.info("message leave_msg update event: {}", message);
+ // log.info("message leave_msg update event: {}", message);
//
- if (message.getStatus().equals(MessageStatusEnum.LEAVE_MSG_SUBMIT)) {
+ if (message.getStatus().equals(MessageStatusEnum.LEAVE_MSG_SUBMIT.name())) {
LeaveMsgExtra extra = JSON.parseObject(message.getContent(), LeaveMsgExtra.class);
//
LeaveMsgRequest request = LeaveMsgRequest.builder()
diff --git a/modules/service/src/main/java/com/bytedesk/service/settings/ServiceSettings.java b/modules/service/src/main/java/com/bytedesk/service/settings/ServiceSettings.java
index 9c520bdb..e6d15059 100644
--- a/modules/service/src/main/java/com/bytedesk/service/settings/ServiceSettings.java
+++ b/modules/service/src/main/java/com/bytedesk/service/settings/ServiceSettings.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-29 13:57:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 07:41:24
+ * @LastEditTime: 2024-09-03 10:37:12
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -79,4 +79,19 @@ public boolean isWorkTime() {
}
return this.worktimes.stream().anyMatch(w -> w.isWorkTime());
}
+
+ //
+ public Boolean shouldTransferToRobot(Boolean isOffline) {
+ if (this.defaultRobot) {
+ // 默认机器人优先接待
+ return true;
+ } else if (isOffline && this.offlineRobot) {
+ // 所有客服离线,且设置机器人离线优先接待
+ return true;
+ } else if (this.nonWorktimeRobot && !isWorkTime()) {
+ // 设置非工作时间机器人接待,且当前非工作时间,转机器人
+ return true;
+ }
+ return false;
+ }
}
diff --git a/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLog.java b/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLog.java
index ad6028ee..136e7697 100644
--- a/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLog.java
+++ b/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLog.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-09 16:34:13
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:32:59
+ * @LastEditTime: 2024-09-06 16:41:59
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -56,11 +56,6 @@ public class ThreadLog extends BaseEntity {
private static final long serialVersionUID = 1L;
- // @NotBlank
- // private String title;
- // @NotBlank
- // private String avatar;
-
/**
* @{TopicConsts}
*/
@@ -102,7 +97,6 @@ public class ThreadLog extends BaseEntity {
@JdbcTypeCode(SqlTypes.JSON)
private String extra = BdConstants.EMPTY_JSON_STRING;
- //
// h2 db 不能使用 user, 所以重定义为 _user
@Builder.Default
@Column(name = "thread_user", columnDefinition = TypeConsts.COLUMN_TYPE_JSON)
diff --git a/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLogEventListener.java b/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLogEventListener.java
index b333f315..dee90675 100644
--- a/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLogEventListener.java
+++ b/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLogEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-05-29 15:09:26
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-02 23:05:32
+ * @LastEditTime: 2024-09-10 23:21:44
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -17,9 +17,9 @@
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.quartz.QuartzFiveSecondEvent;
import com.bytedesk.core.thread.ThreadCreateEvent;
import com.bytedesk.core.thread.ThreadUpdateEvent;
+import com.bytedesk.core.quartz.event.QuartzOneMinEvent;
import com.bytedesk.core.thread.Thread;
import lombok.AllArgsConstructor;
@@ -35,7 +35,7 @@ public class ThreadLogEventListener {
@EventListener
public void onThreadCreateEvent(ThreadCreateEvent event) {
Thread thread = event.getThread();
- log.info("thread log ThreadCreateEvent: {}", thread.getUid());
+ // log.info("thread log ThreadCreateEvent: {}", thread.getUid());
//
threadLogService.create(thread);
}
@@ -54,7 +54,7 @@ public void onThreadUpdateEvent(ThreadUpdateEvent event) {
}
@EventListener
- public void onQuartzFiveSecondEvent(QuartzFiveSecondEvent event) {
+ public void onQuartzOneMinEvent(QuartzOneMinEvent event) {
// log.info("threadlog quartz five second event: " + event);
// auto close thread
threadLogService.autoCloseThread();
diff --git a/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLogService.java b/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLogService.java
index e9b08878..81a25b70 100644
--- a/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLogService.java
+++ b/modules/service/src/main/java/com/bytedesk/service/thread_log/ThreadLogService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-18 10:47:38
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:47:41
+ * @LastEditTime: 2024-09-06 16:42:43
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -34,9 +34,9 @@
import com.bytedesk.kbase.service_settings.ServiceSettingsResponseVisitor;
import lombok.AllArgsConstructor;
-// import lombok.extern.slf4j.Slf4j;
+import lombok.extern.slf4j.Slf4j;
-// @Slf4j
+@Slf4j
@Service
@AllArgsConstructor
public class ThreadLogService {
@@ -54,9 +54,8 @@ public Page queryByOrg(ThreadLogRequest threadLogRequest) {
"updatedAt");
Specification spec = ThreadLogSpecification.search(threadLogRequest);
+
Page threadLogPage = threadLogRepository.findAll(spec, pageable);
- // Page threadLogPage =
- // threadLogRepository.findByOrgUid(threadLogRequest.getOrgUid(), pageable);
return threadLogPage.map(this::convertThreadLogResponse);
}
@@ -89,21 +88,30 @@ public void autoCloseThread() {
long diffInMilliseconds = Math.abs(new Date().getTime() - thread.getUpdatedAt().getTime());
// 转换为分钟
long diffInMinutes = TimeUnit.MILLISECONDS.toMinutes(diffInMilliseconds);
- if (thread.getType() == ThreadTypeEnum.WORKGROUP.name() || thread.getType() == ThreadTypeEnum.AGENT.name()) {
- ServiceSettingsResponseVisitor settings = JSON.parseObject(thread.getExtra(),
- ServiceSettingsResponseVisitor.class);
- Double autoCloseMinites = settings.getAutoCloseMin();
- if (diffInMinutes > autoCloseMinites) {
- threadService.autoClose(thread);
- }
- } else if (thread.getType() == ThreadTypeEnum.ROBOT.name()) {
+ // log.info("1.autoCloseThread threadUid {} threadType {} threadId {} diffInMinutes {}", thread.getUid(), thread.getType(),
+ // thread.getUid(), diffInMinutes);
+ //
+ // log.info("{}, {}, {} ", ThreadTypeEnum.WORKGROUP.name(), ThreadTypeEnum.AGENT.name(), ThreadTypeEnum.ROBOT.name());
+ if (thread.getType().equals(ThreadTypeEnum.WORKGROUP.name())
+ || thread.getType().equals(ThreadTypeEnum.AGENT.name())
+ || thread.getType().equals(ThreadTypeEnum.ROBOT.name())) {
ServiceSettingsResponseVisitor settings = JSON.parseObject(thread.getExtra(),
ServiceSettingsResponseVisitor.class);
Double autoCloseMinites = settings.getAutoCloseMin();
+ // log.info("2. autoCloseThread threadUid {} threadType {} autoCloseMinites {}, diffInMinutes {}",
+ // thread.getUid(), thread.getType(), autoCloseMinites, diffInMinutes);
if (diffInMinutes > autoCloseMinites) {
threadService.autoClose(thread);
}
}
+ // else if (thread.getType() == ThreadTypeEnum.ROBOT.name()) {
+ // ServiceSettingsResponseVisitor settings = JSON.parseObject(thread.getExtra(),
+ // ServiceSettingsResponseVisitor.class);
+ // Double autoCloseMinites = settings.getAutoCloseMin();
+ // if (diffInMinutes > autoCloseMinites) {
+ // threadService.autoClose(thread);
+ // }
+ // }
});
}
diff --git a/modules/service/src/main/java/com/bytedesk/service/utils/ConvertServiceUtils.java b/modules/service/src/main/java/com/bytedesk/service/utils/ConvertServiceUtils.java
index 9689fe15..c6240f81 100644
--- a/modules/service/src/main/java/com/bytedesk/service/utils/ConvertServiceUtils.java
+++ b/modules/service/src/main/java/com/bytedesk/service/utils/ConvertServiceUtils.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-04 11:25:45
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-04 20:28:24
+ * @LastEditTime: 2024-09-07 10:24:56
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -24,18 +24,16 @@
import com.bytedesk.core.message.MessageProtobuf;
import com.bytedesk.core.message.MessageResponse;
import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.rbac.user.UserTypeEnum;
import com.bytedesk.service.agent.Agent;
import com.bytedesk.service.agent.AgentResponse;
-import com.bytedesk.service.agent.AgentResponseSimple;
import com.bytedesk.kbase.service_settings.ServiceSettingsResponseVisitor;
import com.bytedesk.service.settings.ServiceSettings;
import com.bytedesk.service.visitor.Visitor;
import com.bytedesk.service.visitor.VisitorRequest;
import com.bytedesk.service.visitor.VisitorResponse;
-import com.bytedesk.service.visitor.VisitorProtobuf;
import com.bytedesk.service.workgroup.Workgroup;
import com.bytedesk.service.workgroup.WorkgroupResponse;
-import com.bytedesk.service.workgroup.WorkgroupResponseSimple;
public class ConvertServiceUtils {
private ConvertServiceUtils() {
@@ -46,19 +44,22 @@ public static VisitorResponse convertToVisitorResponse(Visitor visitor) {
return new ModelMapper().map(visitor, VisitorResponse.class);
}
- public static VisitorProtobuf convertToVisitorProtobuf(Visitor visitor) {
- return new ModelMapper().map(visitor, VisitorProtobuf.class);
+ public static UserProtobuf convertToUserProtobuf(Visitor visitor) {
+ return new ModelMapper().map(visitor, UserProtobuf.class);
}
- public static VisitorProtobuf convertToVisitorProtobuf(VisitorRequest visitorRequest) {
- return new ModelMapper().map(visitorRequest, VisitorProtobuf.class);
+ public static UserProtobuf convertToUserProtobuf(VisitorRequest visitorRequest) {
+ UserProtobuf userProtobuf = new ModelMapper().map(visitorRequest, UserProtobuf.class);
+ userProtobuf.setType(UserTypeEnum.VISITOR.name());
+ return userProtobuf;
}
- public static UserProtobuf convertToUserResponseSimple(AgentResponseSimple agentResponseSimple) {
- return new ModelMapper().map(agentResponseSimple, UserProtobuf.class);
- }
+ // public static UserProtobuf convertToUserResponseSimple(AgentResponseSimple
+ // agentResponseSimple) {
+ // return new ModelMapper().map(agentResponseSimple, UserProtobuf.class);
+ // }
- public static UserProtobuf convertToUserResponseSimple(VisitorProtobuf visitorResponseSimple) {
+ public static UserProtobuf convertToUserResponseSimple(UserProtobuf visitorResponseSimple) {
return new ModelMapper().map(visitorResponseSimple, UserProtobuf.class);
}
@@ -95,8 +96,10 @@ public static AgentResponse convertToAgentResponse(Agent agent) {
return new ModelMapper().map(agent, AgentResponse.class);
}
- public static AgentResponseSimple convertToAgentResponseSimple(Agent agent) {
- return new ModelMapper().map(agent, AgentResponseSimple.class);
+ public static UserProtobuf convertToUserProtobuf(Agent agent) {
+ UserProtobuf userProtobuf = new ModelMapper().map(agent, UserProtobuf.class);
+ userProtobuf.setType(UserTypeEnum.AGENT.name());
+ return userProtobuf;
}
//
@@ -104,9 +107,10 @@ public static WorkgroupResponse convertToWorkgroupResponse(Workgroup workgroup)
return new ModelMapper().map(workgroup, WorkgroupResponse.class);
}
- public static WorkgroupResponseSimple convertToWorkgroupResponseSimple(Workgroup workgroup) {
- return new ModelMapper().map(workgroup, WorkgroupResponseSimple.class);
- }
+ // public static WorkgroupResponseSimple
+ // convertToWorkgroupResponseSimple(Workgroup workgroup) {
+ // return new ModelMapper().map(workgroup, WorkgroupResponseSimple.class);
+ // }
//
public static ServiceSettingsResponseVisitor convertToServiceSettingsResponseVisitor(
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/Visitor.java b/modules/service/src/main/java/com/bytedesk/service/visitor/Visitor.java
index 78c2b345..f38d117f 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/Visitor.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/Visitor.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:34:03
+ * @LastEditTime: 2024-09-07 13:03:23
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -38,7 +38,6 @@
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
-// @DiscriminatorValue("Visitor")
@Table(name = "service_visitor")
public class Visitor extends BaseEntity {
@@ -69,5 +68,8 @@ public class Visitor extends BaseEntity {
// @Enumerated(EnumType.STRING)
// private ClientEnum client;
private String client = ClientEnum.WEB.name();
+
+ @Builder.Default
+ private String status = VisitorStatusEnum.OFFLINE.name();
}
\ No newline at end of file
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorController.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorController.java
index 0061b738..23af1df7 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorController.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-04 10:46:45
+ * @LastEditTime: 2024-09-07 13:07:00
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -29,6 +29,7 @@
import com.bytedesk.core.message.MessageProtobuf;
import com.bytedesk.core.message.MessageResponse;
import com.bytedesk.core.message_unread.MessageUnreadService;
+import com.bytedesk.core.rbac.user.UserProtobuf;
import com.bytedesk.core.socket.MqService;
import com.bytedesk.core.utils.JsonResult;
import jakarta.servlet.http.HttpServletRequest;
@@ -85,7 +86,7 @@ public ResponseEntity> pre(HttpServletRequest request) {
@GetMapping("/init")
public ResponseEntity> init(VisitorRequest visitorRequest, HttpServletRequest request) {
//
- VisitorProtobuf visitor = visitorService.create(visitorRequest, request);
+ UserProtobuf visitor = visitorService.create(visitorRequest, request);
if (visitor == null) {
return ResponseEntity.ok(JsonResult.error("init visitor failed", -1));
}
@@ -125,6 +126,8 @@ public ResponseEntity> query(VisitorRequest visitorRequest) {
@GetMapping("/ping")
public ResponseEntity> ping(VisitorRequest request) {
+ visitorService.updateStatus(request.getUid(), VisitorStatusEnum.ONLINE.name());
+
int count = messageUnreadService.getUnreadCount(request.getUid());
return ResponseEntity.ok(JsonResult.success("pong", count));
@@ -158,30 +161,34 @@ public ResponseEntity> sendRestMessage(@RequestBody Map map) {
// 机器人关键词问答
// TODO: 写入聊天记录
- // @VisitorAnnotation(title = "visitor", action = "sendKeywordMessage", description = "sendKeywordMessage")
+ // @VisitorAnnotation(title = "visitor", action = "sendKeywordMessage",
+ // description = "sendKeywordMessage")
// @PostMapping("/message/keyword")
- // public ResponseEntity> sendKeywordMessage(@RequestBody VisitorRequest request) {
- // //
- // String keyword = request.getContent();
- // String robotUid = request.getSid();
- // String orgUid = request.getOrgUid();
- // List keywordList = keywordService.ask(keyword, robotUid, orgUid);
-
- // // 随机从keywordList中选择一个元素
- // Random random = new Random();
- // KeywordResponse randomKeywordResponse = null;
- // if (!keywordList.isEmpty()) {
- // int randomIndex = random.nextInt(keywordList.size());
- // randomKeywordResponse = keywordList.get(randomIndex);
- // }
-
- // // 返回随机选择的元素或空列表(如果keywordList为空)
- // if (randomKeywordResponse != null) {
- // return ResponseEntity.ok(JsonResult.success(randomKeywordResponse.getReply()));
- // } else {
- // // 如果keywordList为空,你可以根据需要返回适当的信息,比如一个空对象或者错误信息
- // return ResponseEntity.ok(JsonResult.error());
- // }
+ // public ResponseEntity> sendKeywordMessage(@RequestBody VisitorRequest
+ // request) {
+ // //
+ // String keyword = request.getContent();
+ // String robotUid = request.getSid();
+ // String orgUid = request.getOrgUid();
+ // List keywordList = keywordService.ask(keyword, robotUid,
+ // orgUid);
+
+ // // 随机从keywordList中选择一个元素
+ // Random random = new Random();
+ // KeywordResponse randomKeywordResponse = null;
+ // if (!keywordList.isEmpty()) {
+ // int randomIndex = random.nextInt(keywordList.size());
+ // randomKeywordResponse = keywordList.get(randomIndex);
+ // }
+
+ // // 返回随机选择的元素或空列表(如果keywordList为空)
+ // if (randomKeywordResponse != null) {
+ // return
+ // ResponseEntity.ok(JsonResult.success(randomKeywordResponse.getReply()));
+ // } else {
+ // // 如果keywordList为空,你可以根据需要返回适当的信息,比如一个空对象或者错误信息
+ // return ResponseEntity.ok(JsonResult.error());
+ // }
// }
// TODO: 访客输入关联/联想
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorEntityListener.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorEntityListener.java
new file mode 100644
index 00000000..c098b201
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorEntityListener.java
@@ -0,0 +1,19 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 13:17:13
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 13:17:16
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.visitor;
+
+public class VisitorEntityListener {
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorEventListener.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorEventListener.java
new file mode 100644
index 00000000..125fbc1a
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorEventListener.java
@@ -0,0 +1,50 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 13:16:52
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 14:54:34
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.visitor;
+
+import java.util.List;
+
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.quartz.event.QuartzFiveMinEvent;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class VisitorEventListener {
+
+ private final VisitorService visitorService;
+
+ // 更新访客在线状态:检测updatedAt时间戳,如果超过五分钟则更新为离线状态
+ @EventListener
+ public void onQuartzFiveMinEvent(QuartzFiveMinEvent event) {
+ // log.info("visitor quartz five min event");
+ //
+ List visitorList = visitorService.findByStatus(VisitorStatusEnum.ONLINE.name());
+ visitorList.forEach(visitor -> {
+ log.info("visitor: {}", visitor.getUid());
+ if (System.currentTimeMillis() - visitor.getUpdatedAt().getTime() > 5 * 60 * 1000) {
+ log.info("visitor: {} offline", visitor.getUid());
+ visitorService.updateStatus(visitor.getUid(), VisitorStatusEnum.OFFLINE.name());
+ }
+ });
+
+ }
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorExtra.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorExtra.java
deleted file mode 100644
index 8ec6762b..00000000
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorExtra.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-04-08 12:03:27
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-04 17:22:40
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.service.visitor;
-
-// import com.bytedesk.service.common.ServiceSettings;
-// import lombok.AllArgsConstructor;
-// import lombok.Builder;
-// import lombok.Data;
-// import lombok.EqualsAndHashCode;
-// import lombok.NoArgsConstructor;
-// import lombok.experimental.Accessors;
-
-/**
- * used for visitor thread extra info
- */
-// @Data
-// @Builder
-// @Accessors(chain = true)
-// @EqualsAndHashCode(callSuper = false)
-// @AllArgsConstructor
-// @NoArgsConstructor
-// public class VisitorExtra {
-
-// // @Builder.Default
-// // private String welcomeTip = I18Consts.I18N_WELCOME_TIP;
-
-// // visitor_vid
-// // private String uid;
-// // private VisitorResponseSimple visitor;
-
-// // private AgentResponseSimple agent;
-
-// // private ServiceSettings serviceSettings;
-
-// // whether thread is closed
-// // @Builder.Default
-// // private boolean isClosed = false;
-
-// /** auto close time in min - 默认自动关闭时间,单位分钟 */
-// @Builder.Default
-// private Double autoCloseMin = Double.valueOf(25);
-
-// }
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorProtobuf.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorProtobuf.java
deleted file mode 100644
index a5ae8b1f..00000000
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorProtobuf.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * @Author: jackning 270580156@qq.com
- * @Date: 2024-04-04 17:05:59
- * @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-04 18:05:18
- * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
- * Please be aware of the BSL license restrictions before installing Bytedesk IM –
- * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
- * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
- * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
- * contact: 270580156@qq.com
- * 联系:270580156@qq.com
- * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
- */
-package com.bytedesk.service.visitor;
-
-import com.bytedesk.core.base.BaseResponse;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-
-/**
- * used for user.proto protobuf
- */
-@Data
-@Builder
-@Accessors(chain = true)
-@AllArgsConstructor
-@NoArgsConstructor
-@EqualsAndHashCode(callSuper = true)
-public class VisitorProtobuf extends BaseResponse {
-
- private static final long serialVersionUID = 1L;
-
- private String nickname;
-
- private String avatar;
-
- private String orgUid;
-}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorRepository.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorRepository.java
index 9720bb39..7b5daee8 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorRepository.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorRepository.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-04 15:48:03
+ * @LastEditTime: 2024-09-07 13:06:07
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,17 +14,25 @@
*/
package com.bytedesk.service.visitor;
+import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
-/**
- *
- */
@Repository
public interface VisitorRepository extends JpaRepository, JpaSpecificationExecutor {
Optional findByUidAndDeleted(String uid, Boolean deleted);
+
+ List findByStatusAndDeleted(String status, Boolean deleted);
+
+ @Modifying
+ @Transactional
+ @Query("UPDATE Visitor v SET v.status = :status WHERE v.uid = :uid")
+ int updateStatusByUid(String uid, String status);
}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorRequest.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorRequest.java
index 79baa610..20b6299a 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorRequest.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorRequest.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-04 17:05:48
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-04 18:26:13
+ * @LastEditTime: 2024-09-07 13:02:59
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -58,15 +58,12 @@ public class VisitorRequest extends BaseRequest {
private String note;
// for thread request
- // private String type; // use super.type
private String sid;
- // private String orgUid;
- // public String formatTopic() {
- // // 格式化topic,sid/uid, 其中:sid为agentUid或者workgroupUid, uid为访客visitorUid
- // return this.sid + "/" + super.uid;
- // // return formatType() + "/" + this.sid + "/" + uid;
- // }
+ // 强制转人工服务,默认false
+ private Boolean forceAgent = false;
+
+ private String status;
public ThreadTypeEnum formatType() {
int typeInt;
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorResponse.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorResponse.java
index 67fda28c..08e66e66 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorResponse.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorResponse.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-04-04 17:05:59
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-10 10:52:28
+ * @LastEditTime: 2024-09-07 13:02:52
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -61,4 +61,6 @@ public class VisitorResponse extends BaseResponse {
private Date updatedAt;
+ private String status;
+
}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorService.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorService.java
index 759cbc89..c979394b 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorService.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:21:24
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:49:05
+ * @LastEditTime: 2024-09-07 14:53:50
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,7 +14,7 @@
*/
package com.bytedesk.service.visitor;
-import java.util.Date;
+import java.util.List;
import java.util.Optional;
import org.modelmapper.ModelMapper;
import org.springframework.cache.annotation.Cacheable;
@@ -27,38 +27,17 @@
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
-// import org.springframework.util.SerializationUtils;
import org.springframework.util.StringUtils;
-import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONObject;
-import com.bytedesk.ai.robot.Robot;
-import com.bytedesk.ai.robot.RobotService;
-import com.bytedesk.ai.utils.ConvertAiUtils;
import com.bytedesk.core.base.BaseService;
import com.bytedesk.core.constant.AvatarConsts;
-import com.bytedesk.core.constant.I18Consts;
import com.bytedesk.core.enums.ClientEnum;
import com.bytedesk.core.ip.IpService;
-import com.bytedesk.core.message.Message;
-import com.bytedesk.core.message.MessageExtra;
import com.bytedesk.core.message.MessageProtobuf;
-import com.bytedesk.core.message.MessageService;
-import com.bytedesk.core.message.MessageStatusEnum;
-import com.bytedesk.core.message.MessageTypeEnum;
import com.bytedesk.core.rbac.user.UserProtobuf;
-import com.bytedesk.core.thread.Thread;
-import com.bytedesk.core.thread.ThreadService;
-import com.bytedesk.core.thread.ThreadStatusEnum;
-import com.bytedesk.core.thread.ThreadTypeEnum;
-import com.bytedesk.core.topic.TopicUtils;
import com.bytedesk.core.uid.UidUtils;
-import com.bytedesk.service.agent.Agent;
-import com.bytedesk.service.agent.AgentService;
import com.bytedesk.service.utils.ConvertServiceUtils;
-import com.bytedesk.service.workgroup.Workgroup;
-import com.bytedesk.service.workgroup.WorkgroupService;
-
+import com.bytedesk.service.visitor.strategy.CsThreadCreationContext;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -76,15 +55,7 @@ public class VisitorService extends BaseService queryByOrg(VisitorRequest request) {
@@ -103,7 +74,6 @@ public VisitorResponse query(VisitorRequest visitorRequest) {
if (!visitorOptional.isPresent()) {
throw new RuntimeException("visitor not found");
}
-
return ConvertServiceUtils.convertToVisitorResponse(visitorOptional.get());
}
@@ -113,14 +83,14 @@ public VisitorResponse query(VisitorRequest visitorRequest) {
* @param visitorRequest
* @return
*/
- public VisitorProtobuf create(VisitorRequest visitorRequest, HttpServletRequest request) {
+ public UserProtobuf create(VisitorRequest visitorRequest, HttpServletRequest request) {
//
String uid = visitorRequest.getUid();
log.info("visitor init, uid: {}", uid);
//
Visitor visitor = findByUid(uid).orElse(null);
if (visitor != null) {
- return ConvertServiceUtils.convertToVisitorProtobuf(visitor);
+ return ConvertServiceUtils.convertToUserProtobuf(visitor);
}
//
if (!StringUtils.hasText(visitorRequest.getNickname())) {
@@ -150,431 +120,25 @@ public VisitorProtobuf create(VisitorRequest visitorRequest, HttpServletRequest
throw new RuntimeException("visitor not saved");
}
//
- return ConvertServiceUtils.convertToVisitorProtobuf(savedVisitor);
+ return ConvertServiceUtils.convertToUserProtobuf(savedVisitor);
}
- // private Map strategyMap = new
- // HashMap<>();
-
- // public VisitorService() {
- // // 在构造函数中初始化策略映射
- // strategyMap.put(ThreadTypeEnum.AGENT, new AgentCsThreadCreationStrategy());
- // strategyMap.put(ThreadTypeEnum.WORKGROUP, new
- // WorkgroupCsThreadCreationStrategy());
- // strategyMap.put(ThreadTypeEnum.ROBOT, new RobotCsThreadCreationStrategy());
- // }
-
- // public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
- // ThreadTypeEnum type = visitorRequest.formatType();
- // CsThreadCreationStrategy strategy = strategyMap.get(type);
- // if (strategy == null) {
- // throw new RuntimeException("Thread type " + type.name() + " not supported");
- // }
- // return strategy.createCsThread(visitorRequest);
- // }
-
- /** TODO: 重构策略模式? */
+ /** 策略模式 */
public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
- //
- ThreadTypeEnum type = visitorRequest.formatType();
- //
- if (type.equals(ThreadTypeEnum.AGENT)) {
- // 一对一客服
- return createAgentCsThread(visitorRequest);
- //
- } else if (type.equals(ThreadTypeEnum.WORKGROUP)) {
- // 技能组
- return createWorkgroupCsThread(visitorRequest);
- //
- } else if (type.equals(ThreadTypeEnum.ROBOT)) {
- // 机器人对话
- return createRobotCsThread(visitorRequest);
- //
- } else {
- throw new RuntimeException("Thread type " + type.name() + " not supported");
- }
- }
-
- ////////////////// Agent/////////////////////
-
- public MessageProtobuf createAgentCsThread(VisitorRequest visitorRequest) {
- //
- String agentUid = visitorRequest.getSid();
- Agent agent = agentService.findByUid(agentUid)
- .orElseThrow(() -> new RuntimeException("Agent uid " + agentUid + " not found"));
- //
- boolean transferToRobot = false;
- if (agent.getServiceSettings().isDefaultRobot()) {
- // 默认转机器人优先接待
- transferToRobot = true;
- } else if (agent.getServiceSettings().isOfflineRobot()) {
- // 设置客服离线机器人,且当前客服离线,转机器人
- if (!agent.isConnected() || !agent.isAvailable()) {
- // 离线转机器人
- transferToRobot = true;
- }
- } else if (!agent.getServiceSettings().isWorkTime()
- && agent.getServiceSettings().isNonWorktimeRobot()) {
- // 当前非工作时间,且设置非工作时间转机器人,转机器人
- transferToRobot = true;
- }
- //
- if (transferToRobot) {
- // 转机器人
- Robot robot = agent.getServiceSettings().getRobot();
- if (robot != null) {
- Thread thread = getRobotThread(visitorRequest, robot);
- return getRobotMessage(visitorRequest, thread, robot);
- } else {
- throw new RuntimeException("route " + agentUid + " to a robot");
- }
- }
- // 下面进入人工接待
-
- // TODO: 判断是否达到最大接待人数,如果达到则进入排队
-
- Thread thread = getAgentThread(visitorRequest, agent);
- //
- MessageProtobuf messageProtobuf = getAgentMessage(visitorRequest, thread, agent);
- // 广播消息,由消息通道统一处理
- messageService.notifyUser(messageProtobuf);
- //
- // if (agent.isConnected() && agent.isAvailable()) {
- // // notify agent - 通知客服
- // notifyAgent(messageProtobuf);
- // } else {
- // // 离线状态
- // }
- // else if (agent.isAvailable()) {
- // // TODO: 断开连接,但是接待状态,判断是否有客服移动端token,有则发送通知
- // }
-
- return messageProtobuf;
- }
-
- private Thread getAgentThread(VisitorRequest visitorRequest, Agent agent) {
- //
- String topic = TopicUtils.formatOrgAgentThreadTopic(visitorRequest.getSid(), visitorRequest.getUid());
- // TODO: 到visitor thread表中拉取
- Optional threadOptional = threadService.findByTopic(topic);
- if (threadOptional.isPresent()) {
- return threadOptional.get();
- }
- //
- Thread thread = Thread.builder().build();
- thread.setUid(uidUtils.getCacheSerialUid());
- thread.setTopic(topic);
- thread.setType(ThreadTypeEnum.AGENT.name());
- thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
- //
- VisitorProtobuf visitor = ConvertServiceUtils.convertToVisitorProtobuf(visitorRequest);
- thread.setUser(JSON.toJSONString(visitor));
- //
- thread.setOwner(agent.getMember().getUser());
- thread.setOrgUid(agent.getOrgUid());
- thread.setExtra(JSON
- .toJSONString(ConvertServiceUtils.convertToServiceSettingsResponseVisitor(agent.getServiceSettings())));
- thread.setAgent(JSON.toJSONString(ConvertServiceUtils.convertToAgentResponseSimple(agent)));
- //
- return thread;
- }
-
- private MessageProtobuf getAgentMessage(VisitorRequest visitorRequest, Thread thread, Agent agent) {
- //
- if (thread == null) {
- throw new RuntimeException("Thread cannot be null");
- }
- if (agent == null) {
- throw new RuntimeException("Agent cannot be null");
- }
- //
- boolean isReenter = true;
- if (thread.isInit()) {
- // 访客首次进入会话
- isReenter = false;
- }
- //
- if (!agent.isConnected() || !agent.isAvailable()) {
- // 离线状态永远显示离线提示语,不显示“继续会话”
- isReenter = false;
- // 客服离线 或 非接待状态
- thread.setContent(agent.getServiceSettings().getLeavemsgTip());
- thread.setStatus(ThreadStatusEnum.OFFLINE.name());
- } else {
- // 客服在线 且 接待状态
- thread.setUnreadCount(1);
- thread.setContent(agent.getServiceSettings().getWelcomeTip());
- thread.setExtra(JSON.toJSONString(
- ConvertServiceUtils.convertToServiceSettingsResponseVisitor(agent.getServiceSettings())));
- thread.setAgent(JSON.toJSONString(ConvertServiceUtils.convertToAgentResponseSimple(agent)));
- // if thread is closed, reopen it and then create a new message
- if (thread.isClosed()) {
- // 访客会话关闭之后,重新进入
- isReenter = false;
- thread.setStatus(ThreadStatusEnum.REOPEN.name());
- } else {
- thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
- }
- }
- threadService.save(thread);
- //
- UserProtobuf user = modelMapper.map(agent, UserProtobuf.class);
- //
- return getThreadMessage(user, thread, isReenter);
+ return csThreadCreationContext.createCsThread(visitorRequest);
}
- ///////////////////// Workgroup///////////////////
-
- public MessageProtobuf createWorkgroupCsThread(VisitorRequest visitorRequest) {
- //
- String workgroupUid = visitorRequest.getSid();
- Workgroup workgroup = workgroupService.findByUid(workgroupUid)
- .orElseThrow(() -> new RuntimeException("Workgroup uid " + workgroupUid + " not found"));
- //
- boolean transferToRobot = false;
- if (workgroup.getServiceSettings().isDefaultRobot()) {
- // 默认机器人优先接待
- transferToRobot = true;
- } else if (!workgroup.isConnected()
- && workgroup.getServiceSettings().isOfflineRobot()) {
- // 所有客服离线,且设置机器人离线优先接待
- transferToRobot = true;
- } else if (!workgroup.getServiceSettings().isWorkTime()
- && workgroup.getServiceSettings().isNonWorktimeRobot()) {
- // 设置非工作时间机器人接待,且当前非工作时间,转机器人
- transferToRobot = true;
- }
- //
- if (transferToRobot) {
- // 转机器人
- Robot robot = workgroup.getServiceSettings().getRobot();
- if (robot != null) {
- Thread thread = getRobotThread(visitorRequest, robot);
- return getRobotMessage(visitorRequest, thread, robot);
- } else {
- throw new RuntimeException("route " + workgroupUid + " to a robot");
- }
- }
- // 下面人工接待
- // TODO: 所有客服都达到最大接待人数,则进入排队
-
- if (workgroup.getAgents().isEmpty()) {
- throw new RuntimeException("No agents found in workgroup with uid " + workgroupUid);
- }
- //
- Agent agent = workgroup.nextAgent();
- if (agent == null) {
- throw new RuntimeException("No available agent found in workgroup with uid " + workgroupUid);
- }
- //
- Thread thread = getWorkgroupThread(visitorRequest, agent, workgroup);
- //
- MessageProtobuf messageProtobuf = getWorkgroupMessage(visitorRequest, thread, agent, workgroup);
- // 广播消息,由消息通道统一处理
- messageService.notifyUser(messageProtobuf);
- // if (agent.isConnected() && agent.isAvailable()) {
- // log.info("agent is connected and available");
- // // notify agent - 通知客服
- // notifyAgent(messageProtobuf);
- // }
- // else if (agent.isAvailable()) {
- // // TODO: 断开连接,但是接待状态,判断是否有客服移动端token,有则发送通知
- // log.info("agent is available");
- // cacheService.pushForPersist(JSON.toJSONString(messageProtobuf));
- // } else {
- // cacheService.pushForPersist(JSON.toJSONString(messageProtobuf));
- // }
- //
- return messageProtobuf;
- }
-
- private Thread getWorkgroupThread(VisitorRequest visitorRequest, Agent agent, Workgroup workgroup) {
- //
- String topic = TopicUtils.formatOrgWorkgroupThreadTopic(workgroup.getUid(), agent.getUid(),
- visitorRequest.getUid());
- // TODO: 到visitor thread表中拉取
- Optional threadOptional = threadService.findByTopic(topic);
- if (threadOptional.isPresent()) {
- return threadOptional.get();
- }
- //
- Thread thread = Thread.builder().build();
- thread.setUid(uidUtils.getCacheSerialUid());
- thread.setTopic(topic);
- thread.setType(ThreadTypeEnum.WORKGROUP.name());
- thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
- //
- VisitorProtobuf visitor = ConvertServiceUtils.convertToVisitorProtobuf(visitorRequest);
- thread.setUser(JSON.toJSONString(visitor));
- //
- thread.setOwner(agent.getMember().getUser());
- thread.setOrgUid(agent.getOrgUid());
- thread.setExtra(JSON.toJSONString(
- ConvertServiceUtils.convertToServiceSettingsResponseVisitor(workgroup.getServiceSettings())));
- thread.setAgent(JSON.toJSONString(ConvertServiceUtils.convertToWorkgroupResponseSimple(workgroup)));
- //
- return thread;
- }
-
- private MessageProtobuf getWorkgroupMessage(VisitorRequest visitorRequest, Thread thread, Agent agent,
- Workgroup workgroup) {
- if (thread == null) {
- throw new RuntimeException("Thread cannot be null");
- }
- if (agent == null) {
- throw new RuntimeException("Agent cannot be null");
- }
- if (workgroup == null) {
- throw new RuntimeException("Workgroup cannot be null");
- }
- //
- boolean isReenter = true;
- if (thread.isInit()) {
- // 访客首次进入会话
- isReenter = false;
- }
- //
- if (!agent.isConnected() || !agent.isAvailable()) {
- // 离线状态永远显示离线提示语,不显示“继续会话”
- isReenter = false;
- // 客服离线 或 非接待状态
- thread.setContent(workgroup.getServiceSettings().getLeavemsgTip());
- thread.setStatus(ThreadStatusEnum.OFFLINE.name());
- } else {
- // 客服在线 且 接待状态
- thread.setUnreadCount(1);
- thread.setContent(workgroup.getServiceSettings().getWelcomeTip());
- thread.setExtra(JSON.toJSONString(
- ConvertServiceUtils.convertToServiceSettingsResponseVisitor(workgroup.getServiceSettings())));
- thread.setAgent(JSON.toJSONString(ConvertServiceUtils.convertToWorkgroupResponseSimple(workgroup)));
- // if thread is closed, reopen it and then create a new message
- if (thread.isClosed()) {
- // 访客会话关闭之后,重新进入
- isReenter = false;
- thread.setStatus(ThreadStatusEnum.REOPEN.name());
- } else {
- thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
- }
- }
- threadService.save(thread);
- //
- UserProtobuf user = modelMapper.map(agent, UserProtobuf.class);
- //
- return getThreadMessage(user, thread, isReenter);
- }
-
- //////////////////// Robot/////////////////////////
-
- public MessageProtobuf createRobotCsThread(VisitorRequest visitorRequest) {
- //
- String robotUid = visitorRequest.getSid();
- Robot robot = robotService.findByUid(robotUid)
- .orElseThrow(() -> new RuntimeException("Robot uid " + robotUid + " not found"));
- //
- Thread thread = getRobotThread(visitorRequest, robot);
- //
- return getRobotMessage(visitorRequest, thread, robot);
- }
-
- private Thread getRobotThread(VisitorRequest visitorRequest, Robot robot) {
- if (robot == null) {
- throw new RuntimeException("Robot cannot be null");
- }
- //
- String topic = TopicUtils.formatOrgRobotThreadTopic(robot.getUid(), visitorRequest.getUid());
- // TODO: 到visitor thread表中拉取
- Optional threadOptional = threadService.findByTopic(topic);
- if (threadOptional.isPresent()) {
- return threadOptional.get();
- }
- //
- Thread thread = Thread.builder().build();
- thread.setUid(uidUtils.getCacheSerialUid());
- thread.setTopic(topic);
- thread.setType(ThreadTypeEnum.ROBOT.name());
- thread.setUnreadCount(0);
- thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
- //
- VisitorProtobuf visitor = ConvertServiceUtils.convertToVisitorProtobuf(visitorRequest);
- thread.setUser(JSON.toJSONString(visitor));
- //
- thread.setOrgUid(robot.getOrgUid());
- thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
- robot.getServiceSettings())));
- thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
- //
- return thread;
- }
-
- private MessageProtobuf getRobotMessage(VisitorRequest visitorRequest, Thread thread, Robot robot) {
- if (thread == null) {
- throw new RuntimeException("Thread cannot be null");
- }
- if (robot == null) {
- throw new RuntimeException("Robot cannot be null");
- }
- thread.setContent(robot.getServiceSettings().getWelcomeTip());
- //
- boolean isReenter = true;
- if (thread.isInit()) {
- isReenter = false;
- }
- // 更新机器人配置+大模型相关信息
- thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
- robot.getServiceSettings())));
- thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
- thread.setContent(robot.getServiceSettings().getWelcomeTip());
- // if thread is closed, reopen it and then create a new message
- if (thread.isClosed()) {
- isReenter = false;
- thread.setStatus(ThreadStatusEnum.REOPEN.name());
- } else {
- thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
- }
- threadService.save(thread);
- //
- UserProtobuf user = modelMapper.map(robot, UserProtobuf.class);
- //
- JSONObject userExtra = new JSONObject();
- userExtra.put("llm", robot.getLlm().isEnabled());
- userExtra.put("defaultReply", robot.getDefaultReply());
- user.setExtra(JSON.toJSONString(userExtra));
- //
- return getThreadMessage(user, thread, isReenter);
+ @Cacheable(value = "visitor", key = "#uid", unless = "#result == null")
+ public Optional findByUid(String uid) {
+ return visitorRepository.findByUidAndDeleted(uid, false);
}
- //////////////////// Common /////////////////////////
-
- // thread
- private MessageProtobuf getThreadMessage(UserProtobuf user, Thread thread, boolean isReenter) {
- //
- Message message = Message.builder()
- .content(isReenter ? I18Consts.I18N_REENTER_TIP : thread.getContent())
- .type(isReenter ? MessageTypeEnum.CONTINUE.name() : MessageTypeEnum.WELCOME.name())
- .status(MessageStatusEnum.READ.name())
- .client(ClientEnum.SYSTEM.name())
- .user(JSON.toJSONString(user))
- .build();
- message.setUid(uidUtils.getCacheSerialUid());
- message.setOrgUid(thread.getOrgUid());
- message.setCreatedAt(new Date());
- message.setUpdatedAt(new Date());
- //
- if (thread.getStatus().equals(ThreadStatusEnum.OFFLINE)) {
- message.setType(MessageTypeEnum.LEAVE_MSG.name());
- }
- // message.getThreads().add(thread);
- message.setThreadTopic(thread.getTopic());
- //
- MessageExtra extraObject = MessageExtra.builder().orgUid(thread.getOrgUid()).build();
- message.setExtra(JSON.toJSONString(extraObject));
- //
- return ConvertServiceUtils.convertToMessageProtobuf(message, thread);
+ public List findByStatus(String status) {
+ return visitorRepository.findByStatusAndDeleted(status, false);
}
- @Cacheable(value = "visitor", key = "#uid", unless = "#result == null")
- public Optional findByUid(String uid) {
- return visitorRepository.findByUidAndDeleted(uid, false);
+ public int updateStatus(String uid, String newStatus) {
+ return visitorRepository.updateStatusByUid(uid, newStatus);
}
@Caching(put = {
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorStatusEnum.java b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorStatusEnum.java
new file mode 100644
index 00000000..a2cdddb4
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/VisitorStatusEnum.java
@@ -0,0 +1,20 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-09-07 13:01:32
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 13:01:36
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.visitor;
+
+public enum VisitorStatusEnum {
+ ONLINE,
+ OFFLINE
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/AgentCsThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/AgentCsThreadCreationStrategy.java
index 6b48d093..8feac4c3 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/AgentCsThreadCreationStrategy.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/AgentCsThreadCreationStrategy.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-15 15:58:11
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-15 15:59:36
+ * @LastEditTime: 2024-09-11 08:50:53
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,13 +14,246 @@
*/
package com.bytedesk.service.visitor.strategy;
+import java.util.Optional;
+
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson2.JSON;
+import com.bytedesk.ai.robot.Robot;
+import com.bytedesk.ai.utils.ConvertAiUtils;
+import com.bytedesk.core.enums.ClientEnum;
import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.message.MessageUtils;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.thread.ThreadService;
+import com.bytedesk.core.thread.ThreadStatusEnum;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.topic.TopicUtils;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.service.agent.Agent;
+import com.bytedesk.service.agent.AgentService;
+import com.bytedesk.service.utils.ConvertServiceUtils;
import com.bytedesk.service.visitor.VisitorRequest;
+import jakarta.annotation.Nonnull;
+
+import com.bytedesk.core.thread.Thread;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+// 一对一客服对话
+@Slf4j
+@Component("agentCsThreadStrategy")
+@AllArgsConstructor
public class AgentCsThreadCreationStrategy implements CsThreadCreationStrategy {
+
+ private final AgentService agentService;
+
+ private final ThreadService threadService;
+
+ // private final MessageService messageService;
+
+ private final UidUtils uidUtils;
+
+ // private final ModelMapper modelMapper;
+
@Override
public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
- // return createAgentCsThread(visitorRequest);
+ return createAgentCsThread(visitorRequest);
+ }
+
+ public MessageProtobuf createAgentCsThread(VisitorRequest visitorRequest) {
+ //
+ String agentUid = visitorRequest.getSid();
+ //
+ String topic = TopicUtils.formatOrgAgentThreadTopic(visitorRequest.getSid(), visitorRequest.getUid());
+ // 是否已经存在进行中会话
+ Thread thread = getProcessingThread(topic);
+ if (thread != null && !visitorRequest.getForceAgent()) {
+ log.info("Already have a processing thread " + JSON.toJSONString(thread));
+ return getAgentProcessingMessage(visitorRequest, thread);
+ }
+ //
+ Agent agent = agentService.findByUid(agentUid)
+ .orElseThrow(() -> new RuntimeException("Agent uid " + agentUid + " not found"));
+ //
+ thread = getAgentThread(visitorRequest, agent, topic);
+
+ // 未强制转人工的情况下,判断是否转机器人
+ if (!visitorRequest.getForceAgent()) {
+ // 判断是否需要转机器人
+ Boolean isOffline = !agent.isConnected() || !agent.isAvailable();
+ Boolean transferToRobot = agent.getServiceSettings().shouldTransferToRobot(isOffline);
+ if (transferToRobot) {
+ // 转机器人
+ // TODO: 将robot设置为agent
+ Robot robot = agent.getServiceSettings().getRobot();
+ if (robot != null) {
+ // visitorRequest.setSid(robot.getUid());
+ // return robotCsThreadCreationStrategy.createCsThread(visitorRequest);
+ //
+ thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ // 使用agent的serviceSettings配置
+ // thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ // robot.getServiceSettings())));
+ UserProtobuf agenProtobuf = ConvertAiUtils.convertToUserProtobuf(robot);
+ // thread.setAgentProtobuf(agenProtobuf);
+ thread.setAgent(JSON.toJSONString(agenProtobuf));
+ //
+ return getRobotMessage(visitorRequest, thread, agenProtobuf);
+ //
+ } else {
+ throw new RuntimeException("please set robot for " + agent.getNickname() + " in the admin panel first");
+ }
+ }
+ }
+ // TODO: 判断是否达到最大接待人数,如果达到则进入排队
+
+ return getAgentMessage(visitorRequest, thread, agent);
+ }
+
+ // 是否存在未关闭的会话
+ private Thread getProcessingThread(String topic) {
+ // TODO: 到visitor thread表中拉取
+ // 拉取未关闭会话
+ Optional threadOptional = threadService.findByTopicNotClosed(topic, "CLOSED");
+ if (threadOptional.isPresent()) {
+ return threadOptional.get();
+ }
return null;
}
+
+ private Thread getAgentThread(VisitorRequest visitorRequest, Agent agent, String topic) {
+ // TODO: 到visitor thread表中拉取
+ Thread thread = Thread.builder().build();
+ Optional threadOptional = threadService.findByTopic(topic);
+ if (threadOptional.isPresent()) {
+ // return threadOptional.get();
+ thread = threadOptional.get();
+ } else {
+ //
+ thread = Thread.builder().build();
+ thread.setUid(uidUtils.getCacheSerialUid());
+ thread.setTopic(topic);
+ thread.setType(ThreadTypeEnum.AGENT.name());
+ thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
+ //
+ UserProtobuf visitor = ConvertServiceUtils.convertToUserProtobuf(visitorRequest);
+ thread.setUser(JSON.toJSONString(visitor));
+ //
+ thread.setOwner(agent.getMember().getUser());
+ thread.setOrgUid(agent.getOrgUid());
+ }
+ // 考虑到配置可能变化,更新配置
+ thread.setExtra(JSON
+ .toJSONString(
+ ConvertServiceUtils.convertToServiceSettingsResponseVisitor(agent.getServiceSettings())));
+ // 考虑到客服信息发生变化,更新客服信息
+ UserProtobuf agentProtobuf = ConvertServiceUtils.convertToUserProtobuf(agent);
+ thread.setAgent(JSON.toJSONString(agentProtobuf));
+ //
+ return thread;
+ }
+
+ private MessageProtobuf getAgentMessage(VisitorRequest visitorRequest, @Nonnull Thread thread, @Nonnull Agent agent) {
+ //
+ boolean isReenter = true;
+ if (thread.getStatus() == ThreadStatusEnum.NORMAL.name()) {
+ // 访客首次进入会话
+ isReenter = false;
+ }
+ //
+ if (!agent.isConnected() || !agent.isAvailable()) {
+ // 离线状态永远显示离线提示语,不显示“继续会话”
+ isReenter = false;
+ // 客服离线 或 非接待状态
+ thread.setContent(agent.getServiceSettings().getLeavemsgTip());
+ thread.setStatus(ThreadStatusEnum.OFFLINE.name());
+ } else {
+ // 客服在线 且 接待状态
+ thread.setUnreadCount(1);
+ thread.setContent(agent.getServiceSettings().getWelcomeTip());
+ // thread.setExtra(JSON.toJSONString(
+ // ConvertServiceUtils.convertToServiceSettingsResponseVisitor(agent.getServiceSettings())));
+ // thread.setAgent(JSON.toJSONString(ConvertServiceUtils.convertToAgentResponseSimple(agent)));
+ // if thread is closed, reopen it and then create a new message
+ if (visitorRequest.getForceAgent()) {
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.NORMAL.name());
+ } else if (thread.isClosed()) {
+ // 访客会话关闭之后,重新进入
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.REOPEN.name());
+ } else {
+ thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
+ }
+ }
+ threadService.save(thread);
+ //
+ // UserProtobuf user = modelMapper.map(agent, UserProtobuf.class);
+ UserProtobuf user = ConvertServiceUtils.convertToUserProtobuf(agent);
+ //
+ MessageProtobuf messageProtobuf = ThreadMessageUtil.getThreadMessage(user, thread, isReenter);
+ // 广播消息,由消息通道统一处理
+ MessageUtils.notifyUser(messageProtobuf);
+
+ return messageProtobuf;
+ }
+
+ private MessageProtobuf getRobotMessage(VisitorRequest visitorRequest, Thread thread, UserProtobuf user) {
+ if (thread == null) {
+ throw new RuntimeException("Thread cannot be null");
+ }
+ // 客服在线 且 接待状态
+ thread.setUnreadCount(0);
+ thread.setStatus(ThreadStatusEnum.NORMAL.name());
+ threadService.save(thread);
+ // log.info("getAgentProcessingMessage agent: {}", thread.getAgent());
+ //
+ MessageProtobuf messageProtobuf = ThreadMessageUtil.getThreadMessage(user, thread, false);
+ // 广播消息,由消息通道统一处理
+ // messageService.notifyUser(messageProtobuf);
+
+ return messageProtobuf;
+ }
+
+ private MessageProtobuf getAgentProcessingMessage(VisitorRequest visitorRequest, Thread thread) {
+ if (thread == null) {
+ throw new RuntimeException("Thread cannot be null");
+ }
+ // 客服在线 且 接待状态
+ thread.setUnreadCount(1);
+ thread.setStatus(ThreadStatusEnum.CONTINUE.name());
+ threadService.save(thread);
+ //
+ UserProtobuf user = JSON.parseObject(thread.getAgent(), UserProtobuf.class);
+ //
+ MessageProtobuf messageProtobuf = ThreadMessageUtil.getThreadMessage(user, thread, true);
+ // 广播消息,由消息通道统一处理
+ MessageUtils.notifyUser(messageProtobuf);
+
+ return messageProtobuf;
+ }
+
+ // private MessageProtobuf notifyAgent(MessageProtobuf message) {
+ // //
+ // MessageProtobuf messageProtobuf = getAgentMessage(visitorRequest, thread,
+ // agent);
+ // // 广播消息,由消息通道统一处理
+ // messageService.notifyUser(messageProtobuf);
+ // //
+ // // if (agent.isConnected() && agent.isAvailable()) {
+ // // // notify agent - 通知客服
+ // // notifyAgent(messageProtobuf);
+ // // } else {
+ // // // 离线状态
+ // // }
+ // // else if (agent.isAvailable()) {
+ // // // TODO: 断开连接,但是接待状态,判断是否有客服移动端token,有则发送通知
+ // // }
+
+ // return messageProtobuf;
+ // }
+
}
\ No newline at end of file
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/AgentasistantThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/AgentasistantThreadCreationStrategy.java
new file mode 100644
index 00000000..f9e30a07
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/AgentasistantThreadCreationStrategy.java
@@ -0,0 +1,123 @@
+package com.bytedesk.service.visitor.strategy;
+
+import java.util.Optional;
+
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.bytedesk.ai.robot.Robot;
+import com.bytedesk.ai.robot.RobotService;
+import com.bytedesk.ai.utils.ConvertAiUtils;
+import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.thread.ThreadService;
+import com.bytedesk.core.thread.ThreadStatusEnum;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.topic.TopicUtils;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.service.utils.ConvertServiceUtils;
+import com.bytedesk.service.visitor.VisitorRequest;
+import com.bytedesk.core.thread.Thread;
+
+import lombok.AllArgsConstructor;
+
+// 客服助手会话
+@Component("agentasistantCsThreadStrategy")
+@AllArgsConstructor
+public class AgentasistantThreadCreationStrategy implements CsThreadCreationStrategy {
+
+ private final RobotService robotService;
+
+ private final ThreadService threadService;
+
+ private final UidUtils uidUtils;
+
+ private final ModelMapper modelMapper;
+
+ @Override
+ public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
+ return createAgentasistantCsThread(visitorRequest);
+ }
+
+ public MessageProtobuf createAgentasistantCsThread(VisitorRequest visitorRequest) {
+
+ String agentAsistantRobotUid = visitorRequest.getSid();
+ Robot robot = robotService.findByUid(agentAsistantRobotUid)
+ .orElseThrow(
+ () -> new RuntimeException("agentAsistantRobotUid " + agentAsistantRobotUid + " not found"));
+ //
+ Thread thread = getAgentasistantThread(visitorRequest, robot);
+ //
+ return getAgentasistantMessage(visitorRequest, thread, robot);
+ }
+
+ private Thread getAgentasistantThread(VisitorRequest visitorRequest, Robot robot) {
+ if (robot == null) {
+ throw new RuntimeException("Robot cannot be null");
+ }
+ //
+ String topic = TopicUtils.formatOrgRobotThreadTopic(robot.getUid(), visitorRequest.getUid());
+ Optional threadOptional = threadService.findByTopic(topic);
+ if (threadOptional.isPresent()) {
+ return threadOptional.get();
+ }
+ //
+ Thread thread = Thread.builder().build();
+ thread.setUid(uidUtils.getCacheSerialUid());
+ thread.setTopic(topic);
+ thread.setType(ThreadTypeEnum.AGENTASISTANT.name());
+ thread.setUnreadCount(0);
+ thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
+ //
+ UserProtobuf visitor = ConvertServiceUtils.convertToUserProtobuf(visitorRequest);
+ thread.setUser(JSON.toJSONString(visitor));
+ //
+ thread.setOrgUid(robot.getOrgUid());
+ thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ robot.getServiceSettings())));
+ thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ //
+ return thread;
+ }
+
+ private MessageProtobuf getAgentasistantMessage(VisitorRequest visitorRequest, Thread thread, Robot robot) {
+ if (thread == null) {
+ throw new RuntimeException("Thread cannot be null");
+ }
+ if (robot == null) {
+ throw new RuntimeException("Robot cannot be null");
+ }
+ thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ //
+ boolean isReenter = true;
+ if (thread.getStatus() == ThreadStatusEnum.NORMAL.name()) {
+ isReenter = false;
+ }
+ // 更新机器人配置+大模型相关信息
+ thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ robot.getServiceSettings())));
+ thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ // if thread is closed, reopen it and then create a new message
+ if (thread.isClosed()) {
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.REOPEN.name());
+ } else {
+ thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
+ }
+ threadService.save(thread);
+ //
+ UserProtobuf user = modelMapper.map(robot, UserProtobuf.class);
+ //
+ JSONObject userExtra = new JSONObject();
+ userExtra.put("llm", robot.getLlm().isEnabled());
+ userExtra.put("defaultReply", robot.getDefaultReply());
+ user.setExtra(JSON.toJSONString(userExtra));
+ //
+ return ThreadMessageUtil.getThreadMessage(user, thread, isReenter);
+ }
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/CsThreadCreationContext.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/CsThreadCreationContext.java
new file mode 100644
index 00000000..168b4283
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/CsThreadCreationContext.java
@@ -0,0 +1,59 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-29 22:07:52
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-06 19:03:31
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.visitor.strategy;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.service.visitor.VisitorRequest;
+
+/**
+ * 创建新策略:
+ * 1. 创建一个策略接口,定义创建线程的方法
+ * 2. 按照此格式命名策略:ThreadTypeEnum.**.name() + CsThreadStrategy
+ */
+@Component
+public class CsThreadCreationContext {
+
+ // 策略模式,将每个策略都封装起来
+ private final Map strategyMap;
+
+ @Autowired
+ public CsThreadCreationContext(List strategies) {
+ strategyMap = new EnumMap<>(ThreadTypeEnum.class);
+ for (CsThreadCreationStrategy strategy : strategies) {
+ // 假设每个策略类都有一个与之对应的Bean名称,可以通过Bean名称和枚举值进行匹配。
+ // 在实际应用中,可能需要其他机制来确保策略与枚举的正确匹配。
+ String beanName = strategy.getClass().getAnnotation(Component.class).value();
+ ThreadTypeEnum type = ThreadTypeEnum.valueOf(beanName.toUpperCase().replace("CSTHREADSTRATEGY", ""));
+ strategyMap.put(type, strategy);
+ }
+ }
+
+ public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
+ ThreadTypeEnum type = visitorRequest.formatType();
+ CsThreadCreationStrategy strategy = strategyMap.get(type);
+ if (strategy == null) {
+ throw new RuntimeException("Thread type " + type.name() + " not supported");
+ }
+ return strategy.createCsThread(visitorRequest);
+ }
+}
\ No newline at end of file
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/CsThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/CsThreadCreationStrategy.java
index 03145c6d..c18a9fa7 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/CsThreadCreationStrategy.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/CsThreadCreationStrategy.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-15 15:57:29
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-15 16:01:17
+ * @LastEditTime: 2024-08-29 22:25:57
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -18,6 +18,8 @@
import com.bytedesk.service.visitor.VisitorRequest;
public interface CsThreadCreationStrategy {
+
MessageProtobuf createCsThread(VisitorRequest visitorRequest);
+
}
\ No newline at end of file
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/FeedbackCsThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/FeedbackCsThreadCreationStrategy.java
new file mode 100644
index 00000000..7c9f5d05
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/FeedbackCsThreadCreationStrategy.java
@@ -0,0 +1,34 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-29 23:01:49
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-08-29 23:02:58
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.visitor.strategy;
+
+import org.springframework.stereotype.Component;
+
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.service.visitor.VisitorRequest;
+
+@Component("feedbackCsThreadStrategy")
+public class FeedbackCsThreadCreationStrategy implements CsThreadCreationStrategy {
+
+ @Override
+ public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
+ return createFeedbackCsThread(visitorRequest);
+ }
+
+ public MessageProtobuf createFeedbackCsThread(VisitorRequest visitorRequest) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException("Unimplemented method 'createCsThread'");
+ }
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/KbCsThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/KbCsThreadCreationStrategy.java
new file mode 100644
index 00000000..1dd3e9be
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/KbCsThreadCreationStrategy.java
@@ -0,0 +1,135 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-29 22:59:36
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 09:45:09
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.visitor.strategy;
+
+import java.util.Optional;
+
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson2.JSON;
+import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.thread.ThreadService;
+import com.bytedesk.core.thread.ThreadStatusEnum;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.topic.TopicUtils;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.kbase.knowledge_base.Knowledgebase;
+import com.bytedesk.kbase.knowledge_base.KnowledgebaseService;
+import com.bytedesk.service.utils.ConvertServiceUtils;
+import com.bytedesk.service.visitor.VisitorRequest;
+import com.bytedesk.core.thread.Thread;
+
+import lombok.AllArgsConstructor;
+
+// 知识库对话
+@Component("kbCsThreadStrategy")
+@AllArgsConstructor
+public class KbCsThreadCreationStrategy implements CsThreadCreationStrategy {
+
+ private final KnowledgebaseService knowledgebaseService;
+
+ private final ThreadService threadService;
+
+ private final UidUtils uidUtils;
+
+ @Override
+ public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
+ return createKbCsThread(visitorRequest);
+ }
+
+ public MessageProtobuf createKbCsThread(VisitorRequest visitorRequest) {
+
+ String kbUid = visitorRequest.getSid();
+ Knowledgebase knowledgebase = knowledgebaseService.findByUid(kbUid)
+ .orElseThrow(() -> new RuntimeException("Knowledgebase " + kbUid + " not found"));
+ //
+ Thread thread = getKbThread(visitorRequest, knowledgebase);
+ //
+ return getKbMessage(visitorRequest, thread, knowledgebase);
+ }
+
+ private Thread getKbThread(VisitorRequest visitorRequest, Knowledgebase kb) {
+ if (kb == null) {
+ throw new RuntimeException("Knowledgebase cannot be null");
+ }
+ //
+ String topic = TopicUtils.formatOrgKbThreadTopic(kb.getUid(), visitorRequest.getUid());
+ Optional threadOptional = threadService.findByTopic(topic);
+ if (threadOptional.isPresent()) {
+ return threadOptional.get();
+ }
+ //
+ Thread thread = Thread.builder().build();
+ thread.setUid(uidUtils.getCacheSerialUid());
+ thread.setTopic(topic);
+ thread.setType(ThreadTypeEnum.KB.name());
+ thread.setUnreadCount(0);
+ thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
+ //
+ UserProtobuf visitor = ConvertServiceUtils.convertToUserProtobuf(visitorRequest);
+ thread.setUser(JSON.toJSONString(visitor));
+ //
+ thread.setOrgUid(kb.getOrgUid());
+ // thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ // robot.getServiceSettings())));
+ // thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ //
+ return thread;
+ }
+
+ private MessageProtobuf getKbMessage(VisitorRequest visitorRequest, Thread thread, Knowledgebase kb) {
+ if (thread == null) {
+ throw new RuntimeException("Thread cannot be null");
+ }
+ if (kb == null) {
+ throw new RuntimeException("Knowledgebase cannot be null");
+ }
+ thread.setContent(kb.getName());
+ //
+ boolean isReenter = true;
+ if (thread.getStatus() == ThreadStatusEnum.NORMAL.name()) {
+ isReenter = false;
+ }
+ // 更新机器人配置+大模型相关信息
+ // thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ // robot.getServiceSettings())));
+ // thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ //
+ // if thread is closed, reopen it and then create a new message
+ if (thread.isClosed()) {
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.REOPEN.name());
+ } else {
+ thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
+ }
+ threadService.save(thread);
+ //
+ UserProtobuf user = UserProtobuf.builder()
+ .nickname(kb.getName())
+ .avatar(kb.getLogoUrl())
+ .build();
+ user.setUid(kb.getUid());
+ //
+ // JSONObject userExtra = new JSONObject();
+ // userExtra.put("llm", robot.getLlm().isEnabled());
+ // userExtra.put("defaultReply", robot.getDefaultReply());
+ // user.setExtra(JSON.toJSONString(userExtra));
+ //
+ return ThreadMessageUtil.getThreadMessage(user, thread, isReenter);
+ }
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/KbdocCsThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/KbdocCsThreadCreationStrategy.java
new file mode 100644
index 00000000..11aa5719
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/KbdocCsThreadCreationStrategy.java
@@ -0,0 +1,135 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-29 23:00:00
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 09:45:18
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.visitor.strategy;
+
+import java.util.Optional;
+
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson2.JSON;
+import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.thread.ThreadService;
+import com.bytedesk.core.thread.ThreadStatusEnum;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.topic.TopicUtils;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.kbase.upload.Upload;
+import com.bytedesk.kbase.upload.UploadService;
+import com.bytedesk.service.utils.ConvertServiceUtils;
+import com.bytedesk.service.visitor.VisitorRequest;
+import com.bytedesk.core.thread.Thread;
+
+import lombok.AllArgsConstructor;
+
+// 知识库某一个文档对话
+@Component("kbdocCsThreadStrategy")
+@AllArgsConstructor
+public class KbdocCsThreadCreationStrategy implements CsThreadCreationStrategy {
+
+ private final UploadService uploadService;
+
+ private final ThreadService threadService;
+
+ private final UidUtils uidUtils;
+
+ @Override
+ public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
+ return createKbdocCsThread(visitorRequest);
+ }
+
+ public MessageProtobuf createKbdocCsThread(VisitorRequest visitorRequest) {
+
+ String uploadUid = visitorRequest.getSid();
+ Upload upload = uploadService.findByUid(uploadUid)
+ .orElseThrow(() -> new RuntimeException("Upload " + uploadUid + " not found"));
+ //
+ Thread thread = getKbdocThread(visitorRequest, upload);
+ //
+ return getKbdocMessage(visitorRequest, thread, upload);
+ }
+
+ private Thread getKbdocThread(VisitorRequest visitorRequest, Upload upload) {
+ if (upload == null) {
+ throw new RuntimeException("Upload cannot be null");
+ }
+ //
+ String topic = TopicUtils.formatOrgKbdocThreadTopic(upload.getUid(), visitorRequest.getUid());
+ Optional threadOptional = threadService.findByTopic(topic);
+ if (threadOptional.isPresent()) {
+ return threadOptional.get();
+ }
+ //
+ Thread thread = Thread.builder().build();
+ thread.setUid(uidUtils.getCacheSerialUid());
+ thread.setTopic(topic);
+ thread.setType(ThreadTypeEnum.KBDOC.name());
+ thread.setUnreadCount(0);
+ thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
+ //
+ UserProtobuf visitor = ConvertServiceUtils.convertToUserProtobuf(visitorRequest);
+ thread.setUser(JSON.toJSONString(visitor));
+ //
+ thread.setOrgUid(upload.getOrgUid());
+ // thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ // robot.getServiceSettings())));
+ // thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ //
+ return thread;
+ }
+
+ private MessageProtobuf getKbdocMessage(VisitorRequest visitorRequest, Thread thread, Upload upload) {
+ if (thread == null) {
+ throw new RuntimeException("Thread cannot be null");
+ }
+ if (upload == null) {
+ throw new RuntimeException("Robot cannot be null");
+ }
+ // thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ //
+ boolean isReenter = true;
+ if (thread.getStatus() == ThreadStatusEnum.NORMAL.name()) {
+ isReenter = false;
+ }
+ // 更新机器人配置+大模型相关信息
+ // thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ // robot.getServiceSettings())));
+ // thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ // thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ // if thread is closed, reopen it and then create a new message
+ if (thread.isClosed()) {
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.REOPEN.name());
+ } else {
+ thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
+ }
+ threadService.save(thread);
+ //
+ UserProtobuf user = UserProtobuf.builder()
+ // .nickname(upload.getName())
+ // .avatar(kb.getLogoUrl())
+ .build();
+ user.setUid(upload.getUid());
+ //
+ // JSONObject userExtra = new JSONObject();
+ // userExtra.put("llm", robot.getLlm().isEnabled());
+ // userExtra.put("defaultReply", robot.getDefaultReply());
+ // user.setExtra(JSON.toJSONString(userExtra));
+ //
+ return ThreadMessageUtil.getThreadMessage(user, thread, isReenter);
+ }
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/RobotCsThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/RobotCsThreadCreationStrategy.java
index f51ed9a2..b8f3b917 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/RobotCsThreadCreationStrategy.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/RobotCsThreadCreationStrategy.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-15 15:58:33
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-15 16:00:16
+ * @LastEditTime: 2024-09-07 22:42:24
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,13 +14,113 @@
*/
package com.bytedesk.service.visitor.strategy;
+import java.util.Optional;
+
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.bytedesk.ai.robot.Robot;
+import com.bytedesk.ai.robot.RobotService;
+import com.bytedesk.ai.utils.ConvertAiUtils;
+import com.bytedesk.core.enums.ClientEnum;
import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.thread.ThreadService;
+import com.bytedesk.core.thread.ThreadStatusEnum;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.topic.TopicUtils;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.service.utils.ConvertServiceUtils;
import com.bytedesk.service.visitor.VisitorRequest;
+import com.bytedesk.core.thread.Thread;
+import lombok.AllArgsConstructor;
+
+// 机器人对话
+@Component("robotCsThreadStrategy")
+@AllArgsConstructor
public class RobotCsThreadCreationStrategy implements CsThreadCreationStrategy {
+
+ private final RobotService robotService;
+
+ private final ThreadService threadService;
+
+ private final UidUtils uidUtils;
+
@Override
public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
- // return createRobotCsThread(visitorRequest);
- return null;
+ return createRobotCsThread(visitorRequest);
}
+
+ public MessageProtobuf createRobotCsThread(VisitorRequest visitorRequest) {
+ //
+ String robotUid = visitorRequest.getSid();
+ Robot robot = robotService.findByUid(robotUid)
+ .orElseThrow(() -> new RuntimeException("Robot uid " + robotUid + " not found"));
+ //
+ Thread thread = getRobotThread(visitorRequest, robot);
+ //
+ return getRobotMessage(visitorRequest, thread, robot);
+ }
+
+ private Thread getRobotThread(VisitorRequest visitorRequest, Robot robot) {
+ //
+ String topic = TopicUtils.formatOrgRobotThreadTopic(robot.getUid(), visitorRequest.getUid());
+ // TODO: 到visitor thread表中拉取
+ Thread thread = Thread.builder().build();
+ Optional threadOptional = threadService.findByTopic(topic);
+ if (threadOptional.isPresent()) {
+ thread = threadOptional.get();
+ } else {
+ //
+ thread = Thread.builder().build();
+ thread.setUid(uidUtils.getCacheSerialUid());
+ thread.setTopic(topic);
+ thread.setType(ThreadTypeEnum.ROBOT.name());
+ thread.setUnreadCount(0);
+ thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
+ thread.setOrgUid(robot.getOrgUid());
+ //
+ UserProtobuf visitor = ConvertServiceUtils.convertToUserProtobuf(visitorRequest);
+ thread.setUser(JSON.toJSONString(visitor));
+ }
+ //
+ thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ robot.getServiceSettings())));
+ //
+ UserProtobuf agenProtobuf = ConvertAiUtils.convertToUserProtobuf(robot);
+ thread.setAgent(JSON.toJSONString(agenProtobuf));
+ //
+ return thread;
+ }
+
+ private MessageProtobuf getRobotMessage(VisitorRequest visitorRequest, Thread thread, Robot robot) {
+ //
+ thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ //
+ boolean isReenter = true;
+ if (thread.getStatus() == ThreadStatusEnum.NORMAL.name()) {
+ isReenter = false;
+ }
+ // if thread is closed, reopen it and then create a new message
+ if (thread.isClosed()) {
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.REOPEN.name());
+ } else {
+ thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
+ }
+ threadService.save(thread);
+ //
+ // UserProtobuf user = modelMapper.map(robot, UserProtobuf.class);
+ UserProtobuf user = ConvertAiUtils.convertToUserProtobuf(robot);
+ //
+ JSONObject userExtra = new JSONObject();
+ userExtra.put("llm", robot.getLlm().isEnabled());
+ userExtra.put("defaultReply", robot.getDefaultReply());
+ user.setExtra(JSON.toJSONString(userExtra));
+ //
+ return ThreadMessageUtil.getThreadMessage(user, thread, isReenter);
+ }
+
}
\ No newline at end of file
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/ThreadMessageUtil.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/ThreadMessageUtil.java
new file mode 100644
index 00000000..f9cbf975
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/ThreadMessageUtil.java
@@ -0,0 +1,73 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-08-29 22:22:38
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 22:35:25
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
+package com.bytedesk.service.visitor.strategy;
+
+import com.alibaba.fastjson2.JSON;
+import com.bytedesk.core.constant.I18Consts;
+import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.message.Message;
+import com.bytedesk.core.message.MessageExtra;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.message.MessageStatusEnum;
+import com.bytedesk.core.message.MessageTypeEnum;
+import com.bytedesk.core.message.MessageUtils;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.rbac.user.UserTypeEnum;
+import com.bytedesk.core.thread.Thread;
+import com.bytedesk.core.thread.ThreadStatusEnum;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.service.utils.ConvertServiceUtils;
+
+import java.util.Date;
+
+// import org.modelmapper.ModelMapper;
+
+// 可以根据需要选择是否使用 @Component 注解
+// 如果该方法不需要被Spring容器管理,则不需要此注解
+public class ThreadMessageUtil {
+
+ // 将此方法设为静态,以便在没有实例化类的情况下调用
+ public static MessageProtobuf getThreadMessage(UserProtobuf user, Thread thread, boolean isReenter) {
+ // ... 方法的实现保持不变 ...
+ Message message = Message.builder()
+ .content(isReenter ? I18Consts.I18N_REENTER_TIP : thread.getContent())
+ .type(isReenter ? MessageTypeEnum.CONTINUE.name() : MessageTypeEnum.WELCOME.name())
+ .status(MessageStatusEnum.READ.name())
+ .client(ClientEnum.SYSTEM.name())
+ .user(JSON.toJSONString(user))
+ .build();
+ // message.setUid(uidUtils.getCacheSerialUid());
+ // message.setUid(Utils.getUid());
+ message.setUid(UidUtils.getInstance().getDefaultSerialUid());
+ message.setOrgUid(thread.getOrgUid());
+ message.setCreatedAt(new Date());
+ message.setUpdatedAt(new Date());
+ //
+ if (user.getType().equals(UserTypeEnum.ROBOT.name())) {
+ message.setType(MessageTypeEnum.WELCOME.name());
+ message.setContent(thread.getContent());
+ }
+ //
+ if (thread.getStatus().equals(ThreadStatusEnum.OFFLINE.name())) {
+ message.setType(MessageTypeEnum.LEAVE_MSG.name());
+ }
+ message.setThreadTopic(thread.getTopic());
+ //
+ MessageExtra extra = MessageUtils.getMessageExtra(thread.getOrgUid());
+ message.setExtra(JSON.toJSONString(extra));
+ //
+ return ConvertServiceUtils.convertToMessageProtobuf(message, thread);
+ }
+}
\ No newline at end of file
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/TicketCsThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/TicketCsThreadCreationStrategy.java
new file mode 100644
index 00000000..9cdd518e
--- /dev/null
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/TicketCsThreadCreationStrategy.java
@@ -0,0 +1,122 @@
+package com.bytedesk.service.visitor.strategy;
+
+import java.util.Optional;
+
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.bytedesk.ai.robot.Robot;
+import com.bytedesk.ai.robot.RobotService;
+import com.bytedesk.ai.utils.ConvertAiUtils;
+import com.bytedesk.core.enums.ClientEnum;
+import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.thread.ThreadService;
+import com.bytedesk.core.thread.ThreadStatusEnum;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.topic.TopicUtils;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.service.utils.ConvertServiceUtils;
+import com.bytedesk.service.visitor.VisitorRequest;
+import com.bytedesk.core.thread.Thread;
+
+import lombok.AllArgsConstructor;
+
+// 工单会话
+@Component("ticketCsThreadStrategy")
+@AllArgsConstructor
+public class TicketCsThreadCreationStrategy implements CsThreadCreationStrategy {
+
+ private final RobotService robotService;
+
+ private final ThreadService threadService;
+
+ private final UidUtils uidUtils;
+
+ private final ModelMapper modelMapper;
+
+ @Override
+ public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
+ return createTicketCsThread(visitorRequest);
+ }
+
+ public MessageProtobuf createTicketCsThread(VisitorRequest visitorRequest) {
+ String agentAsistantRobotUid = visitorRequest.getSid();
+ Robot robot = robotService.findByUid(agentAsistantRobotUid)
+ .orElseThrow(
+ () -> new RuntimeException("agentAsistantRobotUid " + agentAsistantRobotUid + " not found"));
+ //
+ Thread thread = getAgentasistantThread(visitorRequest, robot);
+ //
+ return getAgentasistantMessage(visitorRequest, thread, robot);
+ }
+
+ private Thread getAgentasistantThread(VisitorRequest visitorRequest, Robot robot) {
+ if (robot == null) {
+ throw new RuntimeException("Robot cannot be null");
+ }
+ //
+ String topic = TopicUtils.formatOrgRobotThreadTopic(robot.getUid(), visitorRequest.getUid());
+ Optional threadOptional = threadService.findByTopic(topic);
+ if (threadOptional.isPresent()) {
+ return threadOptional.get();
+ }
+ //
+ Thread thread = Thread.builder().build();
+ thread.setUid(uidUtils.getCacheSerialUid());
+ thread.setTopic(topic);
+ thread.setType(ThreadTypeEnum.TICKET.name());
+ thread.setUnreadCount(0);
+ thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
+ //
+ UserProtobuf visitor = ConvertServiceUtils.convertToUserProtobuf(visitorRequest);
+ thread.setUser(JSON.toJSONString(visitor));
+ //
+ thread.setOrgUid(robot.getOrgUid());
+ thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ robot.getServiceSettings())));
+ thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ //
+ return thread;
+ }
+
+ private MessageProtobuf getAgentasistantMessage(VisitorRequest visitorRequest, Thread thread, Robot robot) {
+ if (thread == null) {
+ throw new RuntimeException("Thread cannot be null");
+ }
+ if (robot == null) {
+ throw new RuntimeException("Robot cannot be null");
+ }
+ thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ //
+ boolean isReenter = true;
+ if (thread.getStatus() == ThreadStatusEnum.NORMAL.name()) {
+ isReenter = false;
+ }
+ // 更新机器人配置+大模型相关信息
+ thread.setExtra(JSON.toJSONString(ConvertAiUtils.convertToServiceSettingsResponseVisitor(
+ robot.getServiceSettings())));
+ thread.setAgent(JSON.toJSONString(ConvertAiUtils.convertToRobotProtobuf(robot)));
+ thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ // if thread is closed, reopen it and then create a new message
+ if (thread.isClosed()) {
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.REOPEN.name());
+ } else {
+ thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
+ }
+ threadService.save(thread);
+ //
+ UserProtobuf user = modelMapper.map(robot, UserProtobuf.class);
+ //
+ JSONObject userExtra = new JSONObject();
+ userExtra.put("llm", robot.getLlm().isEnabled());
+ userExtra.put("defaultReply", robot.getDefaultReply());
+ user.setExtra(JSON.toJSONString(userExtra));
+ //
+ return ThreadMessageUtil.getThreadMessage(user, thread, isReenter);
+ }
+
+}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/WorkgroupCsThreadCreationStrategy.java b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/WorkgroupCsThreadCreationStrategy.java
index 93d4f5ca..6767299d 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/WorkgroupCsThreadCreationStrategy.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor/strategy/WorkgroupCsThreadCreationStrategy.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-15 15:58:23
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-15 15:59:48
+ * @LastEditTime: 2024-09-11 08:50:05
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,13 +14,238 @@
*/
package com.bytedesk.service.visitor.strategy;
+import java.util.Optional;
+
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson2.JSON;
+import com.bytedesk.ai.robot.Robot;
+import com.bytedesk.ai.utils.ConvertAiUtils;
+import com.bytedesk.core.enums.ClientEnum;
import com.bytedesk.core.message.MessageProtobuf;
+import com.bytedesk.core.message.MessageUtils;
+import com.bytedesk.core.rbac.user.UserProtobuf;
+import com.bytedesk.core.thread.ThreadService;
+import com.bytedesk.core.thread.ThreadStatusEnum;
+import com.bytedesk.core.thread.ThreadTypeEnum;
+import com.bytedesk.core.topic.TopicUtils;
+import com.bytedesk.core.uid.UidUtils;
+import com.bytedesk.service.agent.Agent;
+import com.bytedesk.service.utils.ConvertServiceUtils;
import com.bytedesk.service.visitor.VisitorRequest;
+import com.bytedesk.service.workgroup.Workgroup;
+import com.bytedesk.service.workgroup.WorkgroupService;
+import com.bytedesk.core.thread.Thread;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+// 技能组会话
+@Slf4j
+@Component("workgroupCsThreadStrategy")
+@AllArgsConstructor
public class WorkgroupCsThreadCreationStrategy implements CsThreadCreationStrategy {
+
+ private final WorkgroupService workgroupService;
+
+ private final ThreadService threadService;
+
+ private final UidUtils uidUtils;
+
@Override
public MessageProtobuf createCsThread(VisitorRequest visitorRequest) {
- // return createWorkgroupCsThread(visitorRequest);
+ return createWorkgroupCsThread(visitorRequest);
+ }
+
+ public MessageProtobuf createWorkgroupCsThread(VisitorRequest visitorRequest) {
+ //
+ String workgroupUid = visitorRequest.getSid();
+ //
+ String topic = TopicUtils.formatOrgWorkgroupThreadTopic(workgroupUid, visitorRequest.getUid());
+ // 是否已经存在进行中会话
+ Thread thread = getProcessingThread(topic);
+ if (thread != null && !visitorRequest.getForceAgent()) {
+ log.info("Already have a processing thread " + JSON.toJSONString(thread));
+ return getWorkgroupProcessingMessage(visitorRequest, thread);
+ }
+ //
+ Workgroup workgroup = workgroupService.findByUid(workgroupUid)
+ .orElseThrow(() -> new RuntimeException("Workgroup uid " + workgroupUid + " not found"));
+ //
+ thread = getWorkgroupThread(visitorRequest, workgroup, topic);
+
+ // 未强制转人工的情况下,判断是否转机器人
+ if (!visitorRequest.getForceAgent()) {
+ Boolean isOffline = !workgroup.isConnected();
+ Boolean transferToRobot = workgroup.getServiceSettings().shouldTransferToRobot(isOffline);
+ if (transferToRobot) {
+ // 转机器人
+ // TODO: 将robot设置为agent
+ Robot robot = workgroup.getServiceSettings().getRobot();
+ if (robot != null) {
+ //
+ thread.setContent(robot.getServiceSettings().getWelcomeTip());
+ //
+ UserProtobuf agenProtobuf = ConvertAiUtils.convertToUserProtobuf(robot);
+ thread.setAgent(JSON.toJSONString(agenProtobuf));
+ //
+ return getRobotMessage(visitorRequest, thread, agenProtobuf);
+ } else {
+ throw new RuntimeException("route " + workgroupUid + " to a robot");
+ }
+ }
+ }
+ // 下面人工接待
+ // TODO: 所有客服都达到最大接待人数,则进入排队
+ // TODO: 排队人数动态变化,随时通知访客端
+
+ if (workgroup.getAgents().isEmpty()) {
+ throw new RuntimeException("No agents found in workgroup with uid " + workgroupUid);
+ }
+
+ // TODO: 首先完善各个agent的统计数据,比如接待量、等待时长等
+ Agent agent = workgroup.nextAgent();
+ if (agent == null) {
+ throw new RuntimeException("No available agent found in workgroup with uid " + workgroupUid);
+ }
+ //
+ thread.setOwner(agent.getMember().getUser());
+ UserProtobuf agentProtobuf = ConvertServiceUtils.convertToUserProtobuf(agent);
+ thread.setAgent(JSON.toJSONString(agentProtobuf));
+ //
+ return getWorkgroupMessage(visitorRequest, thread, agent, workgroup);
+ }
+
+ // 是否存在未关闭的会话
+ private Thread getProcessingThread(String topic) {
+ // TODO: 到visitor thread表中拉取
+ // 拉取未关闭会话
+ Optional threadOptional = threadService.findByWgTopicNotClosed(topic);
+ if (threadOptional.isPresent()) {
+ return threadOptional.get();
+ }
return null;
}
+
+ private Thread getWorkgroupThread(VisitorRequest visitorRequest, Workgroup workgroup, String topic) {
+ //
+ Thread thread = Thread.builder().build();
+ // TODO: 到visitor thread表中拉取
+ Optional threadOptional = threadService.findByTopic(topic);
+ if (threadOptional.isPresent()) {
+ thread = threadOptional.get();
+ } else {
+ thread.setUid(uidUtils.getCacheSerialUid());
+ thread.setTopic(topic);
+ thread.setType(ThreadTypeEnum.WORKGROUP.name());
+ thread.setClient(ClientEnum.fromValue(visitorRequest.getClient()).name());
+ thread.setOrgUid(workgroup.getOrgUid());
+ }
+ //
+ UserProtobuf visitor = ConvertServiceUtils.convertToUserProtobuf(visitorRequest);
+ thread.setUser(JSON.toJSONString(visitor));
+ //
+ thread.setExtra(JSON.toJSONString(
+ ConvertServiceUtils.convertToServiceSettingsResponseVisitor(workgroup.getServiceSettings())));
+ //
+ return thread;
+ }
+
+ private MessageProtobuf getWorkgroupMessage(VisitorRequest visitorRequest, Thread thread, Agent agent, Workgroup workgroup) {
+ //
+ boolean isReenter = true;
+ if (thread.getStatus() == ThreadStatusEnum.NORMAL.name()) {
+ // 访客首次进入会话
+ isReenter = false;
+ }
+ //
+ if (!agent.isConnected() || !agent.isAvailable()) {
+ // 离线状态永远显示离线提示语,不显示“继续会话”
+ isReenter = false;
+ // 客服离线 或 非接待状态
+ thread.setContent(workgroup.getServiceSettings().getLeavemsgTip());
+ thread.setStatus(ThreadStatusEnum.OFFLINE.name());
+ } else {
+ // 客服在线 且 接待状态
+ thread.setUnreadCount(1);
+ thread.setContent(workgroup.getServiceSettings().getWelcomeTip());
+ // thread.setAgent(JSON.toJSONString(ConvertServiceUtils.convertToWorkgroupResponseSimple(workgroup)));
+ // if thread is closed, reopen it and then create a new message
+ if (visitorRequest.getForceAgent()) {
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.NORMAL.name());
+ } else if (thread.isClosed()) {
+ // 访客会话关闭之后,重新进入
+ isReenter = false;
+ thread.setStatus(ThreadStatusEnum.REOPEN.name());
+ } else {
+ thread.setStatus(isReenter ? ThreadStatusEnum.CONTINUE.name() : ThreadStatusEnum.NORMAL.name());
+ }
+ }
+ threadService.save(thread);
+ //
+ // UserProtobuf user = modelMapper.map(agent, UserProtobuf.class);
+ UserProtobuf user = ConvertServiceUtils.convertToUserProtobuf(agent);
+ //
+ MessageProtobuf messageProtobuf = ThreadMessageUtil.getThreadMessage(user, thread, isReenter);
+ // 广播消息,由消息通道统一处理
+ MessageUtils.notifyUser(messageProtobuf);
+
+ return messageProtobuf;
+ }
+
+ private MessageProtobuf getRobotMessage(VisitorRequest visitorRequest, Thread thread, UserProtobuf user) {
+ if (thread == null) {
+ throw new RuntimeException("Thread cannot be null");
+ }
+ // 客服在线 且 接待状态
+ thread.setUnreadCount(0);
+ thread.setStatus(ThreadStatusEnum.NORMAL.name());
+ threadService.save(thread);
+ // log.info("getAgentProcessingMessage agent: {}", thread.getAgent());
+ //
+ MessageProtobuf messageProtobuf = ThreadMessageUtil.getThreadMessage(user, thread, false);
+ // 广播消息,由消息通道统一处理
+ // messageService.notifyUser(messageProtobuf);
+
+ return messageProtobuf;
+ }
+
+ private MessageProtobuf getWorkgroupProcessingMessage(VisitorRequest visitorRequest, Thread thread) {
+ if (thread == null) {
+ throw new RuntimeException("Thread cannot be null");
+ }
+ //
+ thread.setUnreadCount(1);
+ thread.setStatus(ThreadStatusEnum.CONTINUE.name());
+ threadService.save(thread);
+ //
+ UserProtobuf user = JSON.parseObject(thread.getAgent(), UserProtobuf.class);
+ log.info("getWorkgroupProcessingMessage user: {}, agent {}", user.toString(), thread.getAgent());
+ //
+ MessageProtobuf messageProtobuf = ThreadMessageUtil.getThreadMessage(user, thread, true);
+ // 广播消息,由消息通道统一处理
+ MessageUtils.notifyUser(messageProtobuf);
+
+ return messageProtobuf;
+ }
+
+ // // 广播消息,由消息通道统一处理
+ // messageService.notifyUser(messageProtobuf);
+ //
+ // if (agent.isConnected() && agent.isAvailable()) {
+ // log.info("agent is connected and available");
+ // // notify agent - 通知客服
+ // notifyAgent(messageProtobuf);
+ // }
+ // else if (agent.isAvailable()) {
+ // // TODO: 断开连接,但是接待状态,判断是否有客服移动端token,有则发送通知
+ // log.info("agent is available");
+ // cacheService.pushForPersist(JSON.toJSONString(messageProtobuf));
+ // } else {
+ // cacheService.pushForPersist(JSON.toJSONString(messageProtobuf));
+ // }
+ //
+ // return messageProtobuf;
+
}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor_message/VisitorMessageEventListener.java b/modules/service/src/main/java/com/bytedesk/service/visitor_message/VisitorMessageEventListener.java
index 4e60eefc..1de67df5 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor_message/VisitorMessageEventListener.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor_message/VisitorMessageEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-05 11:07:05
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-05 11:07:24
+ * @LastEditTime: 2024-09-11 09:08:59
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,27 +14,26 @@
*/
package com.bytedesk.service.visitor_message;
-import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.message.MessageCreateEvent;
-import com.bytedesk.core.message.MessageUpdateEvent;
-
// import lombok.extern.slf4j.Slf4j;
+/**
+ * TODO: 监听message中客服消息,包括uid。用于在访客端显示消息,减少message表压力
+ */
// @Slf4j
@Component
public class VisitorMessageEventListener {
- @EventListener
- public void onMessageCreateEvent(MessageCreateEvent event) {
- // log.info("visitor message unread create event: " + event);
+ // @EventListener
+ // public void onMessageCreateEvent(MessageCreateEvent event) {
+ // // log.info("visitor message unread create event: " + event);
- }
+ // }
- @EventListener
- public void onMessageUpdateEvent(MessageUpdateEvent event) {
- // log.info("visitor message unread update event: " + event);
- }
+ // @EventListener
+ // public void onMessageUpdateEvent(MessageUpdateEvent event) {
+ // // log.info("visitor message unread update event: " + event);
+ // }
}
diff --git a/modules/service/src/main/java/com/bytedesk/service/visitor_thread/VisitorThreadService.java b/modules/service/src/main/java/com/bytedesk/service/visitor_thread/VisitorThreadService.java
index 0e1fc89b..e86af151 100644
--- a/modules/service/src/main/java/com/bytedesk/service/visitor_thread/VisitorThreadService.java
+++ b/modules/service/src/main/java/com/bytedesk/service/visitor_thread/VisitorThreadService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-29 13:08:52
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-12 11:35:44
+ * @LastEditTime: 2024-09-07 09:45:59
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -28,9 +28,9 @@
import com.alibaba.fastjson2.JSON;
import com.bytedesk.core.base.BaseService;
+import com.bytedesk.core.rbac.user.UserProtobuf;
import com.bytedesk.core.thread.Thread;
import com.bytedesk.service.visitor.Visitor;
-import com.bytedesk.service.visitor.VisitorProtobuf;
import com.bytedesk.service.visitor.VisitorService;
import lombok.AllArgsConstructor;
@@ -81,7 +81,7 @@ public VisitorThread create(Thread thread) {
VisitorThread visitorThread = modelMapper.map(thread, VisitorThread.class);
//
String visitorString = thread.getUser();
- VisitorProtobuf visitor = JSON.parseObject(visitorString, VisitorProtobuf.class);
+ UserProtobuf visitor = JSON.parseObject(visitorString, UserProtobuf.class);
//
Optional visitorOpt = visitorService.findByUid(visitor.getUid());
if (visitorOpt.isPresent()) {
diff --git a/modules/service/src/main/java/com/bytedesk/service/workgroup/WorkgroupResponse.java b/modules/service/src/main/java/com/bytedesk/service/workgroup/WorkgroupResponse.java
index 6c82b5ae..fda6ea18 100644
--- a/modules/service/src/main/java/com/bytedesk/service/workgroup/WorkgroupResponse.java
+++ b/modules/service/src/main/java/com/bytedesk/service/workgroup/WorkgroupResponse.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-06 10:18:02
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-07 09:52:15
+ * @LastEditTime: 2024-09-07 09:43:31
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -17,7 +17,7 @@
import java.util.List;
import com.bytedesk.core.base.BaseResponse;
-import com.bytedesk.service.agent.AgentResponseSimple;
+import com.bytedesk.core.rbac.user.UserProtobuf;
import com.bytedesk.service.settings.ServiceSettingsResponse;
import lombok.AllArgsConstructor;
@@ -49,5 +49,5 @@ public class WorkgroupResponse extends BaseResponse {
private ServiceSettingsResponse serviceSettings;
//
- private List agents;
+ private List agents;
}
diff --git a/modules/service/src/main/java/com/bytedesk/service/workgroup/WorkgroupResponseSimple.java b/modules/service/src/main/java/com/bytedesk/service/workgroup/WorkgroupResponseSimple.java
index c10a10b3..70e3d7e3 100644
--- a/modules/service/src/main/java/com/bytedesk/service/workgroup/WorkgroupResponseSimple.java
+++ b/modules/service/src/main/java/com/bytedesk/service/workgroup/WorkgroupResponseSimple.java
@@ -1,25 +1,39 @@
+/*
+ * @Author: jackning 270580156@qq.com
+ * @Date: 2024-06-06 11:24:21
+ * @LastEditors: jackning 270580156@qq.com
+ * @LastEditTime: 2024-09-07 08:19:47
+ * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
+ * Please be aware of the BSL license restrictions before installing Bytedesk IM –
+ * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
+ * 仅支持企业内部员工自用,严禁私自用于销售、二次销售或者部署SaaS方式销售
+ * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
+ * contact: 270580156@qq.com
+ * 联系:270580156@qq.com
+ * Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
+ */
package com.bytedesk.service.workgroup;
-import com.bytedesk.core.base.BaseResponse;
+// import com.bytedesk.core.base.BaseResponse;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
+// import lombok.AllArgsConstructor;
+// import lombok.Builder;
+// import lombok.Data;
+// import lombok.EqualsAndHashCode;
+// import lombok.NoArgsConstructor;
+// import lombok.experimental.Accessors;
-@Data
-@Builder
-@Accessors(chain = true)
-@AllArgsConstructor
-@NoArgsConstructor
-@EqualsAndHashCode(callSuper = true)
-public class WorkgroupResponseSimple extends BaseResponse {
+// @Data
+// @Builder
+// @Accessors(chain = true)
+// @AllArgsConstructor
+// @NoArgsConstructor
+// @EqualsAndHashCode(callSuper = true)
+// public class WorkgroupResponseSimple extends BaseResponse {
- private static final long serialVersionUID = 1L;
+// private static final long serialVersionUID = 1L;
- private String nickname;
+// private String nickname;
- private String avatar;
-}
+// private String avatar;
+// }
diff --git a/modules/service/src/main/java/com/bytedesk/service/workgroup_message/WorkgroupMessageEventListener.java b/modules/service/src/main/java/com/bytedesk/service/workgroup_message/WorkgroupMessageEventListener.java
index 9e8eb638..20c55bca 100644
--- a/modules/service/src/main/java/com/bytedesk/service/workgroup_message/WorkgroupMessageEventListener.java
+++ b/modules/service/src/main/java/com/bytedesk/service/workgroup_message/WorkgroupMessageEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-05 11:07:05
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-05 11:10:08
+ * @LastEditTime: 2024-09-07 16:15:28
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,27 +14,23 @@
*/
package com.bytedesk.service.workgroup_message;
-import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.message.MessageCreateEvent;
-import com.bytedesk.core.message.MessageUpdateEvent;
-
// import lombok.extern.slf4j.Slf4j;
// @Slf4j
@Component
public class WorkgroupMessageEventListener {
- @EventListener
- public void onMessageCreateEvent(MessageCreateEvent event) {
- // log.info("visitor message unread create event: " + event);
+ // @EventListener
+ // public void onMessageCreateEvent(MessageCreateEvent event) {
+ // // log.info("visitor message unread create event: " + event);
- }
+ // }
- @EventListener
- public void onMessageUpdateEvent(MessageUpdateEvent event) {
- // log.info("visitor message unread update event: " + event);
- }
+ // @EventListener
+ // public void onMessageUpdateEvent(MessageUpdateEvent event) {
+ // // log.info("visitor message unread update event: " + event);
+ // }
}
diff --git a/modules/social/.DS_Store b/modules/social/.DS_Store
index c0627e30..287781d9 100644
Binary files a/modules/social/.DS_Store and b/modules/social/.DS_Store differ
diff --git a/modules/team/.DS_Store b/modules/team/.DS_Store
index 2ea87726..6715dcdf 100644
Binary files a/modules/team/.DS_Store and b/modules/team/.DS_Store differ
diff --git a/modules/team/src/main/java/com/bytedesk/team/group/Group.java b/modules/team/src/main/java/com/bytedesk/team/group/Group.java
index bbd4a0e7..bd7dbb3c 100644
--- a/modules/team/src/main/java/com/bytedesk/team/group/Group.java
+++ b/modules/team/src/main/java/com/bytedesk/team/group/Group.java
@@ -2,7 +2,7 @@
* @Author: jack ning github@bytedesk.com
* @Date: 2024-01-23 14:53:16
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-19 08:37:19
+ * @LastEditTime: 2024-08-29 18:06:23
* @FilePath: /server/plugins/im/src/main/java/com/bytedesk/im/group/Group.java
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
@@ -61,11 +61,15 @@ public class Group extends BaseEntity {
@Builder.Default
private String topTip = BdConstants.EMPTY_STRING;
+ // 是否外部群
+ @Builder.Default
+ private boolean isExternal = false;
+
@Builder.Default
// @Enumerated(EnumType.STRING)
@Column(name = "group_type", nullable = false)
- // private GroupTypeEnum type = GroupTypeEnum.MEMBER;
- private String type = GroupTypeEnum.MEMBER.name();
+ // private GroupTypeEnum type = GroupTypeEnum.NORMAL;
+ private String type = GroupTypeEnum.NORMAL.name();
@Builder.Default
// @Enumerated(EnumType.STRING)
diff --git a/modules/team/src/main/java/com/bytedesk/team/group/GroupEventListener.java b/modules/team/src/main/java/com/bytedesk/team/group/GroupEventListener.java
index 7825961d..7875b746 100644
--- a/modules/team/src/main/java/com/bytedesk/team/group/GroupEventListener.java
+++ b/modules/team/src/main/java/com/bytedesk/team/group/GroupEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-27 23:00:43
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-05 10:32:00
+ * @LastEditTime: 2024-08-29 23:33:17
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -44,7 +44,7 @@ public void onThreadCreateEvent(ThreadCreateEvent event) {
// ThreadRequest request = event.getRequest();
log.info("group ThreadCreateEvent: {}", thread.getUid());
//
- if (thread.getType().equals(ThreadTypeEnum.GROUP)) {
+ if (thread.getType().equals(ThreadTypeEnum.GROUP.name())) {
String topic = thread.getTopic();
// 同事群组会话:org/group/{group_uid}
String groupUid = topic.split("/")[2];
diff --git a/modules/team/src/main/java/com/bytedesk/team/group/GroupRequest.java b/modules/team/src/main/java/com/bytedesk/team/group/GroupRequest.java
index 682696e2..a7482da2 100644
--- a/modules/team/src/main/java/com/bytedesk/team/group/GroupRequest.java
+++ b/modules/team/src/main/java/com/bytedesk/team/group/GroupRequest.java
@@ -46,6 +46,9 @@ public class GroupRequest extends BaseRequest {
private String topTip;
+ @Builder.Default
+ private Boolean isExternal = false;
+
@Builder.Default
private GroupStatusEnum status = GroupStatusEnum.NORMAL;
diff --git a/modules/team/src/main/java/com/bytedesk/team/group/GroupResponse.java b/modules/team/src/main/java/com/bytedesk/team/group/GroupResponse.java
index 6565ffd5..17d5b0f7 100644
--- a/modules/team/src/main/java/com/bytedesk/team/group/GroupResponse.java
+++ b/modules/team/src/main/java/com/bytedesk/team/group/GroupResponse.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-02-06 09:55:51
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-06-28 14:23:18
+ * @LastEditTime: 2024-08-29 18:05:57
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -51,6 +51,8 @@ public class GroupResponse extends BaseResponse {
private GroupStatusEnum status;
+ private Boolean isExternal;
+
private List members;
private List admins;
diff --git a/modules/team/src/main/java/com/bytedesk/team/group/GroupTypeEnum.java b/modules/team/src/main/java/com/bytedesk/team/group/GroupTypeEnum.java
index 38005e21..606d8723 100644
--- a/modules/team/src/main/java/com/bytedesk/team/group/GroupTypeEnum.java
+++ b/modules/team/src/main/java/com/bytedesk/team/group/GroupTypeEnum.java
@@ -14,25 +14,14 @@
*/
package com.bytedesk.team.group;
-
public enum GroupTypeEnum {
- MEMBER("member"), // 企业内部群组
- USER("user"); // 社交群组
-
- private final String value;
-
- GroupTypeEnum(String value) {
- this.value = value;
- }
-
- public String getValue() {
- return value;
- }
+ NORMAL, // 普通群组
+ TOPIC; // 话题群组
// 根据字符串查找对应的枚举常量
public static GroupTypeEnum fromValue(String value) {
for (GroupTypeEnum type : GroupTypeEnum.values()) {
- if (type.getValue().equalsIgnoreCase(value)) {
+ if (type.name().equalsIgnoreCase(value)) {
return type;
}
}
diff --git a/modules/team/src/main/java/com/bytedesk/team/group_message/GroupMessageEventListener.java b/modules/team/src/main/java/com/bytedesk/team/group_message/GroupMessageEventListener.java
index 519817d6..8a8ee2a0 100644
--- a/modules/team/src/main/java/com/bytedesk/team/group_message/GroupMessageEventListener.java
+++ b/modules/team/src/main/java/com/bytedesk/team/group_message/GroupMessageEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-05 11:07:05
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-05 11:08:53
+ * @LastEditTime: 2024-09-07 16:15:36
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,27 +14,23 @@
*/
package com.bytedesk.team.group_message;
-import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.message.MessageCreateEvent;
-import com.bytedesk.core.message.MessageUpdateEvent;
-
// import lombok.extern.slf4j.Slf4j;
// @Slf4j
@Component
public class GroupMessageEventListener {
- @EventListener
- public void onMessageCreateEvent(MessageCreateEvent event) {
- // log.info("visitor message unread create event: " + event);
+ // @EventListener
+ // public void onMessageCreateEvent(MessageCreateEvent event) {
+ // // log.info("visitor message unread create event: " + event);
- }
+ // }
- @EventListener
- public void onMessageUpdateEvent(MessageUpdateEvent event) {
- // log.info("visitor message unread update event: " + event);
- }
+ // @EventListener
+ // public void onMessageUpdateEvent(MessageUpdateEvent event) {
+ // // log.info("visitor message unread update event: " + event);
+ // }
}
diff --git a/modules/team/src/main/java/com/bytedesk/team/member/MemberEventListener.java b/modules/team/src/main/java/com/bytedesk/team/member/MemberEventListener.java
index 1a7c9a5e..12b3ef1f 100644
--- a/modules/team/src/main/java/com/bytedesk/team/member/MemberEventListener.java
+++ b/modules/team/src/main/java/com/bytedesk/team/member/MemberEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-06-03 14:06:20
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-19 16:51:33
+ * @LastEditTime: 2024-08-29 23:33:27
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -90,7 +90,7 @@ public void onThreadCreateEvent(ThreadCreateEvent event) {
// User user = thread.getOwner();
log.info("member ThreadCreateEvent: {}", thread.getUid());
//
- if (thread.getType().equals(ThreadTypeEnum.MEMBER)) {
+ if (thread.getType().equals(ThreadTypeEnum.MEMBER.name())) {
memberService.createMemberReverseThread(thread);
}
}
diff --git a/modules/team/src/main/java/com/bytedesk/team/member/MemberService.java b/modules/team/src/main/java/com/bytedesk/team/member/MemberService.java
index 909a4126..5c424ed1 100644
--- a/modules/team/src/main/java/com/bytedesk/team/member/MemberService.java
+++ b/modules/team/src/main/java/com/bytedesk/team/member/MemberService.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:20:17
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-26 06:50:05
+ * @LastEditTime: 2024-09-07 10:18:30
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -332,7 +332,7 @@ public Thread getMemberReverseThread(Thread thread) {
String reverseUid = new StringBuffer(thread.getUid()).reverse().toString();
Optional reverseThreadOptional = threadService.findByUid(reverseUid);
if (!reverseThreadOptional.isPresent()) {
- throw new RuntimeException("reverseThread not found");
+ throw new RuntimeException("reverseThread " + reverseUid + " not found");
}
return reverseThreadOptional.get();
}
diff --git a/modules/team/src/main/java/com/bytedesk/team/member_message/MemberMessageEventListener.java b/modules/team/src/main/java/com/bytedesk/team/member_message/MemberMessageEventListener.java
index ab0e93bd..a97b1f0c 100644
--- a/modules/team/src/main/java/com/bytedesk/team/member_message/MemberMessageEventListener.java
+++ b/modules/team/src/main/java/com/bytedesk/team/member_message/MemberMessageEventListener.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-07-05 11:07:05
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-07-05 11:09:16
+ * @LastEditTime: 2024-09-07 16:15:46
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -14,27 +14,23 @@
*/
package com.bytedesk.team.member_message;
-import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
-import com.bytedesk.core.message.MessageCreateEvent;
-import com.bytedesk.core.message.MessageUpdateEvent;
-
// import lombok.extern.slf4j.Slf4j;
// @Slf4j
@Component
public class MemberMessageEventListener {
- @EventListener
- public void onMessageCreateEvent(MessageCreateEvent event) {
- // log.info("visitor message unread create event: " + event);
+ // @EventListener
+ // public void onMessageCreateEvent(MessageCreateEvent event) {
+ // // log.info("visitor message unread create event: " + event);
- }
+ // }
- @EventListener
- public void onMessageUpdateEvent(MessageUpdateEvent event) {
- // log.info("visitor message unread update event: " + event);
- }
+ // @EventListener
+ // public void onMessageUpdateEvent(MessageUpdateEvent event) {
+ // // log.info("visitor message unread update event: " + event);
+ // }
}
diff --git a/modules/ticket/pom.xml b/modules/ticket/pom.xml
index 6a750a78..d53713ed 100644
--- a/modules/ticket/pom.xml
+++ b/modules/ticket/pom.xml
@@ -53,13 +53,6 @@
-
-
- com.google.protobuf
- protobuf-java
- 3.25.2
- provided
-
diff --git a/starter/.DS_Store b/starter/.DS_Store
index b1a00eb2..ff809aa4 100644
Binary files a/starter/.DS_Store and b/starter/.DS_Store differ
diff --git a/starter/pom.xml b/starter/pom.xml
index 435d0b09..f86fd4ba 100644
--- a/starter/pom.xml
+++ b/starter/pom.xml
@@ -254,7 +254,7 @@
com.bytedesk
- bytedesk-vip-flow
+ bytedesk-vip-flowbot
${revision}
diff --git a/starter/src/.DS_Store b/starter/src/.DS_Store
index 0aee3a71..fdc3749a 100644
Binary files a/starter/src/.DS_Store and b/starter/src/.DS_Store differ
diff --git a/starter/src/main/.DS_Store b/starter/src/main/.DS_Store
index e26b28d5..24eab160 100644
Binary files a/starter/src/main/.DS_Store and b/starter/src/main/.DS_Store differ
diff --git a/starter/src/main/java/com/bytedesk/starter/controller/PageRouteController.java b/starter/src/main/java/com/bytedesk/starter/controller/PageRouteController.java
index 3d527e62..3f41b5ae 100644
--- a/starter/src/main/java/com/bytedesk/starter/controller/PageRouteController.java
+++ b/starter/src/main/java/com/bytedesk/starter/controller/PageRouteController.java
@@ -2,7 +2,7 @@
* @Author: jackning 270580156@qq.com
* @Date: 2024-01-29 16:17:36
* @LastEditors: jackning 270580156@qq.com
- * @LastEditTime: 2024-08-09 06:52:34
+ * @LastEditTime: 2024-09-04 20:59:23
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
@@ -109,6 +109,11 @@ public String download() {
return "download";
}
+ @GetMapping("/download.html")
+ public String downloadHtml() {
+ return "download";
+ }
+
/**
* http://127.0.0.1:9003/about
*/
@@ -117,6 +122,11 @@ public String about() {
return "about";
}
+ @GetMapping("/about.html")
+ public String aboutHtml() {
+ return "about";
+ }
+
/**
* http://127.0.0.1:9003/contact
*/
@@ -124,28 +134,10 @@ public String about() {
public String contact() {
return "contact";
}
-
- // FIXME: 在管理后台刷新页面,无法正确路由到 admin/index.html
- // http://127.0.0.1:9003/admin/welcome
- // @GetMapping("/admin/**")
- // public String adminAll(HttpServletRequest request) {
- // // return "admin/index.html";
- // String requestURI = request.getRequestURI();
- // String staticResourcePath = "classpath:/templates/admin/" +
- // requestURI.substring(requestURI.indexOf("/admin"));
- // try {
- // // 尝试访问静态资源文件,如果文件不存在,将抛出异常
- // UrlResource resource = new UrlResource(staticResourcePath);
- // if (!resource.exists()) {
- // throw new RuntimeException("Static resource not found");
- // }
- // // 如果静态资源存在,可以将其内容作为响应返回,或者重定向到静态资源的URL
- // // 这里为了简化,我们假设直接返回静态资源视图名称
- // return "forward:" + staticResourcePath;
- // } catch (Exception e) {
- // // 静态资源不存在,回退到控制器方法逻辑
- // return "admin/index.html"; // 或者其他备用逻辑
- // }
- // }
+
+ @GetMapping("/contact.html")
+ public String contactHtml() {
+ return "contact";
+ }
}
diff --git a/starter/src/main/resources/.DS_Store b/starter/src/main/resources/.DS_Store
index 8ffdd9a7..010bd92e 100644
Binary files a/starter/src/main/resources/.DS_Store and b/starter/src/main/resources/.DS_Store differ
diff --git a/starter/src/main/resources/application-dev.properties b/starter/src/main/resources/application-dev.properties
index 96975a13..7607866b 100644
--- a/starter/src/main/resources/application-dev.properties
+++ b/starter/src/main/resources/application-dev.properties
@@ -1,6 +1,6 @@
# ===============================
-#=bytedesk config using mysql/redis、upload to aliyun oss
+#=bytedesk
# ===============================
bytedesk.debug=true
# default admin username/password/email/mobile info
@@ -23,6 +23,8 @@ bytedesk.mobile-code=123456
bytedesk.organization-name=MyCompany
bytedesk.organization-code=bytedesk
# bytedesk.timezone=GMT+8
+bytedesk.javaai=false
+bytedesk.pythonai=true
# ===============================
#=bytedesk cors config
@@ -164,6 +166,7 @@ tencent.secretkey=
# ===============================
logging.level.web=DEBUG
logging.level.org.springframework.security.web.FilterChainProxy=DEBUG
+# logging.level.org.springframework.messaging=WARN
# logging.level.org.springframework.web=INFO
# logging.level.org.springframework.security=INFO
#trace=true
@@ -209,6 +212,7 @@ spring.datasource.password=r8FqfdbWUaN3
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
+spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# ===============================
#= postgre
@@ -220,6 +224,7 @@ spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
# spring.datasource.password=C8aJEVCCvSA1VFi8
# spring.datasource.driver-class-name=org.postgresql.Driver
# spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+# spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
# ===============================
#=spring-boot-starter-data-jpa
@@ -259,6 +264,8 @@ spring.data.redis.database=0
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=C8aJEVCCvSA1VFi8
+# disable redis repository
+spring.data.redis.repositories.enabled=false
# redis pool config
common.redis.pool-config.maxIdle=64
common.redis.pool-config.maxTotal=64
@@ -374,7 +381,6 @@ spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
# spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
-spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=10000
spring.quartz.properties.org.quartz.jobStore.useProperties=false
#
@@ -426,9 +432,9 @@ logging.level.org.springframework.ai.chat.client.advisor=DEBUG
# moonshot
# https://docs.spring.io/spring-ai/reference/api/chat/moonshot-chat.html
-spring.ai.moonshot.chat.enabled=false
+spring.ai.moonshot.chat.enabled=true
# spring.ai.moonshot.base-url=api.moonshot.cn
-spring.ai.moonshot.api-key=123
+spring.ai.moonshot.api-key=sk-placeholder
# 或 export SPRING_AI_MOONSHOT_API_KEY=
# spring.ai.moonshot.chat.base-url=
# spring.ai.moonshot.chat.api-key=
@@ -438,18 +444,19 @@ spring.ai.moonshot.chat.options.temperature=0.7
# minimax
# https://platform.minimaxi.com/user-center/basic-information/interface-key
-spring.ai.minimax.chat.enabled=false
-spring.ai.minimax.api-key=123
+spring.ai.minimax.chat.enabled=true
+spring.ai.minimax.api-key=placeholder
# spring.ai.minimax.chat.options.model=minimax-v1
# zhipuai
# https://open.bigmodel.cn/overview
# https://docs.spring.io/spring-ai/reference/api/embeddings/zhipuai-embeddings.html
# https://docs.spring.io/spring-ai/reference/api/chat/zhipuai-chat.html
-spring.ai.zhipuai.chat.enabled=false
+spring.ai.zhipuai.chat.enabled=true
# spring.ai.zhipuai.chat.base-url=URL_ADDRESS
spring.ai.zhipuai.api-key=f03410085cb038ec41098ae7ca6ef047.TrJMIwAULDG12345
-spring.ai.zhipuai.chat.options.model=glm-3-turbo
+# 免费模型:https://open.bigmodel.cn/dev/api#glm-4
+spring.ai.zhipuai.chat.options.model=glm-4-flash
spring.ai.zhipuai.chat.options.temperature=0.7
#
# ollama list:
@@ -475,7 +482,7 @@ spring.ai.ollama.embedding.options.model=mistral
# spring.ai.vectorstore.pgvector.dimensions=1536
#
# https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html
-spring.ai.vectorstore.redis.initialize-schema=true
+spring.ai.vectorstore.redis.initialize-schema=false
spring.ai.vectorstore.redis.uri=redis://127.0.0.1:6379
spring.ai.vectorstore.redis.index=bytedesk_vs_index
spring.ai.vectorstore.redis.prefix=bytedesk_vs_prefix:
@@ -501,8 +508,21 @@ bytedesk.oauth.github.clientsecret=clientid
bytedesk.liangshibao.host=http://127.0.0.1:9003
bytedesk.liangshibao.path=/Users/ningjinpeng/Desktop/git/private/liangshibao/web/
+# ===============================
+# = 百度翻译
+# https://fanyi-api.baidu.com/doc/21
+# ===============================
+translate.baidu.appid=placeholder
+translate.baidu.key=placeholder
+
+# 抖音开放平台
+# https://developer.open-douyin.com/webapp/aw74cmav4symty7z/setting/app-info
+douyin.client.key=placeholder
+douyin.client.secret=placeholder
+
# ===============================
# = docker compose
# ===============================
spring.docker.compose.enabled=false
spring.docker.compose.file=docker-compose.yaml
+
diff --git a/starter/src/main/resources/templates/.DS_Store b/starter/src/main/resources/templates/.DS_Store
index 13af6c36..f79b4bc6 100644
Binary files a/starter/src/main/resources/templates/.DS_Store and b/starter/src/main/resources/templates/.DS_Store differ
diff --git a/starter/src/main/resources/templates/admin/.DS_Store b/starter/src/main/resources/templates/admin/.DS_Store
index 36941be4..ca28d06a 100644
Binary files a/starter/src/main/resources/templates/admin/.DS_Store and b/starter/src/main/resources/templates/admin/.DS_Store differ
diff --git a/starter/src/main/resources/templates/admin/1001.416ac433.async.js b/starter/src/main/resources/templates/admin/1001.416ac433.async.js
deleted file mode 100644
index d4649d48..00000000
--- a/starter/src/main/resources/templates/admin/1001.416ac433.async.js
+++ /dev/null
@@ -1,200 +0,0 @@
-"use strict";(self.webpackChunkadmin=self.webpackChunkadmin||[]).push([[1001,6501],{81001:function(i,e,n){n.r(e),n.d(e,{default:function(){return r}});var t=n(96475),a=n(6679),l=n(442),m=n(68429);const s=Object.freeze({displayName:"Handlebars",name:"handlebars",patterns:[{include:"#yfm"},{include:"#extends"},{include:"#block_comments"},{include:"#comments"},{include:"#block_helper"},{include:"#end_block"},{include:"#else_token"},{include:"#partial_and_var"},{include:"#inline_script"},{include:"#html_tags"},{include:"text.html.basic"}],repository:{block_comments:{patterns:[{begin:"\\{\\{!--",end:"--\\}\\}",name:"comment.block.handlebars",patterns:[{match:"@\\w*",name:"keyword.annotation.handlebars"},{include:"#comments"}]},{begin:"/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/?[\da-f]{1,8};/i]},n.languages.markup.tag.inside["attr-value"].inside.entity=n.languages.markup.entity,n.languages.markup.doctype.inside["internal-subset"].inside=n.languages.markup,n.hooks.add("wrap",function(s){s.type==="entity"&&(s.attributes.title=s.content.value.replace(/&/,"&"))}),Object.defineProperty(n.languages.markup.tag,"addInlined",{value:function(l,c){var p={};p["language-"+c]={pattern:/(^$)/i,lookbehind:!0,inside:n.languages[c]},p.cdata=/^$/i;var g={"included-cdata":{pattern://i,inside:p}};g["language-"+c]={pattern:/[\s\S]+/,inside:n.languages[c]};var O={};O[l]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,function(){return l}),"i"),lookbehind:!0,greedy:!0,inside:g},n.languages.insertBefore("markup","cdata",O)}}),Object.defineProperty(n.languages.markup.tag,"addAttribute",{value:function(s,l){n.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+s+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[l,"language-"+l],inside:n.languages[l]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),n.languages.html=n.languages.markup,n.languages.mathml=n.languages.markup,n.languages.svg=n.languages.markup,n.languages.xml=n.languages.extend("markup",{}),n.languages.ssml=n.languages.xml,n.languages.atom=n.languages.xml,n.languages.rss=n.languages.xml}ua.displayName="css",ua.aliases=[];function ua(n){(function(s){var l=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+l.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+l.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+l.source+"$"),alias:"url"}}},selector:{pattern:RegExp(`(^|[{}\\s])[^{}\\s](?:[^{};"'\\s]|\\s+(?![\\s{])|`+l.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:l,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var c=s.languages.markup;c&&(c.tag.addInlined("style","css"),c.tag.addAttribute("style","css"))})(n)}il.displayName="diff",il.aliases=[];function il(n){(function(s){s.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]};var l={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"};Object.keys(l).forEach(function(c){var p=l[c],g=[];/^\w+$/.test(c)||g.push(/\w+/.exec(c)[0]),c==="diff"&&g.push("bold"),s.languages.diff[c]={pattern:RegExp("^(?:["+p+`].*(?:\r
-?|
-|(?![\\s\\S])))+`,"m"),alias:g,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(c)[0]}}}}),Object.defineProperty(s.languages.diff,"PREFIXES",{value:l})})(n)}Cu.displayName="go",Cu.aliases=[];function Cu(n){n.register(Kr),n.languages.go=n.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),n.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete n.languages.go["class-name"]}Au.displayName="ini",Au.aliases=[];function Au(n){n.languages.ini={comment:{pattern:/(^[ \f\t\v]*)[#;][^\n\r]*/m,lookbehind:!0},section:{pattern:/(^[ \f\t\v]*)\[[^\n\r\]]*\]?/m,lookbehind:!0,inside:{"section-name":{pattern:/(^\[[ \f\t\v]*)[^ \f\t\v\]]+(?:[ \f\t\v]+[^ \f\t\v\]]+)*/,lookbehind:!0,alias:"selector"},punctuation:/\[|\]/}},key:{pattern:/(^[ \f\t\v]*)[^ \f\n\r\t\v=]+(?:[ \f\t\v]+[^ \f\n\r\t\v=]+)*(?=[ \f\t\v]*=)/m,lookbehind:!0,alias:"attr-name"},value:{pattern:/(=[ \f\t\v]*)[^ \f\n\r\t\v]+(?:[ \f\t\v]+[^ \f\n\r\t\v]+)*/,lookbehind:!0,alias:"attr-value",inside:{"inner-value":{pattern:/^("|').+(?=\1$)/,lookbehind:!0}}},punctuation:/=/}}sl.displayName="java",sl.aliases=[];function sl(n){n.register(Kr),function(s){var l=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,c=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,p={pattern:RegExp(/(^|[^\w.])/.source+c+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};s.languages.java=s.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[p,{pattern:RegExp(/(^|[^\w.])/.source+c+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:p.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+c+/[A-Z]\w*\b/.source),lookbehind:!0,inside:p.inside}],keyword:l,function:[s.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),s.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),s.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":p,keyword:l,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+c+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:p.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+c+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:p.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,function(){return l.source})),lookbehind:!0,inside:{punctuation:/\./}}})}(n)}_u.displayName="regex",_u.aliases=[];function _u(n){(function(s){var l={pattern:/\\[\\(){}[\]^$+*?|.]/,alias:"escape"},c=/\\(?:x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|0[0-7]{0,2}|[123][0-7]{2}|c[a-zA-Z]|.)/,p={pattern:/\.|\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},g={pattern:/\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},O="(?:[^\\\\-]|"+c.source+")",w=RegExp(O+"-"+O),C={pattern:/(<|')[^<>']+(?=[>']$)/,lookbehind:!0,alias:"variable"};s.languages.regex={"char-class":{pattern:/((?:^|[^\\])(?:\\\\)*)\[(?:[^\\\]]|\\[\s\S])*\]/,lookbehind:!0,inside:{"char-class-negation":{pattern:/(^\[)\^/,lookbehind:!0,alias:"operator"},"char-class-punctuation":{pattern:/^\[|\]$/,alias:"punctuation"},range:{pattern:w,inside:{escape:c,"range-punctuation":{pattern:/-/,alias:"operator"}}},"special-escape":l,"char-set":g,escape:c}},"special-escape":l,"char-set":p,backreference:[{pattern:/\\(?![123][0-7]{2})[1-9]/,alias:"keyword"},{pattern:/\\k<[^<>']+>/,alias:"keyword",inside:{"group-name":C}}],anchor:{pattern:/[$^]|\\[ABbGZz]/,alias:"function"},escape:c,group:[{pattern:/\((?:\?(?:<[^<>']+>|'[^<>']+'|[>:]|[=!]|[idmnsuxU]+(?:-[idmnsuxU]+)?:?))?/,alias:"punctuation",inside:{"group-name":C}},{pattern:/\)/,alias:"punctuation"}],quantifier:{pattern:/(?:[+*?]|\{\d+(?:,\d*)?\})[?+]?/,alias:"number"},alternation:{pattern:/\|/,alias:"keyword"}}})(n)}Ms.displayName="javascript",Ms.aliases=["js"];function Ms(n){n.register(Kr),n.languages.javascript=n.languages.extend("clike",{"class-name":[n.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+(/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source)+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),n.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,n.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:n.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:n.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:n.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:n.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:n.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),n.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:n.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),n.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),n.languages.markup&&(n.languages.markup.tag.addInlined("script","javascript"),n.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),n.languages.js=n.languages.javascript}Vl.displayName="json",Vl.aliases=["webmanifest"];function Vl(n){n.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},n.languages.webmanifest=n.languages.json}Nu.displayName="kotlin",Nu.aliases=["kt","kts"];function Nu(n){n.register(Kr),function(s){s.languages.kotlin=s.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\b/,lookbehind:!0},function:[{pattern:/(?:`[^\r\n`]+`|\b\w+)(?=\s*\()/,greedy:!0},{pattern:/(\.)(?:`[^\r\n`]+`|\w+)(?=\s*\{)/,lookbehind:!0,greedy:!0}],number:/\b(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete s.languages.kotlin["class-name"];var l={"interpolation-punctuation":{pattern:/^\$\{?|\}$/,alias:"punctuation"},expression:{pattern:/[\s\S]+/,inside:s.languages.kotlin}};s.languages.insertBefore("kotlin","string",{"string-literal":[{pattern:/"""(?:[^$]|\$(?:(?!\{)|\{[^{}]*\}))*?"""/,alias:"multiline",inside:{interpolation:{pattern:/\$(?:[a-z_]\w*|\{[^{}]*\})/i,inside:l},string:/[\s\S]+/}},{pattern:/"(?:[^"\\\r\n$]|\\.|\$(?:(?!\{)|\{[^{}]*\}))*"/,alias:"singleline",inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$(?:[a-z_]\w*|\{[^{}]*\})/i,lookbehind:!0,inside:l},string:/[\s\S]+/}}],char:{pattern:/'(?:[^'\\\r\n]|\\(?:.|u[a-fA-F0-9]{0,4}))'/,greedy:!0}}),delete s.languages.kotlin.string,s.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),s.languages.insertBefore("kotlin","function",{label:{pattern:/\b\w+@|@\w+\b/,alias:"symbol"}}),s.languages.kt=s.languages.kotlin,s.languages.kts=s.languages.kotlin}(n)}Sc.displayName="less",Sc.aliases=[];function Sc(n){n.register(ua),n.languages.less=n.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-](?:\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};@\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/,operator:/[+\-*\/]/}),n.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-].*?(?=[(;])/,lookbehind:!0,alias:"function"}})}vc.displayName="lua",vc.aliases=[];function vc(n){n.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[^z]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+(?:\.[a-f\d]*)?(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|(?:\.\d*)?(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/}}Ao.displayName="makefile",Ao.aliases=[];function Ao(n){n.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"builtin-target":{pattern:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,alias:"builtin"},target:{pattern:/^(?:[^:=\s]|[ \t]+(?![\s:]))+(?=\s*:(?!=))/m,alias:"symbol",inside:{variable:/\$+(?:(?!\$)[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:(?!\$)[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,function:{pattern:/(\()(?:abspath|addsuffix|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:list|s)?)(?=[ \t])/,lookbehind:!0},operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/}}jl.displayName="yaml",jl.aliases=["yml"];function jl(n){(function(s){var l=/[*&][^\s[\]{},]+/,c=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,p="(?:"+c.source+"(?:[ ]+"+l.source+")?|"+l.source+"(?:[ ]+"+c.source+")?)",g=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source}),O=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function w(C,L){L=(L||"").replace(/m/g,"")+"m";var H=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,function(){return p}).replace(/<>/g,function(){return C});return RegExp(H,L)}s.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,function(){return p})),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,function(){return p}).replace(/<>/g,function(){return"(?:"+g+"|"+O+")"})),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:w(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:w(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:w(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:w(O),lookbehind:!0,greedy:!0},number:{pattern:w(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:c,important:l,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},s.languages.yml=s.languages.yaml})(n)}Ru.displayName="markdown",Ru.aliases=["md"];function Ru(n){n.register(zi),function(s){var l=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function c(j){return j=j.replace(//g,function(){return l}),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+j+")")}var p=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,g=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,function(){return p}),O=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;s.languages.markdown=s.languages.extend("markup",{}),s.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:s.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+g+O+"(?:"+g+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+g+O+")(?:"+g+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(p),inside:s.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+g+")"+O+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+g+"$"),inside:{"table-header":{pattern:RegExp(p),alias:"important",inside:s.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:c(/\b__(?:(?!_)|_(?:(?!_))+_)+__\b|\*\*(?:(?!\*)|\*(?:(?!\*))+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:c(/\b_(?:(?!_)|__(?:(?!_))+__)+_\b|\*(?:(?!\*)|\*\*(?:(?!\*))+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:c(/(~~?)(?:(?!~))+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:c(/!?\[(?:(?!\]))+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\]))+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach(function(j){["url","bold","italic","strike","code-snippet"].forEach(function(ee){j!==ee&&(s.languages.markdown[j].inside.content.inside[ee]=s.languages.markdown[ee])})}),s.hooks.add("after-tokenize",function(j){if(j.language!=="markdown"&&j.language!=="md")return;function ee(se){if(!(!se||typeof se=="string"))for(var Se=0,at=se.length;Se",quot:'"'},L=String.fromCodePoint||String.fromCharCode;function H(j){var ee=j.replace(w,"");return ee=ee.replace(/&(\w{1,8}|#x?[\da-f]{1,8});/gi,function(se,Se){if(Se=Se.toLowerCase(),Se[0]==="#"){var at;return Se[1]==="x"?at=parseInt(Se.slice(2),16):at=Number(Se.slice(1)),L(at)}else{var ot=C[Se];return ot||se}}),ee}s.languages.md=s.languages.markdown}(n)}Pd.displayName="objectivec",Pd.aliases=["objc"];function Pd(n){n.register(Da),n.languages.objectivec=n.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete n.languages.objectivec["class-name"],n.languages.objc=n.languages.objectivec}Fd.displayName="perl",Fd.aliases=[];function Fd(n){(function(s){var l=/(?:\((?:[^()\\]|\\[\s\S])*\)|\{(?:[^{}\\]|\\[\s\S])*\}|\[(?:[^[\]\\]|\\[\s\S])*\]|<(?:[^<>\\]|\\[\s\S])*>)/.source;s.languages.perl={comment:[{pattern:/(^\s*)=\w[\s\S]*?=cut.*/m,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\$])#.*/,lookbehind:!0,greedy:!0}],string:[{pattern:RegExp(/\b(?:q|qq|qw|qx)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,l].join("|")+")"),greedy:!0},{pattern:/("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/'(?:[^'\\\r\n]|\\.)*'/,greedy:!0}],regex:[{pattern:RegExp(/\b(?:m|qr)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,l].join("|")+")"+/[msixpodualngc]*/.source),greedy:!0},{pattern:RegExp(/(^|[^-])\b(?:s|tr|y)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,/([a-zA-Z0-9])(?:(?!\3)[^\\]|\\[\s\S])*\3(?:(?!\3)[^\\]|\\[\s\S])*\3/.source,l+/\s*/.source+l].join("|")+")"+/[msixpodualngcer]*/.source),lookbehind:!0,greedy:!0},{pattern:/\/(?:[^\/\\\r\n]|\\.)*\/[msixpodualngc]*(?=\s*(?:$|[\r\n,.;})&|\-+*~<>!?^]|(?:and|cmp|eq|ge|gt|le|lt|ne|not|or|x|xor)\b))/,greedy:!0}],variable:[/[&*$@%]\{\^[A-Z]+\}/,/[&*$@%]\^[A-Z_]/,/[&*$@%]#?(?=\{)/,/[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+(?![\w$]))+(?:::)*/,/[&*$@%]\d+/,/(?!%=)[$@%][!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~]/],filehandle:{pattern:/<(?![<=])\S*?>|\b_\b/,alias:"symbol"},"v-string":{pattern:/v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,alias:"string"},function:{pattern:/(\bsub[ \t]+)\w+/,lookbehind:!0},keyword:/\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|return|say|state|sub|switch|undef|unless|until|use|when|while)\b/,number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,operator:/-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:and|cmp|eq|ge|gt|le|lt|ne|not|or|xor)\b/,punctuation:/[{}[\];(),:]/}})(n)}bs.displayName="markup-templating",bs.aliases=[];function bs(n){n.register(zi),function(s){function l(c,p){return"___"+c.toUpperCase()+p+"___"}Object.defineProperties(s.languages["markup-templating"]={},{buildPlaceholders:{value:function(c,p,g,O){if(c.language===p){var w=c.tokenStack=[];c.code=c.code.replace(g,function(C){if(typeof O=="function"&&!O(C))return C;for(var L=w.length,H;c.code.indexOf(H=l(p,L))!==-1;)++L;return w[L]=C,H}),c.grammar=s.languages.markup}}},tokenizePlaceholders:{value:function(c,p){if(c.language!==p||!c.tokenStack)return;c.grammar=s.languages[p];var g=0,O=Object.keys(c.tokenStack);function w(C){for(var L=0;L=O.length);L++){var H=C[L];if(typeof H=="string"||H.content&&typeof H.content=="string"){var j=O[g],ee=c.tokenStack[j],se=typeof H=="string"?H:H.content,Se=l(p,j),at=se.indexOf(Se);if(at>-1){++g;var ot=se.substring(0,at),Ke=new s.Token(p,s.tokenize(ee,c.grammar),"language-"+p,ee),Ot=se.substring(at+Se.length),kt=[];ot&&kt.push.apply(kt,w([ot])),kt.push(Ke),Ot&&kt.push.apply(kt,w([Ot])),typeof H=="string"?C.splice.apply(C,[L,1].concat(kt)):H.content=kt}}else H.content&&w(H.content)}return C}w(c.tokens)}}})}(n)}Zl.displayName="php",Zl.aliases=[];function Zl(n){n.register(bs),function(s){var l=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,c=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],p=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,g=/=>|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,O=/[{}\[\](),:;]/;s.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:l,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:c,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:p,operator:g,punctuation:O};var w={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:s.languages.php},C=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:w}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:w}}];s.languages.insertBefore("php","variable",{string:C,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:l,string:C,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:c,number:p,operator:g,punctuation:O}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),s.hooks.add("before-tokenize",function(L){if(/<\?/.test(L.code)){var H=/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g;s.languages["markup-templating"].buildPlaceholders(L,"php",H)}}),s.hooks.add("after-tokenize",function(L){s.languages["markup-templating"].tokenizePlaceholders(L,"php")})}(n)}kc.displayName="python",kc.aliases=["py"];function kc(n){n.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},n.languages.python["string-interpolation"].inside.interpolation.inside.rest=n.languages.python,n.languages.py=n.languages.python}Du.displayName="r",Du.aliases=[];function Du(n){n.languages.r={comment:/#.*/,string:{pattern:/(['"])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},"percent-operator":{pattern:/%[^%\s]*%/,alias:"operator"},boolean:/\b(?:FALSE|TRUE)\b/,ellipsis:/\.\.(?:\.|\d+)/,number:[/\b(?:Inf|NaN)\b/,/(?:\b0x[\dA-Fa-f]+(?:\.\d*)?|\b\d+(?:\.\d*)?|\B\.\d+)(?:[EePp][+-]?\d+)?[iL]?/],keyword:/\b(?:NA|NA_character_|NA_complex_|NA_integer_|NA_real_|NULL|break|else|for|function|if|in|next|repeat|while)\b/,operator:/->?>?|<(?:=|-)?|[>=!]=?|::?|&&?|\|\|?|[+*\/^$@~]/,punctuation:/[(){}\[\],;]/}}al.displayName="ruby",al.aliases=["rb"];function al(n){n.register(Kr),function(s){s.languages.ruby=s.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===|=>|[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),s.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var l={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:s.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete s.languages.ruby.function;var c="(?:"+[/([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,/\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,/<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source].join("|")+")",p=/(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;s.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp(/%r/.source+c+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:l,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:l,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp(/(^|[^:]):/.source+p),lookbehind:!0,greedy:!0},{pattern:RegExp(/([\r\n{(,][ \t]*)/.source+p+/(?=:(?!:))/.source),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),s.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp(/%[qQiIwWs]?/.source+c),greedy:!0,inside:{interpolation:l,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:l,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:l,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp(/%x/.source+c),greedy:!0,inside:{interpolation:l,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:l,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete s.languages.ruby.string,s.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),s.languages.rb=s.languages.ruby}(n)}wc.displayName="rust",wc.aliases=[];function wc(n){(function(s){for(var l=/\/\*(?:[^*/]|\*(?!\/)|\/(?!\*)|)*\*\//.source,c=0;c<2;c++)l=l.replace(//g,function(){return l});l=l.replace(//g,function(){return/[^\s\S]/.source}),s.languages.rust={comment:[{pattern:RegExp(/(^|[^\\])/.source+l),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|trait|type|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<=?|>>?=?|[@?]/},s.languages.rust["closure-params"].inside.rest=s.languages.rust,s.languages.rust.attribute.inside.string=s.languages.rust.string})(n)}xc.displayName="sass",xc.aliases=[];function xc(n){n.register(ua),function(s){s.languages.sass=s.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t].+)*/m,lookbehind:!0,greedy:!0}}),s.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,greedy:!0,inside:{atrule:/(?:@[\w-]+|[+=])/}}}),delete s.languages.sass.atrule;var l=/\$[-\w]+|#\{\$[-\w]+\}/,c=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|not|or)\b/,{pattern:/(\s)-(?=\s)/,lookbehind:!0}];s.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,greedy:!0,inside:{punctuation:/:/,variable:l,operator:c}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s].*)/m,greedy:!0,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:l,operator:c,important:s.languages.sass.important}}}),delete s.languages.sass.property,delete s.languages.sass.important,s.languages.insertBefore("sass","punctuation",{selector:{pattern:/^([ \t]*)\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*)*/m,lookbehind:!0,greedy:!0}})}(n)}Tc.displayName="scss",Tc.aliases=[];function Tc(n){n.register(ua),n.languages.scss=n.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-](?:\([^()]+\)|[^()\s]|\s+(?!\s))*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)?url(?=\()/i,selector:{pattern:/(?=\S)[^@;{}()]?(?:[^@;{}()\s]|\s+(?!\s)|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}][^:{}]*[:{][^}]))/,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-\w]+/,variable:/\$[-\w]+|#\{\$[-\w]+\}/}},property:{pattern:/(?:[-\w]|\$[-\w]|#\{\$[-\w]+\})+(?=\s*:)/,inside:{variable:/\$[-\w]+|#\{\$[-\w]+\}/}}}),n.languages.insertBefore("scss","atrule",{keyword:[/@(?:content|debug|each|else(?: if)?|extend|for|forward|function|if|import|include|mixin|return|use|warn|while)\b/i,{pattern:/( )(?:from|through)(?= )/,lookbehind:!0}]}),n.languages.insertBefore("scss","important",{variable:/\$[-\w]+|#\{\$[-\w]+\}/}),n.languages.insertBefore("scss","function",{"module-modifier":{pattern:/\b(?:as|hide|show|with)\b/i,alias:"keyword"},placeholder:{pattern:/%[-\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"},operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|not|or)(?=\s)/,lookbehind:!0}}),n.languages.scss.atrule.inside.rest=n.languages.scss}Iu.displayName="sql",Iu.aliases=[];function Iu(n){n.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}}Cc.displayName="swift",Cc.aliases=[];function Cc(n){n.languages.swift={comment:{pattern:/(^|[^\\:])(?:\/\/.*|\/\*(?:[^/*]|\/(?!\*)|\*(?!\/)|\/\*(?:[^*]|\*(?!\/))*\*\/)*\*\/)/,lookbehind:!0,greedy:!0},"string-literal":[{pattern:RegExp(/(^|[^"#])/.source+"(?:"+/"(?:\\(?:\((?:[^()]|\([^()]*\))*\)|\r\n|[^(])|[^\\\r\n"])*"/.source+"|"+/"""(?:\\(?:\((?:[^()]|\([^()]*\))*\)|[^(])|[^\\"]|"(?!""))*"""/.source+")"+/(?!["#])/.source),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\\($/,alias:"punctuation"},punctuation:/\\(?=[\r\n])/,string:/[\s\S]+/}},{pattern:RegExp(/(^|[^"#])(#+)/.source+"(?:"+/"(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|\r\n|[^#])|[^\\\r\n])*?"/.source+"|"+/"""(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|[^#])|[^\\])*?"""/.source+")\\2"),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\#+\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\#+\($/,alias:"punctuation"},string:/[\s\S]+/}}],directive:{pattern:RegExp(/#/.source+"(?:"+(/(?:elseif|if)\b/.source+"(?:[ ]*"+/(?:![ \t]*)?(?:\b\w+\b(?:[ \t]*\((?:[^()]|\([^()]*\))*\))?|\((?:[^()]|\([^()]*\))*\))(?:[ \t]*(?:&&|\|\|))?/.source+")+")+"|"+/(?:else|endif)\b/.source+")"),alias:"property",inside:{"directive-name":/^#\w+/,boolean:/\b(?:false|true)\b/,number:/\b\d+(?:\.\d+)*\b/,operator:/!|&&|\|\||[<>]=?/,punctuation:/[(),]/}},literal:{pattern:/#(?:colorLiteral|column|dsohandle|file(?:ID|Literal|Path)?|function|imageLiteral|line)\b/,alias:"constant"},"other-directive":{pattern:/#\w+\b/,alias:"property"},attribute:{pattern:/@\w+/,alias:"atrule"},"function-definition":{pattern:/(\bfunc\s+)\w+/,lookbehind:!0,alias:"function"},label:{pattern:/\b(break|continue)\s+\w+|\b[a-zA-Z_]\w*(?=\s*:\s*(?:for|repeat|while)\b)/,lookbehind:!0,alias:"important"},keyword:/\b(?:Any|Protocol|Self|Type|actor|as|assignment|associatedtype|associativity|async|await|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|higherThan|if|import|in|indirect|infix|init|inout|internal|is|isolated|lazy|left|let|lowerThan|mutating|none|nonisolated|nonmutating|open|operator|optional|override|postfix|precedencegroup|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|set|some|static|struct|subscript|super|switch|throw|throws|try|typealias|unowned|unsafe|var|weak|where|while|willSet)\b/,boolean:/\b(?:false|true)\b/,nil:{pattern:/\bnil\b/,alias:"constant"},"short-argument":/\$\d+\b/,omit:{pattern:/\b_\b/,alias:"keyword"},number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,"class-name":/\b[A-Z](?:[A-Z_\d]*[a-z]\w*)?\b/,function:/\b[a-z_]\w*(?=\s*\()/i,constant:/\b(?:[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,operator:/[-+*/%=!<>&|^~?]+|\.[.\-+*/%=!<>&|^~?]+/,punctuation:/[{}[\]();,.:\\]/},n.languages.swift["string-literal"].forEach(function(s){s.inside.interpolation.inside=n.languages.swift})}Ac.displayName="typescript",Ac.aliases=["ts"];function Ac(n){n.register(Ms),function(s){s.languages.typescript=s.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),s.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete s.languages.typescript.parameter,delete s.languages.typescript["literal-property"];var l=s.languages.extend("typescript",{});delete l["class-name"],s.languages.typescript["class-name"].inside=l,s.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:l}}}}),s.languages.ts=s.languages.typescript}(n)}_c.displayName="basic",_c.aliases=[];function _c(n){n.languages.basic={comment:{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},string:{pattern:/"(?:""|[!#$%&'()*,\/:;<=>?^\w +\-.])*"/,greedy:!0},number:/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:E[+-]?\d+)?/i,keyword:/\b(?:AS|BEEP|BLOAD|BSAVE|CALL(?: ABSOLUTE)?|CASE|CHAIN|CHDIR|CLEAR|CLOSE|CLS|COM|COMMON|CONST|DATA|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DIM|DO|DOUBLE|ELSE|ELSEIF|END|ENVIRON|ERASE|ERROR|EXIT|FIELD|FILES|FOR|FUNCTION|GET|GOSUB|GOTO|IF|INPUT|INTEGER|IOCTL|KEY|KILL|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|MKDIR|NAME|NEXT|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPTION BASE|OUT|POKE|PUT|READ|REDIM|REM|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SELECT CASE|SHARED|SHELL|SINGLE|SLEEP|STATIC|STEP|STOP|STRING|SUB|SWAP|SYSTEM|THEN|TIMER|TO|TROFF|TRON|TYPE|UNLOCK|UNTIL|USING|VIEW PRINT|WAIT|WEND|WHILE|WRITE)(?:\$|\b)/i,function:/\b(?:ABS|ACCESS|ACOS|ANGLE|AREA|ARITHMETIC|ARRAY|ASIN|ASK|AT|ATN|BASE|BEGIN|BREAK|CAUSE|CEIL|CHR|CLIP|COLLATE|COLOR|CON|COS|COSH|COT|CSC|DATE|DATUM|DEBUG|DECIMAL|DEF|DEG|DEGREES|DELETE|DET|DEVICE|DISPLAY|DOT|ELAPSED|EPS|ERASABLE|EXLINE|EXP|EXTERNAL|EXTYPE|FILETYPE|FIXED|FP|GO|GRAPH|HANDLER|IDN|IMAGE|IN|INT|INTERNAL|IP|IS|KEYED|LBOUND|LCASE|LEFT|LEN|LENGTH|LET|LINE|LINES|LOG|LOG10|LOG2|LTRIM|MARGIN|MAT|MAX|MAXNUM|MID|MIN|MISSING|MOD|NATIVE|NUL|NUMERIC|OF|OPTION|ORD|ORGANIZATION|OUTIN|OUTPUT|PI|POINT|POINTER|POINTS|POS|PRINT|PROGRAM|PROMPT|RAD|RADIANS|RANDOMIZE|RECORD|RECSIZE|RECTYPE|RELATIVE|REMAINDER|REPEAT|REST|RETRY|REWRITE|RIGHT|RND|ROUND|RTRIM|SAME|SEC|SELECT|SEQUENTIAL|SET|SETTER|SGN|SIN|SINH|SIZE|SKIP|SQR|STANDARD|STATUS|STR|STREAM|STYLE|TAB|TAN|TANH|TEMPLATE|TEXT|THERE|TIME|TIMEOUT|TRACE|TRANSFORM|TRUNCATE|UBOUND|UCASE|USE|VAL|VARIABLE|VIEWPORT|WHEN|WINDOW|WITH|ZER|ZONEWIDTH)(?:\$|\b)/i,operator:/<[=>]?|>=?|[+\-*\/^=&]|\b(?:AND|EQV|IMP|NOT|OR|XOR)\b/i,punctuation:/[,;:()]/}}v.displayName="vbnet",v.aliases=[];function v(n){n.register(_c),n.languages.vbnet=n.languages.extend("basic",{comment:[{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},{pattern:/(^|[^\\:])'.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(^|[^"])"(?:""|[^"])*"(?!")/,lookbehind:!0,greedy:!0},keyword:/(?:\b(?:ADDHANDLER|ADDRESSOF|ALIAS|AND|ANDALSO|AS|BEEP|BLOAD|BOOLEAN|BSAVE|BYREF|BYTE|BYVAL|CALL(?: ABSOLUTE)?|CASE|CATCH|CBOOL|CBYTE|CCHAR|CDATE|CDBL|CDEC|CHAIN|CHAR|CHDIR|CINT|CLASS|CLEAR|CLNG|CLOSE|CLS|COBJ|COM|COMMON|CONST|CONTINUE|CSBYTE|CSHORT|CSNG|CSTR|CTYPE|CUINT|CULNG|CUSHORT|DATA|DATE|DECIMAL|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DEFAULT|DELEGATE|DIM|DIRECTCAST|DO|DOUBLE|ELSE|ELSEIF|END|ENUM|ENVIRON|ERASE|ERROR|EVENT|EXIT|FALSE|FIELD|FILES|FINALLY|FOR(?: EACH)?|FRIEND|FUNCTION|GET|GETTYPE|GETXMLNAMESPACE|GLOBAL|GOSUB|GOTO|HANDLES|IF|IMPLEMENTS|IMPORTS|IN|INHERITS|INPUT|INTEGER|INTERFACE|IOCTL|IS|ISNOT|KEY|KILL|LET|LIB|LIKE|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|ME|MKDIR|MOD|MODULE|MUSTINHERIT|MUSTOVERRIDE|MYBASE|MYCLASS|NAME|NAMESPACE|NARROWING|NEW|NEXT|NOT|NOTHING|NOTINHERITABLE|NOTOVERRIDABLE|OBJECT|OF|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPERATOR|OPTION(?: BASE)?|OPTIONAL|OR|ORELSE|OUT|OVERLOADS|OVERRIDABLE|OVERRIDES|PARAMARRAY|PARTIAL|POKE|PRIVATE|PROPERTY|PROTECTED|PUBLIC|PUT|RAISEEVENT|READ|READONLY|REDIM|REM|REMOVEHANDLER|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SBYTE|SELECT(?: CASE)?|SET|SHADOWS|SHARED|SHELL|SHORT|SINGLE|SLEEP|STATIC|STEP|STOP|STRING|STRUCTURE|SUB|SWAP|SYNCLOCK|SYSTEM|THEN|THROW|TIMER|TO|TROFF|TRON|TRUE|TRY|TRYCAST|TYPE|TYPEOF|UINTEGER|ULONG|UNLOCK|UNTIL|USHORT|USING|VIEW PRINT|WAIT|WEND|WHEN|WHILE|WIDENING|WITH|WITHEVENTS|WRITE|WRITEONLY|XOR)|\B(?:#CONST|#ELSE|#ELSEIF|#END|#IF))(?:\$|\b)/i,punctuation:/[,;:(){}]/})}var h=z(96484);const m=["AElig","AMP","Aacute","Acirc","Agrave","Aring","Atilde","Auml","COPY","Ccedil","ETH","Eacute","Ecirc","Egrave","Euml","GT","Iacute","Icirc","Igrave","Iuml","LT","Ntilde","Oacute","Ocirc","Ograve","Oslash","Otilde","Ouml","QUOT","REG","THORN","Uacute","Ucirc","Ugrave","Uuml","Yacute","aacute","acirc","acute","aelig","agrave","amp","aring","atilde","auml","brvbar","ccedil","cedil","cent","copy","curren","deg","divide","eacute","ecirc","egrave","eth","euml","frac12","frac14","frac34","gt","iacute","icirc","iexcl","igrave","iquest","iuml","laquo","lt","macr","micro","middot","nbsp","not","ntilde","oacute","ocirc","ograve","ordf","ordm","oslash","otilde","ouml","para","plusmn","pound","quot","raquo","reg","sect","shy","sup1","sup2","sup3","szlig","thorn","times","uacute","ucirc","ugrave","uml","uuml","yacute","yen","yuml"],y={0:"\uFFFD",128:"\u20AC",130:"\u201A",131:"\u0192",132:"\u201E",133:"\u2026",134:"\u2020",135:"\u2021",136:"\u02C6",137:"\u2030",138:"\u0160",139:"\u2039",140:"\u0152",142:"\u017D",145:"\u2018",146:"\u2019",147:"\u201C",148:"\u201D",149:"\u2022",150:"\u2013",151:"\u2014",152:"\u02DC",153:"\u2122",154:"\u0161",155:"\u203A",156:"\u0153",158:"\u017E",159:"\u0178"};function S(n){const s=typeof n=="string"?n.charCodeAt(0):n;return s>=48&&s<=57}function T(n){const s=typeof n=="string"?n.charCodeAt(0):n;return s>=97&&s<=102||s>=65&&s<=70||s>=48&&s<=57}function P(n){const s=typeof n=="string"?n.charCodeAt(0):n;return s>=97&&s<=122||s>=65&&s<=90}function Q(n){return P(n)||S(n)}var q=z(44301);const fe=String.fromCharCode,le=["","Named character references must be terminated by a semicolon","Numeric character references must be terminated by a semicolon","Named character references cannot be empty","Numeric character references cannot be empty","Named character references must be known","Numeric character references cannot be disallowed","Numeric character references cannot be outside the permissible Unicode range"];function Pe(n,s={}){const l=typeof s.additional=="string"?s.additional.charCodeAt(0):s.additional,c=[];let p=0,g=-1,O="",w,C;s.position&&("start"in s.position||"indent"in s.position?(C=s.position.indent,w=s.position.start):w=s.position);let L=(w?w.line:0)||1,H=(w?w.column:0)||1,j=se(),ee;for(p--;++p<=n.length;)if(ee===10&&(H=(C?C[g]:0)||1),ee=n.charCodeAt(p),ee===38){const ot=n.charCodeAt(p+1);if(ot===9||ot===10||ot===12||ot===32||ot===38||ot===60||Number.isNaN(ot)||l&&ot===l){O+=fe(ee),H++;continue}const Ke=p+1;let Ot=Ke,kt=Ke,Jt;if(ot===35){kt=++Ot;const An=n.charCodeAt(kt);An===88||An===120?(Jt="hexadecimal",kt=++Ot):Jt="decimal"}else Jt="named";let ln="",yt="",tn="";const Qn=Jt==="named"?Q:Jt==="decimal"?S:T;for(kt--;++kt<=n.length;){const An=n.charCodeAt(kt);if(!Qn(An))break;tn+=fe(An),Jt==="named"&&m.includes(tn)&&(ln=tn,yt=(0,q.T)(tn))}let mn=n.charCodeAt(kt)===59;if(mn){kt++;const An=Jt==="named"?(0,q.T)(tn):!1;An&&(ln=tn,yt=An)}let it=1+kt-Ke,wr="";if(!(!mn&&s.nonTerminated===!1))if(!tn)Jt!=="named"&&Se(4,it);else if(Jt==="named"){if(mn&&!yt)Se(5,1);else if(ln!==tn&&(kt=Ot+ln.length,it=1+kt-Ot,mn=!1),!mn){const An=ln?1:3;if(s.attribute){const Ln=n.charCodeAt(kt);Ln===61?(Se(An,it),yt=""):Q(Ln)?yt="":Se(An,it)}else Se(An,it)}wr=yt}else{mn||Se(2,it);let An=Number.parseInt(tn,Jt==="hexadecimal"?16:10);if(We(An))Se(7,it),wr=fe(65533);else if(An in y)Se(6,it),wr=y[An];else{let Ln="";mt(An)&&Se(6,it),An>65535&&(An-=65536,Ln+=fe(An>>>10|55296),An=56320|An&1023),wr=Ln+fe(An)}}if(wr){at(),j=se(),p=kt-1,H+=kt-Ke+1,c.push(wr);const An=se();An.offset++,s.reference&&s.reference.call(s.referenceContext,wr,{start:j,end:An},n.slice(Ke-1,kt)),j=An}else tn=n.slice(Ke-1,kt),O+=tn,H+=tn.length,p=kt-1}else ee===10&&(L++,g++,H=0),Number.isNaN(ee)?at():(O+=fe(ee),H++);return c.join("");function se(){return{line:L,column:H,offset:p+((w?w.offset:0)||0)}}function Se(ot,Ke){let Ot;s.warning&&(Ot=se(),Ot.column+=Ke,Ot.offset+=Ke,s.warning.call(s.warningContext,le[ot],Ot,ot))}function at(){O&&(c.push(O),s.text&&s.text.call(s.textContext,O,{start:j,end:se()}),O="")}}function We(n){return n>=55296&&n<=57343||n>1114111}function mt(n){return n>=1&&n<=8||n===11||n>=13&&n<=31||n>=127&&n<=159||n>=64976&&n<=65007||(n&65535)===65535||(n&65535)===65534}var Ct=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,Bt=0,Ht={},It={util:{type:function(n){return Object.prototype.toString.call(n).slice(8,-1)},objId:function(n){return n.__id||Object.defineProperty(n,"__id",{value:++Bt}),n.__id},clone:function n(s,l){l=l||{};var c,p;switch(It.util.type(s)){case"Object":if(p=It.util.objId(s),l[p])return l[p];c={},l[p]=c;for(var g in s)s.hasOwnProperty(g)&&(c[g]=n(s[g],l));return c;case"Array":return p=It.util.objId(s),l[p]?l[p]:(c=[],l[p]=c,s.forEach(function(O,w){c[w]=n(O,l)}),c);default:return s}}},languages:{plain:Ht,plaintext:Ht,text:Ht,txt:Ht,extend:function(n,s){var l=It.util.clone(It.languages[n]);for(var c in s)l[c]=s[c];return l},insertBefore:function(n,s,l,c){c=c||It.languages;var p=c[n],g={};for(var O in p)if(p.hasOwnProperty(O)){if(O==s)for(var w in l)l.hasOwnProperty(w)&&(g[w]=l[w]);l.hasOwnProperty(O)||(g[O]=p[O])}var C=c[n];return c[n]=g,It.languages.DFS(It.languages,function(L,H){H===C&&L!=n&&(this[L]=g)}),g},DFS:function n(s,l,c,p){p=p||{};var g=It.util.objId;for(var O in s)if(s.hasOwnProperty(O)){l.call(s,O,s[O],c||O);var w=s[O],C=It.util.type(w);C==="Object"&&!p[g(w)]?(p[g(w)]=!0,n(w,l,null,p)):C==="Array"&&!p[g(w)]&&(p[g(w)]=!0,n(w,l,O,p))}}},plugins:{},highlight:function(n,s,l){var c={code:n,grammar:s,language:l};if(It.hooks.run("before-tokenize",c),!c.grammar)throw new Error('The language "'+c.language+'" has no grammar.');return c.tokens=It.tokenize(c.code,c.grammar),It.hooks.run("after-tokenize",c),ar.stringify(It.util.encode(c.tokens),c.language)},tokenize:function(n,s){var l=s.rest;if(l){for(var c in l)s[c]=l[c];delete s.rest}var p=new Lr;return ri(p,p.head,n),gr(n,p,s,p.head,0),ws(p)},hooks:{all:{},add:function(n,s){var l=It.hooks.all;l[n]=l[n]||[],l[n].push(s)},run:function(n,s){var l=It.hooks.all[n];if(!(!l||!l.length))for(var c=0,p;p=l[c++];)p(s)}},Token:ar};function ar(n,s,l,c){this.type=n,this.content=s,this.alias=l,this.length=(c||"").length|0}function Qr(n,s,l,c){n.lastIndex=s;var p=n.exec(l);if(p&&c&&p[1]){var g=p[1].length;p.index+=g,p[0]=p[0].slice(g)}return p}function gr(n,s,l,c,p,g){for(var O in l)if(!(!l.hasOwnProperty(O)||!l[O])){var w=l[O];w=Array.isArray(w)?w:[w];for(var C=0;C=g.reach);Ke+=ot.value.length,ot=ot.next){var Ot=ot.value;if(s.length>n.length)return;if(!(Ot instanceof ar)){var kt=1,Jt;if(ee){if(Jt=Qr(at,Ke,n,j),!Jt||Jt.index>=n.length)break;var Qn=Jt.index,ln=Jt.index+Jt[0].length,yt=Ke;for(yt+=ot.value.length;Qn>=yt;)ot=ot.next,yt+=ot.value.length;if(yt-=ot.value.length,Ke=yt,ot.value instanceof ar)continue;for(var tn=ot;tn!==s.tail&&(ytg.reach&&(g.reach=An);var Ln=ot.prev;it&&(Ln=ri(s,Ln,it),Ke+=it.length),Yi(s,Ln,kt);var hr=new ar(O,H?It.tokenize(mn,H):mn,se,mn);if(ot=ri(s,Ln,hr),wr&&ri(s,ot,wr),kt>1){var Jn={cause:O+","+C,reach:An};gr(n,s,l,ot.prev,Ke,Jn),g&&Jn.reach>g.reach&&(g.reach=Jn.reach)}}}}}}function Lr(){var n={value:null,prev:null,next:null},s={value:null,prev:n,next:null};n.next=s,this.head=n,this.tail=s,this.length=0}function ri(n,s,l){var c=s.next,p={value:l,prev:s,next:c};return s.next=p,c.prev=p,n.length++,p}function Yi(n,s,l){for(var c=s.next,p=0;p]?|>=?|\?=|[-+\/=])(?=\s)/,lookbehind:!0},"string-operator":{pattern:/(\s)&&?(?=\s)/,lookbehind:!0,alias:"keyword"},"token-operator":[{pattern:/(\w)(?:->?|=>|[~|{}])(?=\w)/,lookbehind:!0,alias:"punctuation"},{pattern:/[|{}]/,alias:"punctuation"}],punctuation:/[,.:()]/}}gm.displayName="abnf",gm.aliases=[];function gm(n){(function(s){var l="(?:ALPHA|BIT|CHAR|CR|CRLF|CTL|DIGIT|DQUOTE|HEXDIG|HTAB|LF|LWSP|OCTET|SP|VCHAR|WSP)";s.languages.abnf={comment:/;.*/,string:{pattern:/(?:%[is])?"[^"\n\r]*"/,greedy:!0,inside:{punctuation:/^%[is]/}},range:{pattern:/%(?:b[01]+-[01]+|d\d+-\d+|x[A-F\d]+-[A-F\d]+)/i,alias:"number"},terminal:{pattern:/%(?:b[01]+(?:\.[01]+)*|d\d+(?:\.\d+)*|x[A-F\d]+(?:\.[A-F\d]+)*)/i,alias:"number"},repetition:{pattern:/(^|[^\w-])(?:\d*\*\d*|\d+)/,lookbehind:!0,alias:"operator"},definition:{pattern:/(^[ \t]*)(?:[a-z][\w-]*|<[^<>\r\n]*>)(?=\s*=)/m,lookbehind:!0,alias:"keyword",inside:{punctuation:/<|>/}},"core-rule":{pattern:RegExp("(?:(^|[^<\\w-])"+l+"|<"+l+">)(?![\\w-])","i"),lookbehind:!0,alias:["rule","constant"],inside:{punctuation:/<|>/}},rule:{pattern:/(^|[^<\w-])[a-z][\w-]*|<[^<>\r\n]*>/i,lookbehind:!0,inside:{punctuation:/<|>/}},operator:/=\/?|\//,punctuation:/[()\[\]]/}})(n)}bm.displayName="actionscript",bm.aliases=[];function bm(n){n.register(Ms),n.languages.actionscript=n.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|dynamic|each|else|extends|final|finally|for|function|get|if|implements|import|in|include|instanceof|interface|internal|is|namespace|native|new|null|override|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|use|var|void|while|with)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<|>>?>?|[!=]=?)=?|[~?@]/}),n.languages.actionscript["class-name"].alias="function",delete n.languages.actionscript.parameter,delete n.languages.actionscript["literal-property"],n.languages.markup&&n.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:n.languages.markup}})}yb.displayName="ada",yb.aliases=[];function yb(n){n.languages.ada={comment:/--.*/,string:/"(?:""|[^"\r\f\n])*"/,number:[{pattern:/\b\d(?:_?\d)*#[\dA-F](?:_?[\dA-F])*(?:\.[\dA-F](?:_?[\dA-F])*)?#(?:E[+-]?\d(?:_?\d)*)?/i},{pattern:/\b\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:E[+-]?\d(?:_?\d)*)?\b/i}],attribute:{pattern:/\b'\w+/,alias:"attr-name"},keyword:/\b(?:abort|abs|abstract|accept|access|aliased|all|and|array|at|begin|body|case|constant|declare|delay|delta|digits|do|else|elsif|end|entry|exception|exit|for|function|generic|goto|if|in|interface|is|limited|loop|mod|new|not|null|of|or|others|out|overriding|package|pragma|private|procedure|protected|raise|range|record|rem|renames|requeue|return|reverse|select|separate|some|subtype|synchronized|tagged|task|terminate|then|type|until|use|when|while|with|xor)\b/i,boolean:/\b(?:false|true)\b/i,operator:/<[=>]?|>=?|=>?|:=|\/=?|\*\*?|[&+-]/,punctuation:/\.\.?|[,;():]/,char:/'.'/,variable:/\b[a-z](?:\w)*\b/i}}Eb.displayName="agda",Eb.aliases=[];function Eb(n){(function(s){s.languages.agda={comment:/\{-[\s\S]*?(?:-\}|$)|--.*/,string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},punctuation:/[(){}⦃⦄.;@]/,"class-name":{pattern:/((?:data|record) +)\S+/,lookbehind:!0},function:{pattern:/(^[ \t]*)(?!\s)[^:\r\n]+(?=:)/m,lookbehind:!0},operator:{pattern:/(^\s*|\s)(?:[=|:∀→λ\\?_]|->)(?=\s)/,lookbehind:!0},keyword:/\b(?:Set|abstract|constructor|data|eta-equality|field|forall|hiding|import|in|inductive|infix|infixl|infixr|instance|let|macro|module|mutual|no-eta-equality|open|overlap|pattern|postulate|primitive|private|public|quote|quoteContext|quoteGoal|quoteTerm|record|renaming|rewrite|syntax|tactic|unquote|unquoteDecl|unquoteDef|using|variable|where|with)\b/}})(n)}Sb.displayName="al",Sb.aliases=[];function Sb(n){n.languages.al={comment:/\/\/.*|\/\*[\s\S]*?\*\//,string:{pattern:/'(?:''|[^'\r\n])*'(?!')|"(?:""|[^"\r\n])*"(?!")/,greedy:!0},function:{pattern:/(\b(?:event|procedure|trigger)\s+|(?:^|[^.])\.\s*)[a-z_]\w*(?=\s*\()/i,lookbehind:!0},keyword:[/\b(?:array|asserterror|begin|break|case|do|downto|else|end|event|exit|for|foreach|function|if|implements|in|indataset|interface|internal|local|of|procedure|program|protected|repeat|runonclient|securityfiltering|suppressdispose|temporary|then|to|trigger|until|var|while|with|withevents)\b/i,/\b(?:action|actions|addafter|addbefore|addfirst|addlast|area|assembly|chartpart|codeunit|column|controladdin|cuegroup|customizes|dataitem|dataset|dotnet|elements|enum|enumextension|extends|field|fieldattribute|fieldelement|fieldgroup|fieldgroups|fields|filter|fixed|grid|group|key|keys|label|labels|layout|modify|moveafter|movebefore|movefirst|movelast|page|pagecustomization|pageextension|part|profile|query|repeater|report|requestpage|schema|separator|systempart|table|tableelement|tableextension|textattribute|textelement|type|usercontrol|value|xmlport)\b/i],number:/\b(?:0x[\da-f]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?)(?:F|LL?|U(?:LL?)?)?\b/i,boolean:/\b(?:false|true)\b/i,variable:/\b(?:Curr(?:FieldNo|Page|Report)|x?Rec|RequestOptionsPage)\b/,"class-name":/\b(?:automation|biginteger|bigtext|blob|boolean|byte|char|clienttype|code|completiontriggererrorlevel|connectiontype|database|dataclassification|datascope|date|dateformula|datetime|decimal|defaultlayout|dialog|dictionary|dotnetassembly|dotnettypedeclaration|duration|errorinfo|errortype|executioncontext|executionmode|fieldclass|fieldref|fieldtype|file|filterpagebuilder|guid|httpclient|httpcontent|httpheaders|httprequestmessage|httpresponsemessage|instream|integer|joker|jsonarray|jsonobject|jsontoken|jsonvalue|keyref|list|moduledependencyinfo|moduleinfo|none|notification|notificationscope|objecttype|option|outstream|pageresult|record|recordid|recordref|reportformat|securityfilter|sessionsettings|tableconnectiontype|tablefilter|testaction|testfield|testfilterfield|testpage|testpermissions|testrequestpage|text|textbuilder|textconst|textencoding|time|transactionmodel|transactiontype|variant|verbosity|version|view|views|webserviceactioncontext|webserviceactionresultcode|xmlattribute|xmlattributecollection|xmlcdata|xmlcomment|xmldeclaration|xmldocument|xmldocumenttype|xmlelement|xmlnamespacemanager|xmlnametable|xmlnode|xmlnodelist|xmlprocessinginstruction|xmlreadoptions|xmltext|xmlwriteoptions)\b/i,operator:/\.\.|:[=:]|[-+*/]=?|<>|[<>]=?|=|\b(?:and|div|mod|not|or|xor)\b/i,punctuation:/[()\[\]{}:.;,]/}}Om.displayName="antlr4",Om.aliases=["g4"];function Om(n){n.languages.antlr4={comment:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,string:{pattern:/'(?:\\.|[^\\'\r\n])*'/,greedy:!0},"character-class":{pattern:/\[(?:\\.|[^\\\]\r\n])*\]/,greedy:!0,alias:"regex",inside:{range:{pattern:/([^[]|(?:^|[^\\])(?:\\\\)*\\\[)-(?!\])/,lookbehind:!0,alias:"punctuation"},escape:/\\(?:u(?:[a-fA-F\d]{4}|\{[a-fA-F\d]+\})|[pP]\{[=\w-]+\}|[^\r\nupP])/,punctuation:/[\[\]]/}},action:{pattern:/\{(?:[^{}]|\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})*\}/,greedy:!0,inside:{content:{pattern:/(\{)[\s\S]+(?=\})/,lookbehind:!0},punctuation:/[{}]/}},command:{pattern:/(->\s*(?!\s))(?:\s*(?:,\s*)?\b[a-z]\w*(?:\s*\([^()\r\n]*\))?)+(?=\s*;)/i,lookbehind:!0,inside:{function:/\b\w+(?=\s*(?:[,(]|$))/,punctuation:/[,()]/}},annotation:{pattern:/@\w+(?:::\w+)*/,alias:"keyword"},label:{pattern:/#[ \t]*\w+/,alias:"punctuation"},keyword:/\b(?:catch|channels|finally|fragment|grammar|import|lexer|locals|mode|options|parser|returns|throws|tokens)\b/,definition:[{pattern:/\b[a-z]\w*(?=\s*:)/,alias:["rule","class-name"]},{pattern:/\b[A-Z]\w*(?=\s*:)/,alias:["token","constant"]}],constant:/\b[A-Z][A-Z_]*\b/,operator:/\.\.|->|[|~]|[*+?]\??/,punctuation:/[;:()=]/},n.languages.g4=n.languages.antlr4}vb.displayName="apacheconf",vb.aliases=[];function vb(n){n.languages.apacheconf={comment:/#.*/,"directive-inline":{pattern:/(^[\t ]*)\b(?:AcceptFilter|AcceptPathInfo|AccessFileName|Action|Add(?:Alt|AltByEncoding|AltByType|Charset|DefaultCharset|Description|Encoding|Handler|Icon|IconByEncoding|IconByType|InputFilter|Language|ModuleInfo|OutputFilter|OutputFilterByType|Type)|Alias|AliasMatch|Allow(?:CONNECT|EncodedSlashes|Methods|Override|OverrideList)?|Anonymous(?:_LogEmail|_MustGiveEmail|_NoUserID|_VerifyEmail)?|AsyncRequestWorkerFactor|Auth(?:BasicAuthoritative|BasicFake|BasicProvider|BasicUseDigestAlgorithm|DBDUserPWQuery|DBDUserRealmQuery|DBMGroupFile|DBMType|DBMUserFile|Digest(?:Algorithm|Domain|NonceLifetime|Provider|Qop|ShmemSize)|Form(?:Authoritative|Body|DisableNoStore|FakeBasicAuth|Location|LoginRequiredLocation|LoginSuccessLocation|LogoutLocation|Method|Mimetype|Password|Provider|SitePassphrase|Size|Username)|GroupFile|LDAP(?:AuthorizePrefix|BindAuthoritative|BindDN|BindPassword|CharsetConfig|CompareAsUser|CompareDNOnServer|DereferenceAliases|GroupAttribute|GroupAttributeIsDN|InitialBindAsUser|InitialBindPattern|MaxSubGroupDepth|RemoteUserAttribute|RemoteUserIsDN|SearchAsUser|SubGroupAttribute|SubGroupClass|Url)|Merging|Name|nCache(?:Context|Enable|ProvideFor|SOCache|Timeout)|nzFcgiCheckAuthnProvider|nzFcgiDefineProvider|Type|UserFile|zDBDLoginToReferer|zDBDQuery|zDBDRedirectQuery|zDBMType|zSendForbiddenOnFailure)|BalancerGrowth|BalancerInherit|BalancerMember|BalancerPersist|BrowserMatch|BrowserMatchNoCase|BufferedLogs|BufferSize|Cache(?:DefaultExpire|DetailHeader|DirLength|DirLevels|Disable|Enable|File|Header|IgnoreCacheControl|IgnoreHeaders|IgnoreNoLastMod|IgnoreQueryString|IgnoreURLSessionIdentifiers|KeyBaseURL|LastModifiedFactor|Lock|LockMaxAge|LockPath|MaxExpire|MaxFileSize|MinExpire|MinFileSize|NegotiatedDocs|QuickHandler|ReadSize|ReadTime|Root|Socache(?:MaxSize|MaxTime|MinTime|ReadSize|ReadTime)?|StaleOnError|StoreExpired|StoreNoStore|StorePrivate)|CGIDScriptTimeout|CGIMapExtension|CharsetDefault|CharsetOptions|CharsetSourceEnc|CheckCaseOnly|CheckSpelling|ChrootDir|ContentDigest|CookieDomain|CookieExpires|CookieName|CookieStyle|CookieTracking|CoreDumpDirectory|CustomLog|Dav|DavDepthInfinity|DavGenericLockDB|DavLockDB|DavMinTimeout|DBDExptime|DBDInitSQL|DBDKeep|DBDMax|DBDMin|DBDParams|DBDPersist|DBDPrepareSQL|DBDriver|DefaultIcon|DefaultLanguage|DefaultRuntimeDir|DefaultType|Define|Deflate(?:BufferSize|CompressionLevel|FilterNote|InflateLimitRequestBody|InflateRatio(?:Burst|Limit)|MemLevel|WindowSize)|Deny|DirectoryCheckHandler|DirectoryIndex|DirectoryIndexRedirect|DirectorySlash|DocumentRoot|DTracePrivileges|DumpIOInput|DumpIOOutput|EnableExceptionHook|EnableMMAP|EnableSendfile|Error|ErrorDocument|ErrorLog|ErrorLogFormat|Example|ExpiresActive|ExpiresByType|ExpiresDefault|ExtendedStatus|ExtFilterDefine|ExtFilterOptions|FallbackResource|FileETag|FilterChain|FilterDeclare|FilterProtocol|FilterProvider|FilterTrace|ForceLanguagePriority|ForceType|ForensicLog|GprofDir|GracefulShutdownTimeout|Group|Header|HeaderName|Heartbeat(?:Address|Listen|MaxServers|Storage)|HostnameLookups|IdentityCheck|IdentityCheckTimeout|ImapBase|ImapDefault|ImapMenu|Include|IncludeOptional|Index(?:HeadInsert|Ignore|IgnoreReset|Options|OrderDefault|StyleSheet)|InputSed|ISAPI(?:AppendLogToErrors|AppendLogToQuery|CacheFile|FakeAsync|LogNotSupported|ReadAheadBuffer)|KeepAlive|KeepAliveTimeout|KeptBodySize|LanguagePriority|LDAP(?:CacheEntries|CacheTTL|ConnectionPoolTTL|ConnectionTimeout|LibraryDebug|OpCacheEntries|OpCacheTTL|ReferralHopLimit|Referrals|Retries|RetryDelay|SharedCacheFile|SharedCacheSize|Timeout|TrustedClientCert|TrustedGlobalCert|TrustedMode|VerifyServerCert)|Limit(?:InternalRecursion|Request(?:Body|Fields|FieldSize|Line)|XMLRequestBody)|Listen|ListenBackLog|LoadFile|LoadModule|LogFormat|LogLevel|LogMessage|LuaAuthzProvider|LuaCodeCache|Lua(?:Hook(?:AccessChecker|AuthChecker|CheckUserID|Fixups|InsertFilter|Log|MapToStorage|TranslateName|TypeChecker)|Inherit|InputFilter|MapHandler|OutputFilter|PackageCPath|PackagePath|QuickHandler|Root|Scope)|Max(?:ConnectionsPerChild|KeepAliveRequests|MemFree|RangeOverlaps|RangeReversals|Ranges|RequestWorkers|SpareServers|SpareThreads|Threads)|MergeTrailers|MetaDir|MetaFiles|MetaSuffix|MimeMagicFile|MinSpareServers|MinSpareThreads|MMapFile|ModemStandard|ModMimeUsePathInfo|MultiviewsMatch|Mutex|NameVirtualHost|NoProxy|NWSSLTrustedCerts|NWSSLUpgradeable|Options|Order|OutputSed|PassEnv|PidFile|PrivilegesMode|Protocol|ProtocolEcho|Proxy(?:AddHeaders|BadHeader|Block|Domain|ErrorOverride|ExpressDBMFile|ExpressDBMType|ExpressEnable|FtpDirCharset|FtpEscapeWildcards|FtpListOnWildcard|HTML(?:BufSize|CharsetOut|DocType|Enable|Events|Extended|Fixups|Interp|Links|Meta|StripComments|URLMap)|IOBufferSize|MaxForwards|Pass(?:Inherit|InterpolateEnv|Match|Reverse|ReverseCookieDomain|ReverseCookiePath)?|PreserveHost|ReceiveBufferSize|Remote|RemoteMatch|Requests|SCGIInternalRedirect|SCGISendfile|Set|SourceAddress|Status|Timeout|Via)|ReadmeName|ReceiveBufferSize|Redirect|RedirectMatch|RedirectPermanent|RedirectTemp|ReflectorHeader|RemoteIP(?:Header|InternalProxy|InternalProxyList|ProxiesHeader|TrustedProxy|TrustedProxyList)|RemoveCharset|RemoveEncoding|RemoveHandler|RemoveInputFilter|RemoveLanguage|RemoveOutputFilter|RemoveType|RequestHeader|RequestReadTimeout|Require|Rewrite(?:Base|Cond|Engine|Map|Options|Rule)|RLimitCPU|RLimitMEM|RLimitNPROC|Satisfy|ScoreBoardFile|Script(?:Alias|AliasMatch|InterpreterSource|Log|LogBuffer|LogLength|Sock)?|SecureListen|SeeRequestTail|SendBufferSize|Server(?:Admin|Alias|Limit|Name|Path|Root|Signature|Tokens)|Session(?:Cookie(?:Name|Name2|Remove)|Crypto(?:Cipher|Driver|Passphrase|PassphraseFile)|DBD(?:CookieName|CookieName2|CookieRemove|DeleteLabel|InsertLabel|PerUser|SelectLabel|UpdateLabel)|Env|Exclude|Header|Include|MaxAge)?|SetEnv|SetEnvIf|SetEnvIfExpr|SetEnvIfNoCase|SetHandler|SetInputFilter|SetOutputFilter|SSIEndTag|SSIErrorMsg|SSIETag|SSILastModified|SSILegacyExprParser|SSIStartTag|SSITimeFormat|SSIUndefinedEcho|SSL(?:CACertificateFile|CACertificatePath|CADNRequestFile|CADNRequestPath|CARevocationCheck|CARevocationFile|CARevocationPath|CertificateChainFile|CertificateFile|CertificateKeyFile|CipherSuite|Compression|CryptoDevice|Engine|FIPS|HonorCipherOrder|InsecureRenegotiation|OCSP(?:DefaultResponder|Enable|OverrideResponder|ResponderTimeout|ResponseMaxAge|ResponseTimeSkew|UseRequestNonce)|OpenSSLConfCmd|Options|PassPhraseDialog|Protocol|Proxy(?:CACertificateFile|CACertificatePath|CARevocation(?:Check|File|Path)|CheckPeer(?:CN|Expire|Name)|CipherSuite|Engine|MachineCertificate(?:ChainFile|File|Path)|Protocol|Verify|VerifyDepth)|RandomSeed|RenegBufferSize|Require|RequireSSL|Session(?:Cache|CacheTimeout|TicketKeyFile|Tickets)|SRPUnknownUserSeed|SRPVerifierFile|Stapling(?:Cache|ErrorCacheTimeout|FakeTryLater|ForceURL|ResponderTimeout|ResponseMaxAge|ResponseTimeSkew|ReturnResponderErrors|StandardCacheTimeout)|StrictSNIVHostCheck|UserName|UseStapling|VerifyClient|VerifyDepth)|StartServers|StartThreads|Substitute|Suexec|SuexecUserGroup|ThreadLimit|ThreadsPerChild|ThreadStackSize|TimeOut|TraceEnable|TransferLog|TypesConfig|UnDefine|UndefMacro|UnsetEnv|Use|UseCanonicalName|UseCanonicalPhysicalPort|User|UserDir|VHostCGIMode|VHostCGIPrivs|VHostGroup|VHostPrivs|VHostSecure|VHostUser|Virtual(?:DocumentRoot|ScriptAlias)(?:IP)?|WatchdogInterval|XBitHack|xml2EncAlias|xml2EncDefault|xml2StartParse)\b/im,lookbehind:!0,alias:"property"},"directive-block":{pattern:/<\/?\b(?:Auth[nz]ProviderAlias|Directory|DirectoryMatch|Else|ElseIf|Files|FilesMatch|If|IfDefine|IfModule|IfVersion|Limit|LimitExcept|Location|LocationMatch|Macro|Proxy|Require(?:All|Any|None)|VirtualHost)\b.*>/i,inside:{"directive-block":{pattern:/^<\/?\w+/,inside:{punctuation:/^<\/?/},alias:"tag"},"directive-block-parameter":{pattern:/.*[^>]/,inside:{punctuation:/:/,string:{pattern:/("|').*\1/,inside:{variable:/[$%]\{?(?:\w\.?[-+:]?)+\}?/}}},alias:"attr-value"},punctuation:/>/},alias:"tag"},"directive-flags":{pattern:/\[(?:[\w=],?)+\]/,alias:"keyword"},string:{pattern:/("|').*\1/,inside:{variable:/[$%]\{?(?:\w\.?[-+:]?)+\}?/}},variable:/[$%]\{?(?:\w\.?[-+:]?)+\}?/,regex:/\^?.*\$|\^.*\$?/}}kb.displayName="apex",kb.aliases=[];function kb(n){n.register(Kr),n.register(Iu),function(s){var l=/\b(?:(?:after|before)(?=\s+[a-z])|abstract|activate|and|any|array|as|asc|autonomous|begin|bigdecimal|blob|boolean|break|bulk|by|byte|case|cast|catch|char|class|collect|commit|const|continue|currency|date|datetime|decimal|default|delete|desc|do|double|else|end|enum|exception|exit|export|extends|final|finally|float|for|from|get(?=\s*[{};])|global|goto|group|having|hint|if|implements|import|in|inner|insert|instanceof|int|integer|interface|into|join|like|limit|list|long|loop|map|merge|new|not|null|nulls|number|object|of|on|or|outer|override|package|parallel|pragma|private|protected|public|retrieve|return|rollback|select|set|short|sObject|sort|static|string|super|switch|synchronized|system|testmethod|then|this|throw|time|transaction|transient|trigger|try|undelete|update|upsert|using|virtual|void|webservice|when|where|while|(?:inherited|with|without)\s+sharing)\b/i,c=/\b(?:(?=[a-z_]\w*\s*[<\[])|(?!))[A-Z_]\w*(?:\s*\.\s*[A-Z_]\w*)*\b(?:\s*(?:\[\s*\]|<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>))*/.source.replace(//g,function(){return l.source});function p(O){return RegExp(O.replace(//g,function(){return c}),"i")}var g={keyword:l,punctuation:/[()\[\]{};,:.<>]/};s.languages.apex={comment:s.languages.clike.comment,string:s.languages.clike.string,sql:{pattern:/((?:[=,({:]|\breturn)\s*)\[[^\[\]]*\]/i,lookbehind:!0,greedy:!0,alias:"language-sql",inside:s.languages.sql},annotation:{pattern:/@\w+\b/,alias:"punctuation"},"class-name":[{pattern:p(/(\b(?:class|enum|extends|implements|instanceof|interface|new|trigger\s+\w+\s+on)\s+)/.source),lookbehind:!0,inside:g},{pattern:p(/(\(\s*)(?=\s*\)\s*[\w(])/.source),lookbehind:!0,inside:g},{pattern:p(/(?=\s*\w+\s*[;=,(){:])/.source),inside:g}],trigger:{pattern:/(\btrigger\s+)\w+\b/i,lookbehind:!0,alias:"class-name"},keyword:l,function:/\b[a-z_]\w*(?=\s*\()/i,boolean:/\b(?:false|true)\b/i,number:/(?:\B\.\d+|\b\d+(?:\.\d+|L)?)\b/i,operator:/[!=](?:==?)?|\?\.?|&&|\|\||--|\+\+|[-+*/^&|]=?|:|<=?|>{1,3}=?/,punctuation:/[()\[\]{};,.]/}}(n)}wb.displayName="apl",wb.aliases=[];function wb(n){n.languages.apl={comment:/(?:⍝|#[! ]).*$/m,string:{pattern:/'(?:[^'\r\n]|'')*'/,greedy:!0},number:/¯?(?:\d*\.?\b\d+(?:e[+¯]?\d+)?|¯|∞)(?:j¯?(?:(?:\d+(?:\.\d+)?|\.\d+)(?:e[+¯]?\d+)?|¯|∞))?/i,statement:/:[A-Z][a-z][A-Za-z]*\b/,"system-function":{pattern:/⎕[A-Z]+/i,alias:"function"},constant:/[⍬⌾#⎕⍞]/,function:/[-+×÷⌈⌊∣|⍳⍸?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⊆⊇⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⍯↗¤→]/,"monadic-operator":{pattern:/[\\\/⌿⍀¨⍨⌶&∥]/,alias:"operator"},"dyadic-operator":{pattern:/[.⍣⍠⍤∘⌸@⌺⍥]/,alias:"operator"},assignment:{pattern:/←/,alias:"keyword"},punctuation:/[\[;\]()◇⋄]/,dfn:{pattern:/[{}⍺⍵⍶⍹∇⍫:]/,alias:"builtin"}}}xb.displayName="applescript",xb.aliases=[];function xb(n){n.languages.applescript={comment:[/\(\*(?:\(\*(?:[^*]|\*(?!\)))*\*\)|(?!\(\*)[\s\S])*?\*\)/,/--.+/,/#.+/],string:/"(?:\\.|[^"\\\r\n])*"/,number:/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e-?\d+)?\b/i,operator:[/[&=≠≤≥*+\-\/÷^]|[<>]=?/,/\b(?:(?:begin|end|start)s? with|(?:contains?|(?:does not|doesn't) contain)|(?:is|isn't|is not) (?:contained by|in)|(?:(?:is|isn't|is not) )?(?:greater|less) than(?: or equal)?(?: to)?|(?:comes|(?:does not|doesn't) come) (?:after|before)|(?:is|isn't|is not) equal(?: to)?|(?:(?:does not|doesn't) equal|equal to|equals|is not|isn't)|(?:a )?(?:ref(?: to)?|reference to)|(?:and|as|div|mod|not|or))\b/],keyword:/\b(?:about|above|after|against|apart from|around|aside from|at|back|before|beginning|behind|below|beneath|beside|between|but|by|considering|continue|copy|does|eighth|else|end|equal|error|every|exit|false|fifth|first|for|fourth|from|front|get|given|global|if|ignoring|in|instead of|into|is|it|its|last|local|me|middle|my|ninth|of|on|onto|out of|over|prop|property|put|repeat|return|returning|second|set|seventh|since|sixth|some|tell|tenth|that|the|then|third|through|thru|timeout|times|to|transaction|true|try|until|where|while|whose|with|without)\b/,"class-name":/\b(?:POSIX file|RGB color|alias|application|boolean|centimeters|centimetres|class|constant|cubic centimeters|cubic centimetres|cubic feet|cubic inches|cubic meters|cubic metres|cubic yards|date|degrees Celsius|degrees Fahrenheit|degrees Kelvin|feet|file|gallons|grams|inches|integer|kilograms|kilometers|kilometres|list|liters|litres|meters|metres|miles|number|ounces|pounds|quarts|real|record|reference|script|square feet|square kilometers|square kilometres|square meters|square metres|square miles|square yards|text|yards)\b/,punctuation:/[{}():,¬«»《》]/}}Tb.displayName="aql",Tb.aliases=[];function Tb(n){n.languages.aql={comment:/\/\/.*|\/\*[\s\S]*?\*\//,property:{pattern:/([{,]\s*)(?:(?!\d)\w+|(["'´`])(?:(?!\2)[^\\\r\n]|\\.)*\2)(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\.)*\1/,greedy:!0},identifier:{pattern:/([´`])(?:(?!\1)[^\\\r\n]|\\.)*\1/,greedy:!0},variable:/@@?\w+/,keyword:[{pattern:/(\bWITH\s+)COUNT(?=\s+INTO\b)/i,lookbehind:!0},/\b(?:AGGREGATE|ALL|AND|ANY|ASC|COLLECT|DESC|DISTINCT|FILTER|FOR|GRAPH|IN|INBOUND|INSERT|INTO|K_PATHS|K_SHORTEST_PATHS|LET|LIKE|LIMIT|NONE|NOT|NULL|OR|OUTBOUND|REMOVE|REPLACE|RETURN|SHORTEST_PATH|SORT|UPDATE|UPSERT|WINDOW|WITH)\b/i,{pattern:/(^|[^\w.[])(?:KEEP|PRUNE|SEARCH|TO)\b/i,lookbehind:!0},{pattern:/(^|[^\w.[])(?:CURRENT|NEW|OLD)\b/,lookbehind:!0},{pattern:/\bOPTIONS(?=\s*\{)/i}],function:/\b(?!\d)\w+(?=\s*\()/,boolean:/\b(?:false|true)\b/i,range:{pattern:/\.\./,alias:"operator"},number:[/\b0b[01]+/i,/\b0x[0-9a-f]+/i,/(?:\B\.\d+|\b(?:0|[1-9]\d*)(?:\.\d+)?)(?:e[+-]?\d+)?/i],operator:/\*{2,}|[=!]~|[!=<>]=?|&&|\|\||[-+*/%]/,punctuation:/::|[?.:,;()[\]{}]/}}Cb.displayName="arff",Cb.aliases=[];function Cb(n){n.languages.arff={comment:/%.*/,string:{pattern:/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},keyword:/@(?:attribute|data|end|relation)\b/i,number:/\b\d+(?:\.\d+)?\b/,punctuation:/[{},]/}}$d.displayName="armasm",$d.aliases=["arm-asm"];function $d(n){n.languages.armasm={comment:{pattern:/;.*/,greedy:!0},string:{pattern:/"(?:[^"\r\n]|"")*"/,greedy:!0,inside:{variable:{pattern:/((?:^|[^$])(?:\${2})*)\$\w+/,lookbehind:!0}}},char:{pattern:/'(?:[^'\r\n]{0,4}|'')'/,greedy:!0},"version-symbol":{pattern:/\|[\w@]+\|/,greedy:!0,alias:"property"},boolean:/\b(?:FALSE|TRUE)\b/,directive:{pattern:/\b(?:ALIAS|ALIGN|AREA|ARM|ASSERT|ATTR|CN|CODE|CODE16|CODE32|COMMON|CP|DATA|DCB|DCD|DCDO|DCDU|DCFD|DCFDU|DCI|DCQ|DCQU|DCW|DCWU|DN|ELIF|ELSE|END|ENDFUNC|ENDIF|ENDP|ENTRY|EQU|EXPORT|EXPORTAS|EXTERN|FIELD|FILL|FN|FUNCTION|GBLA|GBLL|GBLS|GET|GLOBAL|IF|IMPORT|INCBIN|INCLUDE|INFO|KEEP|LCLA|LCLL|LCLS|LTORG|MACRO|MAP|MEND|MEXIT|NOFP|OPT|PRESERVE8|PROC|QN|READONLY|RELOC|REQUIRE|REQUIRE8|RLIST|ROUT|SETA|SETL|SETS|SN|SPACE|SUBT|THUMB|THUMBX|TTL|WEND|WHILE)\b/,alias:"property"},instruction:{pattern:/((?:^|(?:^|[^\\])(?:\r\n?|\n))[ \t]*(?:(?:[A-Z][A-Z0-9_]*[a-z]\w*|[a-z]\w*|\d+)[ \t]+)?)\b[A-Z.]+\b/,lookbehind:!0,alias:"keyword"},variable:/\$\w+/,number:/(?:\b[2-9]_\d+|(?:\b\d+(?:\.\d+)?|\B\.\d+)(?:e-?\d+)?|\b0(?:[fd]_|x)[0-9a-f]+|&[0-9a-f]+)\b/i,register:{pattern:/\b(?:r\d|lr)\b/,alias:"symbol"},operator:/<>|<<|>>|&&|\|\||[=!<>/]=?|[+\-*%#?&|^]|:[A-Z]+:/,punctuation:/[()[\],]/},n.languages["arm-asm"]=n.languages.armasm}Ud.displayName="arturo",Ud.aliases=["art"];function Ud(n){(function(s){var l=function(c,p){return{pattern:RegExp(/\{!/.source+"(?:"+(p||c)+")"+/$[\s\S]*\}/.source,"m"),greedy:!0,inside:{embedded:{pattern:/(^\{!\w+\b)[\s\S]+(?=\}$)/,lookbehind:!0,alias:"language-"+c,inside:s.languages[c]},string:/[\s\S]+/}}};s.languages.arturo={comment:{pattern:/;.*/,greedy:!0},character:{pattern:/`.`/,alias:"char",greedy:!0},number:{pattern:/\b\d+(?:\.\d+(?:\.\d+(?:-[\w+-]+)?)?)?\b/},string:{pattern:/"(?:[^"\\\r\n]|\\.)*"/,greedy:!0},regex:{pattern:/\{\/.*?\/\}/,greedy:!0},"html-string":l("html"),"css-string":l("css"),"js-string":l("js"),"md-string":l("md"),"sql-string":l("sql"),"sh-string":l("shell","sh"),multistring:{pattern:/».*|\{:[\s\S]*?:\}|\{[\s\S]*?\}|^-{6}$[\s\S]*/m,alias:"string",greedy:!0},label:{pattern:/\w+\b\??:/,alias:"property"},literal:{pattern:/'(?:\w+\b\??:?)/,alias:"constant"},type:{pattern:/:(?:\w+\b\??:?)/,alias:"class-name"},color:/#\w+/,predicate:{pattern:/\b(?:all|and|any|ascii|attr|attribute|attributeLabel|binary|block|char|contains|database|date|dictionary|empty|equal|even|every|exists|false|floating|function|greater|greaterOrEqual|if|in|inline|integer|is|key|label|leap|less|lessOrEqual|literal|logical|lower|nand|negative|nor|not|notEqual|null|numeric|odd|or|path|pathLabel|positive|prefix|prime|regex|same|set|some|sorted|standalone|string|subset|suffix|superset|symbol|symbolLiteral|true|try|type|unless|upper|when|whitespace|word|xnor|xor|zero)\?/,alias:"keyword"},"builtin-function":{pattern:/\b(?:abs|acos|acosh|acsec|acsech|actan|actanh|add|after|alert|alias|and|angle|append|arg|args|arity|array|as|asec|asech|asin|asinh|atan|atan2|atanh|attr|attrs|average|before|benchmark|blend|break|call|capitalize|case|ceil|chop|clear|clip|close|color|combine|conj|continue|copy|cos|cosh|crc|csec|csech|ctan|ctanh|cursor|darken|dec|decode|define|delete|desaturate|deviation|dialog|dictionary|difference|digest|digits|div|do|download|drop|dup|e|else|empty|encode|ensure|env|escape|execute|exit|exp|extend|extract|factors|fdiv|filter|first|flatten|floor|fold|from|function|gamma|gcd|get|goto|hash|hypot|if|inc|indent|index|infinity|info|input|insert|inspect|intersection|invert|jaro|join|keys|kurtosis|last|let|levenshtein|lighten|list|ln|log|loop|lower|mail|map|match|max|median|min|mod|module|mul|nand|neg|new|nor|normalize|not|now|null|open|or|outdent|pad|palette|panic|path|pause|permissions|permutate|pi|pop|popup|pow|powerset|powmod|prefix|print|prints|process|product|query|random|range|read|relative|remove|rename|render|repeat|replace|request|return|reverse|round|sample|saturate|script|sec|sech|select|serve|set|shl|shr|shuffle|sin|sinh|size|skewness|slice|sort|spin|split|sqrt|squeeze|stack|strip|sub|suffix|sum|switch|symbols|symlink|sys|take|tan|tanh|terminal|terminate|to|truncate|try|type|unclip|union|unique|unless|until|unzip|upper|values|var|variance|volume|webview|while|with|wordwrap|write|xnor|xor|zip)\b/,alias:"keyword"},sugar:{pattern:/->|=>|\||::/,alias:"operator"},punctuation:/[()[\],]/,symbol:{pattern:/<:|-:|ø|@|#|\+|\||\*|\$|---|-|%|\/|\.\.|\^|~|=|<|>|\\/},boolean:{pattern:/\b(?:false|maybe|true)\b/}},s.languages.art=s.languages.arturo})(n)}Ab.displayName="asciidoc",Ab.aliases=["adoc"];function Ab(n){(function(s){var l={pattern:/(^[ \t]*)\[(?!\[)(?:(["'$`])(?:(?!\2)[^\\]|\\.)*\2|\[(?:[^\[\]\\]|\\.)*\]|[^\[\]\\"'$`]|\\.)*\]/m,lookbehind:!0,inside:{quoted:{pattern:/([$`])(?:(?!\1)[^\\]|\\.)*\1/,inside:{punctuation:/^[$`]|[$`]$/}},interpreted:{pattern:/'(?:[^'\\]|\\.)*'/,inside:{punctuation:/^'|'$/}},string:/"(?:[^"\\]|\\.)*"/,variable:/\w+(?==)/,punctuation:/^\[|\]$|,/,operator:/=/,"attr-value":/(?!^\s+$).+/}},c=s.languages.asciidoc={"comment-block":{pattern:/^(\/{4,})$[\s\S]*?^\1/m,alias:"comment"},table:{pattern:/^\|={3,}(?:(?:\r?\n|\r(?!\n)).*)*?(?:\r?\n|\r)\|={3,}$/m,inside:{specifiers:{pattern:/(?:(?:(?:\d+(?:\.\d+)?|\.\d+)[+*](?:[<^>](?:\.[<^>])?|\.[<^>])?|[<^>](?:\.[<^>])?|\.[<^>])[a-z]*|[a-z]+)(?=\|)/,alias:"attr-value"},punctuation:{pattern:/(^|[^\\])[|!]=*/,lookbehind:!0}}},"passthrough-block":{pattern:/^(\+{4,})$[\s\S]*?^\1$/m,inside:{punctuation:/^\++|\++$/}},"literal-block":{pattern:/^(-{4,}|\.{4,})$[\s\S]*?^\1$/m,inside:{punctuation:/^(?:-+|\.+)|(?:-+|\.+)$/}},"other-block":{pattern:/^(--|\*{4,}|_{4,}|={4,})$[\s\S]*?^\1$/m,inside:{punctuation:/^(?:-+|\*+|_+|=+)|(?:-+|\*+|_+|=+)$/}},"list-punctuation":{pattern:/(^[ \t]*)(?:-|\*{1,5}|\.{1,5}|(?:[a-z]|\d+)\.|[xvi]+\))(?= )/im,lookbehind:!0,alias:"punctuation"},"list-label":{pattern:/(^[ \t]*)[a-z\d].+(?::{2,4}|;;)(?=\s)/im,lookbehind:!0,alias:"symbol"},"indented-block":{pattern:/((\r?\n|\r)\2)([ \t]+)\S.*(?:(?:\r?\n|\r)\3.+)*(?=\2{2}|$)/,lookbehind:!0},comment:/^\/\/.*/m,title:{pattern:/^.+(?:\r?\n|\r)(?:={3,}|-{3,}|~{3,}|\^{3,}|\+{3,})$|^={1,5} .+|^\.(?![\s.]).*/m,alias:"important",inside:{punctuation:/^(?:\.|=+)|(?:=+|-+|~+|\^+|\++)$/}},"attribute-entry":{pattern:/^:[^:\r\n]+:(?: .*?(?: \+(?:\r?\n|\r).*?)*)?$/m,alias:"tag"},attributes:l,hr:{pattern:/^'{3,}$/m,alias:"punctuation"},"page-break":{pattern:/^<{3,}$/m,alias:"punctuation"},admonition:{pattern:/^(?:CAUTION|IMPORTANT|NOTE|TIP|WARNING):/m,alias:"keyword"},callout:[{pattern:/(^[ \t]*)\d*>/m,lookbehind:!0,alias:"symbol"},{pattern:/<\d+>/,alias:"symbol"}],macro:{pattern:/\b[a-z\d][a-z\d-]*::?(?:[^\s\[\]]*\[(?:[^\]\\"']|(["'])(?:(?!\1)[^\\]|\\.)*\1|\\.)*\])/,inside:{function:/^[a-z\d-]+(?=:)/,punctuation:/^::?/,attributes:{pattern:/(?:\[(?:[^\]\\"']|(["'])(?:(?!\1)[^\\]|\\.)*\1|\\.)*\])/,inside:l.inside}}},inline:{pattern:/(^|[^\\])(?:(?:\B\[(?:[^\]\\"']|(["'])(?:(?!\2)[^\\]|\\.)*\2|\\.)*\])?(?:\b_(?!\s)(?: _|[^_\\\r\n]|\\.)+(?:(?:\r?\n|\r)(?: _|[^_\\\r\n]|\\.)+)*_\b|\B``(?!\s).+?(?:(?:\r?\n|\r).+?)*''\B|\B`(?!\s)(?:[^`'\s]|\s+\S)+['`]\B|\B(['*+#])(?!\s)(?: \3|(?!\3)[^\\\r\n]|\\.)+(?:(?:\r?\n|\r)(?: \3|(?!\3)[^\\\r\n]|\\.)+)*\3\B)|(?:\[(?:[^\]\\"']|(["'])(?:(?!\4)[^\\]|\\.)*\4|\\.)*\])?(?:(__|\*\*|\+\+\+?|##|\$\$|[~^]).+?(?:(?:\r?\n|\r).+?)*\5|\{[^}\r\n]+\}|\[\[\[?.+?(?:(?:\r?\n|\r).+?)*\]?\]\]|<<.+?(?:(?:\r?\n|\r).+?)*>>|\(\(\(?.+?(?:(?:\r?\n|\r).+?)*\)?\)\)))/m,lookbehind:!0,inside:{attributes:l,url:{pattern:/^(?:\[\[\[?.+?\]?\]\]|<<.+?>>)$/,inside:{punctuation:/^(?:\[\[\[?|<<)|(?:\]\]\]?|>>)$/}},"attribute-ref":{pattern:/^\{.+\}$/,inside:{variable:{pattern:/(^\{)[a-z\d,+_-]+/,lookbehind:!0},operator:/^[=?!#%@$]|!(?=[:}])/,punctuation:/^\{|\}$|::?/}},italic:{pattern:/^(['_])[\s\S]+\1$/,inside:{punctuation:/^(?:''?|__?)|(?:''?|__?)$/}},bold:{pattern:/^\*[\s\S]+\*$/,inside:{punctuation:/^\*\*?|\*\*?$/}},punctuation:/^(?:``?|\+{1,3}|##?|\$\$|[~^]|\(\(\(?)|(?:''?|\+{1,3}|##?|\$\$|[~^`]|\)?\)\))$/}},replacement:{pattern:/\((?:C|R|TM)\)/,alias:"builtin"},entity:/?[\da-z]{1,8};/i,"line-continuation":{pattern:/(^| )\+$/m,lookbehind:!0,alias:"punctuation"}};function p(g){g=g.split(" ");for(var O={},w=0,C=g.length;w/,alias:"tag",inside:{"page-directive":{pattern:/<%\s*@\s*(?:Assembly|Control|Implements|Import|Master(?:Type)?|OutputCache|Page|PreviousPageType|Reference|Register)?|%>/i,alias:"tag"},rest:n.languages.markup.tag.inside}},directive:{pattern:/<%.*%>/,alias:"tag",inside:{directive:{pattern:/<%\s*?[$=%#:]{0,2}|%>/,alias:"tag"},rest:n.languages.csharp}}}),n.languages.aspnet.tag.pattern=/<(?!%)\/?[^\s>\/]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/,n.languages.insertBefore("inside","punctuation",{directive:n.languages.aspnet.directive},n.languages.aspnet.tag.inside["attr-value"]),n.languages.insertBefore("aspnet","comment",{"asp-comment":{pattern:/<%--[\s\S]*?--%>/,alias:["asp","comment"]}}),n.languages.insertBefore("aspnet",n.languages.javascript?"script":"tag",{"asp-script":{pattern:/(