diff --git a/README.md b/README.md index 740c4d3e..57349fca 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,23 @@ ## 平台简介 -[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/ageerle/ruoyi-ai/blob/master/LICENSE) -[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=ruoyi-chatgpt) -
-[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.0.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus) -[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.0-blue.svg)]() -[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]() > 基于ruoyi-plus实现AI聊天和绘画功能-后端 -后台管理界面使用elementUI 服务端使用Java17+SpringBoot3.X -> 项目文档:https://1m29197bp6.k.topthink.com/@ruoyi-ai/xiangmuchushihua.html +> 本项目完全开源免费! +后台管理界面使用elementUI服务端使用Java17+SpringBoot3.X + +> Docker部署文档:https://easydoc.net/s/80136029 实现功能 -1. 支持ChatGPT4,Dall-E-3,ChatGPT-4-All(文件对话,图文对话)模型 -2. 支持GPTS 可以使用Openai的所有的GPTs -3. 支持语音克隆 -4. 支持文生图模型 MidJourney(mj换脸,混图,重绘,角色一致性) +1. 支持ChatGPT4,Dall-E-3,ChatGPT-4-All模型 +2. 支持语音克隆(只需5分钟素材,即可克隆任意音色,基于GPT-SoVITS实现) +3. 支持GPTS 可以使用Openai的所有的GPTs +4. 支持文生图模型 MidJourney 5. 支持微信小程序 6. 支持个人二维码实时到账(易支付) -7. 支持suno-v3(文生音乐) +7. 支持斗鱼、B站等直播间弹幕监听和AI自动回复 8. 支持个人微信接入ChatGPT -9. 支持stable-diffusion(对话式) -10. 支持私有知识库 - ->测试功能: 微信机器人 -> +>测试功能: 私有知识库 +> >项目地址 + ## 小程序演示
drawing @@ -49,38 +43,41 @@
drawing drawing - drawing - drawing
-## 绘图(mj换脸、mj混图、mj角色一致(英文)、sd绘图) +## MJ绘图
drawing drawing - drawing - drawing +
+ +## 微信智能助手 +
+ drawing
## 语音克隆 -
- drawing - drawing -
-## 知识库 -
- drawing - drawing - drawing - drawing - drawing - drawing -
-## 后台管理 +https://github.com/ageerle/ruoyi-ai/assets/32251822/c3dd75eb-c5bd-4ab0-93bf-9221f4888827 + + + +## 弹幕助手 + + +https://github.com/ageerle/ruoyi-ai/assets/32251822/2d809d94-6cfb-41b1-9dc0-a72ccfc63ba2 + + + +## 私有知识库管理
- drawing - drawing + drawing + drawing + drawing + drawing + drawing + drawing
## 进群学习 @@ -88,6 +85,12 @@ drawing
+## 支持一下 + +
+ drawing +
+ ## 参考项目
  1. https://github.com/Grt1228/chatgpt-java
  2. diff --git a/image/25.png b/image/25.png new file mode 100644 index 00000000..26043560 Binary files /dev/null and b/image/25.png differ diff --git a/img.png b/img.png new file mode 100644 index 00000000..28d4cb8a Binary files /dev/null and b/img.png differ diff --git a/ruoyi-admin/Dockerfile b/ruoyi-admin/Dockerfile index 386e378d..bf14c4ee 100644 --- a/ruoyi-admin/Dockerfile +++ b/ruoyi-admin/Dockerfile @@ -1,6 +1,12 @@ #基础镜像 FROM findepi/graalvm:java17-native +# 设置环境变量 +ENV LANG C.UTF-8 +ENV LANGUAGE C.UTF-8 +ENV LC_ALL C.UTF-8 +ENV SERVER_PORT=6039 + MAINTAINER ageerle RUN mkdir -p /ruoyi/server/logs \ @@ -11,12 +17,13 @@ RUN mkdir -p /ruoyi/server/logs \ #工作空间 WORKDIR /ruoyi/server -ENV SERVER_PORT=6039 + EXPOSE ${SERVER_PORT} ADD ./target/ruoyi-admin.jar ./app.jar + ENTRYPOINT ["java", \ "-Djava.security.egd=file:/dev/./urandom", \ "-Dserver.port=${SERVER_PORT}", \ @@ -24,3 +31,5 @@ ENTRYPOINT ["java", \ # "-Dskywalking.agent.service_name=ruoyi-server", \ # "-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar", \ "-jar", "app.jar"] + + diff --git a/ruoyi-admin/src/main/java/com/xmzs/controller/IndexController.java b/ruoyi-admin/src/main/java/com/xmzs/controller/IndexController.java index 5a70eaf5..f37d1e42 100644 --- a/ruoyi-admin/src/main/java/com/xmzs/controller/IndexController.java +++ b/ruoyi-admin/src/main/java/com/xmzs/controller/IndexController.java @@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { - /** * 访问首页,提示语 */ diff --git a/ruoyi-admin/src/main/java/com/xmzs/controller/WeChatController.java b/ruoyi-admin/src/main/java/com/xmzs/controller/WeChatController.java index 0bcf57c2..09ba22b0 100644 --- a/ruoyi-admin/src/main/java/com/xmzs/controller/WeChatController.java +++ b/ruoyi-admin/src/main/java/com/xmzs/controller/WeChatController.java @@ -1,9 +1,11 @@ package com.xmzs.controller; import cn.dev33.satoken.annotation.SaIgnore; +import com.xmzs.common.core.domain.R; import com.xmzs.common.wechat.Wechat; +import com.xmzs.common.wechat.controller.LoginController; +import com.xmzs.common.wechat.core.MsgCenter; import com.xmzs.system.cofing.KeywordConfig; -import com.xmzs.system.cofing.QqConfig; import com.xmzs.system.cofing.WechatConfig; import com.xmzs.system.handler.WechatMessageHandler; import com.xmzs.system.service.ISseService; @@ -11,7 +13,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** @@ -39,13 +41,21 @@ public class WeChatController { * 获取微信登录二维码 * */ - @PostMapping("/getQr") - public void getQr() { + @GetMapping("/getQr") + public R getQr() { //微信 if (wechatConfig.getEnable()){ log.info("正在登录微信,请按提示操作:"); - wechatBot = new Wechat(new WechatMessageHandler(sseService, keywordConfig), wechatConfig.getQrPath()); + wechatBot = new Wechat(new WechatMessageHandler(sseService, keywordConfig)); + // 登陆 + LoginController login = new LoginController(); + String qrCode = login.login_1(); + new Thread(login::login_2).start(); wechatBot.start(); + return R.ok(qrCode); + }else { + return R.fail(); } } + } diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 0b7d2735..60520190 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -49,9 +49,10 @@ spring: driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) - url: jdbc:mysql://127.0.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true - username: ry-vue - password: ry-vue + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + # 从库数据源 # slave: @@ -103,9 +104,9 @@ spring: spring.data: redis: # 地址 - host: localhost + host: ${REDIS_HOST} # 端口,默认为6379 - port: 6379 + port: ${REDIS_PORT} # 数据库索引 database: 0 # 密码(如没有密码请注释掉) diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index b4324f26..cded7aa3 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -48,7 +48,7 @@ server: # 日志配置 logging: level: - com.xmzs: @logging.level@ + com.xmzs: '@logging.level@' org.springframework: warn config: classpath:logback-plus.xml @@ -69,7 +69,7 @@ spring: # 国际化资源文件路径 basename: i18n/messages profiles: - active: @profiles.active@ + active: '@profiles.active@' # 文件上传 servlet: multipart: @@ -332,26 +332,24 @@ keyword: # ai语音指令(TTS模型 https://platform.openai.com/docs/api-reference/audio) audio: "ai语音" +#绘画价格配置(元) mj: - api-secret: 'sk-xx' - task-store: - type: in_memory - timeout: 30d - translate-way: gpt - # proxy: - # host: 127.0.0.1 - # port: 10809 - ng-discord: - server: 'https://xxx.pandarobot.chat/' - cdn: 'https://xxx.pandarobot.chat/' - wss: 'https://xxx.pandarobot.chat/' - openai: - gpt-api-url: 'https://api.pandarobot.chat/' - gpt-api-key: 'sk-xx' - accounts: - - guild-id: 'xx' - channel-id: 'xx' - user-token: 'xx' + # 放大 + upsample: 0.1 + # 变化 + change: 0.3 + # 图生图 + blend: 0.3 + # 图生文 + describe: 0.1 + # 文生图 + imagine: 0.3 + # 局部重绘 + inpaint: 0.3 + # 提示词分析 + shorten: 0.1 + # 换脸 + faceSwapping: 0.3 --- # mail 邮件发送 mail: @@ -365,7 +363,7 @@ mail: # 用户名(注意:如果使用foxmail邮箱,此处user为qq号) user: ageerle@163.com # 密码(填写授权码) - pass: xx + pass: ${MAIL_PASS} # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。 starttlsEnable: true # 使用SSL安全连接 @@ -375,15 +373,14 @@ mail: # Socket连接超时值,单位毫秒,缺省值不超时 connectionTimeout: 0 -# chatgpt配置信息 +# chatgpt和mj共用一个key chat: - apiKey: 'sk-xxx' - apiHost: 'https://api.pandarobot.chat/' - + apiKey: ${CHAT_API_KEY} + apiHost: ${CHAT_API_HOST} # 支付配置信息 pay: - pid: 'xxx' - key: 'xxx' + pid: ${PAY_PID} + key: ${PAY_KEY} payUrl: 'https://pay.pandarobot.chat/mapi.php' notify_url: 'https://www.pandarobot.chat/pay/returnUrl' return_url: 'https://www.pandarobot.chat/pay/notifyUrl' diff --git a/ruoyi-admin/src/main/resources/static/assets/css/iconfont.css b/ruoyi-admin/src/main/resources/static/assets/css/iconfont.css new file mode 100644 index 00000000..f429cec1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/assets/css/iconfont.css @@ -0,0 +1 @@ +@font-face{font-family:"iconfont";src:url('iconfont.eot?t=1538765677655');src:url('iconfont.eot?t=1538765677655#iefix') format('embedded-opentype'),url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAABD4AAsAAAAAGEwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8dkrQY21hcAAAAYAAAACyAAACYAVmbC5nbHlmAAACNAAADFkAABC4LpbxRWhlYWQAAA6QAAAAMQAAADYS3OzPaGhlYQAADsQAAAAgAAAAJAfhA4xobXR4AAAO5AAAABYAAAA4OAH//mxvY2EAAA78AAAAHgAAAB4kriBobWF4cAAADxwAAAAfAAAAIAE6AdRuYW1lAAAPPAAAAUUAAAJtPlT+fXBvc3QAABCEAAAAcQAAAJd6PSu0eJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMby4x9zwv4EhhrmBoQEozAiSAwDtxwzDeJzlksENgzAMRX8I0JKC1FNPvXKibMUKLNB5qp46DxKXL7aAb9xb2aCOXqR8xXb0YwAFgCgeIgfCBwEWb6lh1yPSrud46nzDVUrJyIKJLTv2HDhyXqZ1BYhj/SCCat1/FnDCRR1rnNWx1K2ITK9sUCmlPKz0X1Hv++t7asxzR86BwZGHYObY/zE6ls3CkcNgcuzv2TpyHewcmwH2js0JBwdWe3Rshjg79p5lclBttmI/0AAAeJyFN1uMXddVZ+3ned3zuPc87sx9zb1n7jme8Xhmch/neMb2PDL2jJuXPWnsaVQb4zQOCXEIbUzdRhQ1bgLTOhWto5Bg1LilVVUFRClqi0ol5H4gxCdYhY8gBZBQUfgolBAKsu+wzhnbSvjh3v1Ye6211z57rbXXXlsRirJzk/4n+QdlWTmqfFq5pFxWXlUU6DWJZ5GoEyfYdWbJ0IIIu8ES6SMpXKIZliYNm5CXJcrTWbCgCSn0lsghGETDQTwHcTJLo45F5O1O2MjkdxrgNQkdxB3hBb20ewfIliDZlULfdOqernt1Ry2rS0+//LUv/vKSWlHvIn3HFE5v5dh0Na6XCSnXk+r0AxNRz2GWU775FrODgJlVHz6/+quP3EME0RgzO/Fs1eu0m6bZnJgsV+fijsmYyhiZf+Tjz+mWqlo6XNJNHctbpkmYF1jCgz+0qhNxu2oJtvjYkTg+8tgiE1a1HU8gyrT0ycMHZi11vBN7XtwZV63ZVv3wpG6Z4oLuqWrZ09XR/7QG6zP4kapX8nRRrnumapZ1vYydVy8LHdEq8WbWBy0QOuINQ93tBMPpqup5qqIoBKtCf0i+oJjKmKJ0OwnEg7QXQuCJdiceZJD2Ak+CoF+sWKNfsepdC65Y9Zt/VamV4BWrWyuNnrGuIs6erOdgt55zvFKqT9rIrSgS/WBE3yW3lKZyQNlQHlXOKc8qn0JPiHeNhlbO0iwNwgDHaK5+kGb3pDGNl0gSCyniLI2lBVhyh0BiGoOIkyXI51gAaN4hcuSW7qeB9AKOnCLY5cTpg1y4JzIxC0voAYJOTB8tcdMG3zYNw35+zheUUd8ueTS5dHmMiXJj7pKYBEmzX3ziZbrfYf5bn/z1W+me06MrF9+g9I2LF69Reg1aeomwlwzDsozNErdHJ59tCzBb65+Juweuho1aMN5orqu2xhxH5eRdTVLCKiXHB0KYbBwZrArDaJTB8F+7Ovqm8Oi9nWiSG7VGvNaY/l3t+JPkxV8S2tknzp0afZc9dvzYeclOPrR5EX7L1MnzNWn7tnFKNezHg5c0Mu9Fzbn0SzdI0G1RUu3W7xek5JpYFaWzs6MojJK+4ih15SHU/ovKl5RvKD9Q/l75J+VflP8GAA4alMAFHxKYgWU4Bh+GLXgWLsBnYRt+B67CG/BH8H34y9xqhVFk0C+O1WxulyQLCtOIRMg46eSHMUQMQ74OnrthivQeMg/w1EKSN1lhPS5klBspyT0hysVhCYNuEEqUgibPrdjPelkuGiWRnIVKJMe4UpJmsj+MhkX1I/92Rfb/g8x8iQ5WIPsFIgjTLNwV43t7Ien3DoHMNyHjZcjbML0zH+f60VBmCA8jPwoR72MNscsQkgXc98X7PipO+hGugGugiKxY8TYxu/0BuaS84jjqo5Ssn+D3IOGDYqJCTlZsYndDYTER+2JJhAvOfHKI1e+/b4+5Jm73y5DvV0jUam6fNCPf7w87DtW0Ya8+CMcq+uQ+b65Wao2Xq5zp1ynduyf0GZMYw8rRZIOMe9NbM5QTMug5Npc6N8bMajBGQ3cqaBwlDS5XhaRBve2Q0rKmg2Tttfbxmz/2fN6rhs1J3FY9CGZUlYDTSQeNRB+rdR85CUaFlk3DY77RaASBt88LgvrSE5yrqhZqGKCCUNNcV1QqQDnjptCYITSJ4m3COGM8QDRlPcrUw5NS8jF1nsec2MVvKs5/Xp0JoVFNCJWqnNOKRTjv1UmjAbzV4jUeSFF9qsZrwue+EL4/5/vzbc4FnyhRU/O4p2keq+gmK803qGE+yLmL5X5b2t04xE9gnBioEI4forKK4/By2ZvZOjBMDxqsLAWX0mWurhNq6KShA3EmPvwosVxveCj+gq2yzwElXtmLtgOfUUG4rjIiNCPYV1ddAyNQ2eFUJbqmEiaoNJ2ZioVBh58+tHRgf70GpNT+jY+dpagSzs0yYfsAaD1oxJwCkMgBU8QrA2/cHv2U2FJotmVww17EhmMxhAEedzgWjwiqc+lx0+RCMNyNoQum2oHKWcloErSuOk4pZawP44yFwEBT6cEAwOS2sJjoMY0jQRWccIp6ZpQLHHDbHEeN8LoqtNA3B7bdYuPjDI06phOdcM+QuJ4qkVUtlV1Dr05zPi3k7Ky0OJciajAmNNOhOqlSjdod7nm8ww/yeb6POapGddXhri4JkXp+jeV3zdv0BRrhXTOjLCibykeVi8rvYdQKgzwqLQNehZEM8PCIYZzIvEuzBahQhJJsmIS90IasoGOcikMk5aAbt3Bu2PNlWFDmQGbSz3pehAev0pFJNwu9fmCj+CC/sHDqoB8kVFbydYs/Tg8+uDIGxyxfHKY/InXXLz03c4gx+uoMq9Wotbh5Sl7oM0nnS9g8hJrXOx+TVVev+VQ/r9csSvW5r9QOU4/S2NWpbly59vXLUt+mXxUrU1XQOhuMyhrWZ6SgtEXphmDb5ARmBThVyiszh6T84WHRqVMv072pppT0cItWF6SUnivn+hJnN+uCuYsuvf8haX15e/TS6NtzfV3/+fbzLSqb32xWpyzdvw8XHz3d8gwXJg3Zkt2ubF7pzJdKuuWJv2UnZEmekTClV0vxDB7Wzo+2z2wwKeeq6NBd+LKEfh8/x6NSR2RfGN1W06XeRo3q1X6Tyd8+l9tVFDnK18lfK4YSKpEyV2QSCrSLO6VtgRckUd6G/RwDBSYsqN0P4Iskod1LB3E7yuEDkMPwPXizEceN0Ulsv3U8B/OGPFwgGgXx1oX/H38fxAsx5M1v3oVG/3EXhPch48X4bilysJ136HdooCTKuqJkTciToNk8acU8J+wFoS8R7Ah/NyvGKxfz5yEfLKMjhjlLihxxFAs/TA+AjDpJ0IBXahvd+izA2txwg5ALH5k71d0XHopaERyaW1kH8snTi2f3kg6QdThM4Mx9yysT9Xq1DvVohsx06uTl6iTUWu1m2T+8cuIYXozWVtmDsWqz7gRH7t16GCbapQerr78HH4dzv7AJMO4+asPEzCM4PvPaHyt3c8sb5F8VVfEVpdLGnfDuEkgXN4HmyVwuksvknWQIo9kRPgJSGCa7wwXDIO/041u34t4PRitxH6Afw/V/F7uugPr6L3qN9pQTmEu+iJLTTMg+Sg9klAOYfgQhQlneY06I117+F7cPY55NYOKRJxy7aQcqLUuTTnSHAYnLkPJB2m9jSul6UuyFDmanSEoizGgqnu+F/TBnOgBBmrgDpP9No+s7O1InklwB+apkklD4E1aeumdOFTpoFWe+rxvensqYZrQ65UpJr40Nu7UW/3OqUkPl8iojsHdGSK6FXcelbGIsVOFFwWFNqBjixaekNPdNc+kPuFgXgSXFhyhb4gLkdZWOhWT/oiHGx598khslzzu40Blmmh5U2y3PXzpomg+stcZVXsK7AbOM/vLsuWHF1fB+OZvtd12M8joHVR750Gr1PpZfKLeuqRSoqDY4XQeM6+2f/pskKWGzBASDt9U7drhCX6eninP5MJ7Iwjll4bYZAvkLDtCRK4UvY9jMNZgndthExTMuToLdp17u8YXPL+1mhy0ahHFCFoZHAY4OU/TgjXTzPCHnNzefJuTpEQWmT3jJaam5/tRTq+1Je8VSjUo4Nr9abjuTPvOthU8sfuK55Z8v3/MHydSjHdvZa7utLt5NdTia3hZ7dBUFHs/FHt88f/Mf91Q7Z149/uZ1227U0q0Jx8QIPt5bXHjumc98+uDFA6ZDgLHAcjnt2uX9DXP+0tT0sQf3Tue62NnZ+WcGtKpw1IaNPik1TKep5N0Eg2mYrawSQpTV0cp7GItguPYzePKpx+E7w9FT8Npw9FVCh+++17sOL20+vJnrFgoFb5G/w2in5BFN5oc/93Ef4TYqykU1tfPXD8I5/c6hwjHdP/r9kgvgln6cd4Nk9PZuD2/uokcnz75AsYcfbf0awcz/LxB+AEluyU4GOQO0sd/F0RfOjk5ijyHk2/mLovi2ndfpz+gTyhoOhCKLh9kwEUWCj+l9Ps4z/tyeg9yq2QBT2R46gwwEcvu7voGPtZwpxkBxY/QTTv/stbIDH93gvOHaVqOxR06eqFLXOLnNalWuVe7dOPjZgxtrUFFFtca2t6wKgFO++qeEj35Cz2Fz41s3h8TRT1vz1dKcoYaETJbSUrWtiXOX96zV9CMDqs667qxKB0f08bWplx8Xxmz0ed2h6egbNwCfv8r/AgxxVFsAAAB4nGNgZGBgAOIdr4pmxvPbfGXgZmEAget3r+TC6P///09iYWZOA3I5GJhAogCNrQ5RAAAAeJxjYGRgYG7438AQw8Lw////vyzMDEARFMAHAKDJBnR4nGNhYGBgQcH//7NgiGHKAQBkagQ1AAAAAAAAANQBBAHEBDoFJgWWBgIGLgbyB3gHoAfuCFwAAHicY2BkYGDgYzzBoMQAAkxAzAWEDAz/wXwGACB0AhAAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicbcHLDsIgEAVQbuWhVdtvBIowhA4mdGL061249Rw1qZ9Z/bdgwgkaBhYOZ1ww44ob7liwKhv7Rpyd59ySPM0rUeg6jo1dJc+jiG40Dkux83iYtHtq5lOoiIscWs/DVkmV2Oz+LWwzHUWCUl+qjh+BAAAA') format('woff'),url('iconfont.ttf?t=1538765677655') format('truetype'),url('iconfont.svg?t=1538765677655#iconfont') format('svg')}.iconfont{font-family:"iconfont"!important;font-size:21px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-coding:before{content:"\e62b"}.icon-angleup:before{content:"\e664"}.icon-weibo:before{content:"\e62d"}.icon-csdn:before{content:"\e60a"}.icon-jianshu:before{content:"\e675"}.icon-list:before{content:"\e625"}.icon-iconsf:before{content:"\e600"}.icon-email:before{content:"\e605"}.icon-zhihu:before{content:"\e8de"}.icon-cnblogs:before{content:"\e602"}.icon-juejin:before{content:"\e601"}.icon-mayun:before{content:"\e603"}.icon-github:before{content:"\e6e1"} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/assets/css/onlinewebfonts.css b/ruoyi-admin/src/main/resources/static/assets/css/onlinewebfonts.css new file mode 100644 index 00000000..8e97c002 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/assets/css/onlinewebfonts.css @@ -0,0 +1,8 @@ +@font-face {font-family: "Engravers' Old English BT"; + src: url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot"); /* IE9*/ + src: url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */ + url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff2") format("woff2"), /* chrome firefox */ + url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff") format("woff"), /* chrome firefox */ + url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.ttf") format("truetype"), /* chrome firefox opera Safari, Android, iOS 4.2+*/ + url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.svg#Engravers' Old English BT") format("svg"); /* iOS 4.1- */ +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/assets/css/vno.css b/ruoyi-admin/src/main/resources/static/assets/css/vno.css new file mode 100644 index 00000000..b99730c1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/assets/css/vno.css @@ -0,0 +1,25 @@ +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}.ih-item.circle.effect{margin:0 auto;-webkit-perspective:900px;-moz-perspective:900px;perspective:900px}.ih-item.circle.effect .img{z-index:11;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;transition:all .5s ease-in-out}.ih-item.circle.effect .info{-webkit-transform-style:preserve-3d;-moz-transform-style:preserve-3d;-ms-transform-style:preserve-3d;-o-transform-style:preserve-3d;transform-style:preserve-3d}.ih-item.circle.effect .info .info-back{opacity:1;border-radius:50%;width:100%;height:100%;background:#333}.ih-item.circle.effect .info h2{color:#fff;position:relative;font-size:18px;margin:0 auto;padding-top:40px;height:35px;text-shadow:0 0 1px white,0 1px 2px rgba(0,0,0,0.3)}.ih-item.circle.effect .info p{color:#bbb;padding:0;font-style:italic;padding-left:0;font-size:10px}.ih-item.circle.effect.bottom_to_top .img{-webkit-transform-origin:50% 0;-moz-transform-origin:50% 0;-ms-transform-origin:50% 0;-o-transform-origin:50% 0;transform-origin:50% 0}.ih-item.circle.effect.bottom_to_top a:hover .img{-webkit-transform:rotate3d(1,0,0,180deg);-moz-transform:rotate3d(1,0,0,180deg);-ms-transform:rotate3d(1,0,0,180deg);-o-transform:rotate3d(1,0,0,180deg);transform:rotate3d(1,0,0,180deg)}.ih-item.circle.effect.top_to_bottom .img{-webkit-transform-origin:50% 100%;-moz-transform-origin:50% 100%;-ms-transform-origin:50% 100%;-o-transform-origin:50% 100%;transform-origin:50% 100%}.ih-item.circle.effect.top_to_bottom a:hover .img{-webkit-transform:rotate3d(1,0,0,-180deg);-moz-transform:rotate3d(1,0,0,-180deg);-ms-transform:rotate3d(1,0,0,-180deg);-o-transform:rotate3d(1,0,0,-180deg);transform:rotate3d(1,0,0,-180deg)}.ih-item.circle.effect.left_to_right .img{-webkit-transform-origin:100% 50%;-moz-transform-origin:100% 50%;-ms-transform-origin:100% 50%;-o-transform-origin:100% 50%;transform-origin:100% 50%}.ih-item.circle.effect.left_to_right a:hover .img{-webkit-transform:rotate3d(0,1,0,180deg);-moz-transform:rotate3d(0,1,0,180deg);-ms-transform:rotate3d(0,1,0,180deg);-o-transform:rotate3d(0,1,0,180deg);transform:rotate3d(0,1,0,180deg)}.ih-item.circle.effect.right_to_left .img{-webkit-transform-origin:0 50%;-moz-transform-origin:0 50%;-ms-transform-origin:0 50%;-o-transform-origin:0 50%;transform-origin:0 50%}.ih-item.circle.effect.right_to_left a:hover .img{-webkit-transform:rotate3d(0,1,0,-180deg);-moz-transform:rotate3d(0,1,0,-180deg);-ms-transform:rotate3d(0,1,0,-180deg);-o-transform:rotate3d(0,1,0,-180deg);transform:rotate3d(0,1,0,-180deg)}.ih-item a{color:#333}.ih-item a:hover{text-decoration:none}.ih-item img{width:100%;height:100%}.ih-item.circle{position:relative;width:120px;height:120px;border-radius:50%}.ih-item.circle .img{position:relative;width:120px;height:120px;border-radius:50%}.ih-item.circle .img:before{position:absolute;display:block;content:"";width:100%;height:100%;border-radius:50%;-webkit-transition:all .35s ease-in-out;-moz-transition:all .35s ease-in-out;transition:all .35s ease-in-out}.ih-item.circle .img img{border-radius:50%}.ih-item.circle .info{position:absolute;top:0;bottom:0;left:0;right:0;text-align:center;border-radius:50%;-webkit-backface-visibility:hidden;backface-visibility:hidden}@media all and (max-width:780px){.ih-item.circle .img{position:relative;width:100px;height:100px;border-radius:50%}.ih-item.circle{position:relative;width:100px;height:100px;border-radius:50%}.ih-item.circle .info .info-back h2{font-size:.9em}}.profilepic{text-align:center;display:block;-webkit-box-shadow:0 0 0 2px rgba(255,255,255,0.5),0px 2px 20px 3px rgba(0,0,0,0.25);box-shadow:0 0 0 2px rgba(255,255,255,0.5),0px 2px 20px 3px rgba(0,0,0,0.25);border-radius:300px;width:128px;height:128px;margin:0 auto;position:relative;overflow:hidden;background:#88acdb;-webkit-transition:all .2s ease-in;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-pack:center;-webkit-box-align:center;text-align:center}.animated{-webkit-animation-fill-mode:both;-moz-animation-fill-mode:both;-ms-animation-fill-mode:both;-o-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:1s;-moz-animation-duration:1s;-ms-animation-duration:1s;-o-animation-duration:1s;animation-duration:1s}.animated.hinge{-webkit-animation-duration:1s;-moz-animation-duration:1s;-ms-animation-duration:1s;-o-animation-duration:1s;animation-duration:1s}@-webkit-keyframes flash{0%,50%,100%{opacity:1}25%,75%{opacity:0}}@-moz-keyframes flash{0%,50%,100%{opacity:1}25%,75%{opacity:0}}@-o-keyframes flash{0%,50%,100%{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;-moz-animation-name:flash;-o-animation-name:flash;animation-name:flash}@-webkit-keyframes shake{0%,100%{-webkit-transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px)}}@-moz-keyframes shake{0%,100%{-moz-transform:translateX(0)}10%,30%,50%,70%,90%{-moz-transform:translateX(-10px)} +20%,40%,60%,80%{-moz-transform:translateX(10px)}}@-o-keyframes shake{0%,100%{-o-transform:translateX(0)}10%,30%,50%,70%,90%{-o-transform:translateX(-10px)}20%,40%,60%,80%{-o-transform:translateX(10px)}}.shake{-webkit-animation-name:shake;-moz-animation-name:shake;-o-animation-name:shake;animation-name:shake}@-webkit-keyframes bounce{0%,20%,50%,80%,100%{-webkit-transform:translateY(0)}40%{-webkit-transform:translateY(-30px)}60%{-webkit-transform:translateY(-15px)}}@-moz-keyframes bounce{0%,20%,50%,80%,100%{-moz-transform:translateY(0)}40%{-moz-transform:translateY(-30px)}60%{-moz-transform:translateY(-15px)}}@-o-keyframes bounce{0%,20%,50%,80%,100%{-o-transform:translateY(0)}40%{-o-transform:translateY(-30px)}60%{-o-transform:translateY(-15px)}}.bounce{-webkit-animation-name:bounce;-moz-animation-name:bounce;-o-animation-name:bounce;animation-name:bounce}@-webkit-keyframes tada{0%{-webkit-transform:scale(1)}10%,20%{-webkit-transform:scale(0.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0)}}@-moz-keyframes tada{0%{-moz-transform:scale(1)}10%,20%{-moz-transform:scale(0.9) rotate(-3deg)}30%,50%,70%,90%{-moz-transform:scale(1.1) rotate(3deg)}40%,60%,80%{-moz-transform:scale(1.1) rotate(-3deg)}100%{-moz-transform:scale(1) rotate(0)}}@-o-keyframes tada{0%{-o-transform:scale(1)}10%,20%{-o-transform:scale(0.9) rotate(-3deg)}30%,50%,70%,90%{-o-transform:scale(1.1) rotate(3deg)}40%,60%,80%{-o-transform:scale(1.1) rotate(-3deg)}100%{-o-transform:scale(1) rotate(0)}}.tada{-webkit-animation-name:tada;-moz-animation-name:tada;-o-animation-name:tada;animation-name:tada}@-webkit-keyframes swing{20%,40%,60%,80%,100%{-webkit-transform-origin:top center}20%{-webkit-transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg)}100%{-webkit-transform:rotate(0deg)}}@-moz-keyframes swing{20%{-moz-transform:rotate(15deg)}40%{-moz-transform:rotate(-10deg)}60%{-moz-transform:rotate(5deg)}80%{-moz-transform:rotate(-5deg)}100%{-moz-transform:rotate(0deg)}}@-o-keyframes swing{20%{-o-transform:rotate(15deg)}40%{-o-transform:rotate(-10deg)}60%{-o-transform:rotate(5deg)}80%{-o-transform:rotate(-5deg)}100%{-o-transform:rotate(0deg)}}.swing{-webkit-transform-origin:top center;-moz-transform-origin:top center;-o-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;-moz-animation-name:swing;-o-animation-name:swing;animation-name:swing}@-webkit-keyframes wobble{0%{-webkit-transform:translateX(0%)}15%{-webkit-transform:translateX(-25%) rotate(-5deg)}30%{-webkit-transform:translateX(20%) rotate(3deg)}45%{-webkit-transform:translateX(-15%) rotate(-3deg)}60%{-webkit-transform:translateX(10%) rotate(2deg)}75%{-webkit-transform:translateX(-5%) rotate(-1deg)}100%{-webkit-transform:translateX(0%)}}@-moz-keyframes wobble{0%{-moz-transform:translateX(0%)}15%{-moz-transform:translateX(-25%) rotate(-5deg)}30%{-moz-transform:translateX(20%) rotate(3deg)}45%{-moz-transform:translateX(-15%) rotate(-3deg)}60%{-moz-transform:translateX(10%) rotate(2deg)}75%{-moz-transform:translateX(-5%) rotate(-1deg)}100%{-moz-transform:translateX(0%)}}@-o-keyframes wobble{0%{-o-transform:translateX(0%)}15%{-o-transform:translateX(-25%) rotate(-5deg)}30%{-o-transform:translateX(20%) rotate(3deg)}45%{-o-transform:translateX(-15%) rotate(-3deg)}60%{-o-transform:translateX(10%) rotate(2deg)}75%{-o-transform:translateX(-5%) rotate(-1deg)}100%{-o-transform:translateX(0%)}}.wobble{-webkit-animation-name:wobble;-moz-animation-name:wobble;-o-animation-name:wobble;animation-name:wobble}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1)}50%{-webkit-transform:scale(1.1)}100%{-webkit-transform:scale(1)}}@-moz-keyframes pulse{0%{-moz-transform:scale(1)}50%{-moz-transform:scale(1.1)}100%{-moz-transform:scale(1)}}@-o-keyframes pulse{0%{-o-transform:scale(1)}50%{-o-transform:scale(1.1)}100%{-o-transform:scale(1)}}.pulse{-webkit-animation-name:pulse;-moz-animation-name:pulse;-o-animation-name:pulse;animation-name:pulse}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) translateZ(0) rotateY(0) scale(1);-webkit-animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);-webkit-animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);-webkit-animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(0.95);-webkit-animation-timing-function:ease-in}100%{-webkit-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);-webkit-animation-timing-function:ease-in}}@-moz-keyframes flip{0%{-moz-transform:perspective(400px) translateZ(0) rotateY(0) scale(1);-moz-animation-timing-function:ease-out}40%{-moz-transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);-moz-animation-timing-function:ease-out} +50%{-moz-transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);-moz-animation-timing-function:ease-in}80%{-moz-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(0.95);-moz-animation-timing-function:ease-in}100%{-moz-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);-moz-animation-timing-function:ease-in}}@-o-keyframes flip{0%{-o-transform:perspective(400px) translateZ(0) rotateY(0) scale(1);-o-animation-timing-function:ease-out}40%{-o-transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);-o-animation-timing-function:ease-out}50%{-o-transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);-o-animation-timing-function:ease-in}80%{-o-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(0.95);-o-animation-timing-function:ease-in}100%{-o-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);-o-animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible!important;-webkit-animation-name:flip;-moz-backface-visibility:visible!important;-moz-animation-name:flip;-o-backface-visibility:visible!important;-o-animation-name:flip;backface-visibility:visible!important;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateX(-10deg)}70%{-webkit-transform:perspective(400px) rotateX(10deg)}100%{-webkit-transform:perspective(400px) rotateX(0deg);opacity:1}}@-moz-keyframes flipInX{0%{-moz-transform:perspective(400px) rotateX(90deg);opacity:0}40%{-moz-transform:perspective(400px) rotateX(-10deg)}70%{-moz-transform:perspective(400px) rotateX(10deg)}100%{-moz-transform:perspective(400px) rotateX(0deg);opacity:1}}@-o-keyframes flipInX{0%{-o-transform:perspective(400px) rotateX(90deg);opacity:0}40%{-o-transform:perspective(400px) rotateX(-10deg)}70%{-o-transform:perspective(400px) rotateX(10deg)}100%{-o-transform:perspective(400px) rotateX(0deg);opacity:1}}.flipInX{-webkit-backface-visibility:visible!important;-webkit-animation-name:flipInX;-moz-backface-visibility:visible!important;-moz-animation-name:flipInX;-o-backface-visibility:visible!important;-o-animation-name:flipInX;backface-visibility:visible!important;animation-name:flipInX}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px) rotateX(0deg);opacity:1}100%{-webkit-transform:perspective(400px) rotateX(90deg);opacity:0}}@-moz-keyframes flipOutX{0%{-moz-transform:perspective(400px) rotateX(0deg);opacity:1}100%{-moz-transform:perspective(400px) rotateX(90deg);opacity:0}}@-o-keyframes flipOutX{0%{-o-transform:perspective(400px) rotateX(0deg);opacity:1}100%{-o-transform:perspective(400px) rotateX(90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;-webkit-backface-visibility:visible!important;-moz-animation-name:flipOutX;-moz-backface-visibility:visible!important;-o-animation-name:flipOutX;-o-backface-visibility:visible!important;animation-name:flipOutX;backface-visibility:visible!important}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateY(-10deg)}70%{-webkit-transform:perspective(400px) rotateY(10deg)}100%{-webkit-transform:perspective(400px) rotateY(0deg);opacity:1}}@-moz-keyframes flipInY{0%{-moz-transform:perspective(400px) rotateY(90deg);opacity:0}40%{-moz-transform:perspective(400px) rotateY(-10deg)}70%{-moz-transform:perspective(400px) rotateY(10deg)}100%{-moz-transform:perspective(400px) rotateY(0deg);opacity:1}}@-o-keyframes flipInY{0%{-o-transform:perspective(400px) rotateY(90deg);opacity:0}40%{-o-transform:perspective(400px) rotateY(-10deg)}70%{-o-transform:perspective(400px) rotateY(10deg)}100%{-o-transform:perspective(400px) rotateY(0deg);opacity:1}}.flipInY{-webkit-backface-visibility:visible!important;-webkit-animation-name:flipInY;-moz-backface-visibility:visible!important;-moz-animation-name:flipInY;-o-backface-visibility:visible!important;-o-animation-name:flipInY;backface-visibility:visible!important;animation-name:flipInY}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px) rotateY(0deg);opacity:1}100%{-webkit-transform:perspective(400px) rotateY(90deg);opacity:0}}@-moz-keyframes flipOutY{0%{-moz-transform:perspective(400px) rotateY(0deg);opacity:1}100%{-moz-transform:perspective(400px) rotateY(90deg);opacity:0}}@-o-keyframes flipOutY{0%{-o-transform:perspective(400px) rotateY(0deg);opacity:1}100%{-o-transform:perspective(400px) rotateY(90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;-webkit-animation-name:flipOutY;-moz-backface-visibility:visible!important;-moz-animation-name:flipOutY;-o-backface-visibility:visible!important;-o-animation-name:flipOutY;backface-visibility:visible!important;animation-name:flipOutY}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-moz-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-o-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;-moz-animation-name:fadeIn;-o-animation-name:fadeIn;animation-name:fadeIn} +@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(20px)}100%{opacity:1;-webkit-transform:translateY(0)}}@-moz-keyframes fadeInUp{0%{opacity:0;-moz-transform:translateY(20px)}100%{opacity:1;-moz-transform:translateY(0)}}@-o-keyframes fadeInUp{0%{opacity:0;-o-transform:translateY(20px)}100%{opacity:1;-o-transform:translateY(0)}}.fadeInUp{-webkit-animation-name:fadeInUp;-moz-animation-name:fadeInUp;-o-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-20px)}100%{opacity:1;-webkit-transform:translateY(0)}}@-moz-keyframes fadeInDown{0%{opacity:0;-moz-transform:translateY(-20px)}100%{opacity:1;-moz-transform:translateY(0)}}@-o-keyframes fadeInDown{0%{opacity:0;-o-transform:translateY(-20px)}100%{opacity:1;-o-transform:translateY(0)}}.fadeInDown{-webkit-animation-name:fadeInDown;-moz-animation-name:fadeInDown;-o-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translateX(-20px)}100%{opacity:1;-webkit-transform:translateX(0)}}@-moz-keyframes fadeInLeft{0%{opacity:0;-moz-transform:translateX(-20px)}100%{opacity:1;-moz-transform:translateX(0)}}@-o-keyframes fadeInLeft{0%{opacity:0;-o-transform:translateX(-20px)}100%{opacity:1;-o-transform:translateX(0)}}.fadeInLeft{-webkit-animation-name:fadeInLeft;-moz-animation-name:fadeInLeft;-o-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translateX(20px)}100%{opacity:1;-webkit-transform:translateX(0)}}@-moz-keyframes fadeInRight{0%{opacity:0;-moz-transform:translateX(20px)}100%{opacity:1;-moz-transform:translateX(0)}}@-o-keyframes fadeInRight{0%{opacity:0;-o-transform:translateX(20px)}100%{opacity:1;-o-transform:translateX(0)}}.fadeInRight{-webkit-animation-name:fadeInRight;-moz-animation-name:fadeInRight;-o-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0)}}@-moz-keyframes fadeInUpBig{0%{opacity:0;-moz-transform:translateY(2000px)}100%{opacity:1;-moz-transform:translateY(0)}}@-o-keyframes fadeInUpBig{0%{opacity:0;-o-transform:translateY(2000px)}100%{opacity:1;-o-transform:translateY(0)}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;-moz-animation-name:fadeInUpBig;-o-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translateY(-2000px)}100%{opacity:1;-webkit-transform:translateY(0)}}@-moz-keyframes fadeInDownBig{0%{opacity:0;-moz-transform:translateY(-2000px)}100%{opacity:1;-moz-transform:translateY(0)}}@-o-keyframes fadeInDownBig{0%{opacity:0;-o-transform:translateY(-2000px)}100%{opacity:1;-o-transform:translateY(0)}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;-moz-animation-name:fadeInDownBig;-o-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translateX(-2000px)}100%{opacity:1;-webkit-transform:translateX(0)}}@-moz-keyframes fadeInLeftBig{0%{opacity:0;-moz-transform:translateX(-2000px)}100%{opacity:1;-moz-transform:translateX(0)}}@-o-keyframes fadeInLeftBig{0%{opacity:0;-o-transform:translateX(-2000px)}100%{opacity:1;-o-transform:translateX(0)}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;-moz-animation-name:fadeInLeftBig;-o-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translateX(2000px)}100%{opacity:1;-webkit-transform:translateX(0)}}@-moz-keyframes fadeInRightBig{0%{opacity:0;-moz-transform:translateX(2000px)}100%{opacity:1;-moz-transform:translateX(0)}}@-o-keyframes fadeInRightBig{0%{opacity:0;-o-transform:translateX(2000px)}100%{opacity:1;-o-transform:translateX(0)}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;-moz-animation-name:fadeInRightBig;-o-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-moz-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-o-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;-moz-animation-name:fadeOut;-o-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutUp{0%{opacity:1;-webkit-transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-20px)}}@-moz-keyframes fadeOutUp{0%{opacity:1;-moz-transform:translateY(0)}100%{opacity:0;-moz-transform:translateY(-20px)}}@-o-keyframes fadeOutUp{0%{opacity:1;-o-transform:translateY(0)}100%{opacity:0;-o-transform:translateY(-20px)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;-moz-animation-name:fadeOutUp;-o-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(20px)}}@-moz-keyframes fadeOutDown{0%{opacity:1;-moz-transform:translateY(0)}100%{opacity:0;-moz-transform:translateY(20px)} +}@-o-keyframes fadeOutDown{0%{opacity:1;-o-transform:translateY(0)}100%{opacity:0;-o-transform:translateY(20px)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;-moz-animation-name:fadeOutDown;-o-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutLeft{0%{opacity:1;-webkit-transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(-20px)}}@-moz-keyframes fadeOutLeft{0%{opacity:1;-moz-transform:translateX(0)}100%{opacity:0;-moz-transform:translateX(-20px)}}@-o-keyframes fadeOutLeft{0%{opacity:1;-o-transform:translateX(0)}100%{opacity:0;-o-transform:translateX(-20px)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;-moz-animation-name:fadeOutLeft;-o-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutRight{0%{opacity:1;-webkit-transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(20px)}}@-moz-keyframes fadeOutRight{0%{opacity:1;-moz-transform:translateX(0)}100%{opacity:0;-moz-transform:translateX(20px)}}@-o-keyframes fadeOutRight{0%{opacity:1;-o-transform:translateX(0)}100%{opacity:0;-o-transform:translateX(20px)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;-moz-animation-name:fadeOutRight;-o-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutUpBig{0%{opacity:1;-webkit-transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-2000px)}}@-moz-keyframes fadeOutUpBig{0%{opacity:1;-moz-transform:translateY(0)}100%{opacity:0;-moz-transform:translateY(-2000px)}}@-o-keyframes fadeOutUpBig{0%{opacity:1;-o-transform:translateY(0)}100%{opacity:0;-o-transform:translateY(-2000px)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;-moz-animation-name:fadeOutUpBig;-o-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes fadeOutDownBig{0%{opacity:1;-webkit-transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(2000px)}}@-moz-keyframes fadeOutDownBig{0%{opacity:1;-moz-transform:translateY(0)}100%{opacity:0;-moz-transform:translateY(2000px)}}@-o-keyframes fadeOutDownBig{0%{opacity:1;-o-transform:translateY(0)}100%{opacity:0;-o-transform:translateY(2000px)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;-moz-animation-name:fadeOutDownBig;-o-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1;-webkit-transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(-2000px)}}@-moz-keyframes fadeOutLeftBig{0%{opacity:1;-moz-transform:translateX(0)}100%{opacity:0;-moz-transform:translateX(-2000px)}}@-o-keyframes fadeOutLeftBig{0%{opacity:1;-o-transform:translateX(0)}100%{opacity:0;-o-transform:translateX(-2000px)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;-moz-animation-name:fadeOutLeftBig;-o-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRightBig{0%{opacity:1;-webkit-transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(2000px)}}@-moz-keyframes fadeOutRightBig{0%{opacity:1;-moz-transform:translateX(0)}100%{opacity:0;-moz-transform:translateX(2000px)}}@-o-keyframes fadeOutRightBig{0%{opacity:1;-o-transform:translateX(0)}100%{opacity:0;-o-transform:translateX(2000px)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;-moz-animation-name:fadeOutRightBig;-o-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes slideInDown{0%{opacity:0;-webkit-transform:translateY(-2000px)}100%{-webkit-transform:translateY(0)}}@-moz-keyframes slideInDown{0%{opacity:0;-moz-transform:translateY(-2000px)}100%{-moz-transform:translateY(0)}}@-o-keyframes slideInDown{0%{opacity:0;-o-transform:translateY(-2000px)}100%{-o-transform:translateY(0)}}.slideInDown{-webkit-animation-name:slideInDown;-moz-animation-name:slideInDown;-o-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{opacity:0;-webkit-transform:translateX(-2000px)}100%{-webkit-transform:translateX(0)}}@-moz-keyframes slideInLeft{0%{opacity:0;-moz-transform:translateX(-2000px)}100%{-moz-transform:translateX(0)}}@-o-keyframes slideInLeft{0%{opacity:0;-o-transform:translateX(-2000px)}100%{-o-transform:translateX(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;-moz-animation-name:slideInLeft;-o-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{opacity:0;-webkit-transform:translateX(2000px)}100%{-webkit-transform:translateX(0)}}@-moz-keyframes slideInRight{0%{opacity:0;-moz-transform:translateX(2000px)}100%{-moz-transform:translateX(0)}}@-o-keyframes slideInRight{0%{opacity:0;-o-transform:translateX(2000px)}100%{-o-transform:translateX(0)}}.slideInRight{-webkit-animation-name:slideInRight;-moz-animation-name:slideInRight;-o-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-2000px)}}@-moz-keyframes slideOutUp{0%{-moz-transform:translateY(0)}100%{opacity:0;-moz-transform:translateY(-2000px)}}@-o-keyframes slideOutUp{0%{-o-transform:translateY(0)} +100%{opacity:0;-o-transform:translateY(-2000px)}}.slideOutUp{-webkit-animation-name:slideOutUp;-moz-animation-name:slideOutUp;-o-animation-name:slideOutUp;animation-name:slideOutUp}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(-2000px)}}@-moz-keyframes slideOutLeft{0%{-moz-transform:translateX(0)}100%{opacity:0;-moz-transform:translateX(-2000px)}}@-o-keyframes slideOutLeft{0%{-o-transform:translateX(0)}100%{opacity:0;-o-transform:translateX(-2000px)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;-moz-animation-name:slideOutLeft;-o-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(2000px)}}@-moz-keyframes slideOutRight{0%{-moz-transform:translateX(0)}100%{opacity:0;-moz-transform:translateX(2000px)}}@-o-keyframes slideOutRight{0%{-o-transform:translateX(0)}100%{opacity:0;-o-transform:translateX(2000px)}}.slideOutRight{-webkit-animation-name:slideOutRight;-moz-animation-name:slideOutRight;-o-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(0.3)}50%{opacity:1;-webkit-transform:scale(1.05)}70%{-webkit-transform:scale(0.9)}100%{-webkit-transform:scale(1)}}@-moz-keyframes bounceIn{0%{opacity:0;-moz-transform:scale(0.3)}50%{opacity:1;-moz-transform:scale(1.05)}70%{-moz-transform:scale(0.9)}100%{-moz-transform:scale(1)}}@-o-keyframes bounceIn{0%{opacity:0;-o-transform:scale(0.3)}50%{opacity:1;-o-transform:scale(1.05)}70%{-o-transform:scale(0.9)}100%{-o-transform:scale(1)}}.bounceIn{-webkit-animation-name:bounceIn;-moz-animation-name:bounceIn;-o-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceInUp{0%{opacity:0;-webkit-transform:translateY(2000px)}60%{opacity:1;-webkit-transform:translateY(-30px)}80%{-webkit-transform:translateY(10px)}100%{-webkit-transform:translateY(0)}}@-moz-keyframes bounceInUp{0%{opacity:0;-moz-transform:translateY(2000px)}60%{opacity:1;-moz-transform:translateY(-30px)}80%{-moz-transform:translateY(10px)}100%{-moz-transform:translateY(0)}}@-o-keyframes bounceInUp{0%{opacity:0;-o-transform:translateY(2000px)}60%{opacity:1;-o-transform:translateY(-30px)}80%{-o-transform:translateY(10px)}100%{-o-transform:translateY(0)}}.bounceInUp{-webkit-animation-name:bounceInUp;-moz-animation-name:bounceInUp;-o-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceInDown{0%{opacity:0;-webkit-transform:translateY(-2000px)}60%{opacity:1;-webkit-transform:translateY(30px)}80%{-webkit-transform:translateY(-10px)}100%{-webkit-transform:translateY(0)}}@-moz-keyframes bounceInDown{0%{opacity:0;-moz-transform:translateY(-2000px)}60%{opacity:1;-moz-transform:translateY(30px)}80%{-moz-transform:translateY(-10px)}100%{-moz-transform:translateY(0)}}@-o-keyframes bounceInDown{0%{opacity:0;-o-transform:translateY(-2000px)}60%{opacity:1;-o-transform:translateY(30px)}80%{-o-transform:translateY(-10px)}100%{-o-transform:translateY(0)}}.bounceInDown{-webkit-animation-name:bounceInDown;-moz-animation-name:bounceInDown;-o-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%{opacity:0;-webkit-transform:translateX(-2000px)}60%{opacity:1;-webkit-transform:translateX(30px)}80%{-webkit-transform:translateX(-10px)}100%{-webkit-transform:translateX(0)}}@-moz-keyframes bounceInLeft{0%{opacity:0;-moz-transform:translateX(-2000px)}60%{opacity:1;-moz-transform:translateX(30px)}80%{-moz-transform:translateX(-10px)}100%{-moz-transform:translateX(0)}}@-o-keyframes bounceInLeft{0%{opacity:0;-o-transform:translateX(-2000px)}60%{opacity:1;-o-transform:translateX(30px)}80%{-o-transform:translateX(-10px)}100%{-o-transform:translateX(0)}}.bounceInLeft{-webkit-animation-name:bounceInLeft;-moz-animation-name:bounceInLeft;-o-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%{opacity:0;-webkit-transform:translateX(2000px)}60%{opacity:1;-webkit-transform:translateX(-30px)}80%{-webkit-transform:translateX(10px)}100%{-webkit-transform:translateX(0)}}@-moz-keyframes bounceInRight{0%{opacity:0;-moz-transform:translateX(2000px)}60%{opacity:1;-moz-transform:translateX(-30px)}80%{-moz-transform:translateX(10px)}100%{-moz-transform:translateX(0)}}@-o-keyframes bounceInRight{0%{opacity:0;-o-transform:translateX(2000px)}60%{opacity:1;-o-transform:translateX(-30px)}80%{-o-transform:translateX(10px)}100%{-o-transform:translateX(0)}}.bounceInRight{-webkit-animation-name:bounceInRight;-moz-animation-name:bounceInRight;-o-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceOut{0%{-webkit-transform:scale(1)}25%{-webkit-transform:scale(0.95)}50%{opacity:1;-webkit-transform:scale(1.1)}100%{opacity:0;-webkit-transform:scale(0.3)}}@-moz-keyframes bounceOut{0%{-moz-transform:scale(1)}25%{-moz-transform:scale(0.95)}50%{opacity:1;-moz-transform:scale(1.1)}100%{opacity:0;-moz-transform:scale(0.3)} +}@-o-keyframes bounceOut{0%{-o-transform:scale(1)}25%{-o-transform:scale(0.95)}50%{opacity:1;-o-transform:scale(1.1)}100%{opacity:0;-o-transform:scale(0.3)}}.bounceOut{-webkit-animation-name:bounceOut;-moz-animation-name:bounceOut;-o-animation-name:bounceOut;animation-name:bounceOut}@-webkit-keyframes bounceOutUp{0%{-webkit-transform:translateY(0)}20%{opacity:1;-webkit-transform:translateY(20px)}100%{opacity:0;-webkit-transform:translateY(-2000px)}}@-moz-keyframes bounceOutUp{0%{-moz-transform:translateY(0)}20%{opacity:1;-moz-transform:translateY(20px)}100%{opacity:0;-moz-transform:translateY(-2000px)}}@-o-keyframes bounceOutUp{0%{-o-transform:translateY(0)}20%{opacity:1;-o-transform:translateY(20px)}100%{opacity:0;-o-transform:translateY(-2000px)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;-moz-animation-name:bounceOutUp;-o-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes bounceOutDown{0%{-webkit-transform:translateY(0)}20%{opacity:1;-webkit-transform:translateY(-20px)}100%{opacity:0;-webkit-transform:translateY(2000px)}}@-moz-keyframes bounceOutDown{0%{-moz-transform:translateY(0)}20%{opacity:1;-moz-transform:translateY(-20px)}100%{opacity:0;-moz-transform:translateY(2000px)}}@-o-keyframes bounceOutDown{0%{-o-transform:translateY(0)}20%{opacity:1;-o-transform:translateY(-20px)}100%{opacity:0;-o-transform:translateY(2000px)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;-moz-animation-name:bounceOutDown;-o-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{0%{-webkit-transform:translateX(0)}20%{opacity:1;-webkit-transform:translateX(20px)}100%{opacity:0;-webkit-transform:translateX(-2000px)}}@-moz-keyframes bounceOutLeft{0%{-moz-transform:translateX(0)}20%{opacity:1;-moz-transform:translateX(20px)}100%{opacity:0;-moz-transform:translateX(-2000px)}}@-o-keyframes bounceOutLeft{0%{-o-transform:translateX(0)}20%{opacity:1;-o-transform:translateX(20px)}100%{opacity:0;-o-transform:translateX(-2000px)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;-moz-animation-name:bounceOutLeft;-o-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{0%{-webkit-transform:translateX(0)}20%{opacity:1;-webkit-transform:translateX(-20px)}100%{opacity:0;-webkit-transform:translateX(2000px)}}@-moz-keyframes bounceOutRight{0%{-moz-transform:translateX(0)}20%{opacity:1;-moz-transform:translateX(-20px)}100%{opacity:0;-moz-transform:translateX(2000px)}}@-o-keyframes bounceOutRight{0%{-o-transform:translateX(0)}20%{opacity:1;-o-transform:translateX(-20px)}100%{opacity:0;-o-transform:translateX(2000px)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;-moz-animation-name:bounceOutRight;-o-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes rotateIn{0%{-webkit-transform-origin:center center;-webkit-transform:rotate(-200deg);opacity:0}100%{-webkit-transform-origin:center center;-webkit-transform:rotate(0);opacity:1}}@-moz-keyframes rotateIn{0%{-moz-transform-origin:center center;-moz-transform:rotate(-200deg);opacity:0}100%{-moz-transform-origin:center center;-moz-transform:rotate(0);opacity:1}}@-o-keyframes rotateIn{0%{-o-transform-origin:center center;-o-transform:rotate(-200deg);opacity:0}100%{-o-transform-origin:center center;-o-transform:rotate(0);opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;-moz-animation-name:rotateIn;-o-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(90deg);opacity:0}100%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(0);opacity:1}}@-moz-keyframes rotateInUpLeft{0%{-moz-transform-origin:left bottom;-moz-transform:rotate(90deg);opacity:0}100%{-moz-transform-origin:left bottom;-moz-transform:rotate(0);opacity:1}}@-o-keyframes rotateInUpLeft{0%{-o-transform-origin:left bottom;-o-transform:rotate(90deg);opacity:0}100%{-o-transform-origin:left bottom;-o-transform:rotate(0);opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;-moz-animation-name:rotateInUpLeft;-o-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(-90deg);opacity:0}100%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(0);opacity:1}}@-moz-keyframes rotateInDownLeft{0%{-moz-transform-origin:left bottom;-moz-transform:rotate(-90deg);opacity:0}100%{-moz-transform-origin:left bottom;-moz-transform:rotate(0);opacity:1}}@-o-keyframes rotateInDownLeft{0%{-o-transform-origin:left bottom;-o-transform:rotate(-90deg);opacity:0}100%{-o-transform-origin:left bottom;-o-transform:rotate(0);opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;-moz-animation-name:rotateInDownLeft;-o-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(-90deg);opacity:0} +100%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(0);opacity:1}}@-moz-keyframes rotateInUpRight{0%{-moz-transform-origin:right bottom;-moz-transform:rotate(-90deg);opacity:0}100%{-moz-transform-origin:right bottom;-moz-transform:rotate(0);opacity:1}}@-o-keyframes rotateInUpRight{0%{-o-transform-origin:right bottom;-o-transform:rotate(-90deg);opacity:0}100%{-o-transform-origin:right bottom;-o-transform:rotate(0);opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;-moz-animation-name:rotateInUpRight;-o-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(90deg);opacity:0}100%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(0);opacity:1}}@-moz-keyframes rotateInDownRight{0%{-moz-transform-origin:right bottom;-moz-transform:rotate(90deg);opacity:0}100%{-moz-transform-origin:right bottom;-moz-transform:rotate(0);opacity:1}}@-o-keyframes rotateInDownRight{0%{-o-transform-origin:right bottom;-o-transform:rotate(90deg);opacity:0}100%{-o-transform-origin:right bottom;-o-transform:rotate(0);opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;-moz-animation-name:rotateInDownRight;-o-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateOut{0%{-webkit-transform-origin:center center;-webkit-transform:rotate(0);opacity:1}100%{-webkit-transform-origin:center center;-webkit-transform:rotate(200deg);opacity:0}}@-moz-keyframes rotateOut{0%{-moz-transform-origin:center center;-moz-transform:rotate(0);opacity:1}100%{-moz-transform-origin:center center;-moz-transform:rotate(200deg);opacity:0}}@-o-keyframes rotateOut{0%{-o-transform-origin:center center;-o-transform:rotate(0);opacity:1}100%{-o-transform-origin:center center;-o-transform:rotate(200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;-moz-animation-name:rotateOut;-o-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(0);opacity:1}100%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(-90deg);opacity:0}}@-moz-keyframes rotateOutUpLeft{0%{-moz-transform-origin:left bottom;-moz-transform:rotate(0);opacity:1}100%{-moz-transform-origin:left bottom;-moz-transform:rotate(-90deg);opacity:0}}@-o-keyframes rotateOutUpLeft{0%{-o-transform-origin:left bottom;-o-transform:rotate(0);opacity:1}100%{-o-transform-origin:left bottom;-o-transform:rotate(-90deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;-moz-animation-name:rotateOutUpLeft;-o-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(0);opacity:1}100%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(90deg);opacity:0}}@-moz-keyframes rotateOutDownLeft{0%{-moz-transform-origin:left bottom;-moz-transform:rotate(0);opacity:1}100%{-moz-transform-origin:left bottom;-moz-transform:rotate(90deg);opacity:0}}@-o-keyframes rotateOutDownLeft{0%{-o-transform-origin:left bottom;-o-transform:rotate(0);opacity:1}100%{-o-transform-origin:left bottom;-o-transform:rotate(90deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;-moz-animation-name:rotateOutDownLeft;-o-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(0);opacity:1}100%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(90deg);opacity:0}}@-moz-keyframes rotateOutUpRight{0%{-moz-transform-origin:right bottom;-moz-transform:rotate(0);opacity:1}100%{-moz-transform-origin:right bottom;-moz-transform:rotate(90deg);opacity:0}}@-o-keyframes rotateOutUpRight{0%{-o-transform-origin:right bottom;-o-transform:rotate(0);opacity:1}100%{-o-transform-origin:right bottom;-o-transform:rotate(90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;-moz-animation-name:rotateOutUpRight;-o-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(0);opacity:1}100%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(-90deg);opacity:0}}@-moz-keyframes rotateOutDownRight{0%{-moz-transform-origin:right bottom;-moz-transform:rotate(0);opacity:1}100%{-moz-transform-origin:right bottom;-moz-transform:rotate(-90deg);opacity:0}}@-o-keyframes rotateOutDownRight{0%{-o-transform-origin:right bottom;-o-transform:rotate(0);opacity:1}100%{-o-transform-origin:right bottom;-o-transform:rotate(-90deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;-moz-animation-name:rotateOutDownRight;-o-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translateX(100%) skewX(-30deg);opacity:0} +60%{-webkit-transform:translateX(-20%) skewX(30deg);opacity:1}80%{-webkit-transform:translateX(0%) skewX(-15deg);opacity:1}100%{-webkit-transform:translateX(0%) skewX(0deg);opacity:1}}@-moz-keyframes lightSpeedIn{0%{-moz-transform:translateX(100%) skewX(-30deg);opacity:0}60%{-moz-transform:translateX(-20%) skewX(30deg);opacity:1}80%{-moz-transform:translateX(0%) skewX(-15deg);opacity:1}100%{-moz-transform:translateX(0%) skewX(0deg);opacity:1}}@-o-keyframes lightSpeedIn{0%{-o-transform:translateX(100%) skewX(-30deg);opacity:0}60%{-o-transform:translateX(-20%) skewX(30deg);opacity:1}80%{-o-transform:translateX(0%) skewX(-15deg);opacity:1}100%{-o-transform:translateX(0%) skewX(0deg);opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;-moz-animation-name:lightSpeedIn;-o-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;-moz-animation-timing-function:ease-out;-o-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{-webkit-transform:translateX(0%) skewX(0deg);opacity:1}100%{-webkit-transform:translateX(100%) skewX(-30deg);opacity:0}}@-moz-keyframes lightSpeedOut{0%{-moz-transform:translateX(0%) skewX(0deg);opacity:1}100%{-moz-transform:translateX(100%) skewX(-30deg);opacity:0}}@-o-keyframes lightSpeedOut{0%{-o-transform:translateX(0%) skewX(0deg);opacity:1}100%{-o-transform:translateX(100%) skewX(-30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;-moz-animation-name:lightSpeedOut;-o-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;-moz-animation-timing-function:ease-in;-o-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes hinge{0%{-webkit-transform:rotate(0);-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out}40%{-webkit-transform:rotate(60deg);-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out}80%{-webkit-transform:rotate(60deg) translateY(0);opacity:1;-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out}100%{-webkit-transform:translateY(700px);opacity:0}}@-moz-keyframes hinge{0%{-moz-transform:rotate(0);-moz-transform-origin:top left;-moz-animation-timing-function:ease-in-out}20%,60%{-moz-transform:rotate(80deg);-moz-transform-origin:top left;-moz-animation-timing-function:ease-in-out}40%{-moz-transform:rotate(60deg);-moz-transform-origin:top left;-moz-animation-timing-function:ease-in-out}80%{-moz-transform:rotate(60deg) translateY(0);opacity:1;-moz-transform-origin:top left;-moz-animation-timing-function:ease-in-out}100%{-moz-transform:translateY(700px);opacity:0}}@-o-keyframes hinge{0%{-o-transform:rotate(0);-o-transform-origin:top left;-o-animation-timing-function:ease-in-out}20%,60%{-o-transform:rotate(80deg);-o-transform-origin:top left;-o-animation-timing-function:ease-in-out}40%{-o-transform:rotate(60deg);-o-transform-origin:top left;-o-animation-timing-function:ease-in-out}80%{-o-transform:rotate(60deg) translateY(0);opacity:1;-o-transform-origin:top left;-o-animation-timing-function:ease-in-out}100%{-o-transform:translateY(700px);opacity:0}}.hinge{-webkit-animation-name:hinge;-moz-animation-name:hinge;-o-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0px) rotate(0deg)}}@-moz-keyframes rollIn{0%{opacity:0;-moz-transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-moz-transform:translateX(0px) rotate(0deg)}}@-o-keyframes rollIn{0%{opacity:0;-o-transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-o-transform:translateX(0px) rotate(0deg)}}.rollIn{-webkit-animation-name:rollIn;-moz-animation-name:rollIn;-o-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1;-webkit-transform:translateX(0px) rotate(0deg)}100%{opacity:0;-webkit-transform:translateX(100%) rotate(120deg)}}@-moz-keyframes rollOut{0%{opacity:1;-moz-transform:translateX(0px) rotate(0deg)}100%{opacity:0;-moz-transform:translateX(100%) rotate(120deg)}}@-o-keyframes rollOut{0%{opacity:1;-o-transform:translateX(0px) rotate(0deg)}100%{opacity:0;-o-transform:translateX(100%) rotate(120deg)}}.rollOut{-webkit-animation-name:rollOut;-moz-animation-name:rollOut;-o-animation-name:rollOut;animation-name:rollOut}@-moz-keyframes flash{0%,50%,100%{opacity:1}25%,75%{opacity:0}}@-webkit-keyframes flash{0%,50%,100%{opacity:1}25%,75%{opacity:0}}@-o-keyframes flash{0%,50%,100%{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,50%,100%{opacity:1}25%,75%{opacity:0}}@-moz-keyframes shake{0%,100%{transform:translateX(0)}10%,30%,50%,70%,90%{transform:translateX(-10px)}20%,40%,60%,80%{transform:translateX(10px)}}@-webkit-keyframes shake{0%,100%{transform:translateX(0)}10%,30%,50%,70%,90%{transform:translateX(-10px)} +20%,40%,60%,80%{transform:translateX(10px)}}@-o-keyframes shake{0%,100%{transform:translateX(0)}10%,30%,50%,70%,90%{transform:translateX(-10px)}20%,40%,60%,80%{transform:translateX(10px)}}@keyframes shake{0%,100%{transform:translateX(0)}10%,30%,50%,70%,90%{transform:translateX(-10px)}20%,40%,60%,80%{transform:translateX(10px)}}@-moz-keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0)}40%{transform:translateY(-30px)}60%{transform:translateY(-15px)}}@-webkit-keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0)}40%{transform:translateY(-30px)}60%{transform:translateY(-15px)}}@-o-keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0)}40%{transform:translateY(-30px)}60%{transform:translateY(-15px)}}@keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0)}40%{transform:translateY(-30px)}60%{transform:translateY(-15px)}}@-moz-keyframes tada{0%{transform:scale(1)}10%,20%{transform:scale(0.9) rotate(-3deg)}30%,50%,70%,90%{transform:scale(1.1) rotate(3deg)}40%,60%,80%{transform:scale(1.1) rotate(-3deg)}100%{transform:scale(1) rotate(0)}}@-webkit-keyframes tada{0%{transform:scale(1)}10%,20%{transform:scale(0.9) rotate(-3deg)}30%,50%,70%,90%{transform:scale(1.1) rotate(3deg)}40%,60%,80%{transform:scale(1.1) rotate(-3deg)}100%{transform:scale(1) rotate(0)}}@-o-keyframes tada{0%{transform:scale(1)}10%,20%{transform:scale(0.9) rotate(-3deg)}30%,50%,70%,90%{transform:scale(1.1) rotate(3deg)}40%,60%,80%{transform:scale(1.1) rotate(-3deg)}100%{transform:scale(1) rotate(0)}}@keyframes tada{0%{transform:scale(1)}10%,20%{transform:scale(0.9) rotate(-3deg)}30%,50%,70%,90%{transform:scale(1.1) rotate(3deg)}40%,60%,80%{transform:scale(1.1) rotate(-3deg)}100%{transform:scale(1) rotate(0)}}@-moz-keyframes swing{20%{transform:rotate(15deg)}40%{transform:rotate(-10deg)}60%{transform:rotate(5deg)}80%{transform:rotate(-5deg)}100%{transform:rotate(0deg)}}@-webkit-keyframes swing{20%{transform:rotate(15deg)}40%{transform:rotate(-10deg)}60%{transform:rotate(5deg)}80%{transform:rotate(-5deg)}100%{transform:rotate(0deg)}}@-o-keyframes swing{20%{transform:rotate(15deg)}40%{transform:rotate(-10deg)}60%{transform:rotate(5deg)}80%{transform:rotate(-5deg)}100%{transform:rotate(0deg)}}@keyframes swing{20%{transform:rotate(15deg)}40%{transform:rotate(-10deg)}60%{transform:rotate(5deg)}80%{transform:rotate(-5deg)}100%{transform:rotate(0deg)}}@-moz-keyframes wobble{0%{transform:translateX(0%)}15%{transform:translateX(-25%) rotate(-5deg)}30%{transform:translateX(20%) rotate(3deg)}45%{transform:translateX(-15%) rotate(-3deg)}60%{transform:translateX(10%) rotate(2deg)}75%{transform:translateX(-5%) rotate(-1deg)}100%{transform:translateX(0%)}}@-webkit-keyframes wobble{0%{transform:translateX(0%)}15%{transform:translateX(-25%) rotate(-5deg)}30%{transform:translateX(20%) rotate(3deg)}45%{transform:translateX(-15%) rotate(-3deg)}60%{transform:translateX(10%) rotate(2deg)}75%{transform:translateX(-5%) rotate(-1deg)}100%{transform:translateX(0%)}}@-o-keyframes wobble{0%{transform:translateX(0%)}15%{transform:translateX(-25%) rotate(-5deg)}30%{transform:translateX(20%) rotate(3deg)}45%{transform:translateX(-15%) rotate(-3deg)}60%{transform:translateX(10%) rotate(2deg)}75%{transform:translateX(-5%) rotate(-1deg)}100%{transform:translateX(0%)}}@keyframes wobble{0%{transform:translateX(0%)}15%{transform:translateX(-25%) rotate(-5deg)}30%{transform:translateX(20%) rotate(3deg)}45%{transform:translateX(-15%) rotate(-3deg)}60%{transform:translateX(10%) rotate(2deg)}75%{transform:translateX(-5%) rotate(-1deg)}100%{transform:translateX(0%)}}@-moz-keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@-webkit-keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@-o-keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@-moz-keyframes flip{0%{transform:perspective(400px) translateZ(0) rotateY(0) scale(1);animation-timing-function:ease-out}40%{transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);animation-timing-function:ease-out}50%{transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);animation-timing-function:ease-in}80%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(0.95);animation-timing-function:ease-in}100%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);animation-timing-function:ease-in}}@-webkit-keyframes flip{0%{transform:perspective(400px) translateZ(0) rotateY(0) scale(1);animation-timing-function:ease-out}40%{transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);animation-timing-function:ease-out}50%{transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);animation-timing-function:ease-in}80%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(0.95);animation-timing-function:ease-in}100%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);animation-timing-function:ease-in} +}@-o-keyframes flip{0%{transform:perspective(400px) translateZ(0) rotateY(0) scale(1);animation-timing-function:ease-out}40%{transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);animation-timing-function:ease-out}50%{transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);animation-timing-function:ease-in}80%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(0.95);animation-timing-function:ease-in}100%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);animation-timing-function:ease-in}}@keyframes flip{0%{transform:perspective(400px) translateZ(0) rotateY(0) scale(1);animation-timing-function:ease-out}40%{transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);animation-timing-function:ease-out}50%{transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);animation-timing-function:ease-in}80%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(0.95);animation-timing-function:ease-in}100%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);animation-timing-function:ease-in}}@-moz-keyframes flipInX{0%{transform:perspective(400px) rotateX(90deg);opacity:0}40%{transform:perspective(400px) rotateX(-10deg)}70%{transform:perspective(400px) rotateX(10deg)}100%{transform:perspective(400px) rotateX(0deg);opacity:1}}@-webkit-keyframes flipInX{0%{transform:perspective(400px) rotateX(90deg);opacity:0}40%{transform:perspective(400px) rotateX(-10deg)}70%{transform:perspective(400px) rotateX(10deg)}100%{transform:perspective(400px) rotateX(0deg);opacity:1}}@-o-keyframes flipInX{0%{transform:perspective(400px) rotateX(90deg);opacity:0}40%{transform:perspective(400px) rotateX(-10deg)}70%{transform:perspective(400px) rotateX(10deg)}100%{transform:perspective(400px) rotateX(0deg);opacity:1}}@keyframes flipInX{0%{transform:perspective(400px) rotateX(90deg);opacity:0}40%{transform:perspective(400px) rotateX(-10deg)}70%{transform:perspective(400px) rotateX(10deg)}100%{transform:perspective(400px) rotateX(0deg);opacity:1}}@-moz-keyframes flipOutX{0%{transform:perspective(400px) rotateX(0deg);opacity:1}100%{transform:perspective(400px) rotateX(90deg);opacity:0}}@-webkit-keyframes flipOutX{0%{transform:perspective(400px) rotateX(0deg);opacity:1}100%{transform:perspective(400px) rotateX(90deg);opacity:0}}@-o-keyframes flipOutX{0%{transform:perspective(400px) rotateX(0deg);opacity:1}100%{transform:perspective(400px) rotateX(90deg);opacity:0}}@keyframes flipOutX{0%{transform:perspective(400px) rotateX(0deg);opacity:1}100%{transform:perspective(400px) rotateX(90deg);opacity:0}}@-moz-keyframes flipInY{0%{transform:perspective(400px) rotateY(90deg);opacity:0}40%{transform:perspective(400px) rotateY(-10deg)}70%{transform:perspective(400px) rotateY(10deg)}100%{transform:perspective(400px) rotateY(0deg);opacity:1}}@-webkit-keyframes flipInY{0%{transform:perspective(400px) rotateY(90deg);opacity:0}40%{transform:perspective(400px) rotateY(-10deg)}70%{transform:perspective(400px) rotateY(10deg)}100%{transform:perspective(400px) rotateY(0deg);opacity:1}}@-o-keyframes flipInY{0%{transform:perspective(400px) rotateY(90deg);opacity:0}40%{transform:perspective(400px) rotateY(-10deg)}70%{transform:perspective(400px) rotateY(10deg)}100%{transform:perspective(400px) rotateY(0deg);opacity:1}}@keyframes flipInY{0%{transform:perspective(400px) rotateY(90deg);opacity:0}40%{transform:perspective(400px) rotateY(-10deg)}70%{transform:perspective(400px) rotateY(10deg)}100%{transform:perspective(400px) rotateY(0deg);opacity:1}}@-moz-keyframes flipOutY{0%{transform:perspective(400px) rotateY(0deg);opacity:1}100%{transform:perspective(400px) rotateY(90deg);opacity:0}}@-webkit-keyframes flipOutY{0%{transform:perspective(400px) rotateY(0deg);opacity:1}100%{transform:perspective(400px) rotateY(90deg);opacity:0}}@-o-keyframes flipOutY{0%{transform:perspective(400px) rotateY(0deg);opacity:1}100%{transform:perspective(400px) rotateY(90deg);opacity:0}}@keyframes flipOutY{0%{transform:perspective(400px) rotateY(0deg);opacity:1}100%{transform:perspective(400px) rotateY(90deg);opacity:0}}@-moz-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-o-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-moz-keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}@-webkit-keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}@-o-keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}@-moz-keyframes fadeInDown{0%{opacity:0;transform:translateY(-20px)}100%{opacity:1;transform:translateY(0)}}@-webkit-keyframes fadeInDown{0%{opacity:0;transform:translateY(-20px)}100%{opacity:1;transform:translateY(0)}}@-o-keyframes fadeInDown{0%{opacity:0;transform:translateY(-20px)} +100%{opacity:1;transform:translateY(0)}}@keyframes fadeInDown{0%{opacity:0;transform:translateY(-20px)}100%{opacity:1;transform:translateY(0)}}@-moz-keyframes fadeInLeft{0%{opacity:0;transform:translateX(-20px)}100%{opacity:1;transform:translateX(0)}}@-webkit-keyframes fadeInLeft{0%{opacity:0;transform:translateX(-20px)}100%{opacity:1;transform:translateX(0)}}@-o-keyframes fadeInLeft{0%{opacity:0;transform:translateX(-20px)}100%{opacity:1;transform:translateX(0)}}@keyframes fadeInLeft{0%{opacity:0;transform:translateX(-20px)}100%{opacity:1;transform:translateX(0)}}@-moz-keyframes fadeInRight{0%{opacity:0;transform:translateX(20px)}100%{opacity:1;transform:translateX(0)}}@-webkit-keyframes fadeInRight{0%{opacity:0;transform:translateX(20px)}100%{opacity:1;transform:translateX(0)}}@-o-keyframes fadeInRight{0%{opacity:0;transform:translateX(20px)}100%{opacity:1;transform:translateX(0)}}@keyframes fadeInRight{0%{opacity:0;transform:translateX(20px)}100%{opacity:1;transform:translateX(0)}}@-moz-keyframes fadeInUpBig{0%{opacity:0;transform:translateY(2000px)}100%{opacity:1;transform:translateY(0)}}@-webkit-keyframes fadeInUpBig{0%{opacity:0;transform:translateY(2000px)}100%{opacity:1;transform:translateY(0)}}@-o-keyframes fadeInUpBig{0%{opacity:0;transform:translateY(2000px)}100%{opacity:1;transform:translateY(0)}}@keyframes fadeInUpBig{0%{opacity:0;transform:translateY(2000px)}100%{opacity:1;transform:translateY(0)}}@-moz-keyframes fadeInDownBig{0%{opacity:0;transform:translateY(-2000px)}100%{opacity:1;transform:translateY(0)}}@-webkit-keyframes fadeInDownBig{0%{opacity:0;transform:translateY(-2000px)}100%{opacity:1;transform:translateY(0)}}@-o-keyframes fadeInDownBig{0%{opacity:0;transform:translateY(-2000px)}100%{opacity:1;transform:translateY(0)}}@keyframes fadeInDownBig{0%{opacity:0;transform:translateY(-2000px)}100%{opacity:1;transform:translateY(0)}}@-moz-keyframes fadeInLeftBig{0%{opacity:0;transform:translateX(-2000px)}100%{opacity:1;transform:translateX(0)}}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;transform:translateX(-2000px)}100%{opacity:1;transform:translateX(0)}}@-o-keyframes fadeInLeftBig{0%{opacity:0;transform:translateX(-2000px)}100%{opacity:1;transform:translateX(0)}}@keyframes fadeInLeftBig{0%{opacity:0;transform:translateX(-2000px)}100%{opacity:1;transform:translateX(0)}}@-moz-keyframes fadeInRightBig{0%{opacity:0;transform:translateX(2000px)}100%{opacity:1;transform:translateX(0)}}@-webkit-keyframes fadeInRightBig{0%{opacity:0;transform:translateX(2000px)}100%{opacity:1;transform:translateX(0)}}@-o-keyframes fadeInRightBig{0%{opacity:0;transform:translateX(2000px)}100%{opacity:1;transform:translateX(0)}}@keyframes fadeInRightBig{0%{opacity:0;transform:translateX(2000px)}100%{opacity:1;transform:translateX(0)}}@-moz-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-o-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-moz-keyframes fadeOutUp{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-20px)}}@-webkit-keyframes fadeOutUp{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-20px)}}@-o-keyframes fadeOutUp{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-20px)}}@keyframes fadeOutUp{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-20px)}}@-moz-keyframes fadeOutDown{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(20px)}}@-webkit-keyframes fadeOutDown{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(20px)}}@-o-keyframes fadeOutDown{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(20px)}}@keyframes fadeOutDown{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(20px)}}@-moz-keyframes fadeOutLeft{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(-20px)}}@-webkit-keyframes fadeOutLeft{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(-20px)}}@-o-keyframes fadeOutLeft{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(-20px)}}@keyframes fadeOutLeft{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(-20px)}}@-moz-keyframes fadeOutRight{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(20px)}}@-webkit-keyframes fadeOutRight{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(20px)}}@-o-keyframes fadeOutRight{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(20px)}}@keyframes fadeOutRight{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(20px)}}@-moz-keyframes fadeOutUpBig{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-2000px)}}@-webkit-keyframes fadeOutUpBig{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-2000px)}}@-o-keyframes fadeOutUpBig{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-2000px)} +}@keyframes fadeOutUpBig{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-2000px)}}@-moz-keyframes fadeOutDownBig{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(2000px)}}@-webkit-keyframes fadeOutDownBig{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(2000px)}}@-o-keyframes fadeOutDownBig{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(2000px)}}@keyframes fadeOutDownBig{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(2000px)}}@-moz-keyframes fadeOutLeftBig{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(-2000px)}}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(-2000px)}}@-o-keyframes fadeOutLeftBig{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(-2000px)}}@keyframes fadeOutLeftBig{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(-2000px)}}@-moz-keyframes fadeOutRightBig{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(2000px)}}@-webkit-keyframes fadeOutRightBig{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(2000px)}}@-o-keyframes fadeOutRightBig{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(2000px)}}@keyframes fadeOutRightBig{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(2000px)}}@-moz-keyframes slideInDown{0%{opacity:0;transform:translateY(-2000px)}100%{transform:translateY(0)}}@-webkit-keyframes slideInDown{0%{opacity:0;transform:translateY(-2000px)}100%{transform:translateY(0)}}@-o-keyframes slideInDown{0%{opacity:0;transform:translateY(-2000px)}100%{transform:translateY(0)}}@keyframes slideInDown{0%{opacity:0;transform:translateY(-2000px)}100%{transform:translateY(0)}}@-moz-keyframes slideInLeft{0%{opacity:0;transform:translateX(-2000px)}100%{transform:translateX(0)}}@-webkit-keyframes slideInLeft{0%{opacity:0;transform:translateX(-2000px)}100%{transform:translateX(0)}}@-o-keyframes slideInLeft{0%{opacity:0;transform:translateX(-2000px)}100%{transform:translateX(0)}}@keyframes slideInLeft{0%{opacity:0;transform:translateX(-2000px)}100%{transform:translateX(0)}}@-moz-keyframes slideInRight{0%{opacity:0;transform:translateX(2000px)}100%{transform:translateX(0)}}@-webkit-keyframes slideInRight{0%{opacity:0;transform:translateX(2000px)}100%{transform:translateX(0)}}@-o-keyframes slideInRight{0%{opacity:0;transform:translateX(2000px)}100%{transform:translateX(0)}}@keyframes slideInRight{0%{opacity:0;transform:translateX(2000px)}100%{transform:translateX(0)}}@-moz-keyframes slideOutUp{0%{transform:translateY(0)}100%{opacity:0;transform:translateY(-2000px)}}@-webkit-keyframes slideOutUp{0%{transform:translateY(0)}100%{opacity:0;transform:translateY(-2000px)}}@-o-keyframes slideOutUp{0%{transform:translateY(0)}100%{opacity:0;transform:translateY(-2000px)}}@keyframes slideOutUp{0%{transform:translateY(0)}100%{opacity:0;transform:translateY(-2000px)}}@-moz-keyframes slideOutLeft{0%{transform:translateX(0)}100%{opacity:0;transform:translateX(-2000px)}}@-webkit-keyframes slideOutLeft{0%{transform:translateX(0)}100%{opacity:0;transform:translateX(-2000px)}}@-o-keyframes slideOutLeft{0%{transform:translateX(0)}100%{opacity:0;transform:translateX(-2000px)}}@keyframes slideOutLeft{0%{transform:translateX(0)}100%{opacity:0;transform:translateX(-2000px)}}@-moz-keyframes slideOutRight{0%{transform:translateX(0)}100%{opacity:0;transform:translateX(2000px)}}@-webkit-keyframes slideOutRight{0%{transform:translateX(0)}100%{opacity:0;transform:translateX(2000px)}}@-o-keyframes slideOutRight{0%{transform:translateX(0)}100%{opacity:0;transform:translateX(2000px)}}@keyframes slideOutRight{0%{transform:translateX(0)}100%{opacity:0;transform:translateX(2000px)}}@-moz-keyframes bounceIn{0%{opacity:0;transform:scale(0.3)}50%{opacity:1;transform:scale(1.05)}70%{transform:scale(0.9)}100%{transform:scale(1)}}@-webkit-keyframes bounceIn{0%{opacity:0;transform:scale(0.3)}50%{opacity:1;transform:scale(1.05)}70%{transform:scale(0.9)}100%{transform:scale(1)}}@-o-keyframes bounceIn{0%{opacity:0;transform:scale(0.3)}50%{opacity:1;transform:scale(1.05)}70%{transform:scale(0.9)}100%{transform:scale(1)}}@keyframes bounceIn{0%{opacity:0;transform:scale(0.3)}50%{opacity:1;transform:scale(1.05)}70%{transform:scale(0.9)}100%{transform:scale(1)}}@-moz-keyframes bounceInUp{0%{opacity:0;transform:translateY(2000px)}60%{opacity:1;transform:translateY(-30px)}80%{transform:translateY(10px)}100%{transform:translateY(0)}}@-webkit-keyframes bounceInUp{0%{opacity:0;transform:translateY(2000px)}60%{opacity:1;transform:translateY(-30px)}80%{transform:translateY(10px)}100%{transform:translateY(0)}}@-o-keyframes bounceInUp{0%{opacity:0;transform:translateY(2000px)}60%{opacity:1;transform:translateY(-30px)}80%{transform:translateY(10px)}100%{transform:translateY(0)}}@keyframes bounceInUp{0%{opacity:0;transform:translateY(2000px)} +60%{opacity:1;transform:translateY(-30px)}80%{transform:translateY(10px)}100%{transform:translateY(0)}}@-moz-keyframes bounceInDown{0%{opacity:0;transform:translateY(-2000px)}60%{opacity:1;transform:translateY(30px)}80%{transform:translateY(-10px)}100%{transform:translateY(0)}}@-webkit-keyframes bounceInDown{0%{opacity:0;transform:translateY(-2000px)}60%{opacity:1;transform:translateY(30px)}80%{transform:translateY(-10px)}100%{transform:translateY(0)}}@-o-keyframes bounceInDown{0%{opacity:0;transform:translateY(-2000px)}60%{opacity:1;transform:translateY(30px)}80%{transform:translateY(-10px)}100%{transform:translateY(0)}}@keyframes bounceInDown{0%{opacity:0;transform:translateY(-2000px)}60%{opacity:1;transform:translateY(30px)}80%{transform:translateY(-10px)}100%{transform:translateY(0)}}@-moz-keyframes bounceInLeft{0%{opacity:0;transform:translateX(-2000px)}60%{opacity:1;transform:translateX(30px)}80%{transform:translateX(-10px)}100%{transform:translateX(0)}}@-webkit-keyframes bounceInLeft{0%{opacity:0;transform:translateX(-2000px)}60%{opacity:1;transform:translateX(30px)}80%{transform:translateX(-10px)}100%{transform:translateX(0)}}@-o-keyframes bounceInLeft{0%{opacity:0;transform:translateX(-2000px)}60%{opacity:1;transform:translateX(30px)}80%{transform:translateX(-10px)}100%{transform:translateX(0)}}@keyframes bounceInLeft{0%{opacity:0;transform:translateX(-2000px)}60%{opacity:1;transform:translateX(30px)}80%{transform:translateX(-10px)}100%{transform:translateX(0)}}@-moz-keyframes bounceInRight{0%{opacity:0;transform:translateX(2000px)}60%{opacity:1;transform:translateX(-30px)}80%{transform:translateX(10px)}100%{transform:translateX(0)}}@-webkit-keyframes bounceInRight{0%{opacity:0;transform:translateX(2000px)}60%{opacity:1;transform:translateX(-30px)}80%{transform:translateX(10px)}100%{transform:translateX(0)}}@-o-keyframes bounceInRight{0%{opacity:0;transform:translateX(2000px)}60%{opacity:1;transform:translateX(-30px)}80%{transform:translateX(10px)}100%{transform:translateX(0)}}@keyframes bounceInRight{0%{opacity:0;transform:translateX(2000px)}60%{opacity:1;transform:translateX(-30px)}80%{transform:translateX(10px)}100%{transform:translateX(0)}}@-moz-keyframes bounceOut{0%{transform:scale(1)}25%{transform:scale(0.95)}50%{opacity:1;transform:scale(1.1)}100%{opacity:0;transform:scale(0.3)}}@-webkit-keyframes bounceOut{0%{transform:scale(1)}25%{transform:scale(0.95)}50%{opacity:1;transform:scale(1.1)}100%{opacity:0;transform:scale(0.3)}}@-o-keyframes bounceOut{0%{transform:scale(1)}25%{transform:scale(0.95)}50%{opacity:1;transform:scale(1.1)}100%{opacity:0;transform:scale(0.3)}}@keyframes bounceOut{0%{transform:scale(1)}25%{transform:scale(0.95)}50%{opacity:1;transform:scale(1.1)}100%{opacity:0;transform:scale(0.3)}}@-moz-keyframes bounceOutUp{0%{transform:translateY(0)}20%{opacity:1;transform:translateY(20px)}100%{opacity:0;transform:translateY(-2000px)}}@-webkit-keyframes bounceOutUp{0%{transform:translateY(0)}20%{opacity:1;transform:translateY(20px)}100%{opacity:0;transform:translateY(-2000px)}}@-o-keyframes bounceOutUp{0%{transform:translateY(0)}20%{opacity:1;transform:translateY(20px)}100%{opacity:0;transform:translateY(-2000px)}}@keyframes bounceOutUp{0%{transform:translateY(0)}20%{opacity:1;transform:translateY(20px)}100%{opacity:0;transform:translateY(-2000px)}}@-moz-keyframes bounceOutDown{0%{transform:translateY(0)}20%{opacity:1;transform:translateY(-20px)}100%{opacity:0;transform:translateY(2000px)}}@-webkit-keyframes bounceOutDown{0%{transform:translateY(0)}20%{opacity:1;transform:translateY(-20px)}100%{opacity:0;transform:translateY(2000px)}}@-o-keyframes bounceOutDown{0%{transform:translateY(0)}20%{opacity:1;transform:translateY(-20px)}100%{opacity:0;transform:translateY(2000px)}}@keyframes bounceOutDown{0%{transform:translateY(0)}20%{opacity:1;transform:translateY(-20px)}100%{opacity:0;transform:translateY(2000px)}}@-moz-keyframes bounceOutLeft{0%{transform:translateX(0)}20%{opacity:1;transform:translateX(20px)}100%{opacity:0;transform:translateX(-2000px)}}@-webkit-keyframes bounceOutLeft{0%{transform:translateX(0)}20%{opacity:1;transform:translateX(20px)}100%{opacity:0;transform:translateX(-2000px)}}@-o-keyframes bounceOutLeft{0%{transform:translateX(0)}20%{opacity:1;transform:translateX(20px)}100%{opacity:0;transform:translateX(-2000px)}}@keyframes bounceOutLeft{0%{transform:translateX(0)}20%{opacity:1;transform:translateX(20px)}100%{opacity:0;transform:translateX(-2000px)}}@-moz-keyframes bounceOutRight{0%{transform:translateX(0)}20%{opacity:1;transform:translateX(-20px)}100%{opacity:0;transform:translateX(2000px)}}@-webkit-keyframes bounceOutRight{0%{transform:translateX(0)}20%{opacity:1;transform:translateX(-20px)}100%{opacity:0;transform:translateX(2000px)}}@-o-keyframes bounceOutRight{0%{transform:translateX(0)}20%{opacity:1;transform:translateX(-20px)}100%{opacity:0;transform:translateX(2000px)}}@keyframes bounceOutRight{0%{transform:translateX(0)}20%{opacity:1;transform:translateX(-20px)} +100%{opacity:0;transform:translateX(2000px)}}@-moz-keyframes rotateIn{0%{transform-origin:center center;transform:rotate(-200deg);opacity:0}100%{transform-origin:center center;transform:rotate(0);opacity:1}}@-webkit-keyframes rotateIn{0%{transform-origin:center center;transform:rotate(-200deg);opacity:0}100%{transform-origin:center center;transform:rotate(0);opacity:1}}@-o-keyframes rotateIn{0%{transform-origin:center center;transform:rotate(-200deg);opacity:0}100%{transform-origin:center center;transform:rotate(0);opacity:1}}@keyframes rotateIn{0%{transform-origin:center center;transform:rotate(-200deg);opacity:0}100%{transform-origin:center center;transform:rotate(0);opacity:1}}@-moz-keyframes rotateInUpLeft{0%{transform-origin:left bottom;transform:rotate(90deg);opacity:0}100%{transform-origin:left bottom;transform:rotate(0);opacity:1}}@-webkit-keyframes rotateInUpLeft{0%{transform-origin:left bottom;transform:rotate(90deg);opacity:0}100%{transform-origin:left bottom;transform:rotate(0);opacity:1}}@-o-keyframes rotateInUpLeft{0%{transform-origin:left bottom;transform:rotate(90deg);opacity:0}100%{transform-origin:left bottom;transform:rotate(0);opacity:1}}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;transform:rotate(90deg);opacity:0}100%{transform-origin:left bottom;transform:rotate(0);opacity:1}}@-moz-keyframes rotateInDownLeft{0%{transform-origin:left bottom;transform:rotate(-90deg);opacity:0}100%{transform-origin:left bottom;transform:rotate(0);opacity:1}}@-webkit-keyframes rotateInDownLeft{0%{transform-origin:left bottom;transform:rotate(-90deg);opacity:0}100%{transform-origin:left bottom;transform:rotate(0);opacity:1}}@-o-keyframes rotateInDownLeft{0%{transform-origin:left bottom;transform:rotate(-90deg);opacity:0}100%{transform-origin:left bottom;transform:rotate(0);opacity:1}}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;transform:rotate(-90deg);opacity:0}100%{transform-origin:left bottom;transform:rotate(0);opacity:1}}@-moz-keyframes rotateInUpRight{0%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}100%{transform-origin:right bottom;transform:rotate(0);opacity:1}}@-webkit-keyframes rotateInUpRight{0%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}100%{transform-origin:right bottom;transform:rotate(0);opacity:1}}@-o-keyframes rotateInUpRight{0%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}100%{transform-origin:right bottom;transform:rotate(0);opacity:1}}@keyframes rotateInUpRight{0%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}100%{transform-origin:right bottom;transform:rotate(0);opacity:1}}@-moz-keyframes rotateInDownRight{0%{transform-origin:right bottom;transform:rotate(90deg);opacity:0}100%{transform-origin:right bottom;transform:rotate(0);opacity:1}}@-webkit-keyframes rotateInDownRight{0%{transform-origin:right bottom;transform:rotate(90deg);opacity:0}100%{transform-origin:right bottom;transform:rotate(0);opacity:1}}@-o-keyframes rotateInDownRight{0%{transform-origin:right bottom;transform:rotate(90deg);opacity:0}100%{transform-origin:right bottom;transform:rotate(0);opacity:1}}@keyframes rotateInDownRight{0%{transform-origin:right bottom;transform:rotate(90deg);opacity:0}100%{transform-origin:right bottom;transform:rotate(0);opacity:1}}@-moz-keyframes rotateOut{0%{transform-origin:center center;transform:rotate(0);opacity:1}100%{transform-origin:center center;transform:rotate(200deg);opacity:0}}@-webkit-keyframes rotateOut{0%{transform-origin:center center;transform:rotate(0);opacity:1}100%{transform-origin:center center;transform:rotate(200deg);opacity:0}}@-o-keyframes rotateOut{0%{transform-origin:center center;transform:rotate(0);opacity:1}100%{transform-origin:center center;transform:rotate(200deg);opacity:0}}@keyframes rotateOut{0%{transform-origin:center center;transform:rotate(0);opacity:1}100%{transform-origin:center center;transform:rotate(200deg);opacity:0}}@-moz-keyframes rotateOutUpLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1}100%{-transform-origin:left bottom;-transform:rotate(-90deg);opacity:0}}@-webkit-keyframes rotateOutUpLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1}100%{-transform-origin:left bottom;-transform:rotate(-90deg);opacity:0}}@-o-keyframes rotateOutUpLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1}100%{-transform-origin:left bottom;-transform:rotate(-90deg);opacity:0}}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1}100%{-transform-origin:left bottom;-transform:rotate(-90deg);opacity:0}}@-moz-keyframes rotateOutDownLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1}100%{transform-origin:left bottom;transform:rotate(90deg);opacity:0}}@-webkit-keyframes rotateOutDownLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1}100%{transform-origin:left bottom;transform:rotate(90deg);opacity:0}}@-o-keyframes rotateOutDownLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1} +100%{transform-origin:left bottom;transform:rotate(90deg);opacity:0}}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1}100%{transform-origin:left bottom;transform:rotate(90deg);opacity:0}}@-moz-keyframes rotateOutUpRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1}100%{transform-origin:right bottom;transform:rotate(90deg);opacity:0}}@-webkit-keyframes rotateOutUpRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1}100%{transform-origin:right bottom;transform:rotate(90deg);opacity:0}}@-o-keyframes rotateOutUpRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1}100%{transform-origin:right bottom;transform:rotate(90deg);opacity:0}}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1}100%{transform-origin:right bottom;transform:rotate(90deg);opacity:0}}@-moz-keyframes rotateOutDownRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1}100%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}}@-webkit-keyframes rotateOutDownRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1}100%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}}@-o-keyframes rotateOutDownRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1}100%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1}100%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0}}@-moz-keyframes lightSpeedIn{0%{transform:translateX(100%) skewX(-30deg);opacity:0}60%{transform:translateX(-20%) skewX(30deg);opacity:1}80%{transform:translateX(0%) skewX(-15deg);opacity:1}100%{transform:translateX(0%) skewX(0deg);opacity:1}}@-webkit-keyframes lightSpeedIn{0%{transform:translateX(100%) skewX(-30deg);opacity:0}60%{transform:translateX(-20%) skewX(30deg);opacity:1}80%{transform:translateX(0%) skewX(-15deg);opacity:1}100%{transform:translateX(0%) skewX(0deg);opacity:1}}@-o-keyframes lightSpeedIn{0%{transform:translateX(100%) skewX(-30deg);opacity:0}60%{transform:translateX(-20%) skewX(30deg);opacity:1}80%{transform:translateX(0%) skewX(-15deg);opacity:1}100%{transform:translateX(0%) skewX(0deg);opacity:1}}@keyframes lightSpeedIn{0%{transform:translateX(100%) skewX(-30deg);opacity:0}60%{transform:translateX(-20%) skewX(30deg);opacity:1}80%{transform:translateX(0%) skewX(-15deg);opacity:1}100%{transform:translateX(0%) skewX(0deg);opacity:1}}@-moz-keyframes lightSpeedOut{0%{transform:translateX(0%) skewX(0deg);opacity:1}100%{transform:translateX(100%) skewX(-30deg);opacity:0}}@-webkit-keyframes lightSpeedOut{0%{transform:translateX(0%) skewX(0deg);opacity:1}100%{transform:translateX(100%) skewX(-30deg);opacity:0}}@-o-keyframes lightSpeedOut{0%{transform:translateX(0%) skewX(0deg);opacity:1}100%{transform:translateX(100%) skewX(-30deg);opacity:0}}@keyframes lightSpeedOut{0%{transform:translateX(0%) skewX(0deg);opacity:1}100%{transform:translateX(100%) skewX(-30deg);opacity:0}}@-moz-keyframes hinge{0%{transform:rotate(0);transform-origin:top left;animation-timing-function:ease-in-out}20%,60%{transform:rotate(80deg);transform-origin:top left;animation-timing-function:ease-in-out}40%{transform:rotate(60deg);transform-origin:top left;animation-timing-function:ease-in-out}80%{transform:rotate(60deg) translateY(0);opacity:1;transform-origin:top left;animation-timing-function:ease-in-out}100%{transform:translateY(700px);opacity:0}}@-webkit-keyframes hinge{0%{transform:rotate(0);transform-origin:top left;animation-timing-function:ease-in-out}20%,60%{transform:rotate(80deg);transform-origin:top left;animation-timing-function:ease-in-out}40%{transform:rotate(60deg);transform-origin:top left;animation-timing-function:ease-in-out}80%{transform:rotate(60deg) translateY(0);opacity:1;transform-origin:top left;animation-timing-function:ease-in-out}100%{transform:translateY(700px);opacity:0}}@-o-keyframes hinge{0%{transform:rotate(0);transform-origin:top left;animation-timing-function:ease-in-out}20%,60%{transform:rotate(80deg);transform-origin:top left;animation-timing-function:ease-in-out}40%{transform:rotate(60deg);transform-origin:top left;animation-timing-function:ease-in-out}80%{transform:rotate(60deg) translateY(0);opacity:1;transform-origin:top left;animation-timing-function:ease-in-out}100%{transform:translateY(700px);opacity:0}}@keyframes hinge{0%{transform:rotate(0);transform-origin:top left;animation-timing-function:ease-in-out}20%,60%{transform:rotate(80deg);transform-origin:top left;animation-timing-function:ease-in-out}40%{transform:rotate(60deg);transform-origin:top left;animation-timing-function:ease-in-out}80%{transform:rotate(60deg) translateY(0);opacity:1;transform-origin:top left;animation-timing-function:ease-in-out}100%{transform:translateY(700px);opacity:0}}@-moz-keyframes rollIn{0%{opacity:0;transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;transform:translateX(0px) rotate(0deg)} +}@-webkit-keyframes rollIn{0%{opacity:0;transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;transform:translateX(0px) rotate(0deg)}}@-o-keyframes rollIn{0%{opacity:0;transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;transform:translateX(0px) rotate(0deg)}}@keyframes rollIn{0%{opacity:0;transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;transform:translateX(0px) rotate(0deg)}}@-moz-keyframes rollOut{0%{opacity:1;transform:translateX(0px) rotate(0deg)}100%{opacity:0;transform:translateX(100%) rotate(120deg)}}@-webkit-keyframes rollOut{0%{opacity:1;transform:translateX(0px) rotate(0deg)}100%{opacity:0;transform:translateX(100%) rotate(120deg)}}@-o-keyframes rollOut{0%{opacity:1;transform:translateX(0px) rotate(0deg)}100%{opacity:0;transform:translateX(100%) rotate(120deg)}}@keyframes rollOut{0%{opacity:1;transform:translateX(0px) rotate(0deg)}100%{opacity:0;transform:translateX(100%) rotate(120deg)}}.article-entry pre,.article-entry .highlight{background:#2d2d2d;padding:15px 20px;border-style:solid;border-color:#ddd;border-width:1px 0;overflow:auto;color:#ccc;line-height:22.400000000000002px}.article-entry .highlight .gutter pre,.article-entry .gist .gist-file .gist-data .line-numbers{color:#666;font-size:.85em}.article-entry pre,.article-entry code{font-family:"Source Code Pro",Consolas,Monaco,Menlo,Consolas,monospace}.article-entry code{background:#eee;text-shadow:0 1px #fff;padding:0 .3em}.article-entry pre code{background:0;text-shadow:none;padding:0}.article-entry .highlight pre{border:0;margin:10;padding:0}.article-entry .highlight table{margin:0;width:auto}.article-entry .highlight td{border:0;padding:0}.article-entry .highlight figcaption{font-size:.85em;color:#999;line-height:1em;margin-bottom:1em}.article-entry .highlight figcaption a{float:right}.article-entry .highlight .gutter pre{text-align:right;padding-right:20px}.article-entry .highlight .line{height:22.400000000000002px}.article-entry .gist{margin:0 -20px;border-style:solid;border-color:#ddd;border-width:1px 0;background:#2d2d2d;padding:15px 20px 15px 0}.article-entry .gist .gist-file{border:0;font-family:"Source Code Pro",Consolas,Monaco,Menlo,Consolas,monospace;margin:0}.article-entry .gist .gist-file .gist-data{background:0;border:0}.article-entry .gist .gist-file .gist-data .line-numbers{background:0;border:0;padding:0 20px 0 0}.article-entry .gist .gist-file .gist-data .line-data{padding:0!important}.article-entry .gist .gist-file .highlight{margin:0;padding:0;border:0}.article-entry .gist .gist-file .gist-meta{background:#2d2d2d;color:#999;font:.85em "Helvetica Neue",Helvetica,Arial,sans-serif;text-shadow:0;padding:0;margin-top:1em;margin-left:20px}.article-entry .gist .gist-file .gist-meta a{color:#258fb8;font-weight:normal}.article-entry .gist .gist-file .gist-meta a:hover{text-decoration:underline}pre .comment,pre .title{color:#999}pre .variable,pre .attribute,pre .tag,pre .regexp,pre .ruby .constant,pre .xml .tag .title,pre .xml .pi,pre .xml .doctype,pre .html .doctype,pre .css .id,pre .css .class,pre .css .pseudo{color:#f2777a}pre .number,pre .preprocessor,pre .built_in,pre .literal,pre .params,pre .constant{color:#f99157}pre .class,pre .ruby .class .title,pre .css .rules .attribute{color:#9c9}pre .string,pre .value,pre .inheritance,pre .header,pre .ruby .symbol,pre .xml .cdata{color:#9c9}pre .css .hexcolor{color:#6cc}pre .function,pre .python .decorator,pre .python .title,pre .ruby .function .title,pre .ruby .title .keyword,pre .perl .sub,pre .javascript .title,pre .coffeescript .title{color:#69c}pre .keyword,pre .javascript .function{color:#c9c}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after{content:"";content:none}q:before,q:after{content:"";content:none}figure table{border-collapse:collapse;border-spacing:0;width:100%;table-layout:fixed;text-align:left;border-width:0;margin:auto}figure th,figure td{padding:0}body{width:100%;*zoom:1}body:before,body:after{content:"";display:table}body:after{clear:both}html,body{height:100%}html{height:100%;max-height:100%}body{font-family:"ff-tisa-web-pro-1","ff-tisa-web-pro-2","Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;font-size:1em;color:#666;-webkit-font-smoothing:antialiased} +::selection{background:rgba(37,104,163,0.2)}::-moz-selection{background:rgba(37,104,163,0.2)}a{text-decoration:none;color:#4e97d8}a:hover{color:#4e97d8;-o-transition:.5s;-ms-transition:.5s;-moz-transition:.5s;-webkit-transition:.5s}h1,h2,h3,h4,h5{margin-top:1em;margin-bottom:.5em;font-family:"ff-tisa-web-pro-1","ff-tisa-web-pro-2","Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;font-weight:lighter;color:#333;-webkit-font-smoothing:antialiased}h1{margin-top:0;font-size:2.5em;line-height:1.2em;letter-spacing:.05em}h2{font-size:2em}h3{font-size:1.6em}h4{font-size:1.2em}h4{font-size:1.1em}h5{font-size:1em}p{margin-bottom:1.3em;line-height:1.7em}strong{font-weight:bold}em{font-style:italic}blockquote{padding:10px 20px;margin:0 0 20px;font-family:"ff-tisa-web-pro-1","ff-tisa-web-pro-2","Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;font-weight:lighter;font-size:1em;border-left:3px solid #4e97d8}blockquote p:last-child{margin-bottom:0}ol,ul{margin:0 0 1.3em 2.5em}ol li,ul li{margin:0 0 .2em 0;line-height:1.6em}ol ol,ol ul,ul ol,ul ul{margin:.1em 0 .2em 2em}ol{list-style-type:decimal}ul{list-style-type:disc}code{padding:.1em .4em;background:#e8f2fb;border:1px solid #c9e1f6;border-radius:3px;font-family:Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif;font-size:.9em;vertical-align:bottom;word-wrap:break-word}pre{margin-bottom:1.3em;padding:1em 2.5%;background:#e8f2fb;border:1px solid #c9e1f6;border-radius:3px;font-family:Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif;font-size:.9em;font-weight:normal;line-height:1.7em;overflow:scroll}pre code{padding:0;background:0;border:0;word-wrap:normal}table{color:#333;font-size:.9em;text-align:center;line-height:40px;border-spacing:0;border:2px solid #4e97d8;width:90%;margin:50px auto}thead tr:first-child{background-color:#4e97d8;color:#f8f8f8;border:0}th{font-weight:bold}th,td{padding:0 8px 0 8px}thead tr:last-child th{border-bottom:1px solid #ddd}tbody tr:last-child td{border:0}tbody td{border-bottom:1px solid #ddd;font-size:.9em}.date,.time,.author,.tags{font-size:.8em;color:#c7c7c7}.date a,.time a,.author a,.tags a{color:#666}.date a:hover,.time a:hover,.author a:hover,.tags a:hover{color:#4e97d8}.excerpt{margin:0;font-size:.9em;color:#999}.intro{font-family:"ff-tisa-web-pro-1","ff-tisa-web-pro-2","Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;font-size:1.2em;font-weight:lighter;color:#999}.block-heading{display:inline;float:left;width:940px;margin:0 10px;position:relative;bottom:-15px;font-size:.8em;font-weight:bold;text-align:center;text-transform:uppercase;letter-spacing:1px}.label{position:relative;display:inline-block;padding:8px 18px 9px 18px;background:#4e97d8;border-radius:3px;text-align:center;color:#fff}.container{position:relative;z-index:500;width:940px;margin:0 auto}.content-wrapper{z-index:800;width:70%;margin-left:30%}.content-wrapper__inner{margin:0 10%;padding:50px 0}.footer{display:block;padding:2em 0 0 0;border-top:2px solid #ddd;font-size:.7em;color:#b3b3b3}.footer__copyright{display:block;margin-bottom:.7em}.footer__copyright a{color:#a6a6a6;text-decoration:underline}.footer__copyright a:hover{color:#4e97d8}.avatar,.logo{border-radius:50%;border:3px solid #fff;box-shadow:0 0 1px 1px rgba(0,0,0,0.3)}hr{border:0}.section-title__divider{width:30%;margin:2.2em 0 2.1em 0;border-top:1px solid #ddd}.hidden{display:none!important}.panel{display:table;width:100%;height:100%}.panel__vertical{display:table-cell;vertical-align:middle}.panel-title{font-family:"Old English Text MT","Engravers' Old English BT","Lato","PingFang SC","Microsoft YaHei",sans-serif;margin:0 0 5px 0;font-size:2.5em;color:#fff}.panel-subtitle{font-family:"ff-tisa-web-pro-1","ff-tisa-web-pro-2","Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;font-size:1.2em;font-weight:lighter;letter-spacing:3px;color:#ccc;-webkit-font-smoothing:antialiased}.iUp{opacity:0;-webkit-transform:translate3d(0,80px,0);-moz-transform:translate3d(0,80px,0);-ms-transform:translate3d(0,80px,0);-o-transform:translate3d(0,80px,0);transform:translate3d(0,80px,0);-webkit-transition:all 2s cubic-bezier(0.19,1,0.22,1);-moz-transition:all 2s cubic-bezier(0.19,1,0.22,1);transition:all 2s cubic-bezier(0.19,1,0.22,1)} +.iUp.up{opacity:1;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.panel-cover{display:block;position:fixed;z-index:900;width:100%;max-width:none;height:100%;background:center center no-repeat #666;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0);-webkit-transition:width .6s ease;-moz-transition:width .6s ease;transition:width .6s ease}.panel-cover--collapsed{width:30%}.panel-cover--collapsed .iUp{-webkit-transition:all 0 cubic-bezier(0.19,1,0.22,1);-moz-transition:all 0 cubic-bezier(0.19,1,0.22,1);transition:all 0 cubic-bezier(0.19,1,0.22,1)}.panel-cover--overlay{display:block;position:absolute;top:0;right:0;bottom:0;left:0;background-color:#000;z-index:1;-moz-opacity:.55;opacity:.55}.panel-cover__logo{margin-bottom:.2em}.panel-cover__description{margin:0 30px}.panel-cover__divider{width:50%;margin:20px auto;border-top:1px solid rgba(255,255,255,0.14)}.panel-cover__divider--secondary{width:15%}.panel-main{display:table;width:100%;height:100%}.panel-main__inner{display:table-cell;vertical-align:middle;position:relative;z-index:800;padding:0 60px}.panel-main__content{max-width:620px;margin:0 auto}.panel-main__content--fixed{width:480px;transition:width 1s;-webkit-transition:width 1s}.panel-inverted{font-weight:100;text-align:center;color:#fff;text-shadow:0 1px 1px rgba(0,0,0,0.4)}.panel-inverted a{color:#fff}.cover-navigation{margin-top:42px}.cover-navigation--social{margin-left:30px}.btn,.navigation__item a{padding:10px 20px;border:1px solid #4e97d8;border-radius:20px;font-size:.9em;font-weight:bold;letter-spacing:1px;text-shadow:none;color:#4e97d8;-webkit-font-smoothing:antialiased}.btn:hover,.navigation__item a:hover{color:#4e97d8;border-color:#4e97d8}.btn-border-small{border:1px solid #4e97d8;border-radius:20px;padding:6px 8px;font-size:.8em;margin-left:10px}.btn-secondary{border-color:#5ba4e5;color:#5ba4e5}.btn-secondary:hover{color:#217fd2;border-color:#217fd2}.btn-tertiary{border-color:#999;color:#999}.btn-tertiary:hover{color:#737373;border-color:#737373}.btn-large{padding:10px 24px;font-size:1.1em}.btn-small{padding:8px 12px;font-size:.7em}.btn-mobile-menu{display:none;position:fixed;z-index:9999;top:0;right:0;left:0;width:100%;height:35px;background:rgba(51,51,51,0.98);border-bottom:1px solid rgba(255,255,255,0.1);text-align:center}.btn-mobile-menu__icon,.btn-mobile-close__icon{position:relative;top:10px;color:#fff}nav{display:inline-block;position:relative}.navigation{display:inline-block;float:left;position:relative;margin:0;list-style-type:none}.navigation__item{display:inline-block;margin:5px 1px 0 0;line-height:1em}.navigation__item a{display:block;position:relative;border-color:#fff;color:#fff;opacity:.8}.navigation__item a:hover{color:#fff;border-color:#fff;opacity:1}.navigation--social a{border:0;padding:6px 8px 6px 9px}.navigation--social a .label{display:none}.navigation--social a .icon{display:block;font-size:1.7em}.pagination{display:block;margin:0 0 4em 0}.pagination__page-number{margin:0;font-size:.8em;color:#999}.pagination__newer{margin-right:1em}.pagination__older{margin-left:1em}i{font-family:"entypo";font-weight:normal;font-style:normal;font-size:18px}.social{font-size:22px}.icon-social{font-family:"entypo-social";font-size:22px;display:block;position:relative}*:focus{outline:0}@media all and (max-width:1100px){.panel-cover__logo{width:70px}.panel-title{font-size:2em}.panel-subtitle{font-size:1em}.panel-cover__description{margin:0 10px;font-size:.9em}.navigation--social{margin-top:5px;margin-left:0}}@media all and (max-width:960px){.btn-mobile-menu{display:block}.panel-main{display:table;position:relative}.panel-cover--collapsed{width:100%;max-width:none}.panel-main__inner{display:table-cell;padding:60px 10%}.panel-cover__description{display:block;max-width:600px;margin:0 auto}.panel-cover__divider--secondary{display:none}.panel-cover{width:100%;height:100%;background-position:center center}.panel-cover.panel-cover--collapsed{display:block;position:relative;height:auto;padding:0;background-position:center center}.panel-cover.panel-cover--collapsed .panel-main__inner{display:block;padding:70px 0 30px 0}.panel-cover.panel-cover--collapsed .panel-cover__logo{width:60px;border-width:2px}.panel-cover.panel-cover--collapsed .panel-cover__description{display:none}.panel-cover.panel-cover--collapsed .panel-cover__divider{display:none;margin:1em auto}.navigation-wrapper{display:none;position:fixed;top:0;right:0;left:0;width:100%;padding:20px 0;background:rgba(51,51,51,0.98);border-bottom:1px solid rgba(255,255,255,0.15)}.navigation-wrapper.visible{display:block}.cover-navigation{display:block;position:relative;float:left;clear:left;width:100%} +.cover-navigation .navigation{display:block;width:100%}.cover-navigation .navigation li{width:80%;margin-bottom:.4em}.cover-navigation.navigation--social{padding-top:5px}.cover-navigation.navigation--social .navigation li{display:inline-block;width:20%}.content-wrapper{width:80%;max-width:none;margin:0 auto}.content-wrapper__inner{margin-right:0;margin-left:0}.navigation__item{width:100%;margin:0 0 .4em 0}}@media all and (max-width:340px){.panel-main__inner{padding:0 5%}.panel-title{margin-bottom:.1em;font-size:1.5em}.panel-subtitle{font-size:.9em}.btn,.navigation__item a{display:block;margin-bottom:.4em}}.remark{position:absolute;z-index:900;bottom:0;right:5px;text-align:center;color:#fff;opacity:.6!important;padding-bottom:3px;padding-right:18px;letter-spacing:-3px;font-size:0}@media screen and (max-width:960px){.remark{display:none}}.power{display:inline-block;color:#fff!important;letter-spacing:0;font-size:10px;line-height:16px;margin:0;vertical-align:middle}.beian{position:absolute;z-index:900;bottom:0;right:5px;text-align:center;color:#fff;opacity:.25!important;padding-bottom:3px;letter-spacing:-3px;font-size:0}.gwab,.icp{display:inline-block;color:#fff!important;letter-spacing:0;font-size:10px;line-height:16px;margin:0;vertical-align:middle}.gwab{background:url(/img/beian.png) no-repeat;background-size:12px 12px;background-position:3px 2px;padding-left:18px}@-webkit-keyframes btnGroups{0%{-webkit-transform:scale(1.2,0.8)}1%{-webkit-transform:scale(1.18,0.82)}2%{-webkit-transform:scale(1.16,0.84)}3%{-webkit-transform:scale(1.13,0.87)}4%{-webkit-transform:scale(1.1,0.9)}5%{-webkit-transform:scale(1.07,0.93)}6%{-webkit-transform:scale(1.04,0.96)}7%{-webkit-transform:scale(1.01,0.99)}8%{-webkit-transform:scale(0.99,1.01)}9%{-webkit-transform:scale(0.97,1.03)}10%{-webkit-transform:scale(0.95,1.05)}11%{-webkit-transform:scale(0.94,1.06)}12%{-webkit-transform:scale(0.93,1.07)}13%{-webkit-transform:scale(0.93,1.07)}14%{-webkit-transform:scale(0.93,1.07)}15%{-webkit-transform:scale(0.93,1.07)}16%{-webkit-transform:scale(0.94,1.06)}17%{-webkit-transform:scale(0.94,1.06)}18%{-webkit-transform:scale(0.95,1.05)}19%{-webkit-transform:scale(0.96,1.04)}20%{-webkit-transform:scale(0.98,1.02)}21%{-webkit-transform:scale(0.99,1.01)}22%{-webkit-transform:scale(1,1)}23%{-webkit-transform:scale(1,1)}24%{-webkit-transform:scale(1.01,0.99)}25%{-webkit-transform:scale(1.02,0.98)}26%{-webkit-transform:scale(1.02,0.98)}27%{-webkit-transform:scale(1.02,0.98)}28%{-webkit-transform:scale(1.03,0.97)}29%{-webkit-transform:scale(1.03,0.97)}30%{-webkit-transform:scale(1.02,0.98)}31%{-webkit-transform:scale(1.02,0.98)}32%{-webkit-transform:scale(1.02,0.98)}33%{-webkit-transform:scale(1.02,0.98)}34%{-webkit-transform:scale(1.01,0.99)}35%{-webkit-transform:scale(1.01,0.99)}36%{-webkit-transform:scale(1.01,0.99)}37%{-webkit-transform:scale(1,1)}38%{-webkit-transform:scale(1,1)}39%{-webkit-transform:scale(1,1)}40%{-webkit-transform:scale(0.99,1.01)}41%{-webkit-transform:scale(0.99,1.01)}42%{-webkit-transform:scale(0.99,1.01)}43%{-webkit-transform:scale(0.99,1.01)}44%{-webkit-transform:scale(0.99,1.01)}45%{-webkit-transform:scale(0.99,1.01)}46%{-webkit-transform:scale(0.99,1.01)}47%{-webkit-transform:scale(0.99,1.01)}48%{-webkit-transform:scale(0.99,1.01)}49%{-webkit-transform:scale(1,1)}}@-moz-keyframes btnGroups{0%{-webkit-transform:scale(1.2,0.8);-moz-transform:scale(1.2,0.8);-ms-transform:scale(1.2,0.8);transform:scale(1.2,0.8)}1%{-webkit-transform:scale(1.18,0.82);-moz-transform:scale(1.18,0.82);-ms-transform:scale(1.18,0.82);transform:scale(1.18,0.82)}2%{-webkit-transform:scale(1.16,0.84);-moz-transform:scale(1.16,0.84);-ms-transform:scale(1.16,0.84);transform:scale(1.16,0.84)}3%{-webkit-transform:scale(1.13,0.87);-moz-transform:scale(1.13,0.87);-ms-transform:scale(1.13,0.87);transform:scale(1.13,0.87)}4%{-webkit-transform:scale(1.1,0.9);-moz-transform:scale(1.1,0.9);-ms-transform:scale(1.1,0.9);transform:scale(1.1,0.9)}5%{-webkit-transform:scale(1.07,0.93);-moz-transform:scale(1.07,0.93);-ms-transform:scale(1.07,0.93);transform:scale(1.07,0.93)}6%{-webkit-transform:scale(1.04,0.96);-moz-transform:scale(1.04,0.96);-ms-transform:scale(1.04,0.96);transform:scale(1.04,0.96)}7%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}8%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}9%{-webkit-transform:scale(0.97,1.03);-moz-transform:scale(0.97,1.03);-ms-transform:scale(0.97,1.03);transform:scale(0.97,1.03)}10%{-webkit-transform:scale(0.95,1.05);-moz-transform:scale(0.95,1.05);-ms-transform:scale(0.95,1.05);transform:scale(0.95,1.05)}11%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}12%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}13%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)} +14%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}15%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}16%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}17%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}18%{-webkit-transform:scale(0.95,1.05);-moz-transform:scale(0.95,1.05);-ms-transform:scale(0.95,1.05);transform:scale(0.95,1.05)}19%{-webkit-transform:scale(0.96,1.04);-moz-transform:scale(0.96,1.04);-ms-transform:scale(0.96,1.04);transform:scale(0.96,1.04)}20%{-webkit-transform:scale(0.98,1.02);-moz-transform:scale(0.98,1.02);-ms-transform:scale(0.98,1.02);transform:scale(0.98,1.02)}21%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}22%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}23%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}24%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}25%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}26%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}27%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}28%{-webkit-transform:scale(1.03,0.97);-moz-transform:scale(1.03,0.97);-ms-transform:scale(1.03,0.97);transform:scale(1.03,0.97)}29%{-webkit-transform:scale(1.03,0.97);-moz-transform:scale(1.03,0.97);-ms-transform:scale(1.03,0.97);transform:scale(1.03,0.97)}30%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}31%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}32%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}33%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}34%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}35%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}36%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}37%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}38%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}39%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}40%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}41%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}42%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}43%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}44%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}45%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}46%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}47%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}48%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}49%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}}@-webkit-keyframes btnGroups{0%{-webkit-transform:scale(1.2,0.8);-moz-transform:scale(1.2,0.8);-ms-transform:scale(1.2,0.8);transform:scale(1.2,0.8)}1%{-webkit-transform:scale(1.18,0.82);-moz-transform:scale(1.18,0.82);-ms-transform:scale(1.18,0.82);transform:scale(1.18,0.82)}2%{-webkit-transform:scale(1.16,0.84);-moz-transform:scale(1.16,0.84);-ms-transform:scale(1.16,0.84);transform:scale(1.16,0.84)}3%{-webkit-transform:scale(1.13,0.87);-moz-transform:scale(1.13,0.87);-ms-transform:scale(1.13,0.87);transform:scale(1.13,0.87)} +4%{-webkit-transform:scale(1.1,0.9);-moz-transform:scale(1.1,0.9);-ms-transform:scale(1.1,0.9);transform:scale(1.1,0.9)}5%{-webkit-transform:scale(1.07,0.93);-moz-transform:scale(1.07,0.93);-ms-transform:scale(1.07,0.93);transform:scale(1.07,0.93)}6%{-webkit-transform:scale(1.04,0.96);-moz-transform:scale(1.04,0.96);-ms-transform:scale(1.04,0.96);transform:scale(1.04,0.96)}7%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}8%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}9%{-webkit-transform:scale(0.97,1.03);-moz-transform:scale(0.97,1.03);-ms-transform:scale(0.97,1.03);transform:scale(0.97,1.03)}10%{-webkit-transform:scale(0.95,1.05);-moz-transform:scale(0.95,1.05);-ms-transform:scale(0.95,1.05);transform:scale(0.95,1.05)}11%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}12%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}13%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}14%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}15%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}16%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}17%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}18%{-webkit-transform:scale(0.95,1.05);-moz-transform:scale(0.95,1.05);-ms-transform:scale(0.95,1.05);transform:scale(0.95,1.05)}19%{-webkit-transform:scale(0.96,1.04);-moz-transform:scale(0.96,1.04);-ms-transform:scale(0.96,1.04);transform:scale(0.96,1.04)}20%{-webkit-transform:scale(0.98,1.02);-moz-transform:scale(0.98,1.02);-ms-transform:scale(0.98,1.02);transform:scale(0.98,1.02)}21%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}22%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}23%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}24%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}25%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}26%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}27%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}28%{-webkit-transform:scale(1.03,0.97);-moz-transform:scale(1.03,0.97);-ms-transform:scale(1.03,0.97);transform:scale(1.03,0.97)}29%{-webkit-transform:scale(1.03,0.97);-moz-transform:scale(1.03,0.97);-ms-transform:scale(1.03,0.97);transform:scale(1.03,0.97)}30%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}31%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}32%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}33%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}34%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}35%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}36%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}37%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}38%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}39%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}40%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}41%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}42%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}43%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)} +44%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}45%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}46%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}47%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}48%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}49%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}}@-o-keyframes btnGroups{0%{-webkit-transform:scale(1.2,0.8);-moz-transform:scale(1.2,0.8);-ms-transform:scale(1.2,0.8);transform:scale(1.2,0.8)}1%{-webkit-transform:scale(1.18,0.82);-moz-transform:scale(1.18,0.82);-ms-transform:scale(1.18,0.82);transform:scale(1.18,0.82)}2%{-webkit-transform:scale(1.16,0.84);-moz-transform:scale(1.16,0.84);-ms-transform:scale(1.16,0.84);transform:scale(1.16,0.84)}3%{-webkit-transform:scale(1.13,0.87);-moz-transform:scale(1.13,0.87);-ms-transform:scale(1.13,0.87);transform:scale(1.13,0.87)}4%{-webkit-transform:scale(1.1,0.9);-moz-transform:scale(1.1,0.9);-ms-transform:scale(1.1,0.9);transform:scale(1.1,0.9)}5%{-webkit-transform:scale(1.07,0.93);-moz-transform:scale(1.07,0.93);-ms-transform:scale(1.07,0.93);transform:scale(1.07,0.93)}6%{-webkit-transform:scale(1.04,0.96);-moz-transform:scale(1.04,0.96);-ms-transform:scale(1.04,0.96);transform:scale(1.04,0.96)}7%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}8%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}9%{-webkit-transform:scale(0.97,1.03);-moz-transform:scale(0.97,1.03);-ms-transform:scale(0.97,1.03);transform:scale(0.97,1.03)}10%{-webkit-transform:scale(0.95,1.05);-moz-transform:scale(0.95,1.05);-ms-transform:scale(0.95,1.05);transform:scale(0.95,1.05)}11%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}12%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}13%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}14%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}15%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}16%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}17%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}18%{-webkit-transform:scale(0.95,1.05);-moz-transform:scale(0.95,1.05);-ms-transform:scale(0.95,1.05);transform:scale(0.95,1.05)}19%{-webkit-transform:scale(0.96,1.04);-moz-transform:scale(0.96,1.04);-ms-transform:scale(0.96,1.04);transform:scale(0.96,1.04)}20%{-webkit-transform:scale(0.98,1.02);-moz-transform:scale(0.98,1.02);-ms-transform:scale(0.98,1.02);transform:scale(0.98,1.02)}21%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}22%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}23%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}24%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}25%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}26%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}27%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}28%{-webkit-transform:scale(1.03,0.97);-moz-transform:scale(1.03,0.97);-ms-transform:scale(1.03,0.97);transform:scale(1.03,0.97)}29%{-webkit-transform:scale(1.03,0.97);-moz-transform:scale(1.03,0.97);-ms-transform:scale(1.03,0.97);transform:scale(1.03,0.97)}30%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}31%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}32%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}33%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)} +34%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}35%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}36%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}37%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}38%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}39%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}40%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}41%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}42%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}43%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}44%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}45%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}46%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}47%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}48%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}49%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}}@keyframes btnGroups{0%{-webkit-transform:scale(1.2,0.8);-moz-transform:scale(1.2,0.8);-ms-transform:scale(1.2,0.8);transform:scale(1.2,0.8)}1%{-webkit-transform:scale(1.18,0.82);-moz-transform:scale(1.18,0.82);-ms-transform:scale(1.18,0.82);transform:scale(1.18,0.82)}2%{-webkit-transform:scale(1.16,0.84);-moz-transform:scale(1.16,0.84);-ms-transform:scale(1.16,0.84);transform:scale(1.16,0.84)}3%{-webkit-transform:scale(1.13,0.87);-moz-transform:scale(1.13,0.87);-ms-transform:scale(1.13,0.87);transform:scale(1.13,0.87)}4%{-webkit-transform:scale(1.1,0.9);-moz-transform:scale(1.1,0.9);-ms-transform:scale(1.1,0.9);transform:scale(1.1,0.9)}5%{-webkit-transform:scale(1.07,0.93);-moz-transform:scale(1.07,0.93);-ms-transform:scale(1.07,0.93);transform:scale(1.07,0.93)}6%{-webkit-transform:scale(1.04,0.96);-moz-transform:scale(1.04,0.96);-ms-transform:scale(1.04,0.96);transform:scale(1.04,0.96)}7%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}8%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}9%{-webkit-transform:scale(0.97,1.03);-moz-transform:scale(0.97,1.03);-ms-transform:scale(0.97,1.03);transform:scale(0.97,1.03)}10%{-webkit-transform:scale(0.95,1.05);-moz-transform:scale(0.95,1.05);-ms-transform:scale(0.95,1.05);transform:scale(0.95,1.05)}11%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}12%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}13%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}14%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}15%{-webkit-transform:scale(0.93,1.07);-moz-transform:scale(0.93,1.07);-ms-transform:scale(0.93,1.07);transform:scale(0.93,1.07)}16%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}17%{-webkit-transform:scale(0.94,1.06);-moz-transform:scale(0.94,1.06);-ms-transform:scale(0.94,1.06);transform:scale(0.94,1.06)}18%{-webkit-transform:scale(0.95,1.05);-moz-transform:scale(0.95,1.05);-ms-transform:scale(0.95,1.05);transform:scale(0.95,1.05)}19%{-webkit-transform:scale(0.96,1.04);-moz-transform:scale(0.96,1.04);-ms-transform:scale(0.96,1.04);transform:scale(0.96,1.04)}20%{-webkit-transform:scale(0.98,1.02);-moz-transform:scale(0.98,1.02);-ms-transform:scale(0.98,1.02);transform:scale(0.98,1.02)}21%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}22%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}23%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)} +24%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}25%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}26%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}27%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}28%{-webkit-transform:scale(1.03,0.97);-moz-transform:scale(1.03,0.97);-ms-transform:scale(1.03,0.97);transform:scale(1.03,0.97)}29%{-webkit-transform:scale(1.03,0.97);-moz-transform:scale(1.03,0.97);-ms-transform:scale(1.03,0.97);transform:scale(1.03,0.97)}30%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}31%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}32%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}33%{-webkit-transform:scale(1.02,0.98);-moz-transform:scale(1.02,0.98);-ms-transform:scale(1.02,0.98);transform:scale(1.02,0.98)}34%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}35%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}36%{-webkit-transform:scale(1.01,0.99);-moz-transform:scale(1.01,0.99);-ms-transform:scale(1.01,0.99);transform:scale(1.01,0.99)}37%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}38%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}39%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}40%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}41%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}42%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}43%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}44%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}45%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}46%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}47%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}48%{-webkit-transform:scale(0.99,1.01);-moz-transform:scale(0.99,1.01);-ms-transform:scale(0.99,1.01);transform:scale(0.99,1.01)}49%{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot new file mode 100644 index 00000000..09b6df61 Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot differ diff --git a/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.svg b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.svg new file mode 100644 index 00000000..72f7b9d6 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.svg @@ -0,0 +1,963 @@ + + + + +Created by FontForge 20120731 at Wed Jul 10 02:09:32 2019 + By www +Copyright 1990-2003 Bitstream Inc. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.ttf b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.ttf new file mode 100644 index 00000000..e669e665 Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.ttf differ diff --git a/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff new file mode 100644 index 00000000..70f4359e Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff differ diff --git a/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff2 b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff2 new file mode 100644 index 00000000..e8836566 Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff2 differ diff --git a/ruoyi-admin/src/main/resources/static/assets/img/logo.jpg b/ruoyi-admin/src/main/resources/static/assets/img/logo.jpg new file mode 100644 index 00000000..a19036b6 Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/assets/img/logo.jpg differ diff --git a/ruoyi-admin/src/main/resources/static/assets/img/logo1.jpg b/ruoyi-admin/src/main/resources/static/assets/img/logo1.jpg new file mode 100644 index 00000000..774772fc Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/assets/img/logo1.jpg differ diff --git a/ruoyi-admin/src/main/resources/static/assets/img/logo2.jpg b/ruoyi-admin/src/main/resources/static/assets/img/logo2.jpg new file mode 100644 index 00000000..421bf881 Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/assets/img/logo2.jpg differ diff --git a/ruoyi-admin/src/main/resources/static/assets/img/logo3.jpg b/ruoyi-admin/src/main/resources/static/assets/img/logo3.jpg new file mode 100644 index 00000000..df545864 Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/assets/img/logo3.jpg differ diff --git a/ruoyi-admin/src/main/resources/static/assets/js/bing.js b/ruoyi-admin/src/main/resources/static/assets/js/bing.js new file mode 100644 index 00000000..5700f389 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/assets/js/bing.js @@ -0,0 +1,38 @@ +const https = require('https') +const fs = require('fs') + +const options = { + hostname: 'www.bing.com', + port: 443, + path: '/HPImageArchive.aspx?format=js&idx=0&n=8', + method: 'GET' +} + +const req = https.request(options, bing_res => { + let bing_body = [], bing_data = {}; + bing_res.on('data', (chunk) => { + bing_body.push(chunk); + }); + bing_res.on('end', () => { + bing_body = Buffer.concat(bing_body); + bing_data = JSON.parse(bing_body.toString()); + let img_array = bing_data.images; + let img_url = []; + img_array.forEach(img => { + img_url.push(img.url); + }); + var jsonpStr = "getBingImages(" + JSON.stringify(img_url) + ")"; + fs.writeFile('./assets/json/images.json', jsonpStr, (err) => { + if (err) { + throw err; + } + console.log("JSON data is saved: " + jsonpStr); + }); + }); +}) + +req.on('error', error => { + console.error(error) +}) + +req.end() \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/assets/js/main.js b/ruoyi-admin/src/main/resources/static/assets/js/main.js new file mode 100644 index 00000000..a5ead0ce --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/assets/js/main.js @@ -0,0 +1,104 @@ +var iUp = (function () { + var time = 0, + duration = 150, + clean = function () { + time = 0; + }, + up = function (element) { + setTimeout(function () { + element.classList.add("up"); + }, time); + time += duration; + }, + down = function (element) { + element.classList.remove("up"); + }, + toggle = function (element) { + setTimeout(function () { + element.classList.toggle("up"); + }, time); + time += duration; + }; + return { + clean: clean, + up: up, + down: down, + toggle: toggle + }; +})(); + +function getBingImages(imgUrls) { + /** + * 获取Bing壁纸 + * 先使用 GitHub Action 每天获取 Bing 壁纸 URL 并更新 images.json 文件 + * 然后读取 images.json 文件中的数据 + */ + var indexName = "bing-image-index"; + var index = sessionStorage.getItem(indexName); + var panel = document.querySelector('#panel'); + if (isNaN(index) || index == 7) index = 0; + else index++; + var imgUrl = imgUrls[index]; + var url = "https://www.cn.bing.com" + imgUrl; + panel.style.background = "url('" + url + "') center center no-repeat #666"; + panel.style.backgroundSize = "cover"; + sessionStorage.setItem(indexName, index); +} + +function decryptEmail(encoded) { + var address = atob(encoded); + window.location.href = "mailto:" + address; +} + +document.addEventListener('DOMContentLoaded', function () { + // 获取一言数据 + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var res = JSON.parse(this.responseText); + document.getElementById('description').innerHTML = res.hitokoto + "
    -「" + res.from + "」"; + } + }; + xhr.open("GET", "https://v1.hitokoto.cn", true); + xhr.send(); + + var iUpElements = document.querySelectorAll(".iUp"); + iUpElements.forEach(function (element) { + iUp.up(element); + }); + + var avatarElement = document.querySelector(".js-avatar"); + avatarElement.addEventListener('load', function () { + avatarElement.classList.add("show"); + }); +}); + +var btnMobileMenu = document.querySelector('.btn-mobile-menu__icon'); +var navigationWrapper = document.querySelector('.navigation-wrapper'); + +btnMobileMenu.addEventListener('click', function () { + if (navigationWrapper.style.display == "block") { + navigationWrapper.addEventListener('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () { + navigationWrapper.classList.toggle('visible'); + navigationWrapper.classList.toggle('animated'); + navigationWrapper.classList.toggle('bounceOutUp'); + navigationWrapper.removeEventListener('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', arguments.callee); + }); + navigationWrapper.classList.toggle('animated'); + navigationWrapper.classList.toggle('bounceInDown'); + navigationWrapper.classList.toggle('animated'); + navigationWrapper.classList.toggle('bounceOutUp'); + } else { + navigationWrapper.classList.toggle('visible'); + navigationWrapper.classList.toggle('animated'); + navigationWrapper.classList.toggle('bounceInDown'); + } + btnMobileMenu.classList.toggle('social'); + btnMobileMenu.classList.toggle('iconfont'); + btnMobileMenu.classList.toggle('icon-list'); + btnMobileMenu.classList.toggle('social'); + btnMobileMenu.classList.toggle('iconfont'); + btnMobileMenu.classList.toggle('icon-angleup'); + btnMobileMenu.classList.toggle('animated'); + btnMobileMenu.classList.toggle('fadeIn'); +}); diff --git a/ruoyi-admin/src/main/resources/static/assets/json/images.json b/ruoyi-admin/src/main/resources/static/assets/json/images.json new file mode 100644 index 00000000..dd176a45 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/assets/json/images.json @@ -0,0 +1 @@ +getBingImages(["/th?id=OHR.TheRoachesPeakDistrict_EN-US9733115206_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.SanMiguelAllende_EN-US9621237021_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.JediMonastery_EN-US9398447907_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.SonoranSpring_EN-US9207877073_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.CratersOfTheMoon_EN-US6516727783_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.HawaiianLei_EN-US6290126556_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.CheetahRain_EN-US6179670004_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.TulouFujian_EN-US6009679228_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp"]) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/assets/svg/favicon.svg b/ruoyi-admin/src/main/resources/static/assets/svg/favicon.svg new file mode 100644 index 00000000..700dfc45 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/assets/svg/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/index.html b/ruoyi-admin/src/main/resources/static/index.html index e2122961..4e955668 100644 --- a/ruoyi-admin/src/main/resources/static/index.html +++ b/ruoyi-admin/src/main/resources/static/index.html @@ -1,755 +1,104 @@ - + + - - 开发教程分享 - + + + + + + + + 熊猫助手 + + + + + - -
    -
    - -
    - -
    - + + + + +
    +
    +
    +
    + +

    + ageerle +

    +

    Code Create Life

    +
    +

    如何得与凉风约, 不共尘沙一并来! +
    + -「中牟道中」 +

    + - + - - - - - - - - +
    +
    -
    -
    - -
    - - - - - +
    +

    + Copyright © 2023-2024 版权所有:xmzs 备案号:鄂ICP备20231008611号 +

    +
    +
    + + - - - - diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/com/xmzs/common/chat/utils/TikTokensUtil.java b/ruoyi-common/ruoyi-common-chat/src/main/java/com/xmzs/common/chat/utils/TikTokensUtil.java index 6f63dccf..307b5c15 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/com/xmzs/common/chat/utils/TikTokensUtil.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/com/xmzs/common/chat/utils/TikTokensUtil.java @@ -139,7 +139,7 @@ public class TikTokensUtil { * @return Encoding */ public static Encoding getEncoding(@NotNull String modelName) { - return modelMap.get(modelName); + return modelMap.getOrDefault(modelName, modelMap.get(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())); } /** @@ -204,7 +204,9 @@ public class TikTokensUtil { tokensPerMessage = 3; tokensPerName = 1; }else { - log.warn("不支持的model {}. See https://github.com/openai/openai-python/blob/main/chatml.md 更多信息.",modelName); + log.warn("不支持的model {} 按gpt4计费",modelName); + tokensPerMessage = 3; + tokensPerName = 1; } int sum = 0; for (Message msg : messages) { diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/com/xmzs/common/service/impl/PayServiceImpl.java b/ruoyi-common/ruoyi-common-pay/src/main/java/com/xmzs/common/service/impl/PayServiceImpl.java index eb2a90d6..b084aa47 100644 --- a/ruoyi-common/ruoyi-common-pay/src/main/java/com/xmzs/common/service/impl/PayServiceImpl.java +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/com/xmzs/common/service/impl/PayServiceImpl.java @@ -7,6 +7,7 @@ import com.xmzs.common.config.PayConfig; import com.xmzs.common.service.PayService; import com.xmzs.common.utils.MD5Util; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.HashMap; @@ -18,6 +19,7 @@ import java.util.Map; */ @Service @RequiredArgsConstructor +@Slf4j public class PayServiceImpl implements PayService { private final PayConfig payConfig; @@ -42,6 +44,7 @@ public class PayServiceImpl implements PayService { map.put("type", payConfig.getType()); map.put("sign", sign); String body = HttpUtil.post(payConfig.getPayUrl(), map); + log.info("支付返回信息:{},配置信息: {}",body,payConfig); JSONObject jsonObject = new JSONObject(body); return (String) jsonObject.get("qrcode"); } diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/com/xmzs/common/web/interceptor/PlusWebInvokeTimeInterceptor.java b/ruoyi-common/ruoyi-common-web/src/main/java/com/xmzs/common/web/interceptor/PlusWebInvokeTimeInterceptor.java index 2bd93314..7f8a4e42 100644 --- a/ruoyi-common/ruoyi-common-web/src/main/java/com/xmzs/common/web/interceptor/PlusWebInvokeTimeInterceptor.java +++ b/ruoyi-common/ruoyi-common-web/src/main/java/com/xmzs/common/web/interceptor/PlusWebInvokeTimeInterceptor.java @@ -69,12 +69,12 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor { @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { - if (!prodProfile.equals(SpringUtils.getActiveProfile())) { - StopWatch stopWatch = invokeTimeTL.get(); - stopWatch.stop(); - log.debug("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime()); - invokeTimeTL.remove(); - } +// if (!prodProfile.equals(SpringUtils.getActiveProfile())) { +// StopWatch stopWatch = invokeTimeTL.get(); +// stopWatch.stop(); +// log.debug("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime()); +// invokeTimeTL.remove(); +// } } /** diff --git a/ruoyi-common/ruoyi-common-wechat/src/main/java/com/xmzs/common/wechat/Wechat.java b/ruoyi-common/ruoyi-common-wechat/src/main/java/com/xmzs/common/wechat/Wechat.java index d473f3ac..59158fc4 100644 --- a/ruoyi-common/ruoyi-common-wechat/src/main/java/com/xmzs/common/wechat/Wechat.java +++ b/ruoyi-common/ruoyi-common-wechat/src/main/java/com/xmzs/common/wechat/Wechat.java @@ -1,6 +1,5 @@ package com.xmzs.common.wechat; -import com.xmzs.common.wechat.controller.LoginController; import com.xmzs.common.wechat.core.MsgCenter; import com.xmzs.common.wechat.face.IMsgHandlerFace; import org.slf4j.Logger; @@ -12,13 +11,9 @@ public class Wechat { private static final Logger LOG = LoggerFactory.getLogger(Wechat.class); private IMsgHandlerFace msgHandler; - public Wechat(IMsgHandlerFace msgHandler, String qrPath) { + public Wechat(IMsgHandlerFace msgHandler) { System.setProperty("jsse.enableSNIExtension", "false"); // 防止SSL错误 this.msgHandler = msgHandler; - - // 登陆 - LoginController login = new LoginController(); - login.login(qrPath); } public void start() { diff --git a/ruoyi-common/ruoyi-common-wechat/src/main/java/com/xmzs/common/wechat/controller/LoginController.java b/ruoyi-common/ruoyi-common-wechat/src/main/java/com/xmzs/common/wechat/controller/LoginController.java index 34e157a8..4748348c 100644 --- a/ruoyi-common/ruoyi-common-wechat/src/main/java/com/xmzs/common/wechat/controller/LoginController.java +++ b/ruoyi-common/ruoyi-common-wechat/src/main/java/com/xmzs/common/wechat/controller/LoginController.java @@ -1,7 +1,9 @@ package com.xmzs.common.wechat.controller; +import com.xmzs.common.core.exception.base.BaseException; import com.xmzs.common.wechat.utils.SleepUtils; +import com.xmzs.common.wechat.utils.enums.URLEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,74 +19,71 @@ import com.xmzs.common.wechat.utils.tools.CommonTools; * 登陆控制器 * * @author https://github.com/yaphone - * @date 创建时间:2017年5月13日 下午12:56:07 * @version 1.0 - * + * @date 创建时间:2017年5月13日 下午12:56:07 */ public class LoginController { - private static Logger LOG = LoggerFactory.getLogger(LoginController.class); - private ILoginService loginService = new LoginServiceImpl(); - private static Core core = Core.getInstance(); + private static Logger LOG = LoggerFactory.getLogger(LoginController.class); + private ILoginService loginService = new LoginServiceImpl(); + private static Core core = Core.getInstance(); - public void login(String qrPath) { - if (core.isAlive()) { // 已登陆 - LOG.info("itchat4j已登陆"); - return; - } - while (true) { - for (int count = 0; count < 10; count++) { - LOG.info("获取UUID"); - while (loginService.getUuid() == null) { - LOG.info("1. 获取微信UUID"); - while (loginService.getUuid() == null) { - LOG.warn("1.1. 获取微信UUID失败,两秒后重新获取"); - SleepUtils.sleep(2000); - } - } - LOG.info("2. 获取登陆二维码图片"); - if (loginService.getQR(qrPath)) { - break; - } else if (count == 10) { - LOG.error("2.2. 获取登陆二维码图片失败,系统退出"); - System.exit(0); - } - } - LOG.info("3. 请扫描二维码图片,并在手机上确认"); - if (!core.isAlive()) { - loginService.login(); - core.setAlive(true); - LOG.info(("登陆成功")); - break; - } - LOG.info("4. 登陆超时,请重新扫描二维码图片"); - } - LOG.info("5. 登陆成功,微信初始化"); - if (!loginService.webWxInit()) { - LOG.info("6. 微信初始化异常"); - System.exit(0); - } + /** + * 获取二维码地址 + * 风险:已登录账号不可调用该接口,会移除当前core信息 + * @return + */ + public String login_1() { + if (core.isAlive()) { + LOG.warn("微信已登陆"); + throw new BaseException("微信已登陆"); + } + LOG.info("1.获取微信UUID"); + while (loginService.getUuid() == null) { + LOG.warn("1.1. 获取微信UUID失败,一秒后重新获取"); + SleepUtils.sleep(1000); + } + LOG.info("2. 获取登陆二维码图片"); + return URLEnum.QRCODE_URL.getUrl() + core.getUuid(); + } - LOG.info("6. 开启微信状态通知"); - loginService.wxStatusNotify(); - LOG.info("7. 清除。。。。"); - CommonTools.clearScreen(); - LOG.info(String.format("欢迎回来, %s", core.getNickName())); + public void login_2() { + LOG.info("3. 请扫描二维码图片,并在手机上确认"); + if (!core.isAlive()) { + loginService.login(); + core.setAlive(true); + LOG.info(("登陆成功")); + } + LOG.info("4. 登陆超时,请重新扫描二维码图片"); - LOG.info("8. 开始接收消息"); - loginService.startReceiving(); - LOG.info("9. 获取联系人信息"); - loginService.webWxGetContact(); + LOG.info("5. 登陆成功,微信初始化"); + if (!loginService.webWxInit()) { + LOG.info("6. 微信初始化异常"); + System.exit(0); + } - LOG.info("10. 获取群好友及群好友列表"); - loginService.WebWxBatchGetContact(); + LOG.info("6. 开启微信状态通知"); + loginService.wxStatusNotify(); - LOG.info("11. 缓存本次登陆好友相关消息"); - WechatTools.setUserInfo(); // 登陆成功后缓存本次登陆好友相关消息(NickName, UserName) + LOG.info("7. 清除。。。。"); + CommonTools.clearScreen(); + LOG.info(String.format("欢迎回来, %s", core.getNickName())); - LOG.info("12.开启微信状态检测线程"); - new Thread(new CheckLoginStatusThread()).start(); - } + LOG.info("8. 开始接收消息"); + loginService.startReceiving(); + + LOG.info("9. 获取联系人信息"); + loginService.webWxGetContact(); + + LOG.info("10. 获取群好友及群好友列表"); + loginService.WebWxBatchGetContact(); + + LOG.info("11. 缓存本次登陆好友相关消息"); + WechatTools.setUserInfo(); // 登陆成功后缓存本次登陆好友相关消息(NickName, UserName) + + LOG.info("12.开启微信状态检测线程"); + new Thread(new CheckLoginStatusThread()).start(); + } } diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/FaceController.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/FaceController.java index de75cb3c..4ade81ea 100644 --- a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/FaceController.java +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/FaceController.java @@ -1,36 +1,22 @@ package com.xmzs.midjourney.controller; import cn.hutool.json.JSONUtil; -import com.xmzs.common.chat.constant.OpenAIConst; -import com.xmzs.common.core.domain.model.LoginUser; -import com.xmzs.common.core.exception.base.BaseException; -import com.xmzs.common.satoken.utils.LoginHelper; import com.xmzs.midjourney.domain.InsightFace; -import com.xmzs.system.domain.bo.ChatMessageBo; -import com.xmzs.system.service.IChatMessageService; -import com.xmzs.system.service.IChatService; -import com.xmzs.system.service.ISseService; +import com.xmzs.midjourney.domain.MjPriceConfig; +import com.xmzs.midjourney.util.MjOkHttpUtil; + +import com.xmzs.system.service.IChatCostService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.Response; -import okio.Buffer; -import okio.BufferedSink; -import okio.GzipSink; -import okio.Okio; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import org.apache.commons.lang3.math.NumberUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.io.IOException; -import java.util.concurrent.TimeUnit; @Api(tags = "任务查询") @RestController @@ -39,62 +25,22 @@ import java.util.concurrent.TimeUnit; @Slf4j public class FaceController { - @Value("${chat.apiKey}") - private String apiKey; - @Value("${chat.apiHost}") - private String apiHost; + private final IChatCostService chatCostService; - @Autowired - private IChatService chatService; + private final MjOkHttpUtil mjOkHttpUtil; - @Autowired - private ISseService sseService; + private final MjPriceConfig priceConfig; @ApiOperation(value = "换脸") @PostMapping("/insight-face/swap") public String insightFace(@RequestBody InsightFace insightFace) { - // 查询是否是付费用户 - sseService.checkUserGrade(); - // 扣除接口费用 - chatService.mjTaskDeduct("换脸", OpenAIConst.MJ_COST_TYPE2); - OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(30, TimeUnit.SECONDS) // 连接超时时间 - .writeTimeout(30, TimeUnit.SECONDS) // 写入超时时间 - .readTimeout(30, TimeUnit.SECONDS) // 读取超时时间 - .build(); - // 创建一个Request对象来配置你的请求 + // 扣除接口费用并且保存消息记录 + chatCostService.taskDeduct("mj","换脸", NumberUtils.toDouble(priceConfig.getFaceSwapping(), 0.3)); // 创建请求体(这里使用JSON作为媒体类型) - String jsonStr = JSONUtil.toJsonStr(insightFace); - - MediaType JSON = MediaType.get("application/json; charset=utf-8"); - okhttp3.RequestBody body = okhttp3.RequestBody.create(jsonStr, JSON); - Buffer buffer = new Buffer(); - GzipSink gzipSink = new GzipSink(buffer); - BufferedSink gzipBufferedSink = Okio.buffer(gzipSink); - try { - body.writeTo(gzipBufferedSink); - gzipBufferedSink.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - // 创建POST请求 - Request request = new Request.Builder() - .header("mj-api-secret", apiKey) - .header("Content-Encoding", "gzip") - .url(apiHost + "mj/insight-face/swap") // 替换为你的URL - .post(body) - .build(); - - try (Response response = client.newCall(request).execute()) { - if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - if (response.body() != null) { - return response.body().string(); - } - } catch (IOException e) { - log.error("换脸失败! {}", e.getMessage()); - } - return null; + String insightFaceJson = JSONUtil.toJsonStr(insightFace); + String url = "mj/insight-face/swap"; + Request request = mjOkHttpUtil.createPostRequest(url, insightFaceJson); + return mjOkHttpUtil.executeRequest(request); } } diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/SubmitController.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/SubmitController.java index fb3fe290..a9e1a146 100644 --- a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/SubmitController.java +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/SubmitController.java @@ -1,61 +1,24 @@ package com.xmzs.midjourney.controller; -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.util.RandomUtil; import cn.hutool.json.JSONUtil; -import com.xmzs.common.chat.constant.OpenAIConst; -import com.xmzs.common.core.domain.model.LoginUser; -import com.xmzs.common.core.exception.base.BaseException; -import com.xmzs.common.satoken.utils.LoginHelper; -import com.xmzs.midjourney.Constants; -import com.xmzs.midjourney.ProxyProperties; -import com.xmzs.midjourney.ReturnCode; +import com.xmzs.midjourney.domain.MjPriceConfig; import com.xmzs.midjourney.dto.*; -import com.xmzs.midjourney.enums.TaskAction; -import com.xmzs.midjourney.enums.TaskStatus; -import com.xmzs.midjourney.enums.TranslateWay; -import com.xmzs.midjourney.exception.BannedPromptException; -import com.xmzs.midjourney.result.SubmitResultVO; -import com.xmzs.midjourney.service.TaskService; -import com.xmzs.midjourney.service.TaskStoreService; -import com.xmzs.midjourney.service.TranslateService; -import com.xmzs.midjourney.support.Task; -import com.xmzs.midjourney.support.TaskCondition; -import com.xmzs.midjourney.util.BannedPromptUtils; -import com.xmzs.midjourney.util.ConvertUtils; -import com.xmzs.midjourney.util.MimeTypeUtils; -import com.xmzs.midjourney.util.SnowFlake; -import com.xmzs.midjourney.util.TaskChangeParams; -import com.xmzs.system.domain.bo.ChatMessageBo; -import com.xmzs.system.service.IChatMessageService; -import com.xmzs.system.service.IChatService; -import com.xmzs.system.service.ISseService; -import eu.maxschuster.dataurl.DataUrl; -import eu.maxschuster.dataurl.DataUrlSerializer; -import eu.maxschuster.dataurl.IDataUrlSerializer; +import com.xmzs.midjourney.enums.ActionType; +import com.xmzs.midjourney.util.*; +import com.xmzs.system.service.IChatCostService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import okhttp3.OkHttpClient; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import okhttp3.Request; +import org.apache.commons.lang3.math.NumberUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.io.IOException; -import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import okhttp3.*; +import java.util.Optional; @Api(tags = "任务提交") @RestController @@ -63,285 +26,114 @@ import okhttp3.*; @RequiredArgsConstructor @Slf4j public class SubmitController { - private final TranslateService translateService; - private final ProxyProperties properties; - private final TaskService taskService; - private final TaskStoreService taskStoreService; - @Value("${chat.apiKey}") - private String apiKey; - @Value("${chat.apiHost}") - private String apiHost; - @Autowired - private IChatService chatService; - @Autowired - private IChatMessageService chatMessageService; - @Autowired - private ISseService sseService; - @ApiOperation(value = "提交Imagine任务") - @PostMapping("/imagine") - public SubmitResultVO imagine(@RequestBody SubmitImagineDTO imagineDTO) { - String prompt = imagineDTO.getPrompt(); - if (CharSequenceUtil.isBlank(prompt)) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "prompt不能为空"); - } - prompt = prompt.trim(); - Task task = newTask(imagineDTO); - task.setAction(TaskAction.IMAGINE); - task.setPrompt(prompt); - String promptEn = translatePrompt(prompt); - try { - BannedPromptUtils.checkBanned(promptEn); - } catch (BannedPromptException e) { - return SubmitResultVO.fail(ReturnCode.BANNED_PROMPT, "可能包含敏感词") - .setProperty("promptEn", promptEn).setProperty("bannedWord", e.getMessage()); - } - List base64Array = Optional.ofNullable(imagineDTO.getBase64Array()).orElse(new ArrayList<>()); - if (CharSequenceUtil.isNotBlank(imagineDTO.getBase64())) { - base64Array.add(imagineDTO.getBase64()); - } - List dataUrls; - try { - dataUrls = ConvertUtils.convertBase64Array(base64Array); - } catch (MalformedURLException e) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误"); - } - task.setPromptEn(promptEn); - task.setDescription("/imagine " + prompt); - return this.taskService.submitImagine(task, dataUrls); + private final MjPriceConfig priceConfig; + + private final IChatCostService chatCostService; + + private final MjOkHttpUtil mjOkHttpUtil; + + @ApiOperation(value = "绘图变化") + @PostMapping("/change") + public String change(@RequestBody SubmitChangeDTO changeDTO) { + String jsonStr = JSONUtil.toJsonStr(changeDTO); + String url = "mj/submit/change"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "执行动作") + @PostMapping("/action") + public String action(@RequestBody SubmitActionDTO changeDTO) { + ActionType actionType = ActionType.fromCustomId(getAction(changeDTO.getCustomId())); + Optional.ofNullable(actionType).ifPresentOrElse( + type -> { + switch (type) { + case UP_SAMPLE: + chatCostService.taskDeduct("mj","放大", NumberUtils.toDouble(priceConfig.getUpsample(), 0.1)); + break; + case IN_PAINT: + // 局部重绘已经扣费,不执行任何操作 + break; + default: + chatCostService.taskDeduct("mj","变化", NumberUtils.toDouble(priceConfig.getChange(), 0.3)); + break; + } + }, + () -> chatCostService.taskDeduct("mj","变化", NumberUtils.toDouble(priceConfig.getChange(), 0.3)) + ); + + String jsonStr = JSONUtil.toJsonStr(changeDTO); + String url = "mj/submit/action"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); } @ApiOperation(value = "绘图变化-simple") @PostMapping("/simple-change") - public SubmitResultVO simpleChange(@RequestBody SubmitSimpleChangeDTO simpleChangeDTO) { - TaskChangeParams changeParams = ConvertUtils.convertChangeParams(simpleChangeDTO.getContent()); - if (changeParams == null) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "content参数错误"); - } - SubmitChangeDTO changeDTO = new SubmitChangeDTO(); - changeDTO.setAction(changeParams.getAction()); - changeDTO.setTaskId(changeParams.getId()); - changeDTO.setIndex(changeParams.getIndex()); - changeDTO.setState(simpleChangeDTO.getState()); - changeDTO.setNotifyHook(simpleChangeDTO.getNotifyHook()); - return change(changeDTO); + public String simpleChange(@RequestBody SubmitSimpleChangeDTO simpleChangeDTO) { + String jsonStr = JSONUtil.toJsonStr(simpleChangeDTO); + String url = "mj/submit/simple-change"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); } - @ApiOperation(value = "绘图变化") - @PostMapping("/change") - public SubmitResultVO change(@RequestBody SubmitChangeDTO changeDTO) { - if (CharSequenceUtil.isBlank(changeDTO.getTaskId())) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "taskId不能为空"); - } - if (!Set.of(TaskAction.UPSCALE, TaskAction.VARIATION, TaskAction.REROLL).contains(changeDTO.getAction())) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "action参数错误"); - } - String description = "/up " + changeDTO.getTaskId(); - if (TaskAction.REROLL.equals(changeDTO.getAction())) { - description += " R"; - } else { - description += " " + changeDTO.getAction().name().charAt(0) + changeDTO.getIndex(); - } - if (TaskAction.UPSCALE.equals(changeDTO.getAction())) { - TaskCondition condition = new TaskCondition().setDescription(description); - Task existTask = this.taskStoreService.findOne(condition); - if (existTask != null) { - return SubmitResultVO.of(ReturnCode.EXISTED, "任务已存在", existTask.getId()) - .setProperty("status", existTask.getStatus()) - .setProperty("imageUrl", existTask.getImageUrl()); - } - } - Task targetTask = this.taskStoreService.get(changeDTO.getTaskId()); - if (targetTask == null) { - return SubmitResultVO.fail(ReturnCode.NOT_FOUND, "关联任务不存在或已失效"); - } - if (!TaskStatus.SUCCESS.equals(targetTask.getStatus())) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "关联任务状态错误"); - } - if (!Set.of(TaskAction.IMAGINE, TaskAction.VARIATION, TaskAction.REROLL, TaskAction.BLEND).contains(targetTask.getAction())) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "关联任务不允许执行变化"); - } - Task task = newTask(changeDTO); - task.setAction(changeDTO.getAction()); - task.setPrompt(targetTask.getPrompt()); - task.setPromptEn(targetTask.getPromptEn()); - task.setProperty(Constants.TASK_PROPERTY_FINAL_PROMPT, targetTask.getProperty(Constants.TASK_PROPERTY_FINAL_PROMPT)); - task.setProperty(Constants.TASK_PROPERTY_PROGRESS_MESSAGE_ID, targetTask.getProperty(Constants.TASK_PROPERTY_MESSAGE_ID)); - task.setProperty(Constants.TASK_PROPERTY_DISCORD_INSTANCE_ID, targetTask.getProperty(Constants.TASK_PROPERTY_DISCORD_INSTANCE_ID)); - task.setDescription(description); - int messageFlags = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_FLAGS); - String messageId = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_MESSAGE_ID); - String messageHash = targetTask.getPropertyGeneric(Constants.TASK_PROPERTY_MESSAGE_HASH); - if (TaskAction.UPSCALE.equals(changeDTO.getAction())) { - return this.taskService.submitUpscale(task, messageId, messageHash, changeDTO.getIndex(), messageFlags); - } else if (TaskAction.VARIATION.equals(changeDTO.getAction())) { - return this.taskService.submitVariation(task, messageId, messageHash, changeDTO.getIndex(), messageFlags); - } else { - return this.taskService.submitReroll(task, messageId, messageHash, messageFlags); - } + @ApiOperation(value = "提交图生图、混图任务") + @PostMapping("/blend") + public String blend(@RequestBody SubmitBlendDTO blendDTO) { + chatCostService.taskDeduct("mj","图生图", NumberUtils.toDouble(priceConfig.getBlend(), 0.3)); + String jsonStr = JSONUtil.toJsonStr(blendDTO); + String url = "mj/submit/blend"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); } - @ApiOperation(value = "绘图变化") - @PostMapping("/action") - public String action(@RequestBody SubmitActionDTO changeDTO) { - // 查询是否是付费用户 - sseService.checkUserGrade(); - // 扣除接口费用 - if ("upsample".equals(getAction(changeDTO.getCustomId()))) { - mjTaskDeduct("放大", OpenAIConst.MJ_COST_TYPE2); - } else { - // Inpaint: 局部重绘 - // reroll 重绘 - // upsample 放大 - // zoom 变焦 - // upscale 高清放大 - // variation 变化 - if (!"Inpaint".equals(getAction(changeDTO.getCustomId()))) { - mjTaskDeduct("变化", OpenAIConst.MJ_COST_TYPE1); - } - } - OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(30, TimeUnit.SECONDS) // 连接超时时间 - .writeTimeout(30, TimeUnit.SECONDS) // 写入超时时间 - .readTimeout(30, TimeUnit.SECONDS) // 读取超时时间 - .build(); + @ApiOperation(value = "提交图生文任务") + @PostMapping("/describe") + public String describe(@RequestBody SubmitDescribeDTO describeDTO) { + chatCostService.taskDeduct("mj","图生文", NumberUtils.toDouble(priceConfig.getDescribe(), 0.1)); + String jsonStr = JSONUtil.toJsonStr(describeDTO); + String url = "mj/submit/describe"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } - String jsonStr = JSONUtil.toJsonStr(changeDTO); + @ApiOperation(value = "提交文生图任务") + @PostMapping("/imagine") + public String imagine(@RequestBody SubmitImagineDTO imagineDTO) { + chatCostService.taskDeduct("mj",imagineDTO.getPrompt(), NumberUtils.toDouble(priceConfig.getImagine(), 0.3)); + String jsonStr = JSONUtil.toJsonStr(imagineDTO); + String url = "mj/submit/imagine"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } - MediaType mediaType = MediaType.parse("application/json"); - okhttp3.RequestBody body = okhttp3.RequestBody.create(jsonStr, mediaType); - Request request = new Request.Builder() - .url(apiHost + "mj/submit/action") - .method("POST", body) - .header("mj-api-secret", apiKey) // 设置Authorization header - .build(); - try { - Response response = client.newCall(request).execute(); - return response.body().string(); - } catch (IOException e) { - log.error("绘图变化失败:{}", e.getMessage()); - } - return null; + @ApiOperation(value = "提交局部重绘任务") + @PostMapping("/modal") + public String modal(@RequestBody SubmitModalDTO submitModalDTO) { + chatCostService.taskDeduct("mj","局部重绘", NumberUtils.toDouble(priceConfig.getInpaint(), 0.1)); + String jsonStr = JSONUtil.toJsonStr(submitModalDTO); + String url = "mj/submit/modal"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "提交提示词分析任务") + @PostMapping("/shorten") + public String shorten(@RequestBody SubmitShortenDTO submitShortenDTO) { + chatCostService.taskDeduct("mj","提示词分析", NumberUtils.toDouble(priceConfig.getShorten(), 0.1)); + String jsonStr = JSONUtil.toJsonStr(submitShortenDTO); + String url = "mj/submit/shorten"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); } public String getAction(String customId) { - // 检查 customId 是否为空 - if(customId == null || customId.isEmpty()) { + if (customId == null || customId.isEmpty()) { return null; } - // 使用 "::" 分割字符串 String[] parts = customId.split("::"); - // "MJ", "Inpaint", "1", "4fca7c14-181c-4...", "SOLO" - if(customId.endsWith("SOLO")) { - return parts[1]; - } - // 返回 "upsample" 值,假设它总是在第三个位置 - return parts[2]; - } - - public void mjTaskDeduct(String prompt, double cost) { - //扣除费用 - chatService.deductUserBalance(getUserId(), cost); - // 保存消息记录 - ChatMessageBo chatMessageBo = new ChatMessageBo(); - chatMessageBo.setUserId(getUserId()); - chatMessageBo.setModelName("mj"); - chatMessageBo.setContent(prompt); - chatMessageBo.setDeductCost(cost); - chatMessageBo.setTotalTokens(0); - chatMessageService.insertByBo(chatMessageBo); - } - - /** - * 获取用户Id - * - * @return - */ - public Long getUserId() { - LoginUser loginUser = LoginHelper.getLoginUser(); - if (loginUser == null) { - throw new BaseException("用户未登录!"); - } - return loginUser.getUserId(); - } - - @ApiOperation(value = "提交Describe任务") - @PostMapping("/describe") - public SubmitResultVO describe(@RequestBody SubmitDescribeDTO describeDTO) { - if (CharSequenceUtil.isBlank(describeDTO.getBase64())) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64不能为空"); - } - IDataUrlSerializer serializer = new DataUrlSerializer(); - DataUrl dataUrl; - try { - dataUrl = serializer.unserialize(describeDTO.getBase64()); - } catch (MalformedURLException e) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误"); - } - Task task = newTask(describeDTO); - task.setAction(TaskAction.DESCRIBE); - String taskFileName = task.getId() + "." + MimeTypeUtils.guessFileSuffix(dataUrl.getMimeType()); - task.setDescription("/describe " + taskFileName); - return this.taskService.submitDescribe(task, dataUrl); - } - - @ApiOperation(value = "提交Blend任务") - @PostMapping("/blend") - public SubmitResultVO blend(@RequestBody SubmitBlendDTO blendDTO) { - List base64Array = blendDTO.getBase64Array(); - if (base64Array == null || base64Array.size() < 2 || base64Array.size() > 5) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64List参数错误"); - } - if (blendDTO.getDimensions() == null) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "dimensions参数错误"); - } - IDataUrlSerializer serializer = new DataUrlSerializer(); - List dataUrlList = new ArrayList<>(); - try { - for (String base64 : base64Array) { - DataUrl dataUrl = serializer.unserialize(base64); - dataUrlList.add(dataUrl); - } - } catch (MalformedURLException e) { - return SubmitResultVO.fail(ReturnCode.VALIDATION_ERROR, "base64格式错误"); - } - Task task = newTask(blendDTO); - task.setAction(TaskAction.BLEND); - task.setDescription("/blend " + task.getId() + " " + dataUrlList.size()); - return this.taskService.submitBlend(task, dataUrlList, blendDTO.getDimensions()); - } - - private Task newTask(BaseSubmitDTO base) { - Task task = new Task(); - task.setId(System.currentTimeMillis() + RandomUtil.randomNumbers(3)); - task.setSubmitTime(System.currentTimeMillis()); - task.setState(base.getState()); - String notifyHook = CharSequenceUtil.isBlank(base.getNotifyHook()) ? this.properties.getNotifyHook() : base.getNotifyHook(); - task.setProperty(Constants.TASK_PROPERTY_NOTIFY_HOOK, notifyHook); - task.setProperty(Constants.TASK_PROPERTY_NONCE, SnowFlake.INSTANCE.nextId()); - return task; - } - - private String translatePrompt(String prompt) { - if (TranslateWay.NULL.equals(this.properties.getTranslateWay()) || CharSequenceUtil.isBlank(prompt)) { - return prompt; - } - List imageUrls = new ArrayList<>(); - Matcher imageMatcher = Pattern.compile("https?://[a-z0-9-_:@&?=+,.!/~*'%$]+\\x20+", Pattern.CASE_INSENSITIVE).matcher(prompt); - while (imageMatcher.find()) { - imageUrls.add(imageMatcher.group(0)); - } - String paramStr = ""; - Matcher paramMatcher = Pattern.compile("\\x20+-{1,2}[a-z]+.*$", Pattern.CASE_INSENSITIVE).matcher(prompt); - if (paramMatcher.find()) { - paramStr = paramMatcher.group(0); - } - String imageStr = CharSequenceUtil.join("", imageUrls); - String text = prompt.substring(imageStr.length(), prompt.length() - paramStr.length()); - if (CharSequenceUtil.isNotBlank(text)) { - text = this.translateService.translateToEnglish(text).trim(); - } - return imageStr + text + paramStr; + return customId.endsWith("SOLO") ? parts[1] : parts[2]; } } diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/TaskController.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/TaskController.java index 1704fa56..f0f93913 100644 --- a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/TaskController.java +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/controller/TaskController.java @@ -1,32 +1,15 @@ package com.xmzs.midjourney.controller; -import cn.hutool.core.comparator.CompareUtil; import cn.hutool.json.JSONUtil; -import com.xmzs.midjourney.dto.SubmitImagineDTO; +import com.xmzs.midjourney.util.MjOkHttpUtil; import com.xmzs.midjourney.dto.TaskConditionDTO; -import com.xmzs.midjourney.loadbalancer.DiscordLoadBalancer; -import com.xmzs.midjourney.result.SubmitResultVO; -import com.xmzs.midjourney.service.TaskStoreService; -import com.xmzs.midjourney.support.Task; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import okhttp3.*; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.io.IOException; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; +import okhttp3.Request; +import org.springframework.web.bind.annotation.*; @Api(tags = "任务查询") @RestController @@ -34,58 +17,32 @@ import java.util.Objects; @RequiredArgsConstructor @Slf4j public class TaskController { - private final TaskStoreService taskStoreService; - private final DiscordLoadBalancer discordLoadBalancer; - @Value("${chat.apiKey}") - private String apiKey; - @Value("${chat.apiHost}") - private String apiHost; + private final MjOkHttpUtil mjOkHttpUtil; @ApiOperation(value = "指定ID获取任务") @GetMapping("/{id}/fetch") public String fetch(@ApiParam(value = "任务ID") @PathVariable String id) { - OkHttpClient client = new OkHttpClient(); - // 创建一个Request对象来配置你的请求 - Request request = new Request.Builder() - .header("mj-api-secret", apiKey) // 设置Authorization header - .url(apiHost+"mj/task/" + id + "/fetch") - .build(); - try (Response response = client.newCall(request).execute()) { - if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - if (response.body() != null) { - return response.body().string(); - } - } catch (IOException e) { - log.error("任务:{}查询失败:{}",id,e.getMessage()); - } - return null; + String url = "mj/task/" + id + "/fetch"; + Request request = mjOkHttpUtil.createGetRequest(url); + return mjOkHttpUtil.executeRequest(request); } - @ApiOperation(value = "查询任务队列") - @GetMapping("/queue") - public List queue() { - return this.discordLoadBalancer.getQueueTaskIds().stream() - .map(this.taskStoreService::get).filter(Objects::nonNull) - .sorted(Comparator.comparing(Task::getSubmitTime)) - .toList(); - } - - @ApiOperation(value = "查询所有任务") - @GetMapping("/list") - public List list() { - return this.taskStoreService.list().stream() - .sorted((t1, t2) -> CompareUtil.compare(t2.getSubmitTime(), t1.getSubmitTime())) - .toList(); - } - @ApiOperation(value = "根据ID列表查询任务") @PostMapping("/list-by-condition") - public List listByIds(@RequestBody TaskConditionDTO conditionDTO) { - if (conditionDTO.getIds() == null) { - return Collections.emptyList(); - } - return conditionDTO.getIds().stream().map(this.taskStoreService::get).filter(Objects::nonNull).toList(); + public String listByIds(@RequestBody TaskConditionDTO conditionDTO) { + String url = "mj/task/list-by-condition"; + String conditionJson = JSONUtil.toJsonStr(conditionDTO); + Request request = mjOkHttpUtil.createPostRequest(url,conditionJson); + return mjOkHttpUtil.executeRequest(request); } + @ApiOperation(value = "获取任务图片的seed") + @GetMapping("/{id}/image-seed") + public String getSeed(@ApiParam(value = "任务ID") @PathVariable String id) { + String url = "mj/task/" + id + "/image-seed"; + Request request = mjOkHttpUtil.createGetRequest(url); + return mjOkHttpUtil.executeRequest(request); + } + } diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/domain/MjPriceConfig.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/domain/MjPriceConfig.java new file mode 100644 index 00000000..6fd07866 --- /dev/null +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/domain/MjPriceConfig.java @@ -0,0 +1,56 @@ +package com.xmzs.midjourney.domain; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 绘画费用信息 + * + * @author Admin + */ +@Data +@Component +@ConfigurationProperties(prefix = "mj") +public class MjPriceConfig { + /** + * 放大图像 + */ + private String upsample; + + /** + * 变化 + */ + private String change; + + /** + * 图生图 + */ + private String blend; + + /** + * 图生文 + */ + private String describe; + + /** + * 文生图 + */ + private String imagine; + + /** + * 局部重绘 + */ + private String inpaint; + + /** + * 提示词分析 + */ + private String shorten; + + /** + * 换脸 + */ + private String faceSwapping; + +} diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitActionDTO.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitActionDTO.java index 4e668588..7a5c8ba2 100644 --- a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitActionDTO.java +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitActionDTO.java @@ -1,10 +1,7 @@ package com.xmzs.midjourney.dto; -import com.xmzs.midjourney.enums.TaskAction; import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import lombok.EqualsAndHashCode; @Data diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitModalDTO.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitModalDTO.java new file mode 100644 index 00000000..a5658141 --- /dev/null +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitModalDTO.java @@ -0,0 +1,19 @@ +package com.xmzs.midjourney.dto; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("局部重绘提交参数") +public class SubmitModalDTO extends BaseSubmitDTO{ + + private String maskBase64; + + private String taskId; + + private String prompt; + +} diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitShortenDTO.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitShortenDTO.java new file mode 100644 index 00000000..a6e5bd9c --- /dev/null +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/dto/SubmitShortenDTO.java @@ -0,0 +1,17 @@ +package com.xmzs.midjourney.dto; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("prompt分析提交参数") +public class SubmitShortenDTO extends BaseSubmitDTO{ + + private String botType; + + private String prompt; + +} diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/enums/ActionType.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/enums/ActionType.java new file mode 100644 index 00000000..8bb90fba --- /dev/null +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/enums/ActionType.java @@ -0,0 +1,32 @@ +package com.xmzs.midjourney.enums; + +import lombok.Getter; + +/** + * @author WangLe + */ +@Getter +public enum ActionType { + IN_PAINT("Inpaint"), // 局部重绘操作 + RE_ROLL("reroll"), // 重绘操作 + UP_SAMPLE("upsample"), // 放大操作 + ZOOM("zoom"), // 变焦操作 + UPSCALE("upscale"), // 高清放大操作 + VARIATION("variation"); // 变化操作 + + private final String action; + + ActionType(String action) { + this.action = action; + } + + public static ActionType fromCustomId(String customId) { + for (ActionType type : values()) { + if (type.getAction().equals(customId)) { + return type; + } + } + return null; + } +} + diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/enums/BlendDimensions.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/enums/BlendDimensions.java index 62a22bc4..76a272e8 100644 --- a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/enums/BlendDimensions.java +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/enums/BlendDimensions.java @@ -1,6 +1,9 @@ package com.xmzs.midjourney.enums; +import lombok.Getter; + +@Getter public enum BlendDimensions { PORTRAIT("2:3"), @@ -15,7 +18,4 @@ public enum BlendDimensions { this.value = value; } - public String getValue() { - return this.value; - } } diff --git a/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/util/MjOkHttpUtil.java b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/util/MjOkHttpUtil.java new file mode 100644 index 00000000..3088fd4d --- /dev/null +++ b/ruoyi-modules/ruoyi-midjourney/src/main/java/com/xmzs/midjourney/util/MjOkHttpUtil.java @@ -0,0 +1,65 @@ +package com.xmzs.midjourney.util; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author WangLe + */ +@RequiredArgsConstructor +@Component +@Slf4j +public class MjOkHttpUtil { + + @Value("${chat.apiKey}") + private List apiKey; + @Value("${chat.apiHost}") + private String apiHost; + + private static final String API_SECRET_HEADER = "mj-api-secret"; + + private final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(300, TimeUnit.SECONDS) + .writeTimeout(300, TimeUnit.SECONDS) + .readTimeout(300, TimeUnit.SECONDS) + .build(); + + public String executeRequest(Request request) { + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + return response.body() != null ? response.body().string() : null; + } catch (IOException e) { + // 这里应根据实际情况使用适当的日志记录方式 + log.error("请求失败: {}",e.getMessage()); + return null; + } + } + + public Request createPostRequest(String url, String json) { + MediaType JSON = MediaType.get("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(json, JSON); + return new Request.Builder() + .url(apiHost + url) + .post(body) + .header(API_SECRET_HEADER, apiKey.get(0)) + .build(); + } + + public Request createGetRequest(String url) { + return new Request.Builder() + .url(apiHost + url) + .header(API_SECRET_HEADER, apiKey.get(0)) + .build(); + } + +} + diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/controller/system/SysModelController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/controller/system/SysModelController.java new file mode 100644 index 00000000..8f1d99f7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/controller/system/SysModelController.java @@ -0,0 +1,114 @@ +package com.xmzs.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.xmzs.common.core.domain.R; +import com.xmzs.common.core.validate.AddGroup; +import com.xmzs.common.core.validate.EditGroup; +import com.xmzs.common.excel.utils.ExcelUtil; +import com.xmzs.common.idempotent.annotation.RepeatSubmit; +import com.xmzs.common.log.annotation.Log; +import com.xmzs.common.log.enums.BusinessType; +import com.xmzs.common.mybatis.core.page.PageQuery; +import com.xmzs.common.mybatis.core.page.TableDataInfo; +import com.xmzs.common.web.core.BaseController; +import com.xmzs.system.domain.bo.SysModelBo; +import com.xmzs.system.domain.vo.SysModelVo; +import com.xmzs.system.service.ISysModelService; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 系统模型 + * + * @author Lion Li + * @date 2024-04-04 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/model") +public class SysModelController extends BaseController { + + private final ISysModelService sysModelService; + + /** + * 查询系统模型列表 + */ + @GetMapping("/list") + public TableDataInfo list(SysModelBo bo, PageQuery pageQuery) { + return sysModelService.queryPageList(bo, pageQuery); + } + + /** + * 查询系统模型列表 + */ + @GetMapping("/modelList") + public R> modelList(SysModelBo bo) { + bo.setModelShow("0"); + return R.ok(sysModelService.queryList(bo)); + } + + /** + * 导出系统模型列表 + */ + @SaCheckPermission("system:model:export") + @Log(title = "系统模型", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SysModelBo bo, HttpServletResponse response) { + List list = sysModelService.queryList(bo); + ExcelUtil.exportExcel(list, "系统模型", SysModelVo.class, response); + } + + /** + * 获取系统模型详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:model:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(sysModelService.queryById(id)); + } + + /** + * 新增系统模型 + */ + @SaCheckPermission("system:model:add") + @Log(title = "系统模型", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysModelBo bo) { + return toAjax(sysModelService.insertByBo(bo)); + } + + /** + * 修改系统模型 + */ + @SaCheckPermission("system:model:edit") + @Log(title = "系统模型", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SysModelBo bo) { + return toAjax(sysModelService.updateByBo(bo)); + } + + /** + * 删除系统模型 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:model:remove") + @Log(title = "系统模型", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(sysModelService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/PaymentOrders.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/PaymentOrders.java index 0bc52de1..0729322b 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/PaymentOrders.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/PaymentOrders.java @@ -17,7 +17,7 @@ import java.math.BigDecimal; */ @Data @EqualsAndHashCode(callSuper = true) -@TableName("payment_orders") +@TableName("sys_pay_order") public class PaymentOrders extends BaseEntity { @Serial diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/SysModel.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/SysModel.java new file mode 100644 index 00000000..690f4061 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/SysModel.java @@ -0,0 +1,68 @@ +package com.xmzs.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.xmzs.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 系统模型对象 sys_model + * + * @author Lion Li + * @date 2024-04-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_model") +public class SysModel extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 模型名称 + */ + private String modelName; + + /** + * 模型描述 + */ + private String modelDescribe; + + /** + * 模型价格 + */ + private double modelPrice; + + /** + * 计费类型 + */ + private String modelType; + + /** + * 是否显示 + */ + private String modelShow; + + + /** + * 系统提示词 + */ + private String systemPrompt; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/bo/SysModelBo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/bo/SysModelBo.java new file mode 100644 index 00000000..a2b822e7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/bo/SysModelBo.java @@ -0,0 +1,73 @@ +package com.xmzs.system.domain.bo; + +import com.xmzs.common.core.validate.AddGroup; +import com.xmzs.common.core.validate.EditGroup; +import com.xmzs.common.mybatis.core.domain.BaseEntity; +import com.xmzs.system.domain.SysModel; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 系统模型业务对象 sys_model + * + * @author Lion Li + * @date 2024-04-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysModel.class, reverseConvertGenerate = false) +public class SysModelBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 模型名称 + */ + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelName; + + + /** + * 模型描述 + */ + @NotBlank(message = "模型描述不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelDescribe; + + /** + * 模型价格 + */ + @NotNull(message = "模型价格不能为空", groups = { AddGroup.class, EditGroup.class }) + private double modelPrice; + + /** + * 计费类型 (1 token扣费; 2 次数扣费 ) + */ + @NotBlank(message = "计费类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelType; + + /** + * 模型状态 (0 显示; 1 隐藏 ) + */ + private String modelShow; + + + /** + * 系统提示词 + */ + private String systemPrompt; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/vo/SysModelVo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/vo/SysModelVo.java new file mode 100644 index 00000000..3ed94eb1 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/domain/vo/SysModelVo.java @@ -0,0 +1,76 @@ +package com.xmzs.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.xmzs.system.domain.SysModel; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 系统模型视图对象 sys_model + * + * @author Lion Li + * @date 2024-04-04 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysModel.class) +public class SysModelVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 模型名称 + */ + @ExcelProperty(value = "模型名称") + private String modelName; + + + + /** + * 模型描述 + */ + @ExcelProperty(value = "模型描述") + private String modelDescribe; + + /** + * 模型价格 + */ + @ExcelProperty(value = "模型价格") + private double modelPrice; + + /** + * 计费类型 + */ + @ExcelProperty(value = "计费类型") + private String modelType; + + /** + * 是否显示 + */ + private String modelShow; + + + /** + * 系统提示词 + */ + private String systemPrompt; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/listener/SSEEventSourceListener.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/listener/SSEEventSourceListener.java index 35e10603..1a5dbc42 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/listener/SSEEventSourceListener.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/listener/SSEEventSourceListener.java @@ -4,14 +4,16 @@ package com.xmzs.system.listener; import cn.hutool.core.collection.CollectionUtil; import com.fasterxml.jackson.databind.ObjectMapper; import com.xmzs.common.chat.config.LocalCache; -import com.xmzs.common.chat.entity.chat.ChatCompletion; import com.xmzs.common.chat.entity.chat.ChatCompletionResponse; import com.xmzs.common.chat.utils.TikTokensUtil; import com.xmzs.common.core.utils.SpringUtils; import com.xmzs.common.core.utils.StringUtils; import com.xmzs.system.domain.bo.ChatMessageBo; +import com.xmzs.system.domain.bo.SysModelBo; +import com.xmzs.system.domain.vo.SysModelVo; import com.xmzs.system.service.IChatMessageService; -import com.xmzs.system.service.IChatService; +import com.xmzs.system.service.IChatCostService; +import com.xmzs.system.service.ISysModelService; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -24,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter; +import java.util.List; import java.util.Objects; /** @@ -45,7 +48,7 @@ public class SSEEventSourceListener extends EventSourceListener { public SSEEventSourceListener(ResponseBodyEmitter emitter) { this.emitter = emitter; } - + private static final ISysModelService sysModelService = SpringUtils.getBean(ISysModelService.class); private String modelName; /** * {@inheritDoc} @@ -66,34 +69,34 @@ public class SSEEventSourceListener extends EventSourceListener { //成功响应 emitter.complete(); if(StringUtils.isNotEmpty(modelName)){ - IChatService IChatService = SpringUtils.context().getBean(IChatService.class); + IChatCostService IChatCostService = SpringUtils.context().getBean(IChatCostService.class); IChatMessageService chatMessageService = SpringUtils.context().getBean(IChatMessageService.class); ChatMessageBo chatMessageBo = new ChatMessageBo(); chatMessageBo.setModelName(modelName); chatMessageBo.setContent(stringBuffer.toString()); Long userId = (Long)LocalCache.CACHE.get("userId"); chatMessageBo.setUserId(userId); - if(ChatCompletion.Model.GPT_4_ALL.getName().equals(modelName) - || modelName.startsWith(ChatCompletion.Model.GPT_4_GIZMO.getName()) - || modelName.startsWith(ChatCompletion.Model.NET.getName()) - || ChatCompletion.Model.GPT_4_VISION_PREVIEW.getName().equals(modelName) - || ChatCompletion.Model.CLAUDE_3_SONNET.getName().equals(modelName) - || ChatCompletion.Model.STABLE_DIFFUSION.getName().equals(modelName) - || ChatCompletion.Model.SUNO_V3.getName().equals(modelName) - ){ - chatMessageBo.setDeductCost(0.0); - chatMessageBo.setTotalTokens(0); + + //查询按次数扣费的模型 + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelType("2"); + sysModelBo.setModelName(modelName); + List sysModelList = sysModelService.queryList(sysModelBo); + if (CollectionUtil.isNotEmpty(sysModelList)){ + chatMessageBo.setDeductCost(0d); + chatMessageBo.setRemark("提问时扣费"); // 保存消息记录 chatMessageService.insertByBo(chatMessageBo); - }else { - // 扣除余额 + }else{ int tokens = TikTokensUtil.tokens(modelName,stringBuffer.toString()); chatMessageBo.setTotalTokens(tokens); - IChatService.deductToken(chatMessageBo); + // 按token扣费并且保存消息记录 + IChatCostService.deductToken(chatMessageBo); } } return; } + // 解析返回内容 ObjectMapper mapper = new ObjectMapper(); ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); if(completionResponse == null || CollectionUtil.isEmpty(completionResponse.getChoices())){ diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/mapper/SysModelMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/mapper/SysModelMapper.java new file mode 100644 index 00000000..ccded060 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/mapper/SysModelMapper.java @@ -0,0 +1,15 @@ +package com.xmzs.system.mapper; + +import com.xmzs.common.mybatis.core.mapper.BaseMapperPlus; +import com.xmzs.system.domain.SysModel; +import com.xmzs.system.domain.vo.SysModelVo; + +/** + * 系统模型Mapper接口 + * + * @author Lion Li + * @date 2024-04-04 + */ +public interface SysModelMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/IChatCostService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/IChatCostService.java new file mode 100644 index 00000000..dc1b3378 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/IChatCostService.java @@ -0,0 +1,37 @@ +package com.xmzs.system.service; + +import com.xmzs.system.domain.bo.ChatMessageBo; + +public interface IChatCostService { + + /** + * 根据消耗的tokens扣除余额 + * + * @param chatMessageBo + * @return 结果 + */ + + void deductToken(ChatMessageBo chatMessageBo); + + /** + * 扣除用户的余额 + * + */ + void deductUserBalance(Long userId, Double numberCost); + + + /** + * 扣除任务费用并且保存记录 + * + * @param type 任务类型 + * @param prompt 任务描述 + * @param cost 扣除费用 + */ + void taskDeduct(String type,String prompt, double cost); + + + /** + * 判断用户是否付费 + */ + void checkUserGrade(); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/ISysModelService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/ISysModelService.java new file mode 100644 index 00000000..ea5ed714 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/ISysModelService.java @@ -0,0 +1,48 @@ +package com.xmzs.system.service; + +import com.xmzs.common.mybatis.core.page.PageQuery; +import com.xmzs.common.mybatis.core.page.TableDataInfo; +import com.xmzs.system.domain.bo.SysModelBo; +import com.xmzs.system.domain.vo.SysModelVo; + +import java.util.Collection; +import java.util.List; + +/** + * 系统模型Service接口 + * + * @author Lion Li + * @date 2024-04-04 + */ +public interface ISysModelService { + + /** + * 查询系统模型 + */ + SysModelVo queryById(Long id); + + /** + * 查询系统模型列表 + */ + TableDataInfo queryPageList(SysModelBo bo, PageQuery pageQuery); + + /** + * 查询系统模型列表 + */ + List queryList(SysModelBo bo); + + /** + * 新增系统模型 + */ + Boolean insertByBo(SysModelBo bo); + + /** + * 修改系统模型 + */ + Boolean updateByBo(SysModelBo bo); + + /** + * 校验并批量删除系统模型信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/ChatCostServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/ChatCostServiceImpl.java new file mode 100644 index 00000000..67aea20b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/ChatCostServiceImpl.java @@ -0,0 +1,160 @@ +package com.xmzs.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.xmzs.common.core.domain.model.LoginUser; +import com.xmzs.common.core.exception.ServiceException; +import com.xmzs.common.core.exception.base.BaseException; +import com.xmzs.common.satoken.utils.LoginHelper; +import com.xmzs.system.domain.ChatToken; +import com.xmzs.system.domain.SysUser; +import com.xmzs.system.domain.bo.ChatMessageBo; +import com.xmzs.system.domain.bo.SysModelBo; +import com.xmzs.system.domain.vo.SysModelVo; +import com.xmzs.system.mapper.SysUserMapper; +import com.xmzs.system.service.IChatCostService; +import com.xmzs.system.service.IChatMessageService; +import com.xmzs.system.service.IChatTokenService; +import com.xmzs.system.service.ISysModelService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author hncboy + * @date 2023/3/22 19:41 + * 聊天相关业务实现类 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ChatCostServiceImpl implements IChatCostService { + + private final SysUserMapper sysUserMapper; + + private final IChatMessageService chatMessageService; + + private final IChatTokenService chatTokenService; + + private final ISysModelService sysModelService; + + /** + * 根据消耗的tokens扣除余额 + * + * @param chatMessageBo + */ + public void deductToken(ChatMessageBo chatMessageBo) { + // 计算总token数 + ChatToken chatToken = chatTokenService.queryByUserId(chatMessageBo.getUserId(), chatMessageBo.getModelName()); + if (chatToken == null) { + chatToken = new ChatToken(); + chatToken.setToken(0); + } + int totalTokens = chatToken.getToken() + chatMessageBo.getTotalTokens(); + // 如果总token数大于等于1000,进行费用扣除 + if (totalTokens >= 1000) { + // 计算费用 + int token1 = totalTokens / 1000; + int token2 = totalTokens % 1000; + if (token2 > 0) { + // 保存剩余tokens + chatToken.setToken(token2); + chatTokenService.editToken(chatToken); + } else { + chatTokenService.resetToken(chatMessageBo.getUserId(), chatMessageBo.getModelName()); + } + // 扣除用户余额 + + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelName(chatMessageBo.getModelName()); + List sysModelList = sysModelService.queryList(sysModelBo); + double modelPrice = sysModelList.get(0).getModelPrice(); + Double numberCost = token1 * modelPrice; + deductUserBalance(chatMessageBo.getUserId(), numberCost); + chatMessageBo.setDeductCost(numberCost); + } else { + // 扣除用户余额 + deductUserBalance(chatMessageBo.getUserId(), 0.0); + chatMessageBo.setDeductCost(0d); + chatMessageBo.setRemark("不满1kToken,计入下一次!"); + chatToken.setToken(totalTokens); + chatToken.setModelName(chatMessageBo.getModelName()); + chatToken.setUserId(chatMessageBo.getUserId()); + chatTokenService.editToken(chatToken); + } + // 保存消息记录 + chatMessageService.insertByBo(chatMessageBo); + } + + + + /** + * 从用户余额中扣除费用 + * + * @param userId 用户ID + * @param numberCost 要扣除的费用 + */ + @Override + public void deductUserBalance(Long userId, Double numberCost) { + SysUser sysUser = sysUserMapper.selectById(userId); + if (sysUser == null) { + return; + } + + Double userBalance = sysUser.getUserBalance(); + if (userBalance < numberCost || userBalance == 0) { + throw new ServiceException("余额不足,请联系管理员充值!"); + } + sysUserMapper.update(null, + new LambdaUpdateWrapper() + .set(SysUser::getUserBalance, Math.max(userBalance - numberCost, 0)) + .eq(SysUser::getUserId, userId)); + } + + + + /** + * 扣除任务费用 + * + */ + @Override + public void taskDeduct(String type,String prompt, double cost) { + // 判断用户是否付费 + checkUserGrade(); + // 扣除费用 + deductUserBalance(getUserId(), cost); + // 保存消息记录 + ChatMessageBo chatMessageBo = new ChatMessageBo(); + chatMessageBo.setUserId(getUserId()); + chatMessageBo.setModelName(type); + chatMessageBo.setContent(prompt); + chatMessageBo.setDeductCost(cost); + chatMessageBo.setTotalTokens(0); + chatMessageService.insertByBo(chatMessageBo); + } + + /** + * 判断用户是否付费 + */ + @Override + public void checkUserGrade() { + SysUser sysUser = sysUserMapper.selectById(getUserId()); + if("0".equals(sysUser.getUserGrade())){ + throw new BaseException("免费用户暂时不支持此模型,请切换gpt-3.5-turbo模型或者点击《进入市场选购您的商品》充值后使用!"); + } + } + + /** + * 获取用户Id + * + * @return + */ + public Long getUserId() { + LoginUser loginUser = LoginHelper.getLoginUser(); + if (loginUser == null) { + throw new BaseException("用户未登录!"); + } + return loginUser.getUserId(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SseServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SseServiceImpl.java index 9ec81906..b0b29d17 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SseServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SseServiceImpl.java @@ -1,5 +1,6 @@ package com.xmzs.system.service.impl; +import cn.hutool.core.collection.CollectionUtil; import com.xmzs.common.chat.config.LocalCache; import com.xmzs.common.chat.constant.OpenAIConst; import com.xmzs.common.chat.domain.request.ChatRequest; @@ -15,14 +16,18 @@ import com.xmzs.common.chat.openai.OpenAiStreamClient; import com.xmzs.common.chat.utils.TikTokensUtil; import com.xmzs.common.core.domain.model.LoginUser; import com.xmzs.common.core.exception.base.BaseException; +import com.xmzs.common.core.utils.StringUtils; import com.xmzs.common.satoken.utils.LoginHelper; import com.xmzs.system.domain.SysUser; import com.xmzs.system.domain.bo.ChatMessageBo; +import com.xmzs.system.domain.bo.SysModelBo; +import com.xmzs.system.domain.vo.SysModelVo; import com.xmzs.system.listener.SSEEventSourceListener; import com.xmzs.system.mapper.SysUserMapper; +import com.xmzs.system.service.IChatCostService; import com.xmzs.system.service.IChatMessageService; -import com.xmzs.system.service.IChatService; import com.xmzs.system.service.ISseService; +import com.xmzs.system.service.ISysModelService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import okhttp3.ResponseBody; @@ -58,13 +63,14 @@ public class SseServiceImpl implements ISseService { private final OpenAiStreamClient openAiStreamClient; - - private final IChatService IChatService; + private final IChatCostService chatService; private final SysUserMapper sysUserMapper; private final IChatMessageService chatMessageService; + private final ISysModelService sysModelService; + @Value("${chat.apiKey}") private String apiKey; @Value("${chat.apiHost}") @@ -91,33 +97,35 @@ public class SseServiceImpl implements ISseService { // 判断用户是否付费 checkUserGrade(); } - // 按次数扣费 - if(ChatCompletion.Model.GPT_4_ALL.getName().equals(chatRequest.getModel()) - || chatRequest.getModel().startsWith(ChatCompletion.Model.GPT_4_GIZMO.getName()) - || chatRequest.getModel().startsWith(ChatCompletion.Model.NET.getName()) - || ChatCompletion.Model.GPT_4_VISION_PREVIEW.getName().equals(chatRequest.getModel()) - || ChatCompletion.Model.CLAUDE_3_SONNET.getName().equals(chatRequest.getModel()) - || ChatCompletion.Model.STABLE_DIFFUSION.getName().equals(chatRequest.getModel()) - || ChatCompletion.Model.SUNO_V3.getName().equals(chatRequest.getModel()) - ){ - double cost = OpenAIConst.GPT4_COST; - if(ChatCompletion.Model.STABLE_DIFFUSION.getName().equals(chatRequest.getModel())){ - cost = 0.1; + //根据模型名称查询模型信息 + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelName(chatRequest.getModel()); + List sysModelList = sysModelService.queryList(sysModelBo); + if (CollectionUtil.isEmpty(sysModelList)) { + // 如果模型不存在默认使用token扣费方式 + processByToken(chatRequest.getModel(), msgList, chatMessageBo); + } else { + // 模型设置默认提示词 + SysModelVo firstModel = sysModelList.get(0); + if (StringUtils.isNotEmpty(firstModel.getSystemPrompt())) { + Message sysMessage = Message.builder().content(firstModel.getSystemPrompt()).role(Message.Role.SYSTEM).build(); + // 假设 msgList 不为空并且至少有一个元素 + if (msgList.get(0).equals(sysMessage)) { + // 如果第一个元素与sysMessage相等,替换第一个元素 + msgList.set(0, sysMessage); + } else { + // 如果不相等,将sysMessage插入到列表的第一个位置 + msgList.add(0, sysMessage); + } } - if(ChatCompletion.Model.SUNO_V3.getName().equals(chatRequest.getModel())){ - cost = 0.5; + // 计费类型: 1 token扣费 2 次数扣费 + if ("2".equals(firstModel.getModelType())) { + processByModelPrice(firstModel, chatMessageBo); + } else { + processByToken(chatRequest.getModel(), msgList, chatMessageBo); } - IChatService.deductUserBalance(getUserId(), cost); - chatMessageBo.setDeductCost(cost); - // 保存消息记录 - chatMessageService.insertByBo(chatMessageBo); - }else { - int tokens = TikTokensUtil.tokens(chatRequest.getModel(), msgList); - chatMessageBo.setTotalTokens(tokens); - // 按token扣费并且保存消息记录 - IChatService.deductToken(chatMessageBo); } - }catch (Exception e){ + } catch (Exception e) { sendErrorEvent(sseEmitter, e.getMessage()); return sseEmitter; } @@ -147,6 +155,32 @@ public class SseServiceImpl implements ISseService { return sseEmitter; } + /** + * 根据次数扣除余额 + * + * @param model 模型信息 + * @param chatMessageBo 对话信息 + */ + private void processByModelPrice(SysModelVo model, ChatMessageBo chatMessageBo) { + double cost = model.getModelPrice(); + chatService.deductUserBalance(getUserId(), cost); + chatMessageBo.setDeductCost(cost); + chatMessageService.insertByBo(chatMessageBo); + } + + /** + * 根据token扣除余额 + * + * @param modelName 模型名称 + * @param msgList 消息列表 + * @param chatMessageBo 消息记录 + */ + private void processByToken(String modelName, List msgList, ChatMessageBo chatMessageBo) { + int tokens = TikTokensUtil.tokens(modelName, msgList); + chatMessageBo.setTotalTokens(tokens); + chatService.deductToken(chatMessageBo); + } + /** * 文字转语音 * @@ -225,9 +259,9 @@ public class SseServiceImpl implements ISseService { // 扣除费用 if(Objects.equals(request.getSize(), "1792x1024") || Objects.equals(request.getSize(), "1024x1792")){ - IChatService.deductUserBalance(getUserId(),OpenAIConst.DALL3_HD_COST); + chatService.deductUserBalance(getUserId(),OpenAIConst.DALL3_HD_COST); }else { - IChatService.deductUserBalance(getUserId(),OpenAIConst.DALL3_COST); + chatService.deductUserBalance(getUserId(),OpenAIConst.DALL3_COST); } // 保存消息记录 ChatMessageBo chatMessageBo = new ChatMessageBo(); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysDictTypeServiceImpl.java index a80c72b9..d43157fe 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysDictTypeServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysDictTypeServiceImpl.java @@ -96,7 +96,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService * @param dictType 字典类型 * @return 字典数据集合信息 */ - @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType") + // @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType") @Override public List selectDictDataByType(String dictType) { List dictDatas = dictDataMapper.selectDictDataByType(dictType); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysModelServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysModelServiceImpl.java new file mode 100644 index 00000000..d298efcc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysModelServiceImpl.java @@ -0,0 +1,110 @@ +package com.xmzs.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.xmzs.common.core.utils.MapstructUtils; +import com.xmzs.common.core.utils.StringUtils; +import com.xmzs.common.mybatis.core.page.PageQuery; +import com.xmzs.common.mybatis.core.page.TableDataInfo; +import com.xmzs.system.domain.SysModel; +import com.xmzs.system.domain.bo.SysModelBo; +import com.xmzs.system.domain.vo.SysModelVo; +import com.xmzs.system.mapper.SysModelMapper; +import com.xmzs.system.service.ISysModelService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; + +/** + * 系统模型Service业务层处理 + * + * @author Lion Li + * @date 2024-04-04 + */ +@RequiredArgsConstructor +@Service +public class SysModelServiceImpl implements ISysModelService { + + private final SysModelMapper baseMapper; + + /** + * 查询系统模型 + */ + @Override + public SysModelVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询系统模型列表 + */ + @Override + public TableDataInfo queryPageList(SysModelBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询系统模型列表 + */ + @Override + public List queryList(SysModelBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysModelBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getModelName()), SysModel::getModelName, bo.getModelName()); + lqw.like(StringUtils.isNotBlank(bo.getModelShow()), SysModel::getModelShow, bo.getModelShow()); + lqw.eq(StringUtils.isNotBlank(bo.getModelDescribe()), SysModel::getModelDescribe, bo.getModelDescribe()); + lqw.eq(StringUtils.isNotBlank(bo.getModelType()), SysModel::getModelType, bo.getModelType()); + return lqw; + } + + /** + * 新增系统模型 + */ + @Override + public Boolean insertByBo(SysModelBo bo) { + SysModel add = MapstructUtils.convert(bo, SysModel.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改系统模型 + */ + @Override + public Boolean updateByBo(SysModelBo bo) { + SysModel update = MapstructUtils.convert(bo, SysModel.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(SysModel entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除系统模型 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysUserServiceImpl.java index 94175ede..8a44a325 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SysUserServiceImpl.java @@ -78,6 +78,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService { QueryWrapper wrapper = Wrappers.query(); wrapper.eq("u.del_flag", UserConstants.USER_NORMAL) .eq(ObjectUtil.isNotNull(user.getUserId()), "u.user_id", user.getUserId()) + .eq(ObjectUtil.isNotNull(user.getUserGrade()), "u.user_grade", user.getUserGrade()) .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName()) .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus()) .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber()) @@ -324,9 +325,9 @@ public class SysUserServiceImpl implements ISysUserService, UserService { @Transactional(rollbackFor = Exception.class) public int updateUser(SysUserBo user) { // 新增用户与角色管理 - insertUserRole(user, true); + //insertUserRole(user, true); // 新增用户与岗位管理 - insertUserPost(user, true); + //insertUserPost(user, true); SysUser sysUser = MapstructUtils.convert(user, SysUser.class); // 防止错误更新后导致的数据误删除 int flag = baseMapper.updateById(sysUser); diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysModelMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysModelMapper.xml new file mode 100644 index 00000000..b194ad50 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysModelMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index f24f084c..86b49460 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -70,7 +70,7 @@