├── .DS_Store ├── .gitignore ├── README.md ├── chatweb.iml ├── doc └── db │ ├── img.png │ ├── img_1.png │ ├── img_2.png │ ├── img_3.png │ ├── img_4.png │ └── user_content.sql ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── chatweb │ │ │ ├── ChatWebApplication.java │ │ │ ├── comm │ │ │ ├── PathConstant.java │ │ │ ├── aop │ │ │ │ ├── LoggerAdvice.java │ │ │ │ └── LoggerManage.java │ │ │ ├── exception │ │ │ │ └── GlobalExceptionHandler.java │ │ │ └── filter │ │ │ │ └── SecurityFilter.java │ │ │ ├── core │ │ │ ├── OpenAiApi.java │ │ │ └── holder │ │ │ │ └── SpringContextHolder.java │ │ │ ├── domain │ │ │ └── result │ │ │ │ ├── ExceptionMsg.java │ │ │ │ ├── Response.java │ │ │ │ └── ResponseData.java │ │ │ ├── mapper │ │ │ └── UserContentDao.java │ │ │ ├── model │ │ │ ├── ExecuteRet.java │ │ │ ├── Usage.java │ │ │ ├── UserContent.java │ │ │ ├── completion │ │ │ │ └── chat │ │ │ │ │ ├── ChatCompletionChoice.java │ │ │ │ │ ├── ChatCompletionRequest.java │ │ │ │ │ ├── ChatCompletionResult.java │ │ │ │ │ ├── ChatMessage.java │ │ │ │ │ └── ChatMessageRole.java │ │ │ └── model │ │ │ │ ├── Model.java │ │ │ │ └── Permission.java │ │ │ ├── service │ │ │ ├── ChatService.java │ │ │ └── impl │ │ │ │ └── ChatServiceImpl.java │ │ │ ├── utils │ │ │ └── DateUtils.java │ │ │ └── web │ │ │ ├── ChatController.java │ │ │ ├── IndexController.java │ │ │ └── api │ │ │ └── RestChatController.java │ └── resources │ │ ├── application.properties │ │ ├── mapper │ │ └── UserContentDao.xml │ │ ├── static │ │ ├── css │ │ │ ├── litewebchat.css │ │ │ ├── litewebchat.min.css │ │ │ ├── litewebchat_input.css │ │ │ ├── litewebchat_input.min.css │ │ │ └── map │ │ │ │ ├── litewebchat.css.map │ │ │ │ ├── litewebchat.min.css.map │ │ │ │ ├── litewebchat_input.css.map │ │ │ │ └── litewebchat_input.min.css.map │ │ ├── img │ │ │ ├── A.jpg │ │ │ └── B.jpg │ │ └── js │ │ │ ├── jquery.autocomplete.min.js │ │ │ ├── jquery.min.js │ │ │ ├── jquery.validate.min.js │ │ │ ├── litewebchat_input.js │ │ │ ├── litewebchat_input.min.js │ │ │ ├── litewebchat_render.js │ │ │ ├── litewebchat_render.min.js │ │ │ └── map │ │ │ ├── litewebchat_input.js.map │ │ │ ├── litewebchat_input.min.js.map │ │ │ ├── litewebchat_render.js.map │ │ │ └── litewebchat_render.min.js.map │ │ └── templates │ │ └── chat.html └── test │ ├── java │ └── com │ │ └── chatweb │ │ └── WebtapApplicationTests.java │ └── resources │ └── application-scratch.properties ├── start.sh └── stop.sh /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/379685397/ChatWeb/65142fa199eaaaa6929b69b26fa60e3e61cec860/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /src/main/resources/application.properties_tmp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1.前言 2 | 3 | 前几天发表了一片保姆级JAVA对接ChatGPT教程,实现自己的AI对话助手的文章,发现对于一些未对接过的朋友们还是有些门槛。这两天把又代码整理了下,简单写了个web端聊天式对话机器人。供大家参考学习。 4 | ![image](https://user-images.githubusercontent.com/34293665/229330058-e52f82a5-ce1c-4d15-ae8c-d56fd1557153.png) 5 | 6 | # 2.项目介绍 7 | 8 | ## 2.1.技术简介 9 | 10 | ### 2.1.1.技术框架 11 | 12 | 项目主要使用SpringBoot开发,tomcat当做项目容器,Mybiats为数据库持久层框架,Maven构建,H5作为去前端技术开发。比较适合新手学习。 13 | 14 | ### 2.1.2 网络拓扑 15 | 16 | ![image](https://user-images.githubusercontent.com/34293665/229330072-beffdac2-1b3f-4d3d-b5f4-3581ac27af98.png) 17 | 因为访问openai是不通的,所以我们中间需要搭建一个代理服务(有魔法科技的当我没说),通过代理服务访问openapi,代理服务部署在香港即可。 18 | 19 | ## 2.2.目录结构 20 | 21 | 项目目录结构如下 22 | 23 | - doc:存放数据库脚本以及文档 24 | - src/main/java:后端工程 25 | - resource/static 和 template:前端工程文件 26 | ![image](https://user-images.githubusercontent.com/34293665/229330110-00ad5f54-0b85-4ba2-a6b3-5da7a0237b85.png) 27 | 28 | ## 2.3.项目启动 29 | 30 | 1. 新建mysql数据库chatweb,将项目中doc目录下的sql脚本导入数据库中 31 | 2. 将项目导入IDEA中,并修改配置文件application.properties中的如下配置 32 | 33 | - **spring.datasource.url**:数据库地址 34 | - **spring.datasource.username** :数据库账号 35 | - **spring.datasource.password** :数据库密码 36 | - **open.ai.token**:openai接口调用key 37 | - **open.ai.url**:openAi访问地址,直接访问不通,需要搭建香港nginx代理进行访问 38 | 39 | ```jsx 40 | #spring.profiles.active = dev 41 | server.port = 8999 42 | spring.application.name = OpenAi 43 | ###### db config start###### 44 | spring.datasource.url = jdbc:mysql://XXXX:3306/chatweb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 45 | spring.datasource.username = XXXX 46 | spring.datasource.password = XXX 47 | spring.datasource.driver - class - name = com.mysql.cj.jdbc.Driver 48 | #mybatis 49 | mybatis.mapper - locations = classpath:mapper/*.xml 50 | mybatis.type-aliases-package=com.wentap.mapper 51 | mybatis.configuration.map-underscore-to-camel-case=true 52 | mybatis.configuration.call-setters-on-nulls=true 53 | ###### logging config start ###### 54 | logging.file=./log/service.log 55 | logging.level.com.chatweb=INFO 56 | logging.level.org.springframework.web=INFO 57 | ##### mail setting end ##### 58 | spring.thymeleaf.cache=false 59 | ## openAi访问地址,直接访问不通,需要搭建nginx代理进行访问 60 | open.ai.url=https://api.openai.com/ 61 | ## API-Key 62 | open.ai.token=XXX 63 | ## 连续会话支持长度 64 | session.num=10 65 | ## 连续会话有效时间(单位:小时) 66 | session.time=4 67 | ## token长度,结果返回的长度 68 | token.num=500 69 | spring.thymeleaf.mode=LEGACYHTML5 70 | ``` 71 | 1. 编译项目,之后运行**`ChatWebApplication`** 72 | 2. 执行成功之后访问[http://127.0.0.1:8999](http://127.0.0.1:8999/)即可打开页面 73 | ![image](https://user-images.githubusercontent.com/34293665/229330117-cd383e63-5f7e-4d88-8cc9-09e87d56fb6f.png) 74 | 75 | 76 | ## 2.4.项目部署 77 | 78 | 如果大家想把项目部署云服务器上,则只需要如下操作 79 | 80 | 1. 在IDEA中,执行maven install 命令,待编译完成之后在targer目录下生成jar包。 81 | ![image](https://user-images.githubusercontent.com/34293665/229330125-487f06c0-ef12-45cb-80c8-d46aca2b61e2.png) 82 | 1. 在IDEA中,执行maven install 命令,待编译完成之后在targer目录下生成chatweb-1.0.jar包 83 | 2. 将服务中的start.sh、stop.sh脚本和chatweb-1.0.jar包一起丢到云服务器上 84 | 3. 执行start.sh脚本即可 85 | 4. 如果是windows下,则直接在jar包所在目录执行java -jar chatweb-1.0.jar 即可。 86 | -------------------------------------------------------------------------------- /chatweb.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /doc/db/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/379685397/ChatWeb/65142fa199eaaaa6929b69b26fa60e3e61cec860/doc/db/img.png -------------------------------------------------------------------------------- /doc/db/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/379685397/ChatWeb/65142fa199eaaaa6929b69b26fa60e3e61cec860/doc/db/img_1.png -------------------------------------------------------------------------------- /doc/db/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/379685397/ChatWeb/65142fa199eaaaa6929b69b26fa60e3e61cec860/doc/db/img_2.png -------------------------------------------------------------------------------- /doc/db/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/379685397/ChatWeb/65142fa199eaaaa6929b69b26fa60e3e61cec860/doc/db/img_3.png -------------------------------------------------------------------------------- /doc/db/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/379685397/ChatWeb/65142fa199eaaaa6929b69b26fa60e3e61cec860/doc/db/img_4.png -------------------------------------------------------------------------------- /doc/db/user_content.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server Type : MySQL 5 | Source Server Version : 50568 6 | Target Server Type : MySQL 7 | Target Server Version : 50568 8 | File Encoding : 65001 9 | 10 | Date: 02/04/2023 10:15:02 11 | */ 12 | 13 | SET NAMES utf8mb4; 14 | SET 15 | FOREIGN_KEY_CHECKS = 0; 16 | 17 | -- ---------------------------- 18 | -- Table structure for user_content 19 | -- ---------------------------- 20 | DROP TABLE IF EXISTS `user_content`; 21 | CREATE TABLE `user_content` 22 | ( 23 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 24 | `user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户名', 25 | `role` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '角色', 26 | `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '信息', 27 | `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 28 | PRIMARY KEY (`id`) USING BTREE 29 | ) ENGINE = InnoDB 30 | AUTO_INCREMENT = 1 31 | CHARACTER SET = utf8mb4 32 | COLLATE = utf8mb4_unicode_ci COMMENT = '用户会话表' 33 | ROW_FORMAT = Compact; 34 | SET FOREIGN_KEY_CHECKS = 1; 35 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.6.RELEASE 9 | 10 | 11 | com.chatweb 12 | chatweb 13 | 1.0 14 | jar 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-thymeleaf 24 | 25 | 26 | nz.net.ultraq.thymeleaf 27 | thymeleaf-layout-dialect 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-jpa 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-devtools 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-mail 44 | 45 | 46 | 47 | mysql 48 | mysql-connector-java 49 | 50 | 51 | org.apache.commons 52 | commons-lang3 53 | 3.4 54 | 55 | 56 | commons-codec 57 | commons-codec 58 | 59 | 60 | org.jsoup 61 | jsoup 62 | 1.9.2 63 | 64 | 65 | 66 | com.alibaba 67 | fastjson 68 | 1.2.56 69 | 70 | 71 | net.sourceforge.nekohtml 72 | nekohtml 73 | 1.9.22 74 | 75 | 76 | com.github.pagehelper 77 | pagehelper-spring-boot-starter 78 | 1.4.1 79 | 80 | 81 | org.projectlombok 82 | lombok 83 | 84 | 85 | org.apache.httpcomponents 86 | httpasyncclient 87 | 4.0.2 88 | 89 | 90 | org.apache.httpcomponents 91 | httpcore-nio 92 | 4.3.2 93 | 94 | 95 | 96 | org.apache.httpcomponents 97 | httpclient 98 | 4.3.5 99 | 100 | 101 | commons-codec 102 | commons-codec 103 | 104 | 105 | 106 | 107 | commons-httpclient 108 | commons-httpclient 109 | 3.1 110 | 111 | 112 | commons-codec 113 | commons-codec 114 | 115 | 116 | 117 | 118 | org.mybatis.spring.boot 119 | mybatis-spring-boot-starter 120 | 1.3.1 121 | 122 | 123 | org.apache.httpcomponents 124 | httpclient 125 | 4.3.5 126 | 127 | 128 | commons-codec 129 | commons-codec 130 | 131 | 132 | 133 | 134 | commons-httpclient 135 | commons-httpclient 136 | 3.1 137 | 138 | 139 | commons-codec 140 | commons-codec 141 | 142 | 143 | 144 | 145 | org.mybatis.spring.boot 146 | mybatis-spring-boot-starter 147 | 1.3.1 148 | 149 | 150 | 151 | 152 | 153 | 154 | org.springframework.boot 155 | spring-boot-maven-plugin 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-surefire-plugin 160 | 161 | true 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/ChatWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.chatweb; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.builder.SpringApplicationBuilder; 7 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 8 | 9 | 10 | @SpringBootApplication(scanBasePackages = {"com.chatweb"}) 11 | //注册过滤器注解 12 | @MapperScan({"com.*.mapper"}) 13 | public class ChatWebApplication extends SpringBootServletInitializer { 14 | 15 | @Override 16 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 17 | return application.sources(ChatWebApplication.class); 18 | } 19 | 20 | public static void main(String[] args) { 21 | SpringApplication.run(ChatWebApplication.class, args); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/comm/PathConstant.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.comm; 2 | 3 | /** 4 | * @author teacher wang 5 | * @version V1.0 6 | * @modificationHistory=========================逻辑或功能性重大变更记录 7 | * @modify by user: {修改人} 2023/3/7 8 | * @modify by reason:{方法名}:{原因} 9 | */ 10 | public class PathConstant { 11 | public static class MODEL { 12 | public static String MODEL_LIST = "/v1/models"; 13 | } 14 | 15 | public static class COMPLETIONS { 16 | public static String CREATE_COMPLETION = "/v1/completions"; 17 | public static String CREATE_CHAT_COMPLETION = "/v1/chat/completions"; 18 | public static String CREATE_COMPLETION_id = "/v1/engines/{engine_id}/completions"; 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/comm/aop/LoggerAdvice.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.comm.aop; 2 | 3 | 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.aspectj.lang.JoinPoint; 6 | import org.aspectj.lang.annotation.AfterReturning; 7 | import org.aspectj.lang.annotation.AfterThrowing; 8 | import org.aspectj.lang.annotation.Aspect; 9 | import org.aspectj.lang.annotation.Before; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.stereotype.Service; 13 | 14 | /** 15 | * @author teacher wang 16 | * @version 1.0 17 | * @Description: 日志管理 18 | */ 19 | @Aspect 20 | @Service 21 | public class LoggerAdvice { 22 | 23 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 24 | 25 | @Before("within(com.chatweb..*) && @annotation(loggerManage)") 26 | public void addBeforeLogger(JoinPoint joinPoint, LoggerManage loggerManage) { 27 | logger.info("执行 " + loggerManage.description() + " 开始"); 28 | logger.info(joinPoint.getSignature().toString()); 29 | logger.info(parseParames(joinPoint.getArgs())); 30 | } 31 | 32 | @AfterReturning("within(com.chatweb..*) && @annotation(loggerManage)") 33 | public void addAfterReturningLogger(JoinPoint joinPoint, LoggerManage loggerManage) { 34 | logger.info("执行 " + loggerManage.description() + " 结束"); 35 | } 36 | 37 | @AfterThrowing(pointcut = "within(com.chatweb..*) && @annotation(loggerManage)", throwing = "ex") 38 | public void addAfterThrowingLogger(JoinPoint joinPoint, LoggerManage loggerManage, Exception ex) { 39 | logger.error("执行 " + loggerManage.description() + " 异常", ex); 40 | } 41 | 42 | private String parseParames(Object[] parames) { 43 | if (null == parames || parames.length <= 0 || parames.length > 1024) { 44 | return ""; 45 | } 46 | StringBuffer param = new StringBuffer("传入参数[{}] "); 47 | for (Object obj : parames) { 48 | param.append(ToStringBuilder.reflectionToString(obj)).append(" "); 49 | } 50 | return param.toString(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/comm/aop/LoggerManage.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.comm.aop; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author teacher wang 7 | * @version 1.0 8 | * @Description: 日志注解 9 | */ 10 | @Target(ElementType.METHOD) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | public @interface LoggerManage { 14 | 15 | public String description(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/comm/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.comm.exception; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.web.bind.annotation.ControllerAdvice; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.servlet.ModelAndView; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | @ControllerAdvice 12 | public class GlobalExceptionHandler { 13 | 14 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 15 | 16 | public static final String DEFAULT_ERROR_VIEW = "error"; 17 | 18 | @ExceptionHandler(value = Exception.class) 19 | public ModelAndView defaultErrorHandler(Exception e, HttpServletRequest request) throws Exception { 20 | logger.info("请求地址:" + request.getRequestURL()); 21 | ModelAndView mav = new ModelAndView(); 22 | logger.error("异常信息:", e); 23 | mav.setViewName(DEFAULT_ERROR_VIEW); 24 | return mav; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/comm/filter/SecurityFilter.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.comm.filter; 2 | 3 | import javax.servlet.*; 4 | import javax.servlet.http.HttpServletRequest; 5 | import java.io.IOException; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | public class SecurityFilter implements Filter { 10 | 11 | private static Set GreenUrlSet = new HashSet(); 12 | 13 | 14 | @Override 15 | public void init(FilterConfig arg0) throws ServletException { 16 | GreenUrlSet.add("/login"); 17 | GreenUrlSet.add("/register"); 18 | GreenUrlSet.add("/index"); 19 | GreenUrlSet.add("/forgotPassword"); 20 | GreenUrlSet.add("/newPassword"); 21 | GreenUrlSet.add("/tool"); 22 | } 23 | 24 | @Override 25 | public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain filterChain) 26 | throws IOException, ServletException { 27 | HttpServletRequest request = (HttpServletRequest) srequest; 28 | String uri = request.getRequestURI(); 29 | filterChain.doFilter(srequest, sresponse); 30 | } 31 | 32 | 33 | /** 34 | * @param url 35 | * @return 36 | * @author neo 37 | * @date 2016-5-4 38 | */ 39 | private boolean containsSuffix(String url) { 40 | if (url.endsWith(".js") 41 | || url.endsWith(".css") 42 | || url.endsWith(".jpg") 43 | || url.endsWith(".gif") 44 | || url.endsWith(".png") 45 | || url.endsWith(".html") 46 | || url.endsWith(".eot") 47 | || url.endsWith(".svg") 48 | || url.endsWith(".ttf") 49 | || url.endsWith(".woff") 50 | || url.endsWith(".ico") 51 | || url.endsWith(".woff2")) { 52 | return true; 53 | } else { 54 | return false; 55 | } 56 | } 57 | 58 | 59 | @Override 60 | public void destroy() { 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/core/OpenAiApi.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.core; 2 | 3 | import com.chatweb.model.ExecuteRet; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.commons.httpclient.HttpClient; 6 | import org.apache.commons.httpclient.HttpMethod; 7 | import org.apache.commons.httpclient.HttpStatus; 8 | import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; 9 | import org.apache.commons.httpclient.methods.GetMethod; 10 | import org.apache.commons.httpclient.methods.PostMethod; 11 | import org.apache.commons.httpclient.methods.StringRequestEntity; 12 | import org.apache.commons.httpclient.params.HttpMethodParams; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.io.BufferedReader; 17 | import java.io.IOException; 18 | import java.io.InputStreamReader; 19 | import java.io.UnsupportedEncodingException; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | *

25 | * 26 | * @author wzq 2023/3/7 14:01 27 | * @version V1.0 28 | * @modificationHistory=========================逻辑或功能性重大变更记录 29 | * @modify by user: {修改人} 2023/3/7 30 | * @modify by reason:{方法名}:{原因} 31 | */ 32 | @Slf4j 33 | @Component 34 | public class OpenAiApi { 35 | 36 | @Value("${open.ai.url}") 37 | private String url; 38 | @Value("${open.ai.token}") 39 | private String token; 40 | 41 | private static final MultiThreadedHttpConnectionManager CONNECTION_MANAGER = new MultiThreadedHttpConnectionManager(); 42 | 43 | static { 44 | // 默认单个host最大链接数 45 | CONNECTION_MANAGER.getParams().setDefaultMaxConnectionsPerHost( 46 | Integer.valueOf(20)); 47 | // 最大总连接数,默认20 48 | CONNECTION_MANAGER.getParams() 49 | .setMaxTotalConnections(20); 50 | // 连接超时时间 51 | CONNECTION_MANAGER.getParams() 52 | .setConnectionTimeout(80000); 53 | // 读取超时时间 54 | CONNECTION_MANAGER.getParams().setSoTimeout(80000); 55 | } 56 | 57 | public ExecuteRet get(String path, Map headers) { 58 | GetMethod method = new GetMethod(url + path); 59 | log.info("GET Url is {} ", url + path); 60 | if (headers == null) { 61 | headers = new HashMap<>(); 62 | } 63 | headers.put("Authorization", "Bearer " + token); 64 | for (Map.Entry h : headers.entrySet()) { 65 | method.setRequestHeader(h.getKey(), h.getValue()); 66 | } 67 | return execute(method); 68 | } 69 | 70 | /** 71 | *

72 | * 发送post请求 73 | *

74 | * 75 | * @param url 76 | * @param json 77 | * @param headers 78 | * @return 79 | * @author songnanjie 2016年5月4日 下午4:48:41 80 | */ 81 | public ExecuteRet post(String path, String json, Map headers) { 82 | try { 83 | PostMethod method = new PostMethod(url + path); 84 | //log.info("POST Url is {} ", url + path); 85 | // 输出传入参数 86 | log.info(String.format("POST JSON HttpMethod's Params = %s", json)); 87 | StringRequestEntity entity = new StringRequestEntity(json, "application/json", "UTF-8"); 88 | method.setRequestEntity(entity); 89 | if (headers == null) { 90 | headers = new HashMap<>(); 91 | } 92 | headers.put("Authorization", "Bearer " + token); 93 | for (Map.Entry h : headers.entrySet()) { 94 | method.setRequestHeader(h.getKey(), h.getValue()); 95 | } 96 | return execute(method); 97 | } catch (UnsupportedEncodingException ex) { 98 | log.error(ex.getMessage(), ex); 99 | } 100 | return new ExecuteRet(false, "", null, -1); 101 | } 102 | 103 | /** 104 | *

105 | *

106 | * 107 | * @param method 108 | * @return 109 | * @author songnanjie 2016年5月4日 下午4:55:45 110 | */ 111 | public ExecuteRet execute(HttpMethod method) { 112 | HttpClient client = new HttpClient(CONNECTION_MANAGER); 113 | int statusCode = -1; 114 | String respStr = null; 115 | boolean isSuccess = false; 116 | try { 117 | client.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF8"); 118 | statusCode = client.executeMethod(method); 119 | method.getRequestHeaders(); 120 | 121 | // log.info("执行结果statusCode = " + statusCode); 122 | InputStreamReader inputStreamReader = new InputStreamReader(method.getResponseBodyAsStream(), "UTF-8"); 123 | BufferedReader reader = new BufferedReader(inputStreamReader); 124 | StringBuilder stringBuffer = new StringBuilder(100); 125 | String str; 126 | while ((str = reader.readLine()) != null) { 127 | log.debug("逐行读取String = " + str); 128 | stringBuffer.append(str.trim()); 129 | } 130 | respStr = stringBuffer.toString(); 131 | if (respStr != null) { 132 | if (respStr.length() < 2048) { 133 | // 当返回结果较少时,打印出log,便于问题排查 134 | log.info(String.format("执行结果String = %s, Length = %d", respStr, respStr.length())); 135 | } 136 | } else { 137 | // log.info(String.format("执行结果String Is Null")); 138 | } 139 | inputStreamReader.close(); 140 | reader.close(); 141 | // 返回200,接口调用成功 142 | isSuccess = (statusCode == HttpStatus.SC_OK); 143 | } catch (IOException ex) { 144 | } finally { 145 | method.releaseConnection(); 146 | } 147 | return new ExecuteRet(isSuccess, respStr, method, statusCode); 148 | } 149 | 150 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/core/holder/SpringContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.core.holder; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | 7 | /** 8 | * @author teacher wang 9 | * @version 1.0 10 | **/ 11 | public class SpringContextHolder implements ApplicationContextAware { 12 | private static ApplicationContext appContext = null; 13 | 14 | /** 15 | * 通过name获取 Bean. 16 | * 17 | * @param name 18 | * @return 19 | */ 20 | public static Object getBean(String name) { 21 | return appContext.getBean(name); 22 | 23 | } 24 | 25 | /** 26 | * 通过class获取Bean. 27 | * 28 | * @param clazz 29 | * @param 30 | * @return 31 | */ 32 | public static T getBean(Class clazz) { 33 | return appContext.getBean(clazz); 34 | } 35 | 36 | /** 37 | * 通过name,以及Clazz返回指定的Bean 38 | * 39 | * @param name 40 | * @param clazz 41 | * @param 42 | * @return 43 | */ 44 | public static T getBean(String name, Class clazz) { 45 | return appContext.getBean(name, clazz); 46 | } 47 | 48 | @Override 49 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 50 | if (appContext == null) { 51 | appContext = applicationContext; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/domain/result/ExceptionMsg.java: -------------------------------------------------------------------------------- 1 | 2 | package com.chatweb.domain.result; 3 | 4 | public enum ExceptionMsg { 5 | SUCCESS("000000", "操作成功"), 6 | FAILED("999999","操作失败"), 7 | ParamError("000001", "参数错误!"), 8 | 9 | LoginNameOrPassWordError("000100", "用户名或者密码错误!"), 10 | EmailUsed("000101","该邮箱已被注册"), 11 | UserNameUsed("000102","该登录名称已存在"), 12 | EmailNotRegister("000103","该邮箱地址未注册"), 13 | LinkOutdated("000104","该链接已过期,请重新请求"), 14 | PassWordError("000105","密码输入错误"), 15 | UserNameLengthLimit("000106","用户名长度超限"), 16 | LoginNameNotExists("000107","该用户未注册"), 17 | UserNameSame("000108","新用户名与原用户名一致"), 18 | 19 | 20 | FileEmpty("000400","上传文件为空"), 21 | LimitPictureSize("000401","图片大小必须小于2M"), 22 | LimitPictureType("000402","图片格式必须为'jpg'、'png'、'jpge'、'gif'、'bmp'") 23 | ; 24 | private ExceptionMsg(String code, String msg) { 25 | this.code = code; 26 | this.msg = msg; 27 | } 28 | private String code; 29 | private String msg; 30 | 31 | public String getCode() { 32 | return code; 33 | } 34 | public String getMsg() { 35 | return msg; 36 | } 37 | 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/domain/result/Response.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.domain.result; 2 | 3 | public class Response { 4 | /** 返回信息码*/ 5 | private String rspCode="000000"; 6 | /** 返回信息内容*/ 7 | private String rspMsg="操作成功"; 8 | 9 | public Response() { 10 | } 11 | 12 | public Response(ExceptionMsg msg){ 13 | this.rspCode=msg.getCode(); 14 | this.rspMsg=msg.getMsg(); 15 | } 16 | 17 | public Response(String rspCode) { 18 | this.rspCode = rspCode; 19 | this.rspMsg = ""; 20 | } 21 | 22 | public Response(String rspCode, String rspMsg) { 23 | this.rspCode = rspCode; 24 | this.rspMsg = rspMsg; 25 | } 26 | public String getRspCode() { 27 | return rspCode; 28 | } 29 | public void setRspCode(String rspCode) { 30 | this.rspCode = rspCode; 31 | } 32 | public String getRspMsg() { 33 | return rspMsg; 34 | } 35 | public void setRspMsg(String rspMsg) { 36 | this.rspMsg = rspMsg; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "Response{" + 42 | "rspCode='" + rspCode + '\'' + 43 | ", rspMsg='" + rspMsg + '\'' + 44 | '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/domain/result/ResponseData.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.domain.result; 2 | 3 | public class ResponseData extends Response{ 4 | private Object data; 5 | 6 | public ResponseData(Object data) { 7 | this.data = data; 8 | } 9 | 10 | public ResponseData(ExceptionMsg msg) { 11 | super(msg); 12 | } 13 | 14 | public ResponseData(String rspCode, String rspMsg) { 15 | super(rspCode, rspMsg); 16 | } 17 | 18 | public ResponseData(String rspCode, String rspMsg, Object data) { 19 | super(rspCode, rspMsg); 20 | this.data = data; 21 | } 22 | 23 | public ResponseData(ExceptionMsg msg, Object data) { 24 | super(msg); 25 | this.data = data; 26 | } 27 | 28 | public Object getData() { 29 | return data; 30 | } 31 | 32 | public void setData(Object data) { 33 | this.data = data; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "ResponseData{" + 39 | "data=" + data + 40 | "} " + super.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/mapper/UserContentDao.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.mapper; 2 | 3 | import com.chatweb.model.UserContent; 4 | import com.chatweb.model.completion.chat.ChatMessage; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import java.util.Date; 9 | import java.util.List; 10 | 11 | @Mapper 12 | public interface UserContentDao { 13 | int deleteByPrimaryKey(Long id); 14 | 15 | int insert(UserContent record); 16 | 17 | int insertSelective(UserContent record); 18 | 19 | UserContent selectByPrimaryKey(Long id); 20 | 21 | List selectForUser(@Param("user") String user, @Param("size") int size); 22 | 23 | List selectMessageForUser(@Param("user") String user, @Param("size") int size, @Param("time") Date time); 24 | 25 | int updateByPrimaryKeySelective(UserContent record); 26 | 27 | int updateByPrimaryKey(UserContent record); 28 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/ExecuteRet.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model;/** 2 | * /** 3 | * 4 | *

5 | * 6 | * @author wzq 2023/3/7 14:05 7 | * @version V1.0 8 | * @modificationHistory=========================逻辑或功能性重大变更记录 9 | * @modify by user: {修改人} 2023/3/7 10 | * @modify by reason:{方法名}:{原因} 11 | */ 12 | 13 | import org.apache.commons.httpclient.HttpMethod; 14 | 15 | /** 16 | * 调用返回 17 | */ 18 | public class ExecuteRet { 19 | 20 | /** 21 | * 操作是否成功 22 | */ 23 | private final boolean success; 24 | 25 | /** 26 | * 返回的内容 27 | */ 28 | private final String respStr; 29 | 30 | /** 31 | * 请求的地址 32 | */ 33 | private final HttpMethod method; 34 | 35 | /** 36 | * statusCode 37 | */ 38 | private final int statusCode; 39 | 40 | /** 41 | * @param success 42 | * @param respStr 43 | * @param method 44 | * @param statusCode 45 | */ 46 | public ExecuteRet(boolean success, String respStr, HttpMethod method, int statusCode) { 47 | this.success = success; 48 | this.respStr = respStr; 49 | this.method = method; 50 | this.statusCode = statusCode; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return String.format("[success:%s,respStr:%s,statusCode:%s]", success, respStr, statusCode); 56 | } 57 | 58 | /** 59 | * @return the isSuccess 60 | */ 61 | public boolean isSuccess() { 62 | return success; 63 | } 64 | 65 | /** 66 | * @return the !isSuccess 67 | */ 68 | public boolean isNotSuccess() { 69 | return !success; 70 | } 71 | 72 | /** 73 | * @return the respStr 74 | */ 75 | public String getRespStr() { 76 | return respStr; 77 | } 78 | 79 | /** 80 | * @return the statusCode 81 | */ 82 | public int getStatusCode() { 83 | return statusCode; 84 | } 85 | 86 | /** 87 | * 请求的地址 88 | * 89 | * @return the method 90 | */ 91 | public HttpMethod getMethod() { 92 | return method; 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/Usage.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * The OpenAI resources used by a request 7 | */ 8 | @Data 9 | public class Usage { 10 | /** 11 | * The number of prompt tokens used. 12 | */ 13 | long promptTokens; 14 | 15 | /** 16 | * The number of completion tokens used. 17 | */ 18 | long completionTokens; 19 | 20 | /** 21 | * The number of total tokens used 22 | */ 23 | long totalTokens; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/UserContent.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | 8 | /** 9 | * user_content 10 | * 11 | * @author 12 | */ 13 | @Data 14 | public class UserContent implements Serializable { 15 | private Long id; 16 | 17 | /** 18 | * 用户名 19 | */ 20 | private String user; 21 | 22 | /** 23 | * 角色 24 | */ 25 | private String role; 26 | 27 | /** 28 | * 信息 29 | */ 30 | private String content; 31 | 32 | /** 33 | * 创建时间 34 | */ 35 | private Date createTime; 36 | 37 | private static final long serialVersionUID = 1L; 38 | 39 | @Override 40 | public boolean equals(Object that) { 41 | if (this == that) { 42 | return true; 43 | } 44 | if (that == null) { 45 | return false; 46 | } 47 | if (getClass() != that.getClass()) { 48 | return false; 49 | } 50 | UserContent other = (UserContent) that; 51 | return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId())) 52 | && (this.getUser() == null ? other.getUser() == null : this.getUser().equals(other.getUser())) 53 | && (this.getRole() == null ? other.getRole() == null : this.getRole().equals(other.getRole())) 54 | && (this.getContent() == null ? other.getContent() == null : this.getContent().equals(other.getContent())) 55 | && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime())); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | final int prime = 31; 61 | int result = 1; 62 | result = prime * result + ((getId() == null) ? 0 : getId().hashCode()); 63 | result = prime * result + ((getUser() == null) ? 0 : getUser().hashCode()); 64 | result = prime * result + ((getRole() == null) ? 0 : getRole().hashCode()); 65 | result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode()); 66 | result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode()); 67 | return result; 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | StringBuilder sb = new StringBuilder(); 73 | sb.append(getClass().getSimpleName()); 74 | sb.append(" ["); 75 | sb.append("Hash = ").append(hashCode()); 76 | sb.append(", id=").append(id); 77 | sb.append(", user=").append(user); 78 | sb.append(", role=").append(role); 79 | sb.append(", content=").append(content); 80 | sb.append(", createTime=").append(createTime); 81 | sb.append(", serialVersionUID=").append(serialVersionUID); 82 | sb.append("]"); 83 | return sb.toString(); 84 | } 85 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/completion/chat/ChatCompletionChoice.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model.completion.chat; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * A chat completion generated by GPT-3.5 7 | */ 8 | @Data 9 | public class ChatCompletionChoice { 10 | 11 | /** 12 | * This index of this completion in the returned list. 13 | */ 14 | Integer index; 15 | 16 | /** 17 | * The {@link ChatMessageRole#assistant} message which was generated. 18 | */ 19 | ChatMessage message; 20 | 21 | /** 22 | * The reason why GPT-3 stopped generating, for example "length". 23 | */ 24 | String finishReason; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/completion/chat/ChatCompletionRequest.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model.completion.chat; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | @Data 10 | @Builder 11 | public class ChatCompletionRequest { 12 | 13 | /** 14 | * ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. 15 | */ 16 | String model; 17 | 18 | /** 19 | * The messages to generate chat completions for, in the chat format.
21 | * see {@link ChatMessage} 22 | */ 23 | List messages; 24 | 25 | /** 26 | * What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower 27 | * values like 0.2 will make it more focused and deterministic.
28 | * We generally recommend altering this or top_p but not both. 29 | */ 30 | Double temperature; 31 | 32 | /** 33 | * An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens 34 | * with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.
35 | * We generally recommend altering this or temperature but not both. 36 | */ 37 | Double topP; 38 | 39 | /** 40 | * How many chat completion chatCompletionChoices to generate for each input message. 41 | */ 42 | Integer n; 43 | 44 | /** 45 | * If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only server-sent 47 | * events as they become available, with the stream terminated by a data: [DONE] message. 48 | */ 49 | Boolean stream; 50 | 51 | /** 52 | * Up to 4 sequences where the API will stop generating further tokens. 53 | */ 54 | List stop; 55 | 56 | /** 57 | * The maximum number of tokens allowed for the generated answer. By default, the number of tokens the model can return will 58 | * be (4096 - prompt tokens). 59 | */ 60 | Integer max_tokens; 61 | 62 | /** 63 | * Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, 64 | * increasing the model's likelihood to talk about new topics. 65 | */ 66 | Double presencePenalty; 67 | 68 | /** 69 | * Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, 70 | * decreasing the model's likelihood to repeat the same line verbatim. 71 | */ 72 | Double frequencyPenalty; 73 | 74 | /** 75 | * Accepts a json object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 76 | * to 100. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will 77 | * vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 78 | * should result in a ban or exclusive selection of the relevant token. 79 | */ 80 | Map logitBias; 81 | 82 | 83 | /** 84 | * A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. 85 | */ 86 | String user; 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/completion/chat/ChatCompletionResult.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model.completion.chat; 2 | 3 | import com.chatweb.model.Usage; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Object containing a response from the chat completions api. 10 | */ 11 | @Data 12 | public class ChatCompletionResult { 13 | 14 | /** 15 | * Unique id assigned to this chat completion. 16 | */ 17 | String id; 18 | 19 | /** 20 | * The type of object returned, should be "chat.completion" 21 | */ 22 | String object; 23 | 24 | /** 25 | * The creation time in epoch seconds. 26 | */ 27 | long created; 28 | 29 | /** 30 | * The GPT-3.5 model used. 31 | */ 32 | String model; 33 | 34 | /** 35 | * A list of all generated completions. 36 | */ 37 | List choices; 38 | 39 | /** 40 | * The API usage for this request. 41 | */ 42 | Usage usage; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/completion/chat/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model.completion.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | *

Each object has a role (either “system”, “user”, or “assistant”) and content (the content of the message). Conversations can be as short as 1 message or fill many pages.

9 | *

Typically, a conversation is formatted with a system message first, followed by alternating user and assistant messages.

10 | *

The system message helps set the behavior of the assistant. In the example above, the assistant was instructed with “You are a helpful assistant.”
11 | * The user messages help instruct the assistant. They can be generated by the end users of an application, or set by a developer as an instruction.
12 | * The assistant messages help store prior responses. They can also be written by a developer to help give examples of desired behavior. 13 | *

14 | *

15 | * see OpenAi documentation 16 | */ 17 | @Data 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class ChatMessage { 21 | 22 | /** 23 | * Must be either 'system', 'user', or 'assistant'.
24 | * You may use {@link ChatMessageRole} enum. 25 | */ 26 | String role; 27 | String content; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/completion/chat/ChatMessageRole.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model.completion.chat; 2 | 3 | /** 4 | * see {@link ChatMessage} documentation. 5 | */ 6 | public enum ChatMessageRole { 7 | SYSTEM("system"), 8 | USER("user"), 9 | ASSISTANT("assistant"); 10 | 11 | private final String value; 12 | 13 | ChatMessageRole(final String value) { 14 | this.value = value; 15 | } 16 | 17 | public String value() { 18 | return value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/model/Model.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * GPT-3 model details 9 | *

10 | * https://beta.openai.com/docs/api-reference/models 11 | */ 12 | @Data 13 | public class Model { 14 | /** 15 | * An identifier for this model, used to specify the model when making completions, etc 16 | */ 17 | public String id; 18 | 19 | /** 20 | * The type of object returned, should be "model" 21 | */ 22 | public String object; 23 | 24 | /** 25 | * The owner of the GPT-3 model, typically "openai" 26 | */ 27 | public String ownedBy; 28 | 29 | /** 30 | * List of permissions for this model 31 | */ 32 | public List permission; 33 | 34 | /** 35 | * The root model that this and its parent (if applicable) are based on 36 | */ 37 | public String root; 38 | 39 | /** 40 | * The parent model that this is based on 41 | */ 42 | public String parent; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/model/model/Permission.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.model.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * GPT-3 model permissions 7 | * I couldn't find documentation for the specific permissions, and I've elected to leave them undocumented rather than 8 | * write something incorrect. 9 | *

10 | * https://beta.openai.com/docs/api-reference/models 11 | */ 12 | @Data 13 | public class Permission { 14 | /** 15 | * An identifier for this model permission 16 | */ 17 | public String id; 18 | 19 | /** 20 | * The type of object returned, should be "model_permission" 21 | */ 22 | public String object; 23 | 24 | /** 25 | * The creation time in epoch seconds. 26 | */ 27 | public long created; 28 | 29 | public boolean allowCreateEngine; 30 | 31 | public boolean allowSampling; 32 | 33 | public boolean allowLogProbs; 34 | 35 | public boolean allowSearchIndices; 36 | 37 | public boolean allowView; 38 | 39 | public boolean allowFineTuning; 40 | 41 | public String organization; 42 | 43 | public String group; 44 | 45 | public boolean isBlocking; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/service/ChatService.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | public interface ChatService { 9 | public JSONObject createChatCompletion(HttpServletRequest request, HttpServletResponse response); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/service/impl/ChatServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.service.impl; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.chatweb.comm.PathConstant; 5 | import com.chatweb.core.OpenAiApi; 6 | import com.chatweb.mapper.UserContentDao; 7 | import com.chatweb.model.ExecuteRet; 8 | import com.chatweb.model.UserContent; 9 | import com.chatweb.model.completion.chat.ChatCompletionChoice; 10 | import com.chatweb.model.completion.chat.ChatCompletionRequest; 11 | import com.chatweb.model.completion.chat.ChatMessage; 12 | import com.chatweb.model.completion.chat.ChatMessageRole; 13 | import com.chatweb.service.ChatService; 14 | import com.chatweb.utils.DateUtils; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.stereotype.Service; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.util.Date; 22 | import java.util.List; 23 | 24 | @Service 25 | public class ChatServiceImpl implements ChatService { 26 | @Value("${session.num}") 27 | private int size; 28 | @Value("${session.time}") 29 | private int sessionTime; 30 | @Value("${token.num}") 31 | private int tokenNum; 32 | 33 | @Autowired 34 | private UserContentDao userContentDao; 35 | 36 | @Autowired 37 | private OpenAiApi openAiApi; 38 | 39 | 40 | public JSONObject createChatCompletion(HttpServletRequest request, HttpServletResponse response) { 41 | String user = "user"; 42 | String content = request.getParameter("content"); 43 | final List messages = userContentDao.selectMessageForUser(user, size, DateUtils.getBeforeByHourTime(sessionTime)); 44 | final ChatMessage systemMessage = new ChatMessage(ChatMessageRole.USER.value(), content); 45 | //消息入库 46 | UserContent userContent = new UserContent(); 47 | userContent.setContent(content); 48 | userContent.setRole(ChatMessageRole.USER.value()); 49 | userContent.setUser(user); 50 | userContent.setCreateTime(new Date()); 51 | userContentDao.insertSelective(userContent); 52 | messages.add(systemMessage); 53 | 54 | ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() 55 | .model("gpt-3.5-turbo-0301") 56 | .messages(messages) 57 | .user("testing") 58 | .max_tokens(tokenNum) 59 | .temperature(1.0) 60 | .build(); 61 | 62 | ExecuteRet executeRet = openAiApi.post(PathConstant.COMPLETIONS.CREATE_CHAT_COMPLETION, JSONObject.toJSONString(chatCompletionRequest), 63 | null); 64 | JSONObject result = JSONObject.parseObject(executeRet.getRespStr()); 65 | List choices = result.getJSONArray("choices").toJavaList(ChatCompletionChoice.class); 66 | System.out.println(choices.get(0).getMessage().getContent()); 67 | ChatMessage context = new ChatMessage(choices.get(0).getMessage().getRole(), choices.get(0).getMessage().getContent()); 68 | //消息入库 69 | UserContent aiContent = new UserContent(); 70 | aiContent.setContent(context.getContent()); 71 | aiContent.setRole(context.getRole()); 72 | aiContent.setUser(user); 73 | aiContent.setCreateTime(new Date()); 74 | userContentDao.insertSelective(aiContent); 75 | return JSONObject.parseObject(JSONObject.toJSONString(context)); 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | 7 | public class DateUtils { 8 | 9 | private final static long minute = 60 * 1000;// 1分钟 10 | private final static long hour = 60 * minute;// 1小时 11 | private final static long day = 24 * hour;// 1天 12 | private final static long month = 31 * day;// 月 13 | private final static long year = 12 * month;// 年 14 | 15 | public final static String YYYYMMDDHHMMSS = "yyyyMMddHHmmssSSS"; 16 | 17 | /** 18 | * @return 19 | * @author neo 20 | * @date 2015-5-21 21 | */ 22 | public static String getDateSequence() { 23 | return new SimpleDateFormat(YYYYMMDDHHMMSS).format(new Date()); 24 | } 25 | 26 | 27 | /** 28 | * @return 29 | * @author neo 30 | * @date 2016年8月10日 31 | */ 32 | public static long getCurrentTime() { 33 | return System.currentTimeMillis(); 34 | } 35 | 36 | 37 | public static String getTimeFormatText(Long date) { 38 | if (date == null) { 39 | return null; 40 | } 41 | long diff = new Date().getTime() - date; 42 | long r = 0; 43 | if (diff > year) { 44 | r = (diff / year); 45 | return r + "年前"; 46 | } 47 | if (diff > month) { 48 | r = (diff / month); 49 | return r + "个月前"; 50 | } 51 | if (diff > day) { 52 | r = (diff / day); 53 | return r + "天前"; 54 | } 55 | if (diff > hour) { 56 | r = (diff / hour); 57 | return r + "个小时前"; 58 | } 59 | if (diff > minute) { 60 | r = (diff / minute); 61 | return r + "分钟前"; 62 | } 63 | return "刚刚"; 64 | } 65 | 66 | /** 67 | * 将时间戳转换成当天0点 68 | * 69 | * @param timestamp 70 | * @return 71 | */ 72 | public static long getDayBegin(long timestamp) { 73 | String format = "yyyy-MM-DD"; 74 | String toDayString = new SimpleDateFormat(format).format(new Date(timestamp)); 75 | Date toDay = null; 76 | try { 77 | toDay = org.apache.commons.lang3.time.DateUtils.parseDate(toDayString, new String[]{format}); 78 | 79 | } catch (Exception e) { 80 | throw new RuntimeException(e); 81 | } 82 | return toDay.getTime(); 83 | } 84 | 85 | /** 86 | * 获取一个月之前的时间戳 87 | * 88 | * @return 89 | */ 90 | public static long getLastMonthTime() { 91 | return getDayBegin(getCurrentTime()) - 86400000l * 30; 92 | } 93 | 94 | public static Date getBeforeByHourTime(int hour) { 95 | Calendar calendar = Calendar.getInstance(); 96 | calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY) - hour); 97 | return calendar.getTime(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/chatweb/web/ChatController.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.web; 2 | 3 | import com.chatweb.comm.aop.LoggerManage; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | 9 | @Controller 10 | @RequestMapping("/") 11 | public class ChatController { 12 | 13 | @RequestMapping(value = "/chat", method = RequestMethod.GET) 14 | @LoggerManage(description = "首页") 15 | public String index(Model model) { 16 | return "chat"; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/web/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.web; 2 | 3 | import com.chatweb.comm.aop.LoggerManage; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | 9 | @Controller 10 | @RequestMapping("/") 11 | public class IndexController { 12 | 13 | @RequestMapping(value = "/", method = RequestMethod.GET) 14 | @LoggerManage(description = "首页") 15 | public String index(Model model) { 16 | return "chat"; 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/chatweb/web/api/RestChatController.java: -------------------------------------------------------------------------------- 1 | package com.chatweb.web.api; 2 | 3 | 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.chatweb.service.ChatService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | /** 14 | *

15 | * 16 | * @author wzq 2023/3/7 13:55 17 | * @version V1.0 18 | * @modificationHistory=========================逻辑或功能性重大变更记录 19 | * @modify by user: {修改人} 2023/3/7 20 | * @modify by reason:{方法名}:{原因} 21 | */ 22 | @RestController 23 | public class RestChatController { 24 | 25 | @Autowired 26 | private ChatService chatService; 27 | 28 | @RequestMapping("/createChatCompletion") 29 | public JSONObject createChatCompletion(HttpServletRequest request, HttpServletResponse response) { 30 | return chatService.createChatCompletion(request, response); 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #spring.profiles.active=dev 2 | server.port=8999 3 | spring.application.name=OpenAi 4 | ###### db config start ###### 5 | spring.datasource.url=jdbc:mysql://XXXX:3306/chatweb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 6 | spring.datasource.username=XXXX 7 | spring.datasource.password=XXX 8 | #spring.datasource.password=Robotbird@123 9 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 10 | #mybatis 11 | mybatis.mapper-locations=classpath:mapper/*.xml 12 | mybatis.type-aliases-package=com.wentap.mapper 13 | mybatis.configuration.map-underscore-to-camel-case=true 14 | mybatis.configuration.call-setters-on-nulls=true 15 | ###### logging config start ###### 16 | logging.file=./log/service.log 17 | logging.level.com.chatweb=INFO 18 | logging.level.org.springframework.web=INFO 19 | ##### mail setting end ##### 20 | spring.thymeleaf.cache=false 21 | ## openAi访问地址,直接访问不通,需要搭建nginx代理进行访问 22 | open.ai.url=https://api.openai.com/ 23 | ## API-Key 24 | open.ai.token=XXX 25 | ## 连续会话支持长度 26 | session.num=10 27 | ## 连续会话有效时间(单位:小时) 28 | session.time=4 29 | ## token长度,结果返回的长度 30 | token.num=500 31 | spring.thymeleaf.mode=LEGACYHTML5 -------------------------------------------------------------------------------- /src/main/resources/mapper/UserContentDao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | id, `user`, `role`, content, create_time 13 | 14 | 20 | 27 | 38 | 39 | delete from user_content 40 | where id = #{id,jdbcType=BIGINT} 41 | 42 | 44 | insert into user_content (`user`, `role`, content, 45 | create_time) 46 | values (#{user,jdbcType=VARCHAR}, #{role,jdbcType=VARCHAR}, #{content,jdbcType=VARCHAR}, 47 | #{createTime,jdbcType=TIMESTAMP}) 48 | 49 | 51 | insert into user_content 52 | 53 | 54 | `user`, 55 | 56 | 57 | `role`, 58 | 59 | 60 | content, 61 | 62 | 63 | create_time, 64 | 65 | 66 | 67 | 68 | #{user,jdbcType=VARCHAR}, 69 | 70 | 71 | #{role,jdbcType=VARCHAR}, 72 | 73 | 74 | #{content,jdbcType=VARCHAR}, 75 | 76 | 77 | #{createTime,jdbcType=TIMESTAMP}, 78 | 79 | 80 | 81 | 82 | update user_content 83 | 84 | 85 | `user` = #{user,jdbcType=VARCHAR}, 86 | 87 | 88 | `role` = #{role,jdbcType=VARCHAR}, 89 | 90 | 91 | content = #{content,jdbcType=VARCHAR}, 92 | 93 | 94 | create_time = #{createTime,jdbcType=TIMESTAMP}, 95 | 96 | 97 | where id = #{id,jdbcType=BIGINT} 98 | 99 | 100 | update user_content 101 | set `user` = #{user,jdbcType=VARCHAR}, 102 | `role` = #{role,jdbcType=VARCHAR}, 103 | content = #{content,jdbcType=VARCHAR}, 104 | create_time = #{createTime,jdbcType=TIMESTAMP} 105 | where id = #{id,jdbcType=BIGINT} 106 | 107 | -------------------------------------------------------------------------------- /src/main/resources/static/css/litewebchat.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame) 3 | * MorFans Lab(c) 2017-2023 4 | * Licensed under LGPL 5 | */@charset "UTF-8"; 6 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 7 | /* Document 8 | ========================================================================== */ 9 | /** 10 | * 1. Correct the line height in all browsers. 11 | * 2. Prevent adjustments of font size after orientation changes in iOS. 12 | */ 13 | html { 14 | line-height: 1.15; /* 1 */ 15 | -webkit-text-size-adjust: 100%; /* 2 */ 16 | } 17 | 18 | /* Sections 19 | ========================================================================== */ 20 | /** 21 | * Remove the margin in all browsers. 22 | */ 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | main { 31 | display: block; 32 | } 33 | 34 | /** 35 | * Correct the font size and margin on `h1` elements within `section` and 36 | * `article` contexts in Chrome, Firefox, and Safari. 37 | */ 38 | h1 { 39 | font-size: 2em; 40 | margin: 0.67em 0; 41 | } 42 | 43 | /* Grouping content 44 | ========================================================================== */ 45 | /** 46 | * 1. Add the correct box sizing in Firefox. 47 | * 2. Show the overflow in Edge and IE. 48 | */ 49 | hr { 50 | -webkit-box-sizing: content-box; 51 | box-sizing: content-box; /* 1 */ 52 | height: 0; /* 1 */ 53 | overflow: visible; /* 2 */ 54 | } 55 | 56 | /** 57 | * 1. Correct the inheritance and scaling of font size in all browsers. 58 | * 2. Correct the odd `em` font sizing in all browsers. 59 | */ 60 | pre { 61 | font-family: monospace, monospace; /* 1 */ 62 | font-size: 1em; /* 2 */ 63 | } 64 | 65 | /* Text-level semantics 66 | ========================================================================== */ 67 | /** 68 | * Remove the gray background on active links in IE 10. 69 | */ 70 | a { 71 | background-color: transparent; 72 | } 73 | 74 | /** 75 | * 1. Remove the bottom border in Chrome 57- 76 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 77 | */ 78 | abbr[title] { 79 | border-bottom: none; /* 1 */ 80 | text-decoration: underline; /* 2 */ 81 | -webkit-text-decoration: underline dotted; 82 | text-decoration: underline dotted; /* 2 */ 83 | } 84 | 85 | /** 86 | * Add the correct font weight in Chrome, Edge, and Safari. 87 | */ 88 | b, 89 | strong { 90 | font-weight: bolder; 91 | } 92 | 93 | /** 94 | * 1. Correct the inheritance and scaling of font size in all browsers. 95 | * 2. Correct the odd `em` font sizing in all browsers. 96 | */ 97 | code, 98 | kbd, 99 | samp { 100 | font-family: monospace, monospace; /* 1 */ 101 | font-size: 1em; /* 2 */ 102 | } 103 | 104 | /** 105 | * Add the correct font size in all browsers. 106 | */ 107 | small { 108 | font-size: 80%; 109 | } 110 | 111 | /** 112 | * Prevent `sub` and `sup` elements from affecting the line height in 113 | * all browsers. 114 | */ 115 | sub, 116 | sup { 117 | font-size: 75%; 118 | line-height: 0; 119 | position: relative; 120 | vertical-align: baseline; 121 | } 122 | 123 | sub { 124 | bottom: -0.25em; 125 | } 126 | 127 | sup { 128 | top: -0.5em; 129 | } 130 | 131 | /* Embedded content 132 | ========================================================================== */ 133 | /** 134 | * Remove the border on images inside links in IE 10. 135 | */ 136 | img { 137 | border-style: none; 138 | } 139 | 140 | /* Forms 141 | ========================================================================== */ 142 | /** 143 | * 1. Change the font styles in all browsers. 144 | * 2. Remove the margin in Firefox and Safari. 145 | */ 146 | button, 147 | input, 148 | optgroup, 149 | select, 150 | textarea { 151 | font-family: inherit; /* 1 */ 152 | font-size: 100%; /* 1 */ 153 | line-height: 1.15; /* 1 */ 154 | margin: 0; /* 2 */ 155 | } 156 | 157 | /** 158 | * Show the overflow in IE. 159 | * 1. Show the overflow in Edge. 160 | */ 161 | button, 162 | input { /* 1 */ 163 | overflow: visible; 164 | } 165 | 166 | /** 167 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 168 | * 1. Remove the inheritance of text transform in Firefox. 169 | */ 170 | button, 171 | select { /* 1 */ 172 | text-transform: none; 173 | } 174 | 175 | /** 176 | * Correct the inability to style clickable types in iOS and Safari. 177 | */ 178 | button, 179 | [type=button], 180 | [type=reset], 181 | [type=submit] { 182 | -webkit-appearance: button; 183 | } 184 | 185 | /** 186 | * Remove the inner border and padding in Firefox. 187 | */ 188 | button::-moz-focus-inner, 189 | [type=button]::-moz-focus-inner, 190 | [type=reset]::-moz-focus-inner, 191 | [type=submit]::-moz-focus-inner { 192 | border-style: none; 193 | padding: 0; 194 | } 195 | 196 | /** 197 | * Restore the focus styles unset by the previous rule. 198 | */ 199 | button:-moz-focusring, 200 | [type=button]:-moz-focusring, 201 | [type=reset]:-moz-focusring, 202 | [type=submit]:-moz-focusring { 203 | outline: 1px dotted ButtonText; 204 | } 205 | 206 | /** 207 | * Correct the padding in Firefox. 208 | */ 209 | fieldset { 210 | padding: 0.35em 0.75em 0.625em; 211 | } 212 | 213 | /** 214 | * 1. Correct the text wrapping in Edge and IE. 215 | * 2. Correct the color inheritance from `fieldset` elements in IE. 216 | * 3. Remove the padding so developers are not caught out when they zero out 217 | * `fieldset` elements in all browsers. 218 | */ 219 | legend { 220 | -webkit-box-sizing: border-box; 221 | box-sizing: border-box; /* 1 */ 222 | color: inherit; /* 2 */ 223 | display: table; /* 1 */ 224 | max-width: 100%; /* 1 */ 225 | padding: 0; /* 3 */ 226 | white-space: normal; /* 1 */ 227 | } 228 | 229 | /** 230 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 231 | */ 232 | progress { 233 | vertical-align: baseline; 234 | } 235 | 236 | /** 237 | * Remove the default vertical scrollbar in IE 10+. 238 | */ 239 | textarea { 240 | overflow: auto; 241 | } 242 | 243 | /** 244 | * 1. Add the correct box sizing in IE 10. 245 | * 2. Remove the padding in IE 10. 246 | */ 247 | [type=checkbox], 248 | [type=radio] { 249 | -webkit-box-sizing: border-box; 250 | box-sizing: border-box; /* 1 */ 251 | padding: 0; /* 2 */ 252 | } 253 | 254 | /** 255 | * Correct the cursor style of increment and decrement buttons in Chrome. 256 | */ 257 | [type=number]::-webkit-inner-spin-button, 258 | [type=number]::-webkit-outer-spin-button { 259 | height: auto; 260 | } 261 | 262 | /** 263 | * 1. Correct the odd appearance in Chrome and Safari. 264 | * 2. Correct the outline style in Safari. 265 | */ 266 | [type=search] { 267 | -webkit-appearance: textfield; /* 1 */ 268 | outline-offset: -2px; /* 2 */ 269 | } 270 | 271 | /** 272 | * Remove the inner padding in Chrome and Safari on macOS. 273 | */ 274 | [type=search]::-webkit-search-decoration { 275 | -webkit-appearance: none; 276 | } 277 | 278 | /** 279 | * 1. Correct the inability to style clickable types in iOS and Safari. 280 | * 2. Change font properties to `inherit` in Safari. 281 | */ 282 | ::-webkit-file-upload-button { 283 | -webkit-appearance: button; /* 1 */ 284 | font: inherit; /* 2 */ 285 | } 286 | 287 | /* Interactive 288 | ========================================================================== */ 289 | /* 290 | * Add the correct display in Edge, IE 10+, and Firefox. 291 | */ 292 | details { 293 | display: block; 294 | } 295 | 296 | /* 297 | * Add the correct display in all browsers. 298 | */ 299 | summary { 300 | display: list-item; 301 | } 302 | 303 | /* Misc 304 | ========================================================================== */ 305 | /** 306 | * Add the correct display in IE 10+. 307 | */ 308 | template { 309 | display: none; 310 | } 311 | 312 | /** 313 | * Add the correct display in IE 10. 314 | */ 315 | [hidden] { 316 | display: none; 317 | } 318 | 319 | * { 320 | scrollbar-color: #5c6163 rgba(56, 59, 60, 0.031372549); 321 | } 322 | 323 | /* else broswer */ 324 | ::-webkit-scrollbar { 325 | /* 滚动条整体样式 */ 326 | width: 7px; 327 | /* 高宽分别对应横竖滚动条的尺寸 */ 328 | height: 1px; 329 | } 330 | 331 | ::-webkit-scrollbar-thumb { 332 | /*滚动条里面小方块*/ 333 | border-radius: 10px; 334 | background-color: rgba(144, 147, 153, 0.5); 335 | border: 0; 336 | } 337 | [litewebchat-theme=dark] ::-webkit-scrollbar-thumb { 338 | background-color: rgba(84, 91, 95, 0.5); 339 | } 340 | 341 | ::-webkit-scrollbar-track { 342 | /*滚动条里面轨道*/ 343 | background: #fff; 344 | min-height: 50%; 345 | min-height: 20px; 346 | } 347 | [litewebchat-theme=dark] ::-webkit-scrollbar-track { 348 | background: rgb(24, 26, 27); 349 | } 350 | 351 | ::-webkit-scrollbar-corner { 352 | background-color: transparent; 353 | } 354 | 355 | ::-moz-selection { 356 | background-color: #1963bd !important; 357 | color: #f8f6f3 !important; 358 | } 359 | 360 | ::selection { 361 | background-color: #1963bd !important; 362 | color: #f8f6f3 !important; 363 | } 364 | 365 | body { 366 | font-family: Helvetica, "PingFang SC", "Microsoft YaHei", sans-serif; 367 | } 368 | 369 | .lite-chatbox { 370 | scroll-behavior: smooth; 371 | padding: 0px; 372 | width: 100%; 373 | position: relative; 374 | font-size: 18px; 375 | background-color: #f8f9fa; 376 | overflow-y: auto; 377 | overflow-x: hidden; 378 | } 379 | .lite-chatbox .tips { 380 | margin: 12px; 381 | text-align: center; 382 | font-size: 12px; 383 | } 384 | .lite-chatbox .tips span { 385 | display: inline-block; 386 | padding: 4px; 387 | background-color: #ccc; 388 | color: #fff; 389 | border-radius: 6px; 390 | } 391 | [litewebchat-theme=dark] .lite-chatbox .tips span { 392 | background-color: rgba(0, 0, 0, 0.3); 393 | } 394 | [litewebchat-theme=dark] .lite-chatbox .tips span { 395 | color: #bec5cc; 396 | } 397 | .lite-chatbox .tips .tips-primary { 398 | background-color: #3986c8; 399 | } 400 | [litewebchat-theme=dark] .lite-chatbox .tips .tips-primary { 401 | background-color: rgb(68, 127, 178); 402 | } 403 | .lite-chatbox .tips .tips-success { 404 | background-color: #49b649; 405 | } 406 | [litewebchat-theme=dark] .lite-chatbox .tips .tips-success { 407 | background-color: rgb(102, 166, 81); 408 | } 409 | .lite-chatbox .tips .tips-info { 410 | background-color: #5bb6d1; 411 | } 412 | [litewebchat-theme=dark] .lite-chatbox .tips .tips-info { 413 | background-color: rgb(63, 136, 158); 414 | } 415 | .lite-chatbox .tips .tips-warning { 416 | background-color: #eea948; 417 | } 418 | [litewebchat-theme=dark] .lite-chatbox .tips .tips-warning { 419 | background-color: rgb(175, 119, 40); 420 | } 421 | .lite-chatbox .tips .tips-danger { 422 | background-color: #e24d48; 423 | } 424 | [litewebchat-theme=dark] .lite-chatbox .tips .tips-danger { 425 | background-color: rgb(173, 53, 49); 426 | } 427 | [litewebchat-theme=dark] .lite-chatbox { 428 | background-color: #131415; 429 | } 430 | .lite-chatbox .cmsg { 431 | position: relative; 432 | margin: 4px 7px; 433 | min-height: 50px; 434 | border: 0; 435 | } 436 | .lite-chatbox .cright { 437 | text-align: right; 438 | margin-left: 64px; 439 | } 440 | .lite-chatbox .cright img.headIcon { 441 | right: 0; 442 | } 443 | .lite-chatbox .cright .name { 444 | margin: 0 48px 2px 0; 445 | } 446 | .lite-chatbox .cright .content { 447 | margin: 0 48px 0 0; 448 | border-radius: 20px 0 20px 20px; 449 | color: white; 450 | background: -o-linear-gradient(70deg, rgba(63, 143, 225, 0.8) 0%, #44d7c9 100%); 451 | background: linear-gradient(20deg, rgba(63, 143, 225, 0.8) 0%, #44d7c9 100%); 452 | -webkit-box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.15); 453 | box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.15); 454 | } 455 | [litewebchat-theme=dark] .lite-chatbox .cright .content { 456 | background: -o-linear-gradient(70deg, rgba(25, 91, 159, 0.8) 0px, rgb(33, 154, 146) 100%); 457 | background: linear-gradient(20deg, rgba(25, 91, 159, 0.8) 0px, rgb(33, 154, 146) 100%); 458 | } 459 | .lite-chatbox .cright .content::after { 460 | left: -12px; 461 | top: 8px; 462 | } 463 | .lite-chatbox .cleft { 464 | text-align: left; 465 | margin-right: 64px; 466 | } 467 | .lite-chatbox .cleft img.headIcon { 468 | left: 0; 469 | } 470 | .lite-chatbox .cleft .name { 471 | margin: 0 0 2px 48px; 472 | } 473 | .lite-chatbox .cleft .content { 474 | margin: 0 0 0 48px; 475 | border-radius: 0 20px 20px 20px; 476 | background: #fff; 477 | color: #373737; 478 | border: 1px solid rgba(0, 0, 0, 0.05); 479 | -webkit-box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.1); 480 | box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.1); 481 | } 482 | [litewebchat-theme=dark] .lite-chatbox .cleft .content { 483 | background: #22242a; 484 | } 485 | [litewebchat-theme=dark] .lite-chatbox .cleft .content { 486 | color: #d4d4d4; 487 | } 488 | .lite-chatbox .cleft .content::after { 489 | left: -12px; 490 | top: 8px; 491 | } 492 | .lite-chatbox img.headIcon { 493 | width: 34px; 494 | height: 34px; 495 | top: 9px; 496 | position: absolute; 497 | } 498 | .lite-chatbox img.radius { 499 | border-radius: 50%; 500 | } 501 | .lite-chatbox .name { 502 | color: #8b8b8b; 503 | font-size: 12px; 504 | display: block; 505 | line-height: 18px; 506 | } 507 | .lite-chatbox .name > span { 508 | vertical-align: middle; 509 | } 510 | .lite-chatbox .name .htitle { 511 | display: inline-block; 512 | padding: 0 3px 0 3px; 513 | background-color: #cccccc; 514 | color: #ffffff; 515 | border-radius: 4px; 516 | margin-right: 4px; 517 | font-size: 11px; 518 | overflow: hidden; 519 | -o-text-overflow: ellipsis; 520 | text-overflow: ellipsis; 521 | white-space: nowrap; 522 | vertical-align: middle; 523 | max-width: 50px; 524 | } 525 | [litewebchat-theme=dark] .lite-chatbox .name .htitle { 526 | background-color: rgb(76, 80, 82); 527 | } 528 | .lite-chatbox .name .htitle.admin { 529 | background-color: #72D6A0; 530 | } 531 | [litewebchat-theme=dark] .lite-chatbox .name .htitle.admin { 532 | background-color: rgb(60, 145, 110); 533 | } 534 | .lite-chatbox .name .htitle.owner { 535 | background-color: #F2BF25; 536 | } 537 | [litewebchat-theme=dark] .lite-chatbox .name .htitle.owner { 538 | background-color: rgb(154, 124, 33); 539 | } 540 | .lite-chatbox .content { 541 | word-break: break-all; 542 | word-wrap: break-word; 543 | text-align: left; 544 | position: relative; 545 | display: inline-block; 546 | font-size: 15px; 547 | padding: 10px 15px; 548 | line-height: 20px; 549 | white-space: pre-wrap; 550 | min-width: 9px; 551 | min-height: 18px; 552 | } 553 | .lite-chatbox .content img { 554 | width: 100%; 555 | height: auto; 556 | } 557 | .lite-chatbox .content a { 558 | color: #0072C1; 559 | margin: 0 5px; 560 | cursor: hand; 561 | } 562 | [litewebchat-theme=dark] .lite-chatbox .content a { 563 | color: #00c3ff; 564 | } 565 | /*# sourceMappingURL=map/litewebchat.css.map */ 566 | -------------------------------------------------------------------------------- /src/main/resources/static/css/litewebchat.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";/*! 2 | * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame) 3 | * MorFans Lab(c) 2017-2023 4 | * Licensed under LGPL 5 | *//*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}*{scrollbar-color:#5c6163 rgba(56,59,60,.031372549)}::-webkit-scrollbar{width:7px;height:1px}::-webkit-scrollbar-thumb{border-radius:10px;background-color:rgba(144,147,153,.5);border:0}[litewebchat-theme=dark] ::-webkit-scrollbar-thumb{background-color:rgba(84,91,95,.5)}::-webkit-scrollbar-track{background:#fff;min-height:50%;min-height:20px}[litewebchat-theme=dark] ::-webkit-scrollbar-track{background:#181a1b}::-webkit-scrollbar-corner{background-color:transparent}::-moz-selection{background-color:#1963bd!important;color:#f8f6f3!important}::selection{background-color:#1963bd!important;color:#f8f6f3!important}body{font-family:Helvetica,"PingFang SC","Microsoft YaHei",sans-serif}.lite-chatbox{scroll-behavior:smooth;padding:0;width:100%;position:relative;font-size:18px;background-color:#f8f9fa;overflow-y:auto;overflow-x:hidden}.lite-chatbox .tips{margin:12px;text-align:center;font-size:12px}.lite-chatbox .tips span{display:inline-block;padding:4px;background-color:#ccc;color:#fff;border-radius:6px}[litewebchat-theme=dark] .lite-chatbox .tips span{background-color:rgba(0,0,0,.3)}[litewebchat-theme=dark] .lite-chatbox .tips span{color:#bec5cc}.lite-chatbox .tips .tips-primary{background-color:#3986c8}[litewebchat-theme=dark] .lite-chatbox .tips .tips-primary{background-color:#447fb2}.lite-chatbox .tips .tips-success{background-color:#49b649}[litewebchat-theme=dark] .lite-chatbox .tips .tips-success{background-color:#66a651}.lite-chatbox .tips .tips-info{background-color:#5bb6d1}[litewebchat-theme=dark] .lite-chatbox .tips .tips-info{background-color:#3f889e}.lite-chatbox .tips .tips-warning{background-color:#eea948}[litewebchat-theme=dark] .lite-chatbox .tips .tips-warning{background-color:#af7728}.lite-chatbox .tips .tips-danger{background-color:#e24d48}[litewebchat-theme=dark] .lite-chatbox .tips .tips-danger{background-color:#ad3531}[litewebchat-theme=dark] .lite-chatbox{background-color:#131415}.lite-chatbox .cmsg{position:relative;margin:4px 7px;min-height:50px;border:0}.lite-chatbox .cright{text-align:right;margin-left:64px}.lite-chatbox .cright img.headIcon{right:0}.lite-chatbox .cright .name{margin:0 48px 2px 0}.lite-chatbox .cright .content{margin:0 48px 0 0;border-radius:20px 0 20px 20px;color:#fff;background:-o-linear-gradient(70deg,rgba(63,143,225,.8) 0,#44d7c9 100%);background:linear-gradient(20deg,rgba(63,143,225,.8) 0,#44d7c9 100%);-webkit-box-shadow:5px 5px 15px 0 rgba(102,102,102,.15);box-shadow:5px 5px 15px 0 rgba(102,102,102,.15)}[litewebchat-theme=dark] .lite-chatbox .cright .content{background:-o-linear-gradient(70deg,rgba(25,91,159,.8) 0,#219a92 100%);background:linear-gradient(20deg,rgba(25,91,159,.8) 0,#219a92 100%)}.lite-chatbox .cright .content::after{left:-12px;top:8px}.lite-chatbox .cleft{text-align:left;margin-right:64px}.lite-chatbox .cleft img.headIcon{left:0}.lite-chatbox .cleft .name{margin:0 0 2px 48px}.lite-chatbox .cleft .content{margin:0 0 0 48px;border-radius:0 20px 20px 20px;background:#fff;color:#373737;border:1px solid rgba(0,0,0,.05);-webkit-box-shadow:5px 5px 15px 0 rgba(102,102,102,.1);box-shadow:5px 5px 15px 0 rgba(102,102,102,.1)}[litewebchat-theme=dark] .lite-chatbox .cleft .content{background:#22242a}[litewebchat-theme=dark] .lite-chatbox .cleft .content{color:#d4d4d4}.lite-chatbox .cleft .content::after{left:-12px;top:8px}.lite-chatbox img.headIcon{width:34px;height:34px;top:9px;position:absolute}.lite-chatbox img.radius{border-radius:50%}.lite-chatbox .name{color:#8b8b8b;font-size:12px;display:block;line-height:18px}.lite-chatbox .name>span{vertical-align:middle}.lite-chatbox .name .htitle{display:inline-block;padding:0 3px 0 3px;background-color:#ccc;color:#fff;border-radius:4px;margin-right:4px;font-size:11px;overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;max-width:50px}[litewebchat-theme=dark] .lite-chatbox .name .htitle{background-color:#4c5052}.lite-chatbox .name .htitle.admin{background-color:#72d6a0}[litewebchat-theme=dark] .lite-chatbox .name .htitle.admin{background-color:#3c916e}.lite-chatbox .name .htitle.owner{background-color:#f2bf25}[litewebchat-theme=dark] .lite-chatbox .name .htitle.owner{background-color:#9a7c21}.lite-chatbox .content{word-break:break-all;word-wrap:break-word;text-align:left;position:relative;display:inline-block;font-size:15px;padding:10px 15px;line-height:20px;white-space:pre-wrap;min-width:9px;min-height:18px}.lite-chatbox .content img{width:100%;height:auto}.lite-chatbox .content a{color:#0072c1;margin:0 5px;cursor:hand}[litewebchat-theme=dark] .lite-chatbox .content a{color:#00c3ff} 6 | /*# sourceMappingURL=map/litewebchat.min.css.map */ 7 | -------------------------------------------------------------------------------- /src/main/resources/static/css/litewebchat_input.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame) 3 | * MorFans Lab(c) 2017-2023 4 | * Licensed under LGPL 5 | */@charset "UTF-8"; 6 | .lite-chatbox { 7 | height: calc(100% - 150px); 8 | } 9 | .lite-chatbox > div:last-child { 10 | margin-bottom: 20px; 11 | } 12 | 13 | .lite-chatinput { 14 | width: 100%; 15 | height: 150px; 16 | position: relative; 17 | bottom: 0px; 18 | background-color: #fff; 19 | } 20 | [litewebchat-theme=dark] .lite-chatinput { 21 | background-color: #202223; 22 | } 23 | .lite-chatinput img { 24 | max-width: 150px; 25 | max-height: 150px; 26 | -o-object-fit: contain; 27 | object-fit: contain; 28 | } 29 | .lite-chatinput .boundary { 30 | cursor: s-resize; 31 | margin: 0 auto; 32 | border-width: 1px 0px 0px 0px; 33 | border-color: rgba(0, 0, 0, 0.2); 34 | height: 5px; 35 | background: #fff; 36 | } 37 | [litewebchat-theme=dark] .lite-chatinput .boundary { 38 | background: #202223; 39 | } 40 | .lite-chatinput > .chatinput { 41 | position: relative; 42 | overflow-y: scroll; 43 | /* margin: 0px 3px 0px 3px; */ 44 | width: calc(100% - 6px); 45 | margin: auto; 46 | /* width: 100%; */ 47 | height: calc(100% - 75px); 48 | /* height: 100%; */ 49 | border: none; 50 | outline: none; 51 | resize: none; 52 | font-size: 18px; 53 | color: #373737; 54 | word-break: break-all; 55 | overflow-wrap: break-word; 56 | padding: 5px; 57 | outline: none; 58 | } 59 | [litewebchat-theme=dark] .lite-chatinput > .chatinput { 60 | color: #d4d4d4; 61 | } 62 | .lite-chatinput .send { 63 | float: right; 64 | padding: 4px 20px 4px 20px; 65 | margin-right: 12px; 66 | margin-top: -2px; 67 | color: white; 68 | background: -o-linear-gradient(70deg, rgba(63, 143, 225, 0.8) 0%, #44d7c9 100%); 69 | background: linear-gradient(20deg, rgba(63, 143, 225, 0.8) 0%, #44d7c9 100%); 70 | -webkit-box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.1); 71 | box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.1); 72 | border: none; 73 | border-radius: 4px; 74 | -webkit-transition: all 0.2s; 75 | -o-transition: all 0.2s; 76 | transition: all 0.2s; 77 | } 78 | [litewebchat-theme=dark] .lite-chatinput .send { 79 | background: -o-linear-gradient(70deg, rgba(25, 91, 159, 0.8) 0px, rgb(33, 154, 146) 100%); 80 | background: linear-gradient(20deg, rgba(25, 91, 159, 0.8) 0px, rgb(33, 154, 146) 100%); 81 | } 82 | .lite-chatinput .send:hover { 83 | -webkit-transform: scale(1.1); 84 | -ms-transform: scale(1.1); 85 | transform: scale(1.1); 86 | opacity: 0.7; 87 | } 88 | .lite-chatinput .send:active { 89 | -webkit-transform: scale(0.9); 90 | -ms-transform: scale(0.9); 91 | transform: scale(0.9); 92 | opacity: 1; 93 | } 94 | .lite-chatinput .tool-button { 95 | padding: 0px 3px 0px 3px; 96 | background: none; 97 | border: none; 98 | margin: 5px; 99 | margin-bottom: 0px; 100 | -webkit-transition: all 0.2s; 101 | -o-transition: all 0.2s; 102 | transition: all 0.2s; 103 | } 104 | .lite-chatinput .tool-button:hover { 105 | -webkit-transform: scale(1.1); 106 | -ms-transform: scale(1.1); 107 | transform: scale(1.1); 108 | opacity: 0.7; 109 | } 110 | .lite-chatinput .tool-button:active { 111 | -webkit-transform: scale(0.9); 112 | -ms-transform: scale(0.9); 113 | transform: scale(0.9); 114 | opacity: 1; 115 | } 116 | .lite-chatinput .tool-button path { 117 | fill: rgb(139, 135, 153); 118 | } 119 | .lite-chatinput .tool-button svg { 120 | width: 18px; 121 | } 122 | 123 | /* 由功能按钮唤起的功能页面 */ 124 | .lite-chatbox-tool { 125 | /* border: 5px solid red; */ 126 | position: absolute; 127 | /* bottom: 20px; */ 128 | margin-left: 3px; 129 | z-index: 3; 130 | } 131 | 132 | /* 部分功能页面需要用到的遮罩 */ 133 | #toolMusk { 134 | /* border: 3px solid red; */ 135 | position: absolute; 136 | width: 100vw; 137 | height: 100vh; 138 | top: 0; 139 | left: 0; 140 | z-index: 2; 141 | } 142 | 143 | .float-left { 144 | float: left; 145 | } 146 | 147 | .float-right { 148 | float: right; 149 | } 150 | /*# sourceMappingURL=map/litewebchat_input.css.map */ 151 | -------------------------------------------------------------------------------- /src/main/resources/static/css/litewebchat_input.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";/*! 2 | * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame) 3 | * MorFans Lab(c) 2017-2023 4 | * Licensed under LGPL 5 | */.lite-chatbox{height:calc(100% - 150px)}.lite-chatbox>div:last-child{margin-bottom:20px}.lite-chatinput{width:100%;height:150px;position:relative;bottom:0;background-color:#fff}[litewebchat-theme=dark] .lite-chatinput{background-color:#202223}.lite-chatinput img{max-width:150px;max-height:150px;-o-object-fit:contain;object-fit:contain}.lite-chatinput .boundary{cursor:s-resize;margin:0 auto;border-width:1px 0 0 0;border-color:rgba(0,0,0,.2);height:5px;background:#fff}[litewebchat-theme=dark] .lite-chatinput .boundary{background:#202223}.lite-chatinput>.chatinput{position:relative;overflow-y:scroll;width:calc(100% - 6px);margin:auto;height:calc(100% - 75px);border:none;outline:0;resize:none;font-size:18px;color:#373737;word-break:break-all;overflow-wrap:break-word;padding:5px;outline:0}[litewebchat-theme=dark] .lite-chatinput>.chatinput{color:#d4d4d4}.lite-chatinput .send{float:right;padding:4px 20px 4px 20px;margin-right:12px;margin-top:-2px;color:#fff;background:-o-linear-gradient(70deg,rgba(63,143,225,.8) 0,#44d7c9 100%);background:linear-gradient(20deg,rgba(63,143,225,.8) 0,#44d7c9 100%);-webkit-box-shadow:5px 5px 15px 0 rgba(102,102,102,.1);box-shadow:5px 5px 15px 0 rgba(102,102,102,.1);border:none;border-radius:4px;-webkit-transition:all .2s;-o-transition:all .2s;transition:all .2s}[litewebchat-theme=dark] .lite-chatinput .send{background:-o-linear-gradient(70deg,rgba(25,91,159,.8) 0,#219a92 100%);background:linear-gradient(20deg,rgba(25,91,159,.8) 0,#219a92 100%)}.lite-chatinput .send:hover{-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1);opacity:.7}.lite-chatinput .send:active{-webkit-transform:scale(.9);-ms-transform:scale(.9);transform:scale(.9);opacity:1}.lite-chatinput .tool-button{padding:0 3px 0 3px;background:0 0;border:none;margin:5px;margin-bottom:0;-webkit-transition:all .2s;-o-transition:all .2s;transition:all .2s}.lite-chatinput .tool-button:hover{-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1);opacity:.7}.lite-chatinput .tool-button:active{-webkit-transform:scale(.9);-ms-transform:scale(.9);transform:scale(.9);opacity:1}.lite-chatinput .tool-button path{fill:#8b8799}.lite-chatinput .tool-button svg{width:18px}.lite-chatbox-tool{position:absolute;margin-left:3px;z-index:3}#toolMusk{position:absolute;width:100vw;height:100vh;top:0;left:0;z-index:2}.float-left{float:left}.float-right{float:right} 6 | /*# sourceMappingURL=map/litewebchat_input.min.css.map */ 7 | -------------------------------------------------------------------------------- /src/main/resources/static/css/map/litewebchat_input.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["chatinput.css","chatinput.scss","mixin/_theme.scss","mixin/_helper.scss"],"names":[],"mappings":"AAAA;;;;EAIE,CAAC,gBAAgB;ACEnB;EACE,0BAAA;ADAF;ACGE;EACE,mBAAA;ADDJ;;ACMA;EACE,WAAA;EACA,aAAA;EACA,kBAAA;EACA,WAAA;ECZI,sBAAA;AFUN;AEJM;EACE,yBAAA;AFMR;ACGE;EACE,gBAAA;EACA,iBAAA;EACA,sBAAA;KAAA,mBAAA;ADDJ;ACKE;EACE,gBAAA;EACA,cAAA;EACA,6BAAA;EACA,gCAAA;EACA,WAAA;EC5BE,gBAAA;AF0BN;AEpBM;EACE,mBAAA;AFsBR;ACIE;EACE,kBAAA;EACA,kBAAA;EACA,6BAAA;EACA,uBAAA;EACA,YAAA;EACA,iBAAA;EACA,yBAAA;EACA,kBAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,eAAA;EC7CE,cAAA;ED+CF,qBAAA;EACA,yBAAA;EACA,YAAA;EACA,aAAA;ADFJ;AE1CM;EACE,cAAA;AF4CR;ACGE;EACE,YAAA;EACA,0BAAA;EAGE,kBAAA;EACA,gBAAA;EAGF,YAAA;EC/DE,+EAAA;EAAA,4EAAA;EDiEF,2DAAA;UAAA,mDAAA;EACA,YAAA;EACA,kBAAA;EElEF,4BAAA;EAAA,uBAAA;EAAA,oBAAA;AH8DF;AEzDM;EACE,yFAAA;EAAA,sFAAA;AF2DR;AG/DE;EACE,6BAAA;MAAA,yBAAA;UAAA,qBAAA;EACA,YAAA;AHiEJ;AG9DE;EACE,6BAAA;MAAA,yBAAA;UAAA,qBAAA;EACA,UAAA;AHgEJ;ACFE;EACE,wBAAA;EACA,gBAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EE5EF,4BAAA;EAAA,uBAAA;EAAA,oBAAA;AHiFF;AG/EE;EACE,6BAAA;MAAA,yBAAA;UAAA,qBAAA;EACA,YAAA;AHiFJ;AG9EE;EACE,6BAAA;MAAA,yBAAA;UAAA,qBAAA;EACA,UAAA;AHgFJ;ACVI;EACE,wBAAA;ADYN;ACTI;EACE,WAAA;ADWN;;ACJA,iBAAA;AACA;EACE,2BAAA;EACA,kBAAA;EACA,kBAAA;EACA,gBAAA;EACA,UAAA;ADOF;;ACJA,kBAAA;AACA;EACE,2BAAA;EACA,kBAAA;EACA,YAAA;EACA,aAAA;EACA,MAAA;EACA,OAAA;EACA,UAAA;ADOF;;ACJA;EACE,WAAA;ADOF;;ACJA;EACE,YAAA;ADOF","file":"../litewebchat_input.css","sourcesContent":["/*!\n * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame)\n * MorFans Lab(c) 2017-2023\n * Licensed under LGPL\n */@charset \"UTF-8\";\n.lite-chatbox {\n height: calc(100% - 150px);\n}\n.lite-chatbox > div:last-child {\n margin-bottom: 20px;\n}\n\n.lite-chatinput {\n width: 100%;\n height: 150px;\n position: relative;\n bottom: 0px;\n background-color: #fff;\n}\n[litewebchat-theme=dark] .lite-chatinput {\n background-color: #202223;\n}\n.lite-chatinput img {\n max-width: 150px;\n max-height: 150px;\n object-fit: contain;\n}\n.lite-chatinput .boundary {\n cursor: s-resize;\n margin: 0 auto;\n border-width: 1px 0px 0px 0px;\n border-color: rgba(0, 0, 0, 0.2);\n height: 5px;\n background: #fff;\n}\n[litewebchat-theme=dark] .lite-chatinput .boundary {\n background: #202223;\n}\n.lite-chatinput > .chatinput {\n position: relative;\n overflow-y: scroll;\n /* margin: 0px 3px 0px 3px; */\n width: calc(100% - 6px);\n margin: auto;\n /* width: 100%; */\n height: calc(100% - 75px);\n /* height: 100%; */\n border: none;\n outline: none;\n resize: none;\n font-size: 18px;\n color: #373737;\n word-break: break-all;\n overflow-wrap: break-word;\n padding: 5px;\n outline: none;\n}\n[litewebchat-theme=dark] .lite-chatinput > .chatinput {\n color: #d4d4d4;\n}\n.lite-chatinput .send {\n float: right;\n padding: 4px 20px 4px 20px;\n margin-right: 12px;\n margin-top: -2px;\n color: white;\n background: linear-gradient(20deg, rgba(63, 143, 225, 0.8) 0%, #44d7c9 100%);\n box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.1);\n border: none;\n border-radius: 4px;\n transition: all 0.2s;\n}\n[litewebchat-theme=dark] .lite-chatinput .send {\n background: linear-gradient(20deg, rgba(25, 91, 159, 0.8) 0px, rgb(33, 154, 146) 100%);\n}\n.lite-chatinput .send:hover {\n transform: scale(1.1);\n opacity: 0.7;\n}\n.lite-chatinput .send:active {\n transform: scale(0.9);\n opacity: 1;\n}\n.lite-chatinput .tool-button {\n padding: 0px 3px 0px 3px;\n background: none;\n border: none;\n margin: 5px;\n margin-bottom: 0px;\n transition: all 0.2s;\n}\n.lite-chatinput .tool-button:hover {\n transform: scale(1.1);\n opacity: 0.7;\n}\n.lite-chatinput .tool-button:active {\n transform: scale(0.9);\n opacity: 1;\n}\n.lite-chatinput .tool-button path {\n fill: rgb(139, 135, 153);\n}\n.lite-chatinput .tool-button svg {\n width: 18px;\n}\n\n/* 由功能按钮唤起的功能页面 */\n.lite-chatbox-tool {\n /* border: 5px solid red; */\n position: absolute;\n /* bottom: 20px; */\n margin-left: 3px;\n z-index: 3;\n}\n\n/* 部分功能页面需要用到的遮罩 */\n#toolMusk {\n /* border: 3px solid red; */\n position: absolute;\n width: 100vw;\n height: 100vh;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.float-left {\n float: left;\n}\n\n.float-right {\n float: right;\n}","@import './mixin/theme';\n@import './mixin/helper';\n\n@import './variable';\n\n// 聊天区域\n.lite-chatbox {\n height: calc(100% - 150px);\n\n //.lite-chatbox内的最后一条消息的margin-bottom设置为20px,离下面远一点\n >div:last-child {\n margin-bottom: 20px;\n }\n}\n\n// 输入框\n.lite-chatinput {\n width: 100%;\n height: 150px;\n position: relative;\n bottom: 0px;\n @include theme('background-color', $chat-bg-color-input);\n\n // 缩小输入框内的图片,方便编辑\n img {\n max-width: 150px;\n max-height: 150px;\n object-fit: contain;\n }\n\n // 聊天区域和输入框的分界线\n .boundary {\n cursor: s-resize;\n margin: 0 auto;\n border-width: 1px 0px 0px 0px;\n border-color: rgba(0, 0, 0, 0.2);\n height: 5px;\n @include theme('background', $chat-bg-color-input);\n }\n\n // 输入框\n >.chatinput {\n position: relative;\n overflow-y: scroll;\n /* margin: 0px 3px 0px 3px; */\n width: calc(100% - 6px);\n margin: auto;\n /* width: 100%; */\n height: calc(100% - 75px);\n /* height: 100%; */\n border: none;\n outline: none;\n resize: none;\n font-size: 18px;\n @include theme('color', $chat-color-input);\n word-break: break-all;\n overflow-wrap: break-word;\n padding: 5px;\n outline: none;\n }\n\n // 发送按钮\n .send {\n float: right;\n padding: 4px 20px 4px 20px;\n\n margin: {\n right: 12px;\n top: -2px;\n }\n\n color: white;\n @include theme('background', $chat-message-bg-color-me);\n box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.1);\n border: none;\n border-radius: 4px;\n @include push-animate;\n }\n\n // 输入框上方的输入表情等功能按钮\n .tool-button {\n padding: 0px 3px 0px 3px;\n background: none;\n border: none;\n margin: 5px;\n margin-bottom: 0px;\n @include push-animate;\n\n path {\n fill: rgb(139, 135, 153);\n }\n\n svg {\n width: 18px;\n }\n }\n\n // end of.tool-button\n}\n\n/* 由功能按钮唤起的功能页面 */\n.lite-chatbox-tool {\n /* border: 5px solid red; */\n position: absolute;\n /* bottom: 20px; */\n margin-left: 3px;\n z-index: 3;\n}\n\n/* 部分功能页面需要用到的遮罩 */\n#toolMusk {\n /* border: 3px solid red; */\n position: absolute;\n width: 100vw;\n height: 100vh;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.float-left {\n float: left;\n}\n\n.float-right {\n float: right;\n}\n","@mixin theme($key, $map_key) {\n $theme_colors: light,\n dark;\n\n @each $theme in $theme_colors {\n\n @if $theme ==light {\n // default light\n #{$key}: map-get($map: $map_key, $key: $theme);\n }\n\n @else {\n\n // 生成其他\n [litewebchat-theme=#{$theme}] & {\n #{$key}: map-get($map: $map_key, $key: $theme);\n }\n }\n }\n}\n","@use \"sass:selector\";\n\n@mixin unify-parent($child) {\n @at-root #{selector.unify(&, $child)} {\n @content;\n }\n}\n\n@mixin push-animate($time: .2, $scaleHover: 1.1, $scalePush: 0.9) {\n transition: all #{$time}s; // 让按下有呼吸感\n\n &:hover {\n transform: scale($scaleHover);\n opacity: .7;\n }\n\n &:active {\n transform: scale($scalePush);\n opacity: 1;\n }\n}\n"]} -------------------------------------------------------------------------------- /src/main/resources/static/css/map/litewebchat_input.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["chatinput.css","chatinput.scss","mixin/_theme.scss","mixin/_helper.scss"],"names":[],"mappings":"iBAAA;;;;ACMA,cACE,OAAA,mBAGA,6BACE,cAAA,KAKJ,gBACE,MAAA,KACA,OAAA,MACA,SAAA,SACA,OAAA,ECZI,iBAAA,KAMA,yCACE,iBAAA,QDSN,oBACE,UAAA,MACA,WAAA,MACA,cAAA,QAAA,WAAA,QAIF,0BACE,OAAA,SACA,OAAA,EAAA,KACA,aAAA,IAAA,EAAA,EAAA,EACA,aAAA,eACA,OAAA,IC5BE,WAAA,KAMA,mDACE,WAAA,QD0BN,2BACE,SAAA,SACA,WAAA,OAEA,MAAA,iBACA,OAAA,KAEA,OAAA,kBAEA,OAAA,KACA,QAAA,EACA,OAAA,KACA,UAAA,KC7CE,MAAA,QD+CF,WAAA,UACA,cAAA,WACA,QAAA,IACA,QAAA,EC5CE,oDACE,MAAA,QD+CN,sBACE,MAAA,MACA,QAAA,IAAA,KAAA,IAAA,KAGE,aAAA,KACA,WAAA,KAGF,MAAA,KC/DE,WAAA,6DAAA,WAAA,0DDiEF,mBAAA,IAAA,IAAA,KAAA,EAAA,qBAAA,WAAA,IAAA,IAAA,KAAA,EAAA,qBACA,OAAA,KACA,cAAA,IElEF,mBAAA,IAAA,IAAA,cAAA,IAAA,IAAA,WAAA,IAAA,IDKI,+CACE,WAAA,4DAAA,WAAA,yDCJN,4BACE,kBAAA,WAAA,cAAA,WAAA,UAAA,WACA,QAAA,GAGF,6BACE,kBAAA,UAAA,cAAA,UAAA,UAAA,UACA,QAAA,EF8DF,6BACE,QAAA,EAAA,IAAA,EAAA,IACA,WAAA,IACA,OAAA,KACA,OAAA,IACA,cAAA,EE5EF,mBAAA,IAAA,IAAA,cAAA,IAAA,IAAA,WAAA,IAAA,IAEA,mCACE,kBAAA,WAAA,cAAA,WAAA,UAAA,WACA,QAAA,GAGF,oCACE,kBAAA,UAAA,cAAA,UAAA,UAAA,UACA,QAAA,EFsEA,kCACE,KAAA,QAGF,iCACE,MAAA,KAQN,mBAEE,SAAA,SAEA,YAAA,IACA,QAAA,EAIF,UAEE,SAAA,SACA,MAAA,MACA,OAAA,MACA,IAAA,EACA,KAAA,EACA,QAAA,EAGF,YACE,MAAA,KAGF,aACE,MAAA","file":"../litewebchat_input.min.css","sourcesContent":["/*!\n * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame)\n * MorFans Lab(c) 2017-2023\n * Licensed under LGPL\n */@charset \"UTF-8\";\n.lite-chatbox {\n height: calc(100% - 150px);\n}\n.lite-chatbox > div:last-child {\n margin-bottom: 20px;\n}\n\n.lite-chatinput {\n width: 100%;\n height: 150px;\n position: relative;\n bottom: 0px;\n background-color: #fff;\n}\n[litewebchat-theme=dark] .lite-chatinput {\n background-color: #202223;\n}\n.lite-chatinput img {\n max-width: 150px;\n max-height: 150px;\n object-fit: contain;\n}\n.lite-chatinput .boundary {\n cursor: s-resize;\n margin: 0 auto;\n border-width: 1px 0px 0px 0px;\n border-color: rgba(0, 0, 0, 0.2);\n height: 5px;\n background: #fff;\n}\n[litewebchat-theme=dark] .lite-chatinput .boundary {\n background: #202223;\n}\n.lite-chatinput > .chatinput {\n position: relative;\n overflow-y: scroll;\n /* margin: 0px 3px 0px 3px; */\n width: calc(100% - 6px);\n margin: auto;\n /* width: 100%; */\n height: calc(100% - 75px);\n /* height: 100%; */\n border: none;\n outline: none;\n resize: none;\n font-size: 18px;\n color: #373737;\n word-break: break-all;\n overflow-wrap: break-word;\n padding: 5px;\n outline: none;\n}\n[litewebchat-theme=dark] .lite-chatinput > .chatinput {\n color: #d4d4d4;\n}\n.lite-chatinput .send {\n float: right;\n padding: 4px 20px 4px 20px;\n margin-right: 12px;\n margin-top: -2px;\n color: white;\n background: linear-gradient(20deg, rgba(63, 143, 225, 0.8) 0%, #44d7c9 100%);\n box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.1);\n border: none;\n border-radius: 4px;\n transition: all 0.2s;\n}\n[litewebchat-theme=dark] .lite-chatinput .send {\n background: linear-gradient(20deg, rgba(25, 91, 159, 0.8) 0px, rgb(33, 154, 146) 100%);\n}\n.lite-chatinput .send:hover {\n transform: scale(1.1);\n opacity: 0.7;\n}\n.lite-chatinput .send:active {\n transform: scale(0.9);\n opacity: 1;\n}\n.lite-chatinput .tool-button {\n padding: 0px 3px 0px 3px;\n background: none;\n border: none;\n margin: 5px;\n margin-bottom: 0px;\n transition: all 0.2s;\n}\n.lite-chatinput .tool-button:hover {\n transform: scale(1.1);\n opacity: 0.7;\n}\n.lite-chatinput .tool-button:active {\n transform: scale(0.9);\n opacity: 1;\n}\n.lite-chatinput .tool-button path {\n fill: rgb(139, 135, 153);\n}\n.lite-chatinput .tool-button svg {\n width: 18px;\n}\n\n/* 由功能按钮唤起的功能页面 */\n.lite-chatbox-tool {\n /* border: 5px solid red; */\n position: absolute;\n /* bottom: 20px; */\n margin-left: 3px;\n z-index: 3;\n}\n\n/* 部分功能页面需要用到的遮罩 */\n#toolMusk {\n /* border: 3px solid red; */\n position: absolute;\n width: 100vw;\n height: 100vh;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.float-left {\n float: left;\n}\n\n.float-right {\n float: right;\n}","@import './mixin/theme';\n@import './mixin/helper';\n\n@import './variable';\n\n// 聊天区域\n.lite-chatbox {\n height: calc(100% - 150px);\n\n //.lite-chatbox内的最后一条消息的margin-bottom设置为20px,离下面远一点\n >div:last-child {\n margin-bottom: 20px;\n }\n}\n\n// 输入框\n.lite-chatinput {\n width: 100%;\n height: 150px;\n position: relative;\n bottom: 0px;\n @include theme('background-color', $chat-bg-color-input);\n\n // 缩小输入框内的图片,方便编辑\n img {\n max-width: 150px;\n max-height: 150px;\n object-fit: contain;\n }\n\n // 聊天区域和输入框的分界线\n .boundary {\n cursor: s-resize;\n margin: 0 auto;\n border-width: 1px 0px 0px 0px;\n border-color: rgba(0, 0, 0, 0.2);\n height: 5px;\n @include theme('background', $chat-bg-color-input);\n }\n\n // 输入框\n >.chatinput {\n position: relative;\n overflow-y: scroll;\n /* margin: 0px 3px 0px 3px; */\n width: calc(100% - 6px);\n margin: auto;\n /* width: 100%; */\n height: calc(100% - 75px);\n /* height: 100%; */\n border: none;\n outline: none;\n resize: none;\n font-size: 18px;\n @include theme('color', $chat-color-input);\n word-break: break-all;\n overflow-wrap: break-word;\n padding: 5px;\n outline: none;\n }\n\n // 发送按钮\n .send {\n float: right;\n padding: 4px 20px 4px 20px;\n\n margin: {\n right: 12px;\n top: -2px;\n }\n\n color: white;\n @include theme('background', $chat-message-bg-color-me);\n box-shadow: 5px 5px 15px 0 rgba(102, 102, 102, 0.1);\n border: none;\n border-radius: 4px;\n @include push-animate;\n }\n\n // 输入框上方的输入表情等功能按钮\n .tool-button {\n padding: 0px 3px 0px 3px;\n background: none;\n border: none;\n margin: 5px;\n margin-bottom: 0px;\n @include push-animate;\n\n path {\n fill: rgb(139, 135, 153);\n }\n\n svg {\n width: 18px;\n }\n }\n\n // end of.tool-button\n}\n\n/* 由功能按钮唤起的功能页面 */\n.lite-chatbox-tool {\n /* border: 5px solid red; */\n position: absolute;\n /* bottom: 20px; */\n margin-left: 3px;\n z-index: 3;\n}\n\n/* 部分功能页面需要用到的遮罩 */\n#toolMusk {\n /* border: 3px solid red; */\n position: absolute;\n width: 100vw;\n height: 100vh;\n top: 0;\n left: 0;\n z-index: 2;\n}\n\n.float-left {\n float: left;\n}\n\n.float-right {\n float: right;\n}\n","@mixin theme($key, $map_key) {\n $theme_colors: light,\n dark;\n\n @each $theme in $theme_colors {\n\n @if $theme ==light {\n // default light\n #{$key}: map-get($map: $map_key, $key: $theme);\n }\n\n @else {\n\n // 生成其他\n [litewebchat-theme=#{$theme}] & {\n #{$key}: map-get($map: $map_key, $key: $theme);\n }\n }\n }\n}\n","@use \"sass:selector\";\n\n@mixin unify-parent($child) {\n @at-root #{selector.unify(&, $child)} {\n @content;\n }\n}\n\n@mixin push-animate($time: .2, $scaleHover: 1.1, $scalePush: 0.9) {\n transition: all #{$time}s; // 让按下有呼吸感\n\n &:hover {\n transform: scale($scaleHover);\n opacity: .7;\n }\n\n &:active {\n transform: scale($scalePush);\n opacity: 1;\n }\n}\n"]} -------------------------------------------------------------------------------- /src/main/resources/static/img/A.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/379685397/ChatWeb/65142fa199eaaaa6929b69b26fa60e3e61cec860/src/main/resources/static/img/A.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/B.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/379685397/ChatWeb/65142fa199eaaaa6929b69b26fa60e3e61cec860/src/main/resources/static/img/B.jpg -------------------------------------------------------------------------------- /src/main/resources/static/js/jquery.autocomplete.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ajax Autocomplete for jQuery, version 1.4.10 3 | * (c) 2017 Tomas Kirda 4 | * 5 | * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. 6 | * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete 7 | */ 8 | !function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports&&"function"==typeof require?require("jquery"):jQuery)}(function(a){"use strict";function b(c,d){var e=this;e.element=c,e.el=a(c),e.suggestions=[],e.badQueries=[],e.selectedIndex=-1,e.currentValue=e.element.value,e.timeoutId=null,e.cachedResponse={},e.onChangeTimeout=null,e.onChange=null,e.isLocal=!1,e.suggestionsContainer=null,e.noSuggestionsContainer=null,e.options=a.extend(!0,{},b.defaults,d),e.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"},e.hint=null,e.hintValue="",e.selection=null,e.initialize(),e.setOptions(d)}function c(a,b,c){return a.value.toLowerCase().indexOf(c)!==-1}function d(b){return"string"==typeof b?a.parseJSON(b):b}function e(a,b){if(!b)return a.value;var c="("+g.escapeRegExChars(b)+")";return a.value.replace(new RegExp(c,"gi"),"$1").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/<(\/?strong)>/g,"<$1>")}function f(a,b){return'
'+b+"
"}var g=function(){return{escapeRegExChars:function(a){return a.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&")},createNode:function(a){var b=document.createElement("div");return b.className=a,b.style.position="absolute",b.style.display="none",b}}}(),h={ESC:27,TAB:9,RETURN:13,LEFT:37,UP:38,RIGHT:39,DOWN:40},i=a.noop;b.utils=g,a.Autocomplete=b,b.defaults={ajaxSettings:{},autoSelectFirst:!1,appendTo:"body",serviceUrl:null,lookup:null,onSelect:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:e,formatGroup:f,delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:i,onSearchComplete:i,onSearchError:i,preserveInput:!1,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",currentRequest:null,triggerSelectOnValidInput:!0,preventBadQueries:!0,lookupFilter:c,paramName:"query",transformResult:d,showNoSuggestionNotice:!1,noSuggestionNotice:"No results",orientation:"bottom",forceFixPosition:!1},b.prototype={initialize:function(){var c,d=this,e="."+d.classes.suggestion,f=d.classes.selected,g=d.options;d.element.setAttribute("autocomplete","off"),d.noSuggestionsContainer=a('
').html(this.options.noSuggestionNotice).get(0),d.suggestionsContainer=b.utils.createNode(g.containerClass),c=a(d.suggestionsContainer),c.appendTo(g.appendTo||"body"),"auto"!==g.width&&c.css("width",g.width),c.on("mouseover.autocomplete",e,function(){d.activate(a(this).data("index"))}),c.on("mouseout.autocomplete",function(){d.selectedIndex=-1,c.children("."+f).removeClass(f)}),c.on("click.autocomplete",e,function(){d.select(a(this).data("index"))}),c.on("click.autocomplete",function(){clearTimeout(d.blurTimeoutId)}),d.fixPositionCapture=function(){d.visible&&d.fixPosition()},a(window).on("resize.autocomplete",d.fixPositionCapture),d.el.on("keydown.autocomplete",function(a){d.onKeyPress(a)}),d.el.on("keyup.autocomplete",function(a){d.onKeyUp(a)}),d.el.on("blur.autocomplete",function(){d.onBlur()}),d.el.on("focus.autocomplete",function(){d.onFocus()}),d.el.on("change.autocomplete",function(a){d.onKeyUp(a)}),d.el.on("input.autocomplete",function(a){d.onKeyUp(a)})},onFocus:function(){var a=this;a.fixPosition(),a.el.val().length>=a.options.minChars&&a.onValueChange()},onBlur:function(){var b=this,c=b.options,d=b.el.val(),e=b.getQuery(d);b.blurTimeoutId=setTimeout(function(){b.hide(),b.selection&&b.currentValue!==e&&(c.onInvalidateSelection||a.noop).call(b.element)},200)},abortAjax:function(){var a=this;a.currentRequest&&(a.currentRequest.abort(),a.currentRequest=null)},setOptions:function(b){var c=this,d=a.extend({},c.options,b);c.isLocal=Array.isArray(d.lookup),c.isLocal&&(d.lookup=c.verifySuggestionsFormat(d.lookup)),d.orientation=c.validateOrientation(d.orientation,"bottom"),a(c.suggestionsContainer).css({"max-height":d.maxHeight+"px",width:d.width+"px","z-index":d.zIndex}),this.options=d},clearCache:function(){this.cachedResponse={},this.badQueries=[]},clear:function(){this.clearCache(),this.currentValue="",this.suggestions=[]},disable:function(){var a=this;a.disabled=!0,clearTimeout(a.onChangeTimeout),a.abortAjax()},enable:function(){this.disabled=!1},fixPosition:function(){var b=this,c=a(b.suggestionsContainer),d=c.parent().get(0);if(d===document.body||b.options.forceFixPosition){var e=b.options.orientation,f=c.outerHeight(),g=b.el.outerHeight(),h=b.el.offset(),i={top:h.top,left:h.left};if("auto"===e){var j=a(window).height(),k=a(window).scrollTop(),l=-k+h.top-f,m=k+j-(h.top+g+f);e=Math.max(l,m)===l?"top":"bottom"}if("top"===e?i.top+=-f:i.top+=g,d!==document.body){var n,o=c.css("opacity");b.visible||c.css("opacity",0).show(),n=c.offsetParent().offset(),i.top-=n.top,i.top+=d.scrollTop,i.left-=n.left,b.visible||c.css("opacity",o).hide()}"auto"===b.options.width&&(i.width=b.el.outerWidth()+"px"),c.css(i)}},isCursorAtEnd:function(){var a,b=this,c=b.el.val().length,d=b.element.selectionStart;return"number"==typeof d?d===c:!document.selection||(a=document.selection.createRange(),a.moveStart("character",-c),c===a.text.length)},onKeyPress:function(a){var b=this;if(!b.disabled&&!b.visible&&a.which===h.DOWN&&b.currentValue)return void b.suggest();if(!b.disabled&&b.visible){switch(a.which){case h.ESC:b.el.val(b.currentValue),b.hide();break;case h.RIGHT:if(b.hint&&b.options.onHint&&b.isCursorAtEnd()){b.selectHint();break}return;case h.TAB:if(b.hint&&b.options.onHint)return void b.selectHint();if(b.selectedIndex===-1)return void b.hide();if(b.select(b.selectedIndex),b.options.tabDisabled===!1)return;break;case h.RETURN:if(b.selectedIndex===-1)return void b.hide();b.select(b.selectedIndex);break;case h.UP:b.moveUp();break;case h.DOWN:b.moveDown();break;default:return}a.stopImmediatePropagation(),a.preventDefault()}},onKeyUp:function(a){var b=this;if(!b.disabled){switch(a.which){case h.UP:case h.DOWN:return}clearTimeout(b.onChangeTimeout),b.currentValue!==b.el.val()&&(b.findBestHint(),b.options.deferRequestBy>0?b.onChangeTimeout=setTimeout(function(){b.onValueChange()},b.options.deferRequestBy):b.onValueChange())}},onValueChange:function(){if(this.ignoreValueChange)return void(this.ignoreValueChange=!1);var b=this,c=b.options,d=b.el.val(),e=b.getQuery(d);return b.selection&&b.currentValue!==e&&(b.selection=null,(c.onInvalidateSelection||a.noop).call(b.element)),clearTimeout(b.onChangeTimeout),b.currentValue=d,b.selectedIndex=-1,c.triggerSelectOnValidInput&&b.isExactMatch(e)?void b.select(0):void(e.lengthh&&(c.suggestions=c.suggestions.slice(0,h)),c},getSuggestions:function(b){var c,d,e,f,g=this,h=g.options,i=h.serviceUrl;if(h.params[h.paramName]=b,h.onSearchStart.call(g.element,h.params)!==!1){if(d=h.ignoreParams?null:h.params,a.isFunction(h.lookup))return void h.lookup(b,function(a){g.suggestions=a.suggestions,g.suggest(),h.onSearchComplete.call(g.element,b,a.suggestions)});g.isLocal?c=g.getSuggestionsLocal(b):(a.isFunction(i)&&(i=i.call(g.element,b)),e=i+"?"+a.param(d||{}),c=g.cachedResponse[e]),c&&Array.isArray(c.suggestions)?(g.suggestions=c.suggestions,g.suggest(),h.onSearchComplete.call(g.element,b,c.suggestions)):g.isBadQuery(b)?h.onSearchComplete.call(g.element,b,[]):(g.abortAjax(),f={url:i,data:d,type:h.type,dataType:h.dataType},a.extend(f,h.ajaxSettings),g.currentRequest=a.ajax(f).done(function(a){var c;g.currentRequest=null,c=h.transformResult(a,b),g.processResponse(c,b,e),h.onSearchComplete.call(g.element,b,c.suggestions)}).fail(function(a,c,d){h.onSearchError.call(g.element,b,a,c,d)}))}},isBadQuery:function(a){if(!this.options.preventBadQueries)return!1;for(var b=this.badQueries,c=b.length;c--;)if(0===a.indexOf(b[c]))return!0;return!1},hide:function(){var b=this,c=a(b.suggestionsContainer);a.isFunction(b.options.onHide)&&b.visible&&b.options.onHide.call(b.element,c),b.visible=!1,b.selectedIndex=-1,clearTimeout(b.onChangeTimeout),a(b.suggestionsContainer).hide(),b.signalHint(null)},suggest:function(){if(!this.suggestions.length)return void(this.options.showNoSuggestionNotice?this.noSuggestions():this.hide());var b,c=this,d=c.options,e=d.groupBy,f=d.formatResult,g=c.getQuery(c.currentValue),h=c.classes.suggestion,i=c.classes.selected,j=a(c.suggestionsContainer),k=a(c.noSuggestionsContainer),l=d.beforeRender,m="",n=function(a,c){var f=a.data[e];return b===f?"":(b=f,d.formatGroup(a,b))};return d.triggerSelectOnValidInput&&c.isExactMatch(g)?void c.select(0):(a.each(c.suggestions,function(a,b){e&&(m+=n(b,g,a)),m+='
'+f(b,g,a)+"
"}),this.adjustContainerWidth(),k.detach(),j.html(m),a.isFunction(l)&&l.call(c.element,j,c.suggestions),c.fixPosition(),j.show(),d.autoSelectFirst&&(c.selectedIndex=0,j.scrollTop(0),j.children("."+h).first().addClass(i)),c.visible=!0,void c.findBestHint())},noSuggestions:function(){var b=this,c=b.options.beforeRender,d=a(b.suggestionsContainer),e=a(b.noSuggestionsContainer);this.adjustContainerWidth(),e.detach(),d.empty(),d.append(e),a.isFunction(c)&&c.call(b.element,d,b.suggestions),b.fixPosition(),d.show(),b.visible=!0},adjustContainerWidth:function(){var b,c=this,d=c.options,e=a(c.suggestionsContainer);"auto"===d.width?(b=c.el.outerWidth(),e.css("width",b>0?b:300)):"flex"===d.width&&e.css("width","")},findBestHint:function(){var b=this,c=b.el.val().toLowerCase(),d=null;c&&(a.each(b.suggestions,function(a,b){var e=0===b.value.toLowerCase().indexOf(c);return e&&(d=b),!e}),b.signalHint(d))},signalHint:function(b){var c="",d=this;b&&(c=d.currentValue+b.value.substr(d.currentValue.length)),d.hintValue!==c&&(d.hintValue=c,d.hint=b,(this.options.onHint||a.noop)(c))},verifySuggestionsFormat:function(b){return b.length&&"string"==typeof b[0]?a.map(b,function(a){return{value:a,data:null}}):b},validateOrientation:function(b,c){return b=a.trim(b||"").toLowerCase(),a.inArray(b,["auto","bottom","top"])===-1&&(b=c),b},processResponse:function(a,b,c){var d=this,e=d.options;a.suggestions=d.verifySuggestionsFormat(a.suggestions),e.noCache||(d.cachedResponse[c]=a,e.preventBadQueries&&!a.suggestions.length&&d.badQueries.push(b)),b===d.getQuery(d.currentValue)&&(d.suggestions=a.suggestions,d.suggest())},activate:function(b){var c,d=this,e=d.classes.selected,f=a(d.suggestionsContainer),g=f.find("."+d.classes.suggestion);return f.find("."+e).removeClass(e),d.selectedIndex=b,d.selectedIndex!==-1&&g.length>d.selectedIndex?(c=g.get(d.selectedIndex),a(c).addClass(e),c):null},selectHint:function(){var b=this,c=a.inArray(b.hint,b.suggestions);b.select(c)},select:function(a){var b=this;b.hide(),b.onSelect(a)},moveUp:function(){var b=this;if(b.selectedIndex!==-1)return 0===b.selectedIndex?(a(b.suggestionsContainer).children("."+b.classes.suggestion).first().removeClass(b.classes.selected),b.selectedIndex=-1,b.ignoreValueChange=!1,b.el.val(b.currentValue),void b.findBestHint()):void b.adjustScroll(b.selectedIndex-1)},moveDown:function(){var a=this;a.selectedIndex!==a.suggestions.length-1&&a.adjustScroll(a.selectedIndex+1)},adjustScroll:function(b){var c=this,d=c.activate(b);if(d){var e,f,g,h=a(d).outerHeight();e=d.offsetTop,f=a(c.suggestionsContainer).scrollTop(),g=f+c.options.maxHeight-h,eg&&a(c.suggestionsContainer).scrollTop(e-c.options.maxHeight+h),c.options.preserveInput||(c.ignoreValueChange=!0,c.el.val(c.getValue(c.suggestions[b].value))),c.signalHint(null)}},onSelect:function(b){var c=this,d=c.options.onSelect,e=c.suggestions[b];c.currentValue=c.getValue(e.value),c.currentValue===c.el.val()||c.options.preserveInput||c.el.val(c.currentValue),c.signalHint(null),c.suggestions=[],c.selection=e,a.isFunction(d)&&d.call(c.element,e)},getValue:function(a){var b,c,d=this,e=d.options.delimiter;return e?(b=d.currentValue,c=b.split(e),1===c.length?a:b.substr(0,b.length-c[c.length-1].length)+a):a},dispose:function(){var b=this;b.el.off(".autocomplete").removeData("autocomplete"),a(window).off("resize.autocomplete",b.fixPositionCapture),a(b.suggestionsContainer).remove()}},a.fn.devbridgeAutocomplete=function(c,d){var e="autocomplete";return arguments.length?this.each(function(){var f=a(this),g=f.data(e);"string"==typeof c?g&&"function"==typeof g[c]&&g[c](d):(g&&g.dispose&&g.dispose(),g=new b(this,c),f.data(e,g))}):this.first().data(e)},a.fn.autocomplete||(a.fn.autocomplete=a.fn.devbridgeAutocomplete)}); -------------------------------------------------------------------------------- /src/main/resources/static/js/jquery.validate.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Validation Plugin - v1.19.1 - 6/15/2019 2 | * https://jqueryvalidation.org/ 3 | * Copyright (c) 2019 Jörn Zaefferer; Licensed MIT */ 4 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.submitButton=b.currentTarget,a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.submitButton&&(c.settings.submitHandler||c.formSubmitted)&&(d=a("").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),!(c.settings.submitHandler&&!c.settings.debug)||(e=c.settings.submitHandler.call(c,c.currentForm,b),d&&d.remove(),void 0!==e&&e)}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,b||(d=d.concat(c.errorList))}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0],k="undefined"!=typeof this.attr("contenteditable")&&"false"!==this.attr("contenteditable");if(null!=j&&(!j.form&&k&&(j.form=this.closest("form")[0],j.name=this.attr("name")),null!=j.form)){if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(a,b){i[b]=f[b],delete f[b]}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g)),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}}),a.extend(a.expr.pseudos||a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){var c=a(b).val();return null!==c&&!!a.trim(""+c)},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:void 0===c?b:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||a.inArray(c.keyCode,d)!==-1||(b.name in this.submitted||b.name in this.invalid)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}."),step:a.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");if(!this.form&&c&&(this.form=a(this).closest("form")[0],this.name=a(this).attr("name")),d===this.form){var e=a.data(this.form,"validator"),f="on"+b.type.replace(/^validate/,""),g=e.settings;g[f]&&!a(this).is(g.ignore)&&g[f].call(e,this,b)}}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.currentForm,e=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){e[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler)},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c,d,e=this.clean(b),f=this.validationTargetFor(e),g=this,h=!0;return void 0===f?delete this.invalid[e.name]:(this.prepareElement(f),this.currentElements=a(f),d=this.groups[f.name],d&&a.each(this.groups,function(a,b){b===d&&a!==f.name&&(e=g.validationTargetFor(g.clean(g.findByName(a))),e&&e.name in g.invalid&&(g.currentElements.push(e),h=g.check(e)&&h))}),c=this.check(f)!==!1,h=h&&c,c?this.invalid[f.name]=!1:this.invalid[f.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),a(b).attr("aria-invalid",!c)),h},showErrors:function(b){if(b){var c=this;a.extend(this.errorMap,b),this.errorList=a.map(this.errorMap,function(a,b){return{message:a,element:c.findByName(b)[0]}}),this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var b=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(b)},resetElements:function(a){var b;if(this.settings.unhighlight)for(b=0;a[b];b++)this.settings.unhighlight.call(this,a[b],this.settings.errorClass,""),this.findByName(a[b].name).removeClass(this.settings.validClass);else a.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)void 0!==a[b]&&null!==a[b]&&a[b]!==!1&&c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").trigger("focus").trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var d=this.name||a(this).attr("name"),e="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");return!d&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),e&&(this.form=a(this).closest("form")[0],this.name=d),this.form===b.currentForm&&(!(d in c||!b.objectLength(a(this).rules()))&&(c[d]=!0,!0))})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([])},reset:function(){this.resetInternals(),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d,e=a(b),f=b.type,g="undefined"!=typeof e.attr("contenteditable")&&"false"!==e.attr("contenteditable");return"radio"===f||"checkbox"===f?this.findByName(b.name).filter(":checked").val():"number"===f&&"undefined"!=typeof b.validity?b.validity.badInput?"NaN":e.val():(c=g?e.text():e.val(),"file"===f?"C:\\fakepath\\"===c.substr(0,12)?c.substr(12):(d=c.lastIndexOf("/"),d>=0?c.substr(d+1):(d=c.lastIndexOf("\\"),d>=0?c.substr(d+1):c)):"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f,g=a(b).rules(),h=a.map(g,function(a,b){return b}).length,i=!1,j=this.elementValue(b);"function"==typeof g.normalizer?f=g.normalizer:"function"==typeof this.settings.normalizer&&(f=this.settings.normalizer),f&&(j=f.call(b,j),delete g.normalizer);for(d in g){e={method:d,parameters:g[d]};try{if(c=a.validator.methods[d].call(this,j,b,e.parameters),"dependency-mismatch"===c&&1===h){i=!0;continue}if(i=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(k){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",k),k instanceof TypeError&&(k.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),k}}if(!i)return this.objectLength(g)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+""),e=/\$?\{(\d+)\}/g;return"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),d},formatAndAdd:function(a,b){var c=this.defaultMessage(a,b);this.errorList.push({message:c,element:a,method:b.method}),this.errorMap[a.name]=c,this.submitted[a.name]=c},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g,h=this.errorsFor(b),i=this.idOrName(b),j=a(b).attr("aria-describedby");h.length?(h.removeClass(this.settings.validClass).addClass(this.settings.errorClass),h.html(c)):(h=a("<"+this.settings.errorElement+">").attr("id",i+"-error").addClass(this.settings.errorClass).html(c||""),d=h,this.settings.wrapper&&(d=h.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement.call(this,d,a(b)):d.insertAfter(b),h.is("label")?h.attr("for",i):0===h.parents("label[for='"+this.escapeCssMeta(i)+"']").length&&(f=h.attr("id"),j?j.match(new RegExp("\\b"+this.escapeCssMeta(f)+"\\b"))||(j+=" "+f):j=f,a(b).attr("aria-describedby",j),e=this.groups[b.name],e&&(g=this,a.each(g.groups,function(b,c){c===e&&a("[name='"+g.escapeCssMeta(b)+"']",g.currentForm).attr("aria-describedby",h.attr("id"))})))),!c&&this.settings.success&&(h.text(""),"string"==typeof this.settings.success?h.addClass(this.settings.success):this.settings.success(h,b)),this.toShow=this.toShow.add(h)},errorsFor:function(b){var c=this.escapeCssMeta(this.idOrName(b)),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+this.escapeCssMeta(d).replace(/\s+/g,", #")),this.errors().filter(e)},escapeCssMeta:function(a){return a.replace(/([\\!"#$%&'()*+,.\/:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+this.escapeCssMeta(b)+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return!this.dependTypes[typeof a]||this.dependTypes[typeof a](a,b)},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(b){this.pending[b.name]||(this.pendingRequest++,a(b).addClass(this.settings.pendingClass),this.pending[b.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],a(b).removeClass(this.settings.pendingClass),c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.submitButton&&a("input:hidden[name='"+this.submitButton.name+"']",this.currentForm).remove(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b,c){return c="string"==typeof c&&c||"remote",a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,{method:c})})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur").find(".validate-lessThan-blur").off(".validate-lessThan").removeClass("validate-lessThan-blur").find(".validate-lessThanEqual-blur").off(".validate-lessThanEqual").removeClass("validate-lessThanEqual-blur").find(".validate-greaterThanEqual-blur").off(".validate-greaterThanEqual").removeClass("validate-greaterThanEqual-blur").find(".validate-greaterThan-blur").off(".validate-greaterThan").removeClass("validate-greaterThan-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max|step/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),""===d&&(d=!0),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0===e.param||e.param:(a.data(c.form,"validator").resetElements(a(c)),delete b[d])}}),a.each(b,function(d,e){b[d]=a.isFunction(e)&&"normalizer"!==d?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:void 0!==b&&null!==b&&b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[\/?#]\S*)?$/i.test(a)},date:function(){var a=!1;return function(b,c){return a||(a=!0,this.settings.debug&&window.console&&console.warn("The `date` method is deprecated and will be removed in version '2.0.0'.\nPlease don't use it, since it relies on the Date constructor, which\nbehaves very differently across browsers and locales. Use `dateISO`\ninstead or one of the locale specific methods in `localizations/`\nand `additional-methods.js`.")),this.optional(c)||!/Invalid|NaN/.test(new Date(b).toString())}}(),dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e<=d},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||a<=c},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},step:function(b,c,d){var e,f=a(c).attr("type"),g="Step attribute on input type "+f+" is not supported.",h=["text","number","range"],i=new RegExp("\\b"+f+"\\b"),j=f&&!i.test(h.join()),k=function(a){var b=(""+a).match(/(?:\.(\d+))?$/);return b&&b[1]?b[1].length:0},l=function(a){return Math.round(a*Math.pow(10,e))},m=!0;if(j)throw new Error(g);return e=k(d),(k(b)>e||l(b)%l(d)!==0)&&(m=!1),this.optional(c)||m},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-equalTo-blur").length&&e.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d,e){if(this.optional(c))return"dependency-mismatch";e="string"==typeof e&&e||"remote";var f,g,h,i=this.previousValue(c,e);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),i.originalMessage=i.originalMessage||this.settings.messages[c.name][e],this.settings.messages[c.name][e]=i.message,d="string"==typeof d&&{url:d}||d,h=a.param(a.extend({data:b},d.data)),i.old===h?i.valid:(i.old=h,f=this,this.startRequest(c),g={},g[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:g,context:f.currentForm,success:function(a){var d,g,h,j=a===!0||"true"===a;f.settings.messages[c.name][e]=i.originalMessage,j?(h=f.formSubmitted,f.resetInternals(),f.toHide=f.errorsFor(c),f.formSubmitted=h,f.successList.push(c),f.invalid[c.name]=!1,f.showErrors()):(d={},g=a||f.defaultMessage(c,{method:e,parameters:b}),d[c.name]=i.message=g,f.invalid[c.name]=!0,f.showErrors(d)),i.valid=j,f.stopRequest(c,j)}},d)),"pending")}}});var b,c={};return a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)}),a}); -------------------------------------------------------------------------------- /src/main/resources/static/js/litewebchat_input.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame) 3 | * MorFans Lab(c) 2017-2023 4 | * Licensed under LGPL 5 | */ 6 | "use strict"; 7 | 8 | 9 | // -------------------------------- 10 | // 上半部分的聊天区域 11 | var upperChild = document.querySelector('.lite-chatbox'); 12 | // 分界线 13 | var oLine = document.querySelector('.lite-chatinput hr'); 14 | // 下半部分的输入框区域 15 | var downChild = document.querySelector('.lite-chatinput'); 16 | var downHeight = downChild.clientHeight; 17 | var upperHeight = upperChild.clientHeight; 18 | 19 | // 以下为输入框区域的按钮 20 | var emojiBtn = document.getElementById("emojiBtn"); // 表情按钮 21 | var imageBtn = document.getElementById("imageBtn"); // 图片按钮 22 | var fileBtn = document.getElementById("fileBtn"); // 文件按钮 23 | var editFullScreen = document.getElementById("editFullScreen"); // 全屏按钮 24 | var exitFullScreen = document.getElementById("exitFullScreen"); // 退出全屏按钮 25 | var emojiMart = document.getElementById("emojiMart"); // 表情面板 26 | var toolMusk = document.getElementById("toolMusk"); // 表情面板遮罩 27 | var sendBtn = document.getElementById("sendBtn"); // 发送按钮 28 | var chatInput = document.querySelector('.lite-chatinput>.chatinput'); // 输入框 29 | // -------------------------------- 30 | 31 | // Emoji Mart(表情面板)设置及唤起 32 | var pickerOptions = { 33 | "locale": "zh", 34 | onEmojiSelect: function onEmojiSelect(e) { 35 | // console.log(e.native); 36 | emojiMart.style.display = "none"; 37 | toolMusk.style.display = "none"; 38 | insertAtCursor(chatInput, e.native); 39 | // insertEmoji(e.native); 40 | } 41 | }; 42 | 43 | var picker = new EmojiMart.Picker(pickerOptions); 44 | emojiMart.appendChild(picker); 45 | 46 | // 负责在光标处插入文字的函数 47 | function insertAtCursor(myField, myValue) { 48 | var editor = myField; 49 | var html = myValue; 50 | editor.focus(); 51 | if (window.getSelection) { 52 | var selection = window.getSelection(); 53 | if (selection.getRangeAt && selection.rangeCount) { 54 | var range = selection.getRangeAt(0); 55 | range.deleteContents(); 56 | var element = document.createElement('div'); 57 | element.innerHTML = html; 58 | var node; 59 | var lastNode; 60 | var fragment = document.createDocumentFragment(); 61 | while (node = element.firstChild) { 62 | lastNode = fragment.appendChild(node); 63 | } 64 | ; 65 | range.insertNode(fragment); 66 | if (lastNode) { 67 | range = range.cloneRange(); 68 | range.setStartAfter(lastNode); 69 | range.collapse(true); 70 | selection.removeAllRanges(); 71 | selection.addRange(range); 72 | } 73 | ; 74 | } 75 | } else if (document.selection && document.selection.type != 'Control') { 76 | editor.focus(); 77 | var range = document.selection.createRange(); 78 | range.pasteHTML(html); 79 | editor.focus(); 80 | } 81 | } 82 | 83 | // 调整聊天区域和输入框区域比例的函数 84 | oLine.onmousedown = function (ev) { 85 | // 更改oLine颜色为蓝色,方便查看分界线 86 | var olineOriBgColor = oLine.style.backgroundColor; 87 | oLine.style.backgroundColor = "#1E90FF"; 88 | var iEvent = ev || event; 89 | var dy = iEvent.clientY; //当你第一次单击的时候,存储y轴的坐标。//相对于浏览器窗口 90 | upperHeight = upperChild.offsetHeight; 91 | downHeight = downChild.offsetHeight; 92 | document.onmousemove = function (ev) { 93 | var iEvent = ev || event; 94 | var diff = iEvent.clientY - dy; //移动的距离(向上滑时为负数,下滑时为正数) 95 | if (100 < upperHeight + diff && 100 < downHeight - diff) { 96 | //两个div的最小高度均为100px 97 | upperChild.style.height = "calc(100% - ".concat(downHeight - diff, "px)"); 98 | downChild.style.height = downHeight - diff + 'px'; 99 | } 100 | }; 101 | document.onmouseup = function () { 102 | // 更改oLine颜色为原色 103 | oLine.style.backgroundColor = olineOriBgColor; 104 | document.onmousedown = null; 105 | document.onmousemove = null; 106 | }; 107 | return false; 108 | }; 109 | 110 | 111 | // 全屏编辑文字 112 | editFullScreen.onclick = function () { 113 | downHeight = downChild.clientHeight; 114 | upperHeight = upperChild.clientHeight; 115 | downChild.style.height = "100%"; 116 | upperChild.style.height = "0px"; 117 | editFullScreen.style.display = "none"; 118 | exitFullScreen.style.display = "block"; 119 | oLine.style.display = "none"; 120 | }; 121 | 122 | // 退出全屏编辑文字 123 | exitFullScreen.onclick = function () { 124 | // 防呆不防傻,用于避免上部聊天窗口被压到没有高度后出现异常 125 | if (upperHeight != 0) { 126 | downChild.style.height = "".concat(downHeight, "px"); 127 | upperChild.style.height = "calc(100% - ".concat(downHeight, "px)"); 128 | } else { 129 | upperChild.style.height = "calc(100% - 150px)"; 130 | downChild.style.height = "150px"; 131 | } 132 | exitFullScreen.style.display = "none"; 133 | editFullScreen.style.display = "block"; 134 | oLine.style.display = "block"; 135 | }; 136 | 137 | // 隐藏musk和表情输入框 138 | toolMusk.onclick = function () { 139 | emojiMart.style.display = "none"; 140 | toolMusk.style.display = "none"; 141 | }; 142 | 143 | // 将图片插入到输入框中 144 | function addImage(file) { 145 | new Promise(function (resolve, reject) { 146 | // console.log(file); 147 | // 获取file的src 148 | var reader = new FileReader(); 149 | reader.onload = function (e) { 150 | var src = e.target.result; 151 | var img = new Image(); 152 | img.src = src; 153 | 154 | // *这里的方法已经转移到了css里,暂时弃用 155 | // // 为了防止图片在输入框内显示过大不好编辑 156 | // img.style.width = "100px"; 157 | // 将img从HEMLElement转化为字符串 158 | // 例如,转化结束后为'' 159 | var imgStr = img.outerHTML; 160 | // 将img字符串插入到输入框中 161 | insertAtCursor(chatInput, imgStr); 162 | }; 163 | reader.readAsDataURL(file); 164 | }); 165 | } 166 | 167 | // 上传图片、文件 168 | function inputFile(settings) { 169 | console.log(settings); 170 | // -----------------设置最大图片大小及数量----------------- 171 | if (settings.maxImageSize != undefined) { 172 | var maxImageSize = settings.maxImageSize; 173 | } else { 174 | var maxImageSize = -1; 175 | } 176 | if (settings.maxImageNumber != undefined) { 177 | var maxImageNumber = settings.maxImageNumber; 178 | } else { 179 | var maxImageNumber = -1; 180 | } 181 | if (settings.enable) { 182 | // -----------------上传图片的按钮----------------- 183 | imageBtn.onclick = function () { 184 | var imageInput = document.createElement('input'); 185 | imageInput.type = 'file'; 186 | imageInput.accept = 'image/*'; 187 | imageInput.multiple = true; 188 | imageInput.style.display = 'none'; 189 | imageInput.onchange = function () { 190 | // 获取输入框内图片数量 191 | // 获取文件 192 | var imgNum = chatInput.getElementsByTagName('img').length; 193 | for (var i = 0; i < this.files.length; i++) { 194 | if (maxImageNumber == -1 || imgNum < maxImageNumber) { 195 | // 如果大小超过限制,改用文件上传 196 | if (maxImageSize == -1 || this.files[i].size <= maxImageSize) { 197 | imgNum++; 198 | addImage(this.files[i]); 199 | } else { 200 | sendFile(this.files[i]); 201 | } 202 | } 203 | } 204 | }; 205 | // 触发点击事件 206 | imageInput.click(); 207 | }; 208 | // -----------------上传文件的按钮----------------- 209 | var sendFile = settings.sendFileFunc; 210 | // 上传文件按钮 211 | fileBtn.onclick = function () { 212 | // 创建一个隐藏的上传文件的input,再借助点击这个input来上传文件 213 | var fileInput = document.createElement('input'); 214 | fileInput.type = 'file'; 215 | fileInput.multiple = true; 216 | fileInput.style.display = 'none'; 217 | fileInput.onchange = function () { 218 | // 获取文件 219 | for (var i = 0; i < this.files.length; i++) { 220 | var file = this.files[i]; 221 | sendFile(file); 222 | } 223 | }; 224 | // 触发点击事件 225 | fileInput.click(); 226 | }; 227 | 228 | // -----------------拖拽上传----------------- 229 | if (settings.enableDrop) { 230 | // 当downChild有文件被拖入时,也调用上传文件的函数 231 | downChild.ondrop = function (e) { 232 | e.preventDefault(); 233 | // 阻止火狐浏览器默认打开文件的行为 234 | e.stopPropagation(); 235 | downChild.style.border = "none"; 236 | // 获取被拖拽的文件并上传 237 | var imgNum = chatInput.getElementsByTagName('img').length; 238 | for (var i = 0; i < e.dataTransfer.files.length; i++) { 239 | var file = e.dataTransfer.files[i]; 240 | // 如果是图片,直接插入到输入框中 241 | if (file.type.indexOf("image") == 0) { 242 | if (maxImageNumber == -1 || imgNum < maxImageNumber) { 243 | // 如果大小超过限制,改用文件上传 244 | if (maxImageSize == -1 || file.size <= maxImageSize) { 245 | addImage(file); 246 | imgNum++; 247 | } else { 248 | sendFile(file); 249 | } 250 | } 251 | } else { 252 | sendFile(file); 253 | } 254 | } 255 | }; 256 | 257 | // 当downChild有文件被拖入时,改变downChild的边框颜色 258 | downChild.ondragover = function (e) { 259 | e.preventDefault(); 260 | downChild.style.border = "3px solid #1E90FF"; 261 | }; 262 | 263 | // 当downChild有文件被拖入后离开时,改回downChild的边框颜色 264 | downChild.ondragleave = function (e) { 265 | e.preventDefault(); 266 | downChild.style.border = "none"; 267 | }; 268 | } 269 | } else { 270 | // 如果不允许上传,那么删除事件 271 | imageBtn.onclick = null; 272 | fileBtn.onclick = null; 273 | // 删除拖拽事件 274 | downChild.ondrop = null; 275 | downChild.ondragover = null; 276 | downChild.ondragleave = null; 277 | } 278 | } 279 | 280 | // TODO:可能富文本输入框的粘贴部分需要对Chrome浏览器做部分额外适配,以优化体验 281 | // 无格式粘贴 282 | chatInput.addEventListener('paste', function (e) { 283 | onPaste(e); 284 | }); 285 | 286 | //格式化粘贴文本方法 287 | function onPaste(event) { 288 | // 如果粘贴的是文本,就清除格式后粘贴 289 | if (event.clipboardData && event.clipboardData.getData) { 290 | var text = event.clipboardData.getData('text/plain'); 291 | if (text) { 292 | event.preventDefault(); 293 | document.execCommand('insertText', false, text); 294 | } 295 | } 296 | } 297 | 298 | window.addEventListener('DOMContentLoaded', function () { 299 | chatInput.focus(); 300 | }); 301 | //# sourceMappingURL=map/litewebchat_input.js.map 302 | -------------------------------------------------------------------------------- /src/main/resources/static/js/litewebchat_input.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame) 3 | * MorFans Lab(c) 2017-2023 4 | * Licensed under LGPL 5 | */"use strict";var upperChild=document.querySelector(".lite-chatbox"),oLine=document.querySelector(".lite-chatinput hr"),downChild=document.querySelector(".lite-chatinput"),downHeight=downChild.clientHeight,upperHeight=upperChild.clientHeight,emojiBtn=document.getElementById("emojiBtn"),imageBtn=document.getElementById("imageBtn"),fileBtn=document.getElementById("fileBtn"),editFullScreen=document.getElementById("editFullScreen"),exitFullScreen=document.getElementById("exitFullScreen"),emojiMart=document.getElementById("emojiMart"),toolMusk=document.getElementById("toolMusk"),sendBtn=document.getElementById("sendBtn"),chatInput=document.querySelector(".lite-chatinput>.chatinput"),pickerOptions={locale:"zh",onEmojiSelect:function(e){emojiMart.style.display="none",toolMusk.style.display="none",insertAtCursor(chatInput,e.native)}},picker=new EmojiMart.Picker(pickerOptions);function insertAtCursor(e,t){var n=e,o=t;if(n.focus(),window.getSelection){var i=window.getSelection();if(i.getRangeAt&&i.rangeCount){(r=i.getRangeAt(0)).deleteContents();var l,a,c=document.createElement("div");c.innerHTML=o;for(var d=document.createDocumentFragment();l=c.firstChild;)a=d.appendChild(l);r.insertNode(d),a&&((r=r.cloneRange()).setStartAfter(a),r.collapse(!0),i.removeAllRanges(),i.addRange(r))}}else if(document.selection&&"Control"!=document.selection.type){var r;n.focus(),(r=document.selection.createRange()).pasteHTML(o),n.focus()}}function addImage(e){new Promise((function(t,n){var o=new FileReader;o.onload=function(e){var t=e.target.result,n=new Image;n.src=t;var o=n.outerHTML;insertAtCursor(chatInput,o)},o.readAsDataURL(e)}))}function inputFile(e){if(console.log(e),null!=e.maxImageSize)var t=e.maxImageSize;else t=-1;if(null!=e.maxImageNumber)var n=e.maxImageNumber;else n=-1;if(e.enable){imageBtn.onclick=function(){var e=document.createElement("input");e.type="file",e.accept="image/*",e.multiple=!0,e.style.display="none",e.onchange=function(){for(var e=chatInput.getElementsByTagName("img").length,i=0;i chatBox.clientHeight) { 36 | chatBox.scrollTop = chatBox.scrollHeight; 37 | chatBox = ''; 38 | htmlStr = ''; 39 | } 40 | }, 300); 41 | } 42 | function renderMessageHtml(data) { 43 | return "
\n \n \n ").concat(renderTitleHtml(data.htitle, TitleType[data.htitleType] || ''), "\n ").concat(escapeHtml(data.name) || ' ', "\n \n ").concat(data.messageType === 'raw' ? data.html : escapeHtml(data.html), "\n
"); 44 | } 45 | function renderTitleHtml(content, css) { 46 | if (!content) return ''; 47 | return "").concat(content, ""); 48 | } 49 | function renderTipHtml(content, css) { 50 | if (!content) return ''; 51 | return "
").concat(escapeHtml(content), "
"); 52 | } 53 | 54 | // 转义 C0 Controls and Basic Latin 中非数字和字母,C1 Controls and Latin-1 Supplement 全部 55 | // https://www.w3schools.com/charsets/ref_html_utf8.asp 56 | function escapeHtml(unsafe) { 57 | return unsafe === null || unsafe === void 0 ? void 0 : unsafe.replace(/[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u00FF]/g, function (c) { 58 | return '&#' + ('000' + c.charCodeAt(0)).slice(-4) + ';'; 59 | }); 60 | } 61 | //# sourceMappingURL=map/litewebchat_render.js.map 62 | -------------------------------------------------------------------------------- /src/main/resources/static/js/litewebchat_render.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * LiteWebChat_Frame 2.2.1 (https://lab.morfans.cn/LiteWebChat_Frame) 3 | * MorFans Lab(c) 2017-2023 4 | * Licensed under LGPL 5 | */"use strict";var TipsType={tipsNormal:"tips",tipsPrimary:"tips-primary",tipsSuccess:"tips-success",tipsInfo:"tips-info",tipsWarning:"tips-warning",tipsDanger:"tips-danger"},TitleType={admin:"admin",owner:"owner"};function beforeRenderingHTML(e,n){for(var t="",s=document.querySelector(n),i=0;is.clientHeight&&(s.scrollTop=s.scrollHeight,s="",t="")}),300)}function renderMessageHtml(e){return'
\n \n \n ').concat(renderTitleHtml(e.htitle,TitleType[e.htitleType]||""),"\n ").concat(escapeHtml(e.name)||" ",'\n \n ').concat("raw"===e.messageType?e.html:escapeHtml(e.html),"\n
")}function renderTitleHtml(e,n){return e?'').concat(e,""):""}function renderTipHtml(e,n){return e?'
').concat(escapeHtml(e),"
"):""}function escapeHtml(e){return null==e?void 0:e.replace(/[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u00FF]/g,(function(e){return"&#"+("000"+e.charCodeAt(0)).slice(-4)+";"}))} 6 | //# sourceMappingURL=map/litewebchat_render.min.js.map 7 | -------------------------------------------------------------------------------- /src/main/resources/static/js/map/litewebchat_input.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["input.js"],"names":["upperChild","document","querySelector","oLine","downChild","downHeight","clientHeight","upperHeight","emojiBtn","getElementById","imageBtn","fileBtn","editFullScreen","exitFullScreen","emojiMart","toolMusk","sendBtn","chatInput","pickerOptions","onEmojiSelect","e","style","display","insertAtCursor","native","picker","EmojiMart","Picker","appendChild","myField","myValue","editor","html","focus","window","getSelection","selection","getRangeAt","rangeCount","range","deleteContents","element","createElement","innerHTML","node","lastNode","fragment","createDocumentFragment","firstChild","insertNode","cloneRange","setStartAfter","collapse","removeAllRanges","addRange","type","createRange","pasteHTML","onmousedown","ev","olineOriBgColor","backgroundColor","iEvent","event","dy","clientY","offsetHeight","onmousemove","diff","height","onmouseup","onclick","emojiHeight","bottom","top","addImage","file","Promise","resolve","reject","reader","FileReader","onload","src","target","result","img","Image","imgStr","outerHTML","readAsDataURL","inputFile","settings","console","log","maxImageSize","undefined","maxImageNumber","enable","imageInput","accept","multiple","onchange","imgNum","getElementsByTagName","length","i","files","size","sendFile","click","sendFileFunc","fileInput","enableDrop","ondrop","preventDefault","stopPropagation","border","dataTransfer","indexOf","ondragover","ondragleave","addEventListener","onPaste","clipboardData","getData","text","execCommand"],"mappings":"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","file":"../litewebchat_input.js","sourcesContent":["// !参考资料来源:\n// !https://blog.csdn.net/weixin_40629244/article/details/104642683\n// !https://github.com/jrainlau/chat-input-box\n// !https://www.zhihu.com/question/20893119/answer/19452676\n// !致谢:感谢@jrainlau提供的思路和代码,我在他的富文本编辑器基础上进行了修改,使其能够在聊天输入框中使用\n// ————YubaC 2023.1.23\n\n// --------------------------------\n// 上半部分的聊天区域\nlet upperChild = document.querySelector('.lite-chatbox');\n// 分界线\nlet oLine = document.querySelector('.lite-chatinput hr');\n// 下半部分的输入框区域\nlet downChild = document.querySelector('.lite-chatinput');\n\nvar downHeight = downChild.clientHeight;\nvar upperHeight = upperChild.clientHeight;\n\n// 以下为输入框区域的按钮\nvar emojiBtn = document.getElementById(\"emojiBtn\"); // 表情按钮\nvar imageBtn = document.getElementById(\"imageBtn\"); // 图片按钮\nvar fileBtn = document.getElementById(\"fileBtn\"); // 文件按钮\nvar editFullScreen = document.getElementById(\"editFullScreen\"); // 全屏按钮\nvar exitFullScreen = document.getElementById(\"exitFullScreen\"); // 退出全屏按钮\nvar emojiMart = document.getElementById(\"emojiMart\"); // 表情面板\nvar toolMusk = document.getElementById(\"toolMusk\"); // 表情面板遮罩\nvar sendBtn = document.getElementById(\"sendBtn\"); // 发送按钮\nvar chatInput = document.querySelector('.lite-chatinput>.chatinput'); // 输入框\n// --------------------------------\n\n// Emoji Mart(表情面板)设置及唤起\nvar pickerOptions = {\n \"locale\": \"zh\",\n onEmojiSelect: function (e) {\n // console.log(e.native);\n emojiMart.style.display = \"none\";\n toolMusk.style.display = \"none\";\n insertAtCursor(chatInput, e.native);\n // insertEmoji(e.native);\n }\n}\nvar picker = new EmojiMart.Picker(pickerOptions);\nemojiMart.appendChild(picker);\n\n// 负责在光标处插入文字的函数\nfunction insertAtCursor(myField, myValue) {\n var editor = myField;\n var html = myValue;\n editor.focus();\n\n if (window.getSelection) {\n var selection = window.getSelection();\n if (selection.getRangeAt && selection.rangeCount) {\n var range = selection.getRangeAt(0);\n range.deleteContents();\n var element = document.createElement('div');\n element.innerHTML = html;\n\n var node;\n var lastNode;\n var fragment = document.createDocumentFragment();\n\n while ((node = element.firstChild)) {\n lastNode = fragment.appendChild(node);\n };\n\n range.insertNode(fragment);\n if (lastNode) {\n range = range.cloneRange();\n range.setStartAfter(lastNode);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n };\n }\n\n } else if (document.selection && document.selection.type != 'Control') {\n editor.focus();\n var range = document.selection.createRange();\n range.pasteHTML(html);\n editor.focus();\n }\n}\n\n// 调整聊天区域和输入框区域比例的函数\noLine.onmousedown = function (ev) {\n // 更改oLine颜色为蓝色,方便查看分界线\n const olineOriBgColor = oLine.style.backgroundColor;\n oLine.style.backgroundColor = \"#1E90FF\";\n var iEvent = ev || event;\n var dy = iEvent.clientY; //当你第一次单击的时候,存储y轴的坐标。//相对于浏览器窗口\n upperHeight = upperChild.offsetHeight;\n downHeight = downChild.offsetHeight;\n document.onmousemove = function (ev) {\n var iEvent = ev || event;\n var diff = iEvent.clientY - dy; //移动的距离(向上滑时为负数,下滑时为正数)\n if (100 < (upperHeight + diff) && 100 < (downHeight - diff)) {\n //两个div的最小高度均为100px\n upperChild.style.height = `calc(100% - ${downHeight - diff}px)`;\n downChild.style.height = (downHeight - diff) + 'px';\n }\n };\n document.onmouseup = function () {\n // 更改oLine颜色为原色\n oLine.style.backgroundColor = olineOriBgColor;\n document.onmousedown = null;\n document.onmousemove = null;\n };\n return false;\n}\n\n// 显示表情输入框\nemojiBtn.onclick = function () {\n emojiMart.style.display = \"block\";\n toolMusk.style.display = \"block\";\n\n let emojiHeight = emojiMart.offsetHeight;\n downHeight = downChild.clientHeight;\n upperHeight = upperChild.clientHeight;\n\n if (emojiHeight < upperHeight) {\n emojiMart.style.bottom = `${downHeight + 3}px`\n emojiMart.style.top = '';\n } else {\n emojiMart.style.bottom = ''\n emojiMart.style.top = '10px';\n }\n\n}\n\n// 全屏编辑文字\neditFullScreen.onclick = function () {\n downHeight = downChild.clientHeight;\n upperHeight = upperChild.clientHeight;\n downChild.style.height = `100%`;\n upperChild.style.height = \"0px\";\n editFullScreen.style.display = \"none\";\n exitFullScreen.style.display = \"block\";\n oLine.style.display = \"none\";\n}\n\n// 退出全屏编辑文字\nexitFullScreen.onclick = function () {\n // 防呆不防傻,用于避免上部聊天窗口被压到没有高度后出现异常\n if (upperHeight != 0) {\n downChild.style.height = `${downHeight}px`;\n upperChild.style.height = `calc(100% - ${downHeight}px)`;\n } else {\n upperChild.style.height = \"calc(100% - 150px)\";\n downChild.style.height = \"150px\";\n }\n\n exitFullScreen.style.display = \"none\";\n editFullScreen.style.display = \"block\";\n oLine.style.display = \"block\";\n}\n\n// 隐藏musk和表情输入框\ntoolMusk.onclick = function () {\n emojiMart.style.display = \"none\";\n toolMusk.style.display = \"none\";\n}\n\n// 将图片插入到输入框中\nfunction addImage(file) {\n new Promise((resolve, reject) => {\n // console.log(file);\n // 获取file的src\n var reader = new FileReader();\n reader.onload = function (e) {\n var src = e.target.result;\n var img = new Image();\n img.src = src;\n\n // *这里的方法已经转移到了css里,暂时弃用\n // // 为了防止图片在输入框内显示过大不好编辑\n // img.style.width = \"100px\";\n // 将img从HEMLElement转化为字符串\n // 例如,转化结束后为''\n var imgStr = img.outerHTML;\n // 将img字符串插入到输入框中\n insertAtCursor(chatInput, imgStr);\n }\n reader.readAsDataURL(file);\n })\n}\n\n// 上传图片、文件\nfunction inputFile(settings) {\n console.log(settings);\n // -----------------设置最大图片大小及数量-----------------\n if (settings.maxImageSize != undefined) {\n var maxImageSize = settings.maxImageSize;\n } else {\n var maxImageSize = -1;\n }\n\n if (settings.maxImageNumber != undefined) {\n var maxImageNumber = settings.maxImageNumber;\n } else {\n var maxImageNumber = -1;\n }\n\n if (settings.enable) {\n // -----------------上传图片的按钮-----------------\n imageBtn.onclick = function () {\n var imageInput = document.createElement('input');\n imageInput.type = 'file';\n imageInput.accept = 'image/*';\n imageInput.multiple = true;\n imageInput.style.display = 'none';\n imageInput.onchange = function () {\n // 获取输入框内图片数量\n // 获取文件\n var imgNum = chatInput.getElementsByTagName('img').length;\n for (var i = 0; i < this.files.length; i++) {\n if (maxImageNumber == -1 || imgNum < maxImageNumber) {\n // 如果大小超过限制,改用文件上传\n if ((maxImageSize == -1 || this.files[i].size <= maxImageSize)) {\n imgNum++;\n addImage(this.files[i]);\n } else {\n sendFile(this.files[i]);\n }\n }\n }\n }\n // 触发点击事件\n imageInput.click();\n }\n // -----------------上传文件的按钮-----------------\n let sendFile = settings.sendFileFunc;\n // 上传文件按钮\n fileBtn.onclick = function () {\n // 创建一个隐藏的上传文件的input,再借助点击这个input来上传文件\n var fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.multiple = true;\n fileInput.style.display = 'none';\n fileInput.onchange = function () {\n // 获取文件\n for (var i = 0; i < this.files.length; i++) {\n var file = this.files[i];\n sendFile(file);\n }\n }\n // 触发点击事件\n fileInput.click();\n }\n\n // -----------------拖拽上传-----------------\n if (settings.enableDrop) {\n // 当downChild有文件被拖入时,也调用上传文件的函数\n downChild.ondrop = function (e) {\n e.preventDefault();\n // 阻止火狐浏览器默认打开文件的行为\n e.stopPropagation();\n downChild.style.border = \"none\";\n // 获取被拖拽的文件并上传\n var imgNum = chatInput.getElementsByTagName('img').length;\n for (var i = 0; i < e.dataTransfer.files.length; i++) {\n var file = e.dataTransfer.files[i];\n // 如果是图片,直接插入到输入框中\n if (file.type.indexOf(\"image\") == 0) {\n if (maxImageNumber == -1 || imgNum < maxImageNumber) {\n // 如果大小超过限制,改用文件上传\n if ((maxImageSize == -1 || file.size <= maxImageSize)) {\n addImage(file);\n imgNum++;\n } else {\n sendFile(file);\n }\n }\n } else {\n sendFile(file);\n }\n }\n }\n\n // 当downChild有文件被拖入时,改变downChild的边框颜色\n downChild.ondragover = function (e) {\n e.preventDefault();\n downChild.style.border = \"3px solid #1E90FF\";\n }\n\n // 当downChild有文件被拖入后离开时,改回downChild的边框颜色\n downChild.ondragleave = function (e) {\n e.preventDefault();\n downChild.style.border = \"none\";\n }\n }\n } else {\n // 如果不允许上传,那么删除事件\n imageBtn.onclick = null;\n fileBtn.onclick = null;\n // 删除拖拽事件\n downChild.ondrop = null;\n downChild.ondragover = null;\n downChild.ondragleave = null;\n }\n}\n\n// TODO:可能富文本输入框的粘贴部分需要对Chrome浏览器做部分额外适配,以优化体验\n// 无格式粘贴\nchatInput.addEventListener('paste', function (e) {\n onPaste(e);\n})\n\n//格式化粘贴文本方法\nfunction onPaste(event) {\n // 如果粘贴的是文本,就清除格式后粘贴\n if (event.clipboardData && event.clipboardData.getData) {\n var text = event.clipboardData.getData('text/plain');\n if (text) {\n event.preventDefault();\n document.execCommand('insertText', false, text);\n }\n }\n}\n\nwindow.addEventListener('DOMContentLoaded', function () {\n chatInput.focus();\n});\n"]} -------------------------------------------------------------------------------- /src/main/resources/static/js/map/litewebchat_input.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["input.js"],"names":["upperChild","document","querySelector","oLine","downChild","downHeight","clientHeight","upperHeight","emojiBtn","getElementById","imageBtn","fileBtn","editFullScreen","exitFullScreen","emojiMart","toolMusk","sendBtn","chatInput","pickerOptions","locale","onEmojiSelect","e","style","display","insertAtCursor","native","picker","EmojiMart","Picker","myField","myValue","editor","html","focus","window","getSelection","selection","getRangeAt","rangeCount","range","deleteContents","node","lastNode","element","createElement","innerHTML","fragment","createDocumentFragment","firstChild","appendChild","insertNode","cloneRange","setStartAfter","collapse","removeAllRanges","addRange","type","createRange","pasteHTML","addImage","file","Promise","resolve","reject","reader","FileReader","onload","src","target","result","img","Image","imgStr","outerHTML","readAsDataURL","inputFile","settings","console","log","undefined","maxImageSize","maxImageNumber","enable","onclick","imageInput","accept","multiple","onchange","imgNum","getElementsByTagName","length","i","this","files","size","sendFile","click","sendFileFunc","fileInput","enableDrop","ondrop","preventDefault","stopPropagation","border","dataTransfer","indexOf","ondragover","ondragleave","onPaste","event","clipboardData","getData","text","execCommand","onmousedown","ev","olineOriBgColor","backgroundColor","dy","clientY","offsetHeight","onmousemove","diff","height","concat","onmouseup","emojiHeight","bottom","top","addEventListener"],"mappings":";;;;GAAA,aASA,IAAIA,WAAaC,SAASC,cAAc,iBAEpCC,MAAQF,SAASC,cAAc,sBAE/BE,UAAYH,SAASC,cAAc,mBAEnCG,WAAaD,UAAUE,aACvBC,YAAcP,WAAWM,aAGzBE,SAAWP,SAASQ,eAAe,YACnCC,SAAWT,SAASQ,eAAe,YACnCE,QAAUV,SAASQ,eAAe,WAClCG,eAAiBX,SAASQ,eAAe,kBACzCI,eAAiBZ,SAASQ,eAAe,kBACzCK,UAAYb,SAASQ,eAAe,aACpCM,SAAWd,SAASQ,eAAe,YACnCO,QAAUf,SAASQ,eAAe,WAClCQ,UAAYhB,SAASC,cAAc,8BAInCgB,cAAgB,CAClBC,OAAU,KACVC,cAAe,SAAUC,GAEvBP,UAAUQ,MAAMC,QAAU,OAC1BR,SAASO,MAAMC,QAAU,OACzBC,eAAeP,UAAWI,EAAEI,OAE9B,GAEEC,OAAS,IAAIC,UAAUC,OAAOV,eAIlC,SAASM,eAAeK,EAASC,GAC/B,IAAIC,EAASF,EACTG,EAAOF,EAGX,GAFAC,EAAOE,QAEHC,OAAOC,aAAc,CACvB,IAAIC,EAAYF,OAAOC,eACvB,GAAIC,EAAUC,YAAcD,EAAUE,WAAY,EAC5CC,EAAQH,EAAUC,WAAW,IAC3BG,iBACN,IAGIC,EACAC,EAJAC,EAAU1C,SAAS2C,cAAc,OACrCD,EAAQE,UAAYb,EAMpB,IAFA,IAAIc,EAAW7C,SAAS8C,yBAEhBN,EAAOE,EAAQK,YACrBN,EAAWI,EAASG,YAAYR,GAGlCF,EAAMW,WAAWJ,GACbJ,KACFH,EAAQA,EAAMY,cACRC,cAAcV,GACpBH,EAAMc,UAAS,GACfjB,EAAUkB,kBACVlB,EAAUmB,SAAShB,GAEvB,CAEF,MAAO,GAAItC,SAASmC,WAAwC,WAA3BnC,SAASmC,UAAUoB,KAAmB,CAErE,IAAIjB,EADJR,EAAOE,SACHM,EAAQtC,SAASmC,UAAUqB,eACzBC,UAAU1B,GAChBD,EAAOE,OACT,CACF,CAkFA,SAAS0B,SAASC,GAChB,IAAIC,SAAQ,SAACC,EAASC,GAGpB,IAAIC,EAAS,IAAIC,WACjBD,EAAOE,OAAS,SAAU7C,GACxB,IAAI8C,EAAM9C,EAAE+C,OAAOC,OACfC,EAAM,IAAIC,MACdD,EAAIH,IAAMA,EAOV,IAAIK,EAASF,EAAIG,UAEjBjD,eAAeP,UAAWuD,EAC5B,EACAR,EAAOU,cAAcd,EACvB,GACF,CAGA,SAASe,UAAUC,GAGjB,GAFAC,QAAQC,IAAIF,GAEiBG,MAAzBH,EAASI,aACX,IAAIA,EAAeJ,EAASI,kBAExBA,GAAgB,EAGtB,GAA+BD,MAA3BH,EAASK,eACX,IAAIA,EAAiBL,EAASK,oBAE1BA,GAAkB,EAGxB,GAAIL,EAASM,OAAQ,CAEnBxE,SAASyE,QAAU,WACjB,IAAIC,EAAanF,SAAS2C,cAAc,SACxCwC,EAAW5B,KAAO,OAClB4B,EAAWC,OAAS,UACpBD,EAAWE,UAAW,EACtBF,EAAW9D,MAAMC,QAAU,OAC3B6D,EAAWG,SAAW,WAIpB,IADA,IAAIC,EAASvE,UAAUwE,qBAAqB,OAAOC,OAC1CC,EAAI,EAAGA,EAAIC,KAAKC,MAAMH,OAAQC,MACd,GAAnBV,GAAwBO,EAASP,MAEb,GAAjBD,GAAsBY,KAAKC,MAAMF,GAAGG,MAAQd,GAC/CQ,IACA7B,SAASiC,KAAKC,MAAMF,KAEpBI,EAASH,KAAKC,MAAMF,IAI5B,EAEAP,EAAWY,OACb,EAEA,IAAID,EAAWnB,EAASqB,aAExBtF,QAAQwE,QAAU,WAEhB,IAAIe,EAAYjG,SAAS2C,cAAc,SACvCsD,EAAU1C,KAAO,OACjB0C,EAAUZ,UAAW,EACrBY,EAAU5E,MAAMC,QAAU,OAC1B2E,EAAUX,SAAW,WAEnB,IAAK,IAAII,EAAI,EAAGA,EAAIC,KAAKC,MAAMH,OAAQC,IAAK,CAC1C,IAAI/B,EAAOgC,KAAKC,MAAMF,GACtBI,EAASnC,EACX,CACF,EAEAsC,EAAUF,OACZ,EAGIpB,EAASuB,aAEX/F,UAAUgG,OAAS,SAAU/E,GAC3BA,EAAEgF,iBAEFhF,EAAEiF,kBACFlG,UAAUkB,MAAMiF,OAAS,OAGzB,IADA,IAAIf,EAASvE,UAAUwE,qBAAqB,OAAOC,OAC1CC,EAAI,EAAGA,EAAItE,EAAEmF,aAAaX,MAAMH,OAAQC,IAAK,CACpD,IAAI/B,EAAOvC,EAAEmF,aAAaX,MAAMF,GAEE,GAA9B/B,EAAKJ,KAAKiD,QAAQ,WACG,GAAnBxB,GAAwBO,EAASP,MAEb,GAAjBD,GAAsBpB,EAAKkC,MAAQd,GACtCrB,SAASC,GACT4B,KAEAO,EAASnC,IAIbmC,EAASnC,EAEb,CACF,EAGAxD,UAAUsG,WAAa,SAAUrF,GAC/BA,EAAEgF,iBACFjG,UAAUkB,MAAMiF,OAAS,mBAC3B,EAGAnG,UAAUuG,YAAc,SAAUtF,GAChCA,EAAEgF,iBACFjG,UAAUkB,MAAMiF,OAAS,MAC3B,EAEJ,MAEE7F,SAASyE,QAAU,KACnBxE,QAAQwE,QAAU,KAElB/E,UAAUgG,OAAS,KACnBhG,UAAUsG,WAAa,KACvBtG,UAAUuG,YAAc,IAE5B,CASA,SAASC,QAAQC,GAEf,GAAIA,EAAMC,eAAiBD,EAAMC,cAAcC,QAAS,CACtD,IAAIC,EAAOH,EAAMC,cAAcC,QAAQ,cACnCC,IACFH,EAAMR,iBACNpG,SAASgH,YAAY,cAAc,EAAOD,GAE9C,CACF,CApRAlG,UAAUmC,YAAYvB,QA2CtBvB,MAAM+G,YAAc,SAAUC,GAE5B,IAAMC,EAAkBjH,MAAMmB,MAAM+F,gBACpClH,MAAMmB,MAAM+F,gBAAkB,UAC9B,IACIC,GADSH,GAAMN,OACHU,QAkBhB,OAjBAhH,YAAcP,WAAWwH,aACzBnH,WAAaD,UAAUoH,aACvBvH,SAASwH,YAAc,SAAUN,GAC/B,IACIO,GADSP,GAAMN,OACDU,QAAUD,EACxB,IAAO/G,YAAcmH,GAAS,IAAOrH,WAAaqH,IAEpD1H,WAAWsB,MAAMqG,OAAM,eAAAC,OAAkBvH,WAAaqH,EAAI,OAC1DtH,UAAUkB,MAAMqG,OAAUtH,WAAaqH,EAAQ,KAEnD,EACAzH,SAAS4H,UAAY,WAEnB1H,MAAMmB,MAAM+F,gBAAkBD,EAC9BnH,SAASiH,YAAc,KACvBjH,SAASwH,YAAc,IACzB,GACO,CACT,EAGAjH,SAAS2E,QAAU,WACjBrE,UAAUQ,MAAMC,QAAU,QAC1BR,SAASO,MAAMC,QAAU,QAEzB,IAAIuG,EAAchH,UAAU0G,aAC3BnH,WAAaD,UAAUE,aAGpBwH,GAFHvH,YAAcP,WAAWM,eAGxBQ,UAAUQ,MAAMyG,OAAM,GAAAH,OAAMvH,WAAa,EAAC,MAC1CS,UAAUQ,MAAM0G,IAAM,KAEtBlH,UAAUQ,MAAMyG,OAAS,GACzBjH,UAAUQ,MAAM0G,IAAM,OAG1B,EAGApH,eAAeuE,QAAU,WACvB9E,WAAaD,UAAUE,aACvBC,YAAcP,WAAWM,aACzBF,UAAUkB,MAAMqG,OAAM,OACtB3H,WAAWsB,MAAMqG,OAAS,MAC1B/G,eAAeU,MAAMC,QAAU,OAC/BV,eAAeS,MAAMC,QAAU,QAC/BpB,MAAMmB,MAAMC,QAAU,MACxB,EAGAV,eAAesE,QAAU,WAEJ,GAAf5E,aACFH,UAAUkB,MAAMqG,OAAM,GAAAC,OAAMvH,WAAU,MACtCL,WAAWsB,MAAMqG,OAAM,eAAAC,OAAkBvH,WAAU,SAEnDL,WAAWsB,MAAMqG,OAAS,qBAC1BvH,UAAUkB,MAAMqG,OAAS,SAG3B9G,eAAeS,MAAMC,QAAU,OAC/BX,eAAeU,MAAMC,QAAU,QAC/BpB,MAAMmB,MAAMC,QAAU,OACxB,EAGAR,SAASoE,QAAU,WACjBrE,UAAUQ,MAAMC,QAAU,OAC1BR,SAASO,MAAMC,QAAU,MAC3B,EA+IAN,UAAUgH,iBAAiB,SAAS,SAAU5G,GAC5CuF,QAAQvF,EACV,IAcAa,OAAO+F,iBAAiB,oBAAoB,WAC1ChH,UAAUgB,OACZ","file":"../litewebchat_input.min.js","sourcesContent":["// !参考资料来源:\n// !https://blog.csdn.net/weixin_40629244/article/details/104642683\n// !https://github.com/jrainlau/chat-input-box\n// !https://www.zhihu.com/question/20893119/answer/19452676\n// !致谢:感谢@jrainlau提供的思路和代码,我在他的富文本编辑器基础上进行了修改,使其能够在聊天输入框中使用\n// ————YubaC 2023.1.23\n\n// --------------------------------\n// 上半部分的聊天区域\nlet upperChild = document.querySelector('.lite-chatbox');\n// 分界线\nlet oLine = document.querySelector('.lite-chatinput hr');\n// 下半部分的输入框区域\nlet downChild = document.querySelector('.lite-chatinput');\n\nvar downHeight = downChild.clientHeight;\nvar upperHeight = upperChild.clientHeight;\n\n// 以下为输入框区域的按钮\nvar emojiBtn = document.getElementById(\"emojiBtn\"); // 表情按钮\nvar imageBtn = document.getElementById(\"imageBtn\"); // 图片按钮\nvar fileBtn = document.getElementById(\"fileBtn\"); // 文件按钮\nvar editFullScreen = document.getElementById(\"editFullScreen\"); // 全屏按钮\nvar exitFullScreen = document.getElementById(\"exitFullScreen\"); // 退出全屏按钮\nvar emojiMart = document.getElementById(\"emojiMart\"); // 表情面板\nvar toolMusk = document.getElementById(\"toolMusk\"); // 表情面板遮罩\nvar sendBtn = document.getElementById(\"sendBtn\"); // 发送按钮\nvar chatInput = document.querySelector('.lite-chatinput>.chatinput'); // 输入框\n// --------------------------------\n\n// Emoji Mart(表情面板)设置及唤起\nvar pickerOptions = {\n \"locale\": \"zh\",\n onEmojiSelect: function (e) {\n // console.log(e.native);\n emojiMart.style.display = \"none\";\n toolMusk.style.display = \"none\";\n insertAtCursor(chatInput, e.native);\n // insertEmoji(e.native);\n }\n}\nvar picker = new EmojiMart.Picker(pickerOptions);\nemojiMart.appendChild(picker);\n\n// 负责在光标处插入文字的函数\nfunction insertAtCursor(myField, myValue) {\n var editor = myField;\n var html = myValue;\n editor.focus();\n\n if (window.getSelection) {\n var selection = window.getSelection();\n if (selection.getRangeAt && selection.rangeCount) {\n var range = selection.getRangeAt(0);\n range.deleteContents();\n var element = document.createElement('div');\n element.innerHTML = html;\n\n var node;\n var lastNode;\n var fragment = document.createDocumentFragment();\n\n while ((node = element.firstChild)) {\n lastNode = fragment.appendChild(node);\n };\n\n range.insertNode(fragment);\n if (lastNode) {\n range = range.cloneRange();\n range.setStartAfter(lastNode);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n };\n }\n\n } else if (document.selection && document.selection.type != 'Control') {\n editor.focus();\n var range = document.selection.createRange();\n range.pasteHTML(html);\n editor.focus();\n }\n}\n\n// 调整聊天区域和输入框区域比例的函数\noLine.onmousedown = function (ev) {\n // 更改oLine颜色为蓝色,方便查看分界线\n const olineOriBgColor = oLine.style.backgroundColor;\n oLine.style.backgroundColor = \"#1E90FF\";\n var iEvent = ev || event;\n var dy = iEvent.clientY; //当你第一次单击的时候,存储y轴的坐标。//相对于浏览器窗口\n upperHeight = upperChild.offsetHeight;\n downHeight = downChild.offsetHeight;\n document.onmousemove = function (ev) {\n var iEvent = ev || event;\n var diff = iEvent.clientY - dy; //移动的距离(向上滑时为负数,下滑时为正数)\n if (100 < (upperHeight + diff) && 100 < (downHeight - diff)) {\n //两个div的最小高度均为100px\n upperChild.style.height = `calc(100% - ${downHeight - diff}px)`;\n downChild.style.height = (downHeight - diff) + 'px';\n }\n };\n document.onmouseup = function () {\n // 更改oLine颜色为原色\n oLine.style.backgroundColor = olineOriBgColor;\n document.onmousedown = null;\n document.onmousemove = null;\n };\n return false;\n}\n\n// 显示表情输入框\nemojiBtn.onclick = function () {\n emojiMart.style.display = \"block\";\n toolMusk.style.display = \"block\";\n\n let emojiHeight = emojiMart.offsetHeight;\n downHeight = downChild.clientHeight;\n upperHeight = upperChild.clientHeight;\n\n if (emojiHeight < upperHeight) {\n emojiMart.style.bottom = `${downHeight + 3}px`\n emojiMart.style.top = '';\n } else {\n emojiMart.style.bottom = ''\n emojiMart.style.top = '10px';\n }\n\n}\n\n// 全屏编辑文字\neditFullScreen.onclick = function () {\n downHeight = downChild.clientHeight;\n upperHeight = upperChild.clientHeight;\n downChild.style.height = `100%`;\n upperChild.style.height = \"0px\";\n editFullScreen.style.display = \"none\";\n exitFullScreen.style.display = \"block\";\n oLine.style.display = \"none\";\n}\n\n// 退出全屏编辑文字\nexitFullScreen.onclick = function () {\n // 防呆不防傻,用于避免上部聊天窗口被压到没有高度后出现异常\n if (upperHeight != 0) {\n downChild.style.height = `${downHeight}px`;\n upperChild.style.height = `calc(100% - ${downHeight}px)`;\n } else {\n upperChild.style.height = \"calc(100% - 150px)\";\n downChild.style.height = \"150px\";\n }\n\n exitFullScreen.style.display = \"none\";\n editFullScreen.style.display = \"block\";\n oLine.style.display = \"block\";\n}\n\n// 隐藏musk和表情输入框\ntoolMusk.onclick = function () {\n emojiMart.style.display = \"none\";\n toolMusk.style.display = \"none\";\n}\n\n// 将图片插入到输入框中\nfunction addImage(file) {\n new Promise((resolve, reject) => {\n // console.log(file);\n // 获取file的src\n var reader = new FileReader();\n reader.onload = function (e) {\n var src = e.target.result;\n var img = new Image();\n img.src = src;\n\n // *这里的方法已经转移到了css里,暂时弃用\n // // 为了防止图片在输入框内显示过大不好编辑\n // img.style.width = \"100px\";\n // 将img从HEMLElement转化为字符串\n // 例如,转化结束后为''\n var imgStr = img.outerHTML;\n // 将img字符串插入到输入框中\n insertAtCursor(chatInput, imgStr);\n }\n reader.readAsDataURL(file);\n })\n}\n\n// 上传图片、文件\nfunction inputFile(settings) {\n console.log(settings);\n // -----------------设置最大图片大小及数量-----------------\n if (settings.maxImageSize != undefined) {\n var maxImageSize = settings.maxImageSize;\n } else {\n var maxImageSize = -1;\n }\n\n if (settings.maxImageNumber != undefined) {\n var maxImageNumber = settings.maxImageNumber;\n } else {\n var maxImageNumber = -1;\n }\n\n if (settings.enable) {\n // -----------------上传图片的按钮-----------------\n imageBtn.onclick = function () {\n var imageInput = document.createElement('input');\n imageInput.type = 'file';\n imageInput.accept = 'image/*';\n imageInput.multiple = true;\n imageInput.style.display = 'none';\n imageInput.onchange = function () {\n // 获取输入框内图片数量\n // 获取文件\n var imgNum = chatInput.getElementsByTagName('img').length;\n for (var i = 0; i < this.files.length; i++) {\n if (maxImageNumber == -1 || imgNum < maxImageNumber) {\n // 如果大小超过限制,改用文件上传\n if ((maxImageSize == -1 || this.files[i].size <= maxImageSize)) {\n imgNum++;\n addImage(this.files[i]);\n } else {\n sendFile(this.files[i]);\n }\n }\n }\n }\n // 触发点击事件\n imageInput.click();\n }\n // -----------------上传文件的按钮-----------------\n let sendFile = settings.sendFileFunc;\n // 上传文件按钮\n fileBtn.onclick = function () {\n // 创建一个隐藏的上传文件的input,再借助点击这个input来上传文件\n var fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.multiple = true;\n fileInput.style.display = 'none';\n fileInput.onchange = function () {\n // 获取文件\n for (var i = 0; i < this.files.length; i++) {\n var file = this.files[i];\n sendFile(file);\n }\n }\n // 触发点击事件\n fileInput.click();\n }\n\n // -----------------拖拽上传-----------------\n if (settings.enableDrop) {\n // 当downChild有文件被拖入时,也调用上传文件的函数\n downChild.ondrop = function (e) {\n e.preventDefault();\n // 阻止火狐浏览器默认打开文件的行为\n e.stopPropagation();\n downChild.style.border = \"none\";\n // 获取被拖拽的文件并上传\n var imgNum = chatInput.getElementsByTagName('img').length;\n for (var i = 0; i < e.dataTransfer.files.length; i++) {\n var file = e.dataTransfer.files[i];\n // 如果是图片,直接插入到输入框中\n if (file.type.indexOf(\"image\") == 0) {\n if (maxImageNumber == -1 || imgNum < maxImageNumber) {\n // 如果大小超过限制,改用文件上传\n if ((maxImageSize == -1 || file.size <= maxImageSize)) {\n addImage(file);\n imgNum++;\n } else {\n sendFile(file);\n }\n }\n } else {\n sendFile(file);\n }\n }\n }\n\n // 当downChild有文件被拖入时,改变downChild的边框颜色\n downChild.ondragover = function (e) {\n e.preventDefault();\n downChild.style.border = \"3px solid #1E90FF\";\n }\n\n // 当downChild有文件被拖入后离开时,改回downChild的边框颜色\n downChild.ondragleave = function (e) {\n e.preventDefault();\n downChild.style.border = \"none\";\n }\n }\n } else {\n // 如果不允许上传,那么删除事件\n imageBtn.onclick = null;\n fileBtn.onclick = null;\n // 删除拖拽事件\n downChild.ondrop = null;\n downChild.ondragover = null;\n downChild.ondragleave = null;\n }\n}\n\n// TODO:可能富文本输入框的粘贴部分需要对Chrome浏览器做部分额外适配,以优化体验\n// 无格式粘贴\nchatInput.addEventListener('paste', function (e) {\n onPaste(e);\n})\n\n//格式化粘贴文本方法\nfunction onPaste(event) {\n // 如果粘贴的是文本,就清除格式后粘贴\n if (event.clipboardData && event.clipboardData.getData) {\n var text = event.clipboardData.getData('text/plain');\n if (text) {\n event.preventDefault();\n document.execCommand('insertText', false, text);\n }\n }\n}\n\nwindow.addEventListener('DOMContentLoaded', function () {\n chatInput.focus();\n});\n"]} -------------------------------------------------------------------------------- /src/main/resources/static/js/map/litewebchat_render.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["render.js"],"names":["TipsType","tipsNormal","tipsPrimary","tipsSuccess","tipsInfo","tipsWarning","tipsDanger","TitleType","admin","owner","beforeRenderingHTML","data","chatboxClass","htmlStr","chatBox","document","querySelector","i","length","isRender","messageType","indexOf","renderTipHtml","html","renderMessageHtml","insertAdjacentHTML","setTimeout","scrollHeight","clientHeight","scrollTop","position","diamond","headIcon","renderTitleHtml","htitle","htitleType","escapeHtml","name","content","css","unsafe","replace","c","charCodeAt","slice"],"mappings":";;;;;;AAAA,IAAMA,QAAQ,GAAG;EACfC,UAAU,EAAE,MAAM;EAClBC,WAAW,EAAE,cAAc;EAC3BC,WAAW,EAAE,cAAc;EAC3BC,QAAQ,EAAE,WAAW;EACrBC,WAAW,EAAE,cAAc;EAC3BC,UAAU,EAAE;AACd,CAAC;AACD,IAAMC,SAAS,GAAG;EAChBC,KAAK,EAAE,OAAO;EACdC,KAAK,EAAE;AACT,CAAC;AAED,SAASC,mBAAmB,CAACC,IAAI,EAAEC,YAAY,EAAE;EAC/C,IAAIC,OAAO,GAAG,EAAE;EAChB,IAAIC,OAAO,GAAGC,QAAQ,CAACC,aAAa,CAACJ,YAAY,CAAC;EAClD,KAAK,IAAIK,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGN,IAAI,CAACO,MAAM,EAAED,CAAC,EAAE,EAAE;IACpC,IAAIN,IAAI,CAACM,CAAC,CAAC,CAACE,QAAQ,EAAE;MACpB;IACF;IACA,IAAIR,IAAI,CAACM,CAAC,CAAC,CAACG,WAAW,CAACC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE;MAC9CR,OAAO,IAAIS,aAAa,CAACX,IAAI,CAACM,CAAC,CAAC,CAACM,IAAI,EAAEvB,QAAQ,CAACW,IAAI,CAACM,CAAC,CAAC,CAACG,WAAW,CAAC,IAAI,MAAM,CAAC;IACjF,CAAC,MAAM;MACLP,OAAO,IAAIW,iBAAiB,CAACb,IAAI,CAACM,CAAC,CAAC,CAAC;IACvC;IACAN,IAAI,CAACM,CAAC,CAAC,CAACE,QAAQ,GAAG,IAAI;EACzB;EAEAL,OAAO,CAACW,kBAAkB,CAAC,WAAW,EAACZ,OAAO,CAAC;EAC/Ca,UAAU,CAAC,YAAM;IACf,IAAIZ,OAAO,CAACa,YAAY,GAAGb,OAAO,CAACc,YAAY,EAAE;MAC/Cd,OAAO,CAACe,SAAS,GAAGf,OAAO,CAACa,YAAY;MACxCb,OAAO,GAAG,EAAE;MACZD,OAAO,GAAG,EAAE;IACd;EACF,CAAC,EAAE,GAAG,CAAC;AACT;AAEA,SAASW,iBAAiB,CAACb,IAAI,EAAE;EAC/B,+BAAuBA,IAAI,CAACmB,QAAQ,qDACPnB,IAAI,CAACoB,OAAO,GAAG,EAAE,GAAG,QAAQ,sBAAUpB,IAAI,CAACqB,QAAQ,4HAEpEC,eAAe,CAACtB,IAAI,CAACuB,MAAM,EAAE3B,SAAS,CAACI,IAAI,CAACwB,UAAU,CAAC,IAAI,EAAE,CAAC,iCACxDC,UAAU,CAACzB,IAAI,CAAC0B,IAAI,CAAC,IAAI,QAAQ,uEAErB1B,IAAI,CAACS,WAAW,KAAK,KAAK,GAAGT,IAAI,CAACY,IAAI,GAAGa,UAAU,CAACzB,IAAI,CAACY,IAAI,CAAC;AAE9F;AAEA,SAASU,eAAe,CAACK,OAAO,EAAEC,GAAG,EAAE;EACrC,IAAI,CAACD,OAAO,EAAE,OAAO,EAAE;EACvB,sCAA8BC,GAAG,6CAAgCD,OAAO;AAC1E;AAEA,SAAShB,aAAa,CAACgB,OAAO,EAAEC,GAAG,EAAE;EACnC,IAAI,CAACD,OAAO,EAAE,OAAO,EAAE;EACvB,mDAAyCC,GAAG,+CAAkCH,UAAU,CAACE,OAAO,CAAC;AACnG;;AAEA;AACA;AACA,SAASF,UAAU,CAACI,MAAM,EAAE;EAC1B,OAAOA,MAAM,aAANA,MAAM,uBAANA,MAAM,CAAEC,OAAO,CACpB,yDAAyD,EACzD,UAAAC,CAAC;IAAA,OAAI,IAAI,GAAG,CAAC,KAAK,GAAGA,CAAC,CAACC,UAAU,CAAC,CAAC,CAAC,EAAEC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG;EAAA,EACtD;AACH","file":"../litewebchat_render.js","sourcesContent":["const TipsType = {\n tipsNormal: 'tips',\n tipsPrimary: 'tips-primary',\n tipsSuccess: 'tips-success',\n tipsInfo: 'tips-info',\n tipsWarning: 'tips-warning',\n tipsDanger: 'tips-danger',\n};\nconst TitleType = {\n admin: 'admin',\n owner: 'owner',\n};\n\nfunction beforeRenderingHTML(data, chatboxClass) {\n let htmlStr = '';\n let chatBox = document.querySelector(chatboxClass);\n for (let i = 0; i < data.length; i++) {\n if (data[i].isRender) {\n continue;\n }\n if (data[i].messageType.indexOf('tips') !== -1) {\n htmlStr += renderTipHtml(data[i].html, TipsType[data[i].messageType] || 'tips');\n } else {\n htmlStr += renderMessageHtml(data[i]);\n }\n data[i].isRender = true;\n }\n\n chatBox.insertAdjacentHTML('beforeend',htmlStr);\n setTimeout(() => {\n if (chatBox.scrollHeight > chatBox.clientHeight) {\n chatBox.scrollTop = chatBox.scrollHeight;\n chatBox = '';\n htmlStr = '';\n }\n }, 300);\n}\n\nfunction renderMessageHtml(data) {\n return `
\n \n \n ${renderTitleHtml(data.htitle, TitleType[data.htitleType] || '')}\n ${escapeHtml(data.name) || ' '}\n \n ${data.messageType === 'raw' ? data.html : escapeHtml(data.html)}\n
`;\n}\n\nfunction renderTitleHtml(content, css) {\n if (!content) return '';\n return `${content}`;\n}\n\nfunction renderTipHtml(content, css) {\n if (!content) return '';\n return `
${escapeHtml(content)}
`;\n}\n\n// 转义 C0 Controls and Basic Latin 中非数字和字母,C1 Controls and Latin-1 Supplement 全部\n// https://www.w3schools.com/charsets/ref_html_utf8.asp\nfunction escapeHtml(unsafe) {\n return unsafe?.replace(\n /[\\u0000-\\u002F\\u003A-\\u0040\\u005B-\\u0060\\u007B-\\u00FF]/g,\n c => '&#' + ('000' + c.charCodeAt(0)).slice(-4) + ';'\n )\n}\n"]} -------------------------------------------------------------------------------- /src/main/resources/static/js/map/litewebchat_render.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["render.js"],"names":["TipsType","tipsNormal","tipsPrimary","tipsSuccess","tipsInfo","tipsWarning","tipsDanger","TitleType","admin","owner","beforeRenderingHTML","data","chatboxClass","htmlStr","chatBox","document","querySelector","i","length","isRender","messageType","indexOf","renderTipHtml","html","renderMessageHtml","insertAdjacentHTML","setTimeout","scrollHeight","clientHeight","scrollTop","concat","position","diamond","headIcon","renderTitleHtml","htitle","htitleType","escapeHtml","name","content","css","unsafe","replace","c","charCodeAt","slice"],"mappings":";;;;GAAA,aAAA,IAAMA,SAAW,CACfC,WAAY,OACZC,YAAa,eACbC,YAAa,eACbC,SAAU,YACVC,YAAa,eACbC,WAAY,eAERC,UAAY,CAChBC,MAAO,QACPC,MAAO,SAGT,SAASC,oBAAoBC,EAAMC,GAGjC,IAFA,IAAIC,EAAU,GACVC,EAAUC,SAASC,cAAcJ,GAC5BK,EAAI,EAAGA,EAAIN,EAAKO,OAAQD,IAC3BN,EAAKM,GAAGE,YAGiC,IAAzCR,EAAKM,GAAGG,YAAYC,QAAQ,QAC9BR,GAAWS,cAAcX,EAAKM,GAAGM,KAAMvB,SAASW,EAAKM,GAAGG,cAAgB,QAExEP,GAAWW,kBAAkBb,EAAKM,IAEpCN,EAAKM,GAAGE,UAAW,GAGrBL,EAAQW,mBAAmB,YAAYZ,GACvCa,YAAW,WACLZ,EAAQa,aAAeb,EAAQc,eACjCd,EAAQe,UAAYf,EAAQa,aAC5Bb,EAAU,GACVD,EAAU,GAEd,GAAG,IACL,CAEA,SAASW,kBAAkBb,GACzB,MAAA,gBAAAmB,OAAuBnB,EAAKoB,SAAQ,0CAAAD,OACPnB,EAAKqB,QAAU,GAAK,SAAQ,WAAAF,OAAUnB,EAAKsB,SAAQ,4GAAAH,OAEpEI,gBAAgBvB,EAAKwB,OAAQ5B,UAAUI,EAAKyB,aAAe,IAAG,wBAAAN,OACxDO,WAAW1B,EAAK2B,OAAS,SAAQ,4DAAAR,OAEA,QAArBnB,EAAKS,YAAwBT,EAAKY,KAAOc,WAAW1B,EAAKY,MAAK,sBAE9F,CAEA,SAASW,gBAAgBK,EAASC,GAChC,OAAKD,EACL,uBAAAT,OAA8BU,EAAG,iCAAAV,OAAgCS,EAAO,WADnD,EAEvB,CAEA,SAASjB,cAAciB,EAASC,GAC9B,OAAKD,EACL,kCAAAT,OAAyCU,EAAG,mCAAAV,OAAkCO,WAAWE,GAAQ,iBAD5E,EAEvB,CAIA,SAASF,WAAWI,GAClB,OAAOA,aAAM,EAANA,EAAQC,QACb,2DACA,SAAAC,GAAC,MAAI,MAAQ,MAAQA,EAAEC,WAAW,IAAIC,OAAO,GAAK,GAAG,GAEzD","file":"../litewebchat_render.min.js","sourcesContent":["const TipsType = {\n tipsNormal: 'tips',\n tipsPrimary: 'tips-primary',\n tipsSuccess: 'tips-success',\n tipsInfo: 'tips-info',\n tipsWarning: 'tips-warning',\n tipsDanger: 'tips-danger',\n};\nconst TitleType = {\n admin: 'admin',\n owner: 'owner',\n};\n\nfunction beforeRenderingHTML(data, chatboxClass) {\n let htmlStr = '';\n let chatBox = document.querySelector(chatboxClass);\n for (let i = 0; i < data.length; i++) {\n if (data[i].isRender) {\n continue;\n }\n if (data[i].messageType.indexOf('tips') !== -1) {\n htmlStr += renderTipHtml(data[i].html, TipsType[data[i].messageType] || 'tips');\n } else {\n htmlStr += renderMessageHtml(data[i]);\n }\n data[i].isRender = true;\n }\n\n chatBox.insertAdjacentHTML('beforeend',htmlStr);\n setTimeout(() => {\n if (chatBox.scrollHeight > chatBox.clientHeight) {\n chatBox.scrollTop = chatBox.scrollHeight;\n chatBox = '';\n htmlStr = '';\n }\n }, 300);\n}\n\nfunction renderMessageHtml(data) {\n return `
\n \n \n ${renderTitleHtml(data.htitle, TitleType[data.htitleType] || '')}\n ${escapeHtml(data.name) || ' '}\n \n ${data.messageType === 'raw' ? data.html : escapeHtml(data.html)}\n
`;\n}\n\nfunction renderTitleHtml(content, css) {\n if (!content) return '';\n return `${content}`;\n}\n\nfunction renderTipHtml(content, css) {\n if (!content) return '';\n return `
${escapeHtml(content)}
`;\n}\n\n// 转义 C0 Controls and Basic Latin 中非数字和字母,C1 Controls and Latin-1 Supplement 全部\n// https://www.w3schools.com/charsets/ref_html_utf8.asp\nfunction escapeHtml(unsafe) {\n return unsafe?.replace(\n /[\\u0000-\\u002F\\u003A-\\u0040\\u005B-\\u0060\\u007B-\\u00FF]/g,\n c => '&#' + ('000' + c.charCodeAt(0)).slice(-4) + ';'\n )\n}\n"]} -------------------------------------------------------------------------------- /src/main/resources/templates/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | LiteChat_Frame 8 | 9 | 10 | 11 | 33 | 34 | 35 |
36 |
37 |
38 |
39 | 40 | 41 |
42 |
43 |
44 | 45 | 48 |
49 |
50 | 51 |
52 |
53 | 54 | 55 | 56 | 57 | 67 | 84 | 135 | 136 | -------------------------------------------------------------------------------- /src/test/java/com/chatweb/WebtapApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.chatweb; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 5 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 6 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 7 | 8 | import java.lang.management.ManagementFactory; 9 | 10 | import javax.management.ObjectName; 11 | 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.test.context.ActiveProfiles; 18 | import org.springframework.test.context.TestPropertySource; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | import org.springframework.test.web.servlet.MockMvc; 21 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 22 | import org.springframework.web.context.WebApplicationContext; 23 | 24 | @RunWith(SpringRunner.class) 25 | @SpringBootTest 26 | // Enable JMX so we can test the MBeans (you can't do this in a properties file) 27 | @TestPropertySource(properties = { "spring.jmx.enabled:true", 28 | "spring.datasource.jmx-enabled:true" }) 29 | @ActiveProfiles("scratch") 30 | public class WebtapApplicationTests { 31 | 32 | @Autowired 33 | private WebApplicationContext context; 34 | 35 | private MockMvc mvc; 36 | 37 | @Before 38 | public void setUp() { 39 | this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); 40 | } 41 | 42 | @Test 43 | public void testHome() throws Exception { 44 | 45 | this.mvc.perform(get("/")).andExpect(status().isOk()) 46 | .andExpect(content().string("Bath")); 47 | } 48 | 49 | @Test 50 | public void testJmx() throws Exception { 51 | assertThat(ManagementFactory.getPlatformMBeanServer() 52 | .queryMBeans(new ObjectName("jpa.sample:type=ConnectionPool,*"), null)) 53 | .hasSize(1); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/resources/application-scratch.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.name=scratchdb 2 | spring.jmx.default-domain=jpa.sample 3 | 4 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "--- begin start server ---" 4 | 5 | # start server 6 | nohup java -server -Xms512m -Xmx512m -Xss512K -jar chatweb-1.0.jar>/dev/null 2>&1& 7 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | serviceName="chatweb-1.0.jar" 2 | 3 | ps -efww|grep -w "$serviceName"|grep -v grep|cut -c 9-15|xargs kill -9|| exit 0 4 | --------------------------------------------------------------------------------