diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index f34b8a83c0..c48cbb63de 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -174,6 +174,19 @@ DS Bot 的 NOTICE 原文附在文末。 | 用途 | 社区论坛的容器配置,基于官方 `samples/standalone.yml` 改写 | | 文件 | `infra/forum/containers/app.yml`、`infra/forum/containers/app.reverse-proxy.yml` | +### SillyTavern(AGPL-3.0) + +| 项 | 内容 | +|---|---| +| 项目 | SillyTavern,https://github.com/SillyTavern/SillyTavern | +| 版本 | 1.19.0,提交 `7e8663cd9c184a550b37238218bdd32c6efc68e9` | +| 许可 | AGPL-3.0(与本项目相同) | +| 用途 | 酒馆引擎照 SillyTavern 的行为移植或改写:提示词组装、世界书、宏、斜杠命令、正则、角色卡与聊天文件的读写、人设 | +| 来源文件 | `public/script.js`;`public/scripts/` 下的 `PromptManager.js`、`openai.js`、`personas.js`、`world-info.js`、`macros.js`、`slash-commands.js`、`bookmarks.js`;`src/character-card-parser.js`、`src/prompt-converters.js`、`src/endpoints/characters.js`、`src/endpoints/chats.js`、`src/endpoints/backends/chat-completions.js` | +| 文件 | `packages/dsh_tavern/lib/src/` 下文件头写明 SillyTavern 来源和提交的文件 | + +酒馆引擎里另有几处移植,许可写在各自的文件头或同目录的许可文件里:Fuse.js 7.1.0(Apache-2.0,`packages/dsh_tavern/lib/src/slash/FUSE-LICENSE.txt`)、json-logic-js(MIT,`packages/dsh_tavern/lib/src/state/JSONLOGIC-LICENSE.txt`)、Unicode 16.0.0 数据(Unicode 许可,`packages/dsh_tavern/lib/src/slash/UNICODE-NAME-LICENSE.txt`)、seedrandom 3.0.5(MIT,David Bau,许可原文在 `packages/dsh_tavern/lib/src/macros/macro_engine.dart`)、Handlebars 4.7.9 与 Moment 2.30.1 的有限行为(各自文件头注明)。 + ## 二、素材 ### Phosphor Icons(MIT) @@ -210,6 +223,32 @@ DS Bot 的 NOTICE 原文附在文末。 | 用途 | 论坛表情的名称与别名表,只用数据,不含图片 | | 文件 | `app/lib/forum/emoji_data.g.dart`,由 `infra/forum/tools/gen_emoji_table.py` 从 `dist/emojis.json`、`dist/aliases.json` 生成 | +### jQuery 3.7.1(MIT) + +| 项 | 内容 | +|---|---| +| 项目 | jQuery 3.7.1,https://jquery.com (npm 包 `jquery@3.7.1`) | +| 版权人 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ | +| 许可 | MIT(包内 `LICENSE.txt` 原文) | +| 用途 | 酒馆整页画布里给卡脚本用的 `$`/`jQuery`,和酒馆助手页面一样预先放好;「不运行卡的脚本」开着时不载入 | + +- `app/assets/tavern/jquery-3.7.1.min.js`:包内 `dist/jquery.min.js` 原样,SHA256 `fc9a93dd241f6b045cbff0481cf4e1901becd0e12fb45166a8f17f95823f0b1a` +- `app/assets/licenses/jquery-3.7.1-LICENSE.txt`:包内 `LICENSE.txt` 原样,SHA256 `d4db9ebe6f29f5168eac45ad713f055623ac5d0dcd5ba92da23d650ae012020d` +- 来源:npm 镜像 registry.npmmirror.com 的 `jquery-3.7.1.tgz`,与 npm 公布的 integrity `sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==` 一致(2026-11-12 核对) + +### Lodash 4.17.21(MIT) + +| 项 | 内容 | +|---|---| +| 项目 | Lodash 4.17.21,https://lodash.com (npm 包 `lodash@4.17.21`) | +| 版权人 | Copyright OpenJS Foundation and other contributors ;基于 Underscore.js(Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors) | +| 许可 | MIT(包内 `LICENSE` 原文,含示例代码 CC0 一节) | +| 用途 | 酒馆整页画布里给卡脚本用的 `_`,同上 | + +- `app/assets/tavern/lodash-4.17.21.min.js`:包内 `lodash.min.js` 原样,SHA256 `a9705dfc47c0763380d851ab1801be6f76019f6b67e40e9b873f8b4a0603f7a9` +- `app/assets/licenses/lodash-4.17.21-LICENSE.txt`:包内 `LICENSE` 原样,SHA256 `f71e8ed126b46346494aad5486874cd8f0aafe95092ed67d2e3cb6110f939abc` +- 来源:npm 镜像的 `lodash-4.17.21.tgz`,与 npm 公布的 integrity `sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==` 一致(2026-11-12 核对) + ### JetBrains Mono(SIL OFL 1.1) | 项 | 内容 | diff --git a/app/assets/licenses/jquery-3.7.1-LICENSE.txt b/app/assets/licenses/jquery-3.7.1-LICENSE.txt new file mode 100644 index 0000000000..f642c3f7a7 --- /dev/null +++ b/app/assets/licenses/jquery-3.7.1-LICENSE.txt @@ -0,0 +1,20 @@ +Copyright OpenJS Foundation and other contributors, https://openjsf.org/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/assets/licenses/lodash-4.17.21-LICENSE.txt b/app/assets/licenses/lodash-4.17.21-LICENSE.txt new file mode 100644 index 0000000000..77c42f1408 --- /dev/null +++ b/app/assets/licenses/lodash-4.17.21-LICENSE.txt @@ -0,0 +1,47 @@ +Copyright OpenJS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. diff --git a/app/assets/tavern/jquery-3.7.1.min.js b/app/assets/tavern/jquery-3.7.1.min.js new file mode 100644 index 0000000000..7f37b5d991 --- /dev/null +++ b/app/assets/tavern/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 + * Copyright OpenJS Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ +(function(){function n(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function t(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u-1}function f(n,t,r){for(var e=-1,u=null==n?0:n.length;++e-1;);return r}function L(n,t){for(var r=n.length;r--&&y(t,n[r],0)>-1;);return r}function C(n,t){for(var r=n.length,e=0;r--;)n[r]===t&&++e; +return e}function U(n){return"\\"+Yr[n]}function B(n,t){return null==n?X:n[t]}function T(n){return Nr.test(n)}function $(n){return Pr.test(n)}function D(n){for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}function M(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n]}),r}function F(n,t){return function(r){return n(t(r))}}function N(n,t){for(var r=-1,e=n.length,u=0,i=[];++r>>1,$n=[["ary",mn],["bind",_n],["bindKey",vn],["curry",yn],["curryRight",dn],["flip",jn],["partial",bn],["partialRight",wn],["rearg",xn]],Dn="[object Arguments]",Mn="[object Array]",Fn="[object AsyncFunction]",Nn="[object Boolean]",Pn="[object Date]",qn="[object DOMException]",Zn="[object Error]",Kn="[object Function]",Vn="[object GeneratorFunction]",Gn="[object Map]",Hn="[object Number]",Jn="[object Null]",Yn="[object Object]",Qn="[object Promise]",Xn="[object Proxy]",nt="[object RegExp]",tt="[object Set]",rt="[object String]",et="[object Symbol]",ut="[object Undefined]",it="[object WeakMap]",ot="[object WeakSet]",ft="[object ArrayBuffer]",ct="[object DataView]",at="[object Float32Array]",lt="[object Float64Array]",st="[object Int8Array]",ht="[object Int16Array]",pt="[object Int32Array]",_t="[object Uint8Array]",vt="[object Uint8ClampedArray]",gt="[object Uint16Array]",yt="[object Uint32Array]",dt=/\b__p \+= '';/g,bt=/\b(__p \+=) '' \+/g,wt=/(__e\(.*?\)|\b__t\)) \+\n'';/g,mt=/&(?:amp|lt|gt|quot|#39);/g,xt=/[&<>"']/g,jt=RegExp(mt.source),At=RegExp(xt.source),kt=/<%-([\s\S]+?)%>/g,Ot=/<%([\s\S]+?)%>/g,It=/<%=([\s\S]+?)%>/g,Rt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,zt=/^\w*$/,Et=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,St=/[\\^$.*+?()[\]{}|]/g,Wt=RegExp(St.source),Lt=/^\s+/,Ct=/\s/,Ut=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Bt=/\{\n\/\* \[wrapped with (.+)\] \*/,Tt=/,? & /,$t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,Dt=/[()=,{}\[\]\/\s]/,Mt=/\\(\\)?/g,Ft=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Nt=/\w*$/,Pt=/^[-+]0x[0-9a-f]+$/i,qt=/^0b[01]+$/i,Zt=/^\[object .+?Constructor\]$/,Kt=/^0o[0-7]+$/i,Vt=/^(?:0|[1-9]\d*)$/,Gt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Ht=/($^)/,Jt=/['\n\r\u2028\u2029\\]/g,Yt="\\ud800-\\udfff",Qt="\\u0300-\\u036f",Xt="\\ufe20-\\ufe2f",nr="\\u20d0-\\u20ff",tr=Qt+Xt+nr,rr="\\u2700-\\u27bf",er="a-z\\xdf-\\xf6\\xf8-\\xff",ur="\\xac\\xb1\\xd7\\xf7",ir="\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf",or="\\u2000-\\u206f",fr=" \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",cr="A-Z\\xc0-\\xd6\\xd8-\\xde",ar="\\ufe0e\\ufe0f",lr=ur+ir+or+fr,sr="['\u2019]",hr="["+Yt+"]",pr="["+lr+"]",_r="["+tr+"]",vr="\\d+",gr="["+rr+"]",yr="["+er+"]",dr="[^"+Yt+lr+vr+rr+er+cr+"]",br="\\ud83c[\\udffb-\\udfff]",wr="(?:"+_r+"|"+br+")",mr="[^"+Yt+"]",xr="(?:\\ud83c[\\udde6-\\uddff]){2}",jr="[\\ud800-\\udbff][\\udc00-\\udfff]",Ar="["+cr+"]",kr="\\u200d",Or="(?:"+yr+"|"+dr+")",Ir="(?:"+Ar+"|"+dr+")",Rr="(?:"+sr+"(?:d|ll|m|re|s|t|ve))?",zr="(?:"+sr+"(?:D|LL|M|RE|S|T|VE))?",Er=wr+"?",Sr="["+ar+"]?",Wr="(?:"+kr+"(?:"+[mr,xr,jr].join("|")+")"+Sr+Er+")*",Lr="\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",Cr="\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])",Ur=Sr+Er+Wr,Br="(?:"+[gr,xr,jr].join("|")+")"+Ur,Tr="(?:"+[mr+_r+"?",_r,xr,jr,hr].join("|")+")",$r=RegExp(sr,"g"),Dr=RegExp(_r,"g"),Mr=RegExp(br+"(?="+br+")|"+Tr+Ur,"g"),Fr=RegExp([Ar+"?"+yr+"+"+Rr+"(?="+[pr,Ar,"$"].join("|")+")",Ir+"+"+zr+"(?="+[pr,Ar+Or,"$"].join("|")+")",Ar+"?"+Or+"+"+Rr,Ar+"+"+zr,Cr,Lr,vr,Br].join("|"),"g"),Nr=RegExp("["+kr+Yt+tr+ar+"]"),Pr=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,qr=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],Zr=-1,Kr={}; +Kr[at]=Kr[lt]=Kr[st]=Kr[ht]=Kr[pt]=Kr[_t]=Kr[vt]=Kr[gt]=Kr[yt]=!0,Kr[Dn]=Kr[Mn]=Kr[ft]=Kr[Nn]=Kr[ct]=Kr[Pn]=Kr[Zn]=Kr[Kn]=Kr[Gn]=Kr[Hn]=Kr[Yn]=Kr[nt]=Kr[tt]=Kr[rt]=Kr[it]=!1;var Vr={};Vr[Dn]=Vr[Mn]=Vr[ft]=Vr[ct]=Vr[Nn]=Vr[Pn]=Vr[at]=Vr[lt]=Vr[st]=Vr[ht]=Vr[pt]=Vr[Gn]=Vr[Hn]=Vr[Yn]=Vr[nt]=Vr[tt]=Vr[rt]=Vr[et]=Vr[_t]=Vr[vt]=Vr[gt]=Vr[yt]=!0,Vr[Zn]=Vr[Kn]=Vr[it]=!1;var Gr={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a", +"\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae", +"\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g", +"\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O", +"\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w", +"\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},Hr={"&":"&","<":"<",">":">",'"':""","'":"'"},Jr={"&":"&","<":"<",">":">",""":'"',"'":"'"},Yr={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Qr=parseFloat,Xr=parseInt,ne="object"==typeof global&&global&&global.Object===Object&&global,te="object"==typeof self&&self&&self.Object===Object&&self,re=ne||te||Function("return this")(),ee="object"==typeof exports&&exports&&!exports.nodeType&&exports,ue=ee&&"object"==typeof module&&module&&!module.nodeType&&module,ie=ue&&ue.exports===ee,oe=ie&&ne.process,fe=function(){ +try{var n=ue&&ue.require&&ue.require("util").types;return n?n:oe&&oe.binding&&oe.binding("util")}catch(n){}}(),ce=fe&&fe.isArrayBuffer,ae=fe&&fe.isDate,le=fe&&fe.isMap,se=fe&&fe.isRegExp,he=fe&&fe.isSet,pe=fe&&fe.isTypedArray,_e=m("length"),ve=x(Gr),ge=x(Hr),ye=x(Jr),de=function p(x){function Z(n){if(cc(n)&&!bh(n)&&!(n instanceof Ct)){if(n instanceof Y)return n;if(bl.call(n,"__wrapped__"))return eo(n)}return new Y(n)}function J(){}function Y(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t, +this.__index__=0,this.__values__=X}function Ct(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=Un,this.__views__=[]}function $t(){var n=new Ct(this.__wrapped__);return n.__actions__=Tu(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Tu(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Tu(this.__views__),n}function Yt(){if(this.__filtered__){var n=new Ct(this);n.__dir__=-1, +n.__filtered__=!0}else n=this.clone(),n.__dir__*=-1;return n}function Qt(){var n=this.__wrapped__.value(),t=this.__dir__,r=bh(n),e=t<0,u=r?n.length:0,i=Oi(0,u,this.__views__),o=i.start,f=i.end,c=f-o,a=e?f:o-1,l=this.__iteratees__,s=l.length,h=0,p=Hl(c,this.__takeCount__);if(!r||!e&&u==c&&p==c)return wu(n,this.__actions__);var _=[];n:for(;c--&&h-1}function lr(n,t){var r=this.__data__,e=Wr(r,n);return e<0?(++this.size,r.push([n,t])):r[e][1]=t,this}function sr(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t=t?n:t)),n}function Fr(n,t,e,u,i,o){var f,c=t&an,a=t&ln,l=t&sn;if(e&&(f=i?e(n,u,i,o):e(n)),f!==X)return f;if(!fc(n))return n;var s=bh(n);if(s){if(f=zi(n),!c)return Tu(n,f)}else{var h=zs(n),p=h==Kn||h==Vn;if(mh(n))return Iu(n,c);if(h==Yn||h==Dn||p&&!i){if(f=a||p?{}:Ei(n),!c)return a?Mu(n,Ur(f,n)):Du(n,Cr(f,n))}else{if(!Vr[h])return i?n:{};f=Si(n,h,c)}}o||(o=new wr);var _=o.get(n);if(_)return _;o.set(n,f),kh(n)?n.forEach(function(r){f.add(Fr(r,t,e,r,n,o))}):jh(n)&&n.forEach(function(r,u){ +f.set(u,Fr(r,t,e,u,n,o))});var v=l?a?di:yi:a?qc:Pc,g=s?X:v(n);return r(g||n,function(r,u){g&&(u=r,r=n[u]),Sr(f,u,Fr(r,t,e,u,n,o))}),f}function Nr(n){var t=Pc(n);return function(r){return Pr(r,n,t)}}function Pr(n,t,r){var e=r.length;if(null==n)return!e;for(n=ll(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===X&&!(u in n)||!i(o))return!1}return!0}function Gr(n,t,r){if("function"!=typeof n)throw new pl(en);return Ws(function(){n.apply(X,r)},t)}function Hr(n,t,r,e){var u=-1,i=o,a=!0,l=n.length,s=[],h=t.length; +if(!l)return s;r&&(t=c(t,z(r))),e?(i=f,a=!1):t.length>=tn&&(i=S,a=!1,t=new yr(t));n:for(;++uu?0:u+r), +e=e===X||e>u?u:kc(e),e<0&&(e+=u),e=r>e?0:Oc(e);r0&&r(f)?t>1?ee(f,t-1,r,e,u):a(u,f):e||(u[u.length]=f)}return u}function ue(n,t){return n&&bs(n,t,Pc)}function oe(n,t){return n&&ws(n,t,Pc)}function fe(n,t){return i(t,function(t){return uc(n[t])})}function _e(n,t){t=ku(t,n);for(var r=0,e=t.length;null!=n&&rt}function xe(n,t){return null!=n&&bl.call(n,t)}function je(n,t){return null!=n&&t in ll(n)}function Ae(n,t,r){return n>=Hl(t,r)&&n=120&&p.length>=120)?new yr(a&&p):X}p=n[0]; +var _=-1,v=l[0];n:for(;++_-1;)f!==n&&Ll.call(f,a,1),Ll.call(n,a,1);return n}function nu(n,t){for(var r=n?t.length:0,e=r-1;r--;){ +var u=t[r];if(r==e||u!==i){var i=u;Ci(u)?Ll.call(n,u,1):yu(n,u)}}return n}function tu(n,t){return n+Nl(Ql()*(t-n+1))}function ru(n,t,r,e){for(var u=-1,i=Gl(Fl((t-n)/(r||1)),0),o=il(i);i--;)o[e?i:++u]=n,n+=r;return o}function eu(n,t){var r="";if(!n||t<1||t>Wn)return r;do t%2&&(r+=n),t=Nl(t/2),t&&(n+=n);while(t);return r}function uu(n,t){return Ls(Vi(n,t,La),n+"")}function iu(n){return Ir(ra(n))}function ou(n,t){var r=ra(n);return Xi(r,Mr(t,0,r.length))}function fu(n,t,r,e){if(!fc(n))return n;t=ku(t,n); +for(var u=-1,i=t.length,o=i-1,f=n;null!=f&&++uu?0:u+t),r=r>u?u:r,r<0&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0;for(var i=il(u);++e>>1,o=n[i];null!==o&&!bc(o)&&(r?o<=t:o=tn){var s=t?null:ks(n);if(s)return P(s);c=!1,u=S,l=new yr}else l=t?[]:a;n:for(;++e=e?n:au(n,t,r)}function Iu(n,t){if(t)return n.slice();var r=n.length,e=zl?zl(r):new n.constructor(r); +return n.copy(e),e}function Ru(n){var t=new n.constructor(n.byteLength);return new Rl(t).set(new Rl(n)),t}function zu(n,t){return new n.constructor(t?Ru(n.buffer):n.buffer,n.byteOffset,n.byteLength)}function Eu(n){var t=new n.constructor(n.source,Nt.exec(n));return t.lastIndex=n.lastIndex,t}function Su(n){return _s?ll(_s.call(n)):{}}function Wu(n,t){return new n.constructor(t?Ru(n.buffer):n.buffer,n.byteOffset,n.length)}function Lu(n,t){if(n!==t){var r=n!==X,e=null===n,u=n===n,i=bc(n),o=t!==X,f=null===t,c=t===t,a=bc(t); +if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&n=f)return c;return c*("desc"==r[e]?-1:1)}}return n.index-t.index}function Uu(n,t,r,e){for(var u=-1,i=n.length,o=r.length,f=-1,c=t.length,a=Gl(i-o,0),l=il(c+a),s=!e;++f1?r[u-1]:X,o=u>2?r[2]:X;for(i=n.length>3&&"function"==typeof i?(u--,i):X,o&&Ui(r[0],r[1],o)&&(i=u<3?X:i,u=1),t=ll(t);++e-1?u[i?t[o]:o]:X}}function Yu(n){return gi(function(t){var r=t.length,e=r,u=Y.prototype.thru;for(n&&t.reverse();e--;){var i=t[e];if("function"!=typeof i)throw new pl(en);if(u&&!o&&"wrapper"==bi(i))var o=new Y([],!0)}for(e=o?e:r;++e1&&d.reverse(),s&&cf))return!1;var a=i.get(n),l=i.get(t);if(a&&l)return a==t&&l==n;var s=-1,p=!0,_=r&pn?new yr:X;for(i.set(n,t),i.set(t,n);++s1?"& ":"")+t[e],t=t.join(r>2?", ":" "),n.replace(Ut,"{\n/* [wrapped with "+t+"] */\n")}function Li(n){return bh(n)||dh(n)||!!(Cl&&n&&n[Cl])}function Ci(n,t){var r=typeof n; +return t=null==t?Wn:t,!!t&&("number"==r||"symbol"!=r&&Vt.test(n))&&n>-1&&n%1==0&&n0){if(++t>=On)return arguments[0]}else t=0; +return n.apply(X,arguments)}}function Xi(n,t){var r=-1,e=n.length,u=e-1;for(t=t===X?e:t;++r=this.__values__.length;return{done:n,value:n?X:this.__values__[this.__index__++]}}function uf(){return this}function of(n){for(var t,r=this;r instanceof J;){var e=eo(r);e.__index__=0,e.__values__=X,t?u.__wrapped__=e:t=e;var u=e;r=r.__wrapped__}return u.__wrapped__=n,t}function ff(){var n=this.__wrapped__;if(n instanceof Ct){var t=n;return this.__actions__.length&&(t=new Ct(this)),t=t.reverse(),t.__actions__.push({func:nf,args:[Eo],thisArg:X}),new Y(t,this.__chain__)}return this.thru(Eo); +}function cf(){return wu(this.__wrapped__,this.__actions__)}function af(n,t,r){var e=bh(n)?u:Jr;return r&&Ui(n,t,r)&&(t=X),e(n,mi(t,3))}function lf(n,t){return(bh(n)?i:te)(n,mi(t,3))}function sf(n,t){return ee(yf(n,t),1)}function hf(n,t){return ee(yf(n,t),Sn)}function pf(n,t,r){return r=r===X?1:kc(r),ee(yf(n,t),r)}function _f(n,t){return(bh(n)?r:ys)(n,mi(t,3))}function vf(n,t){return(bh(n)?e:ds)(n,mi(t,3))}function gf(n,t,r,e){n=Hf(n)?n:ra(n),r=r&&!e?kc(r):0;var u=n.length;return r<0&&(r=Gl(u+r,0)), +dc(n)?r<=u&&n.indexOf(t,r)>-1:!!u&&y(n,t,r)>-1}function yf(n,t){return(bh(n)?c:Pe)(n,mi(t,3))}function df(n,t,r,e){return null==n?[]:(bh(t)||(t=null==t?[]:[t]),r=e?X:r,bh(r)||(r=null==r?[]:[r]),He(n,t,r))}function bf(n,t,r){var e=bh(n)?l:j,u=arguments.length<3;return e(n,mi(t,4),r,u,ys)}function wf(n,t,r){var e=bh(n)?s:j,u=arguments.length<3;return e(n,mi(t,4),r,u,ds)}function mf(n,t){return(bh(n)?i:te)(n,Uf(mi(t,3)))}function xf(n){return(bh(n)?Ir:iu)(n)}function jf(n,t,r){return t=(r?Ui(n,t,r):t===X)?1:kc(t), +(bh(n)?Rr:ou)(n,t)}function Af(n){return(bh(n)?zr:cu)(n)}function kf(n){if(null==n)return 0;if(Hf(n))return dc(n)?V(n):n.length;var t=zs(n);return t==Gn||t==tt?n.size:Me(n).length}function Of(n,t,r){var e=bh(n)?h:lu;return r&&Ui(n,t,r)&&(t=X),e(n,mi(t,3))}function If(n,t){if("function"!=typeof t)throw new pl(en);return n=kc(n),function(){if(--n<1)return t.apply(this,arguments)}}function Rf(n,t,r){return t=r?X:t,t=n&&null==t?n.length:t,ai(n,mn,X,X,X,X,t)}function zf(n,t){var r;if("function"!=typeof t)throw new pl(en); +return n=kc(n),function(){return--n>0&&(r=t.apply(this,arguments)),n<=1&&(t=X),r}}function Ef(n,t,r){t=r?X:t;var e=ai(n,yn,X,X,X,X,X,t);return e.placeholder=Ef.placeholder,e}function Sf(n,t,r){t=r?X:t;var e=ai(n,dn,X,X,X,X,X,t);return e.placeholder=Sf.placeholder,e}function Wf(n,t,r){function e(t){var r=h,e=p;return h=p=X,d=t,v=n.apply(e,r)}function u(n){return d=n,g=Ws(f,t),b?e(n):v}function i(n){var r=n-y,e=n-d,u=t-r;return w?Hl(u,_-e):u}function o(n){var r=n-y,e=n-d;return y===X||r>=t||r<0||w&&e>=_; +}function f(){var n=fh();return o(n)?c(n):(g=Ws(f,i(n)),X)}function c(n){return g=X,m&&h?e(n):(h=p=X,v)}function a(){g!==X&&As(g),d=0,h=y=p=g=X}function l(){return g===X?v:c(fh())}function s(){var n=fh(),r=o(n);if(h=arguments,p=this,y=n,r){if(g===X)return u(y);if(w)return As(g),g=Ws(f,t),e(y)}return g===X&&(g=Ws(f,t)),v}var h,p,_,v,g,y,d=0,b=!1,w=!1,m=!0;if("function"!=typeof n)throw new pl(en);return t=Ic(t)||0,fc(r)&&(b=!!r.leading,w="maxWait"in r,_=w?Gl(Ic(r.maxWait)||0,t):_,m="trailing"in r?!!r.trailing:m), +s.cancel=a,s.flush=l,s}function Lf(n){return ai(n,jn)}function Cf(n,t){if("function"!=typeof n||null!=t&&"function"!=typeof t)throw new pl(en);var r=function(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;if(i.has(u))return i.get(u);var o=n.apply(this,e);return r.cache=i.set(u,o)||i,o};return r.cache=new(Cf.Cache||sr),r}function Uf(n){if("function"!=typeof n)throw new pl(en);return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2: +return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function Bf(n){return zf(2,n)}function Tf(n,t){if("function"!=typeof n)throw new pl(en);return t=t===X?t:kc(t),uu(n,t)}function $f(t,r){if("function"!=typeof t)throw new pl(en);return r=null==r?0:Gl(kc(r),0),uu(function(e){var u=e[r],i=Ou(e,0,r);return u&&a(i,u),n(t,this,i)})}function Df(n,t,r){var e=!0,u=!0;if("function"!=typeof n)throw new pl(en);return fc(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u), +Wf(n,t,{leading:e,maxWait:t,trailing:u})}function Mf(n){return Rf(n,1)}function Ff(n,t){return ph(Au(t),n)}function Nf(){if(!arguments.length)return[];var n=arguments[0];return bh(n)?n:[n]}function Pf(n){return Fr(n,sn)}function qf(n,t){return t="function"==typeof t?t:X,Fr(n,sn,t)}function Zf(n){return Fr(n,an|sn)}function Kf(n,t){return t="function"==typeof t?t:X,Fr(n,an|sn,t)}function Vf(n,t){return null==t||Pr(n,t,Pc(t))}function Gf(n,t){return n===t||n!==n&&t!==t}function Hf(n){return null!=n&&oc(n.length)&&!uc(n); +}function Jf(n){return cc(n)&&Hf(n)}function Yf(n){return n===!0||n===!1||cc(n)&&we(n)==Nn}function Qf(n){return cc(n)&&1===n.nodeType&&!gc(n)}function Xf(n){if(null==n)return!0;if(Hf(n)&&(bh(n)||"string"==typeof n||"function"==typeof n.splice||mh(n)||Oh(n)||dh(n)))return!n.length;var t=zs(n);if(t==Gn||t==tt)return!n.size;if(Mi(n))return!Me(n).length;for(var r in n)if(bl.call(n,r))return!1;return!0}function nc(n,t){return Se(n,t)}function tc(n,t,r){r="function"==typeof r?r:X;var e=r?r(n,t):X;return e===X?Se(n,t,X,r):!!e; +}function rc(n){if(!cc(n))return!1;var t=we(n);return t==Zn||t==qn||"string"==typeof n.message&&"string"==typeof n.name&&!gc(n)}function ec(n){return"number"==typeof n&&Zl(n)}function uc(n){if(!fc(n))return!1;var t=we(n);return t==Kn||t==Vn||t==Fn||t==Xn}function ic(n){return"number"==typeof n&&n==kc(n)}function oc(n){return"number"==typeof n&&n>-1&&n%1==0&&n<=Wn}function fc(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function cc(n){return null!=n&&"object"==typeof n}function ac(n,t){ +return n===t||Ce(n,t,ji(t))}function lc(n,t,r){return r="function"==typeof r?r:X,Ce(n,t,ji(t),r)}function sc(n){return vc(n)&&n!=+n}function hc(n){if(Es(n))throw new fl(rn);return Ue(n)}function pc(n){return null===n}function _c(n){return null==n}function vc(n){return"number"==typeof n||cc(n)&&we(n)==Hn}function gc(n){if(!cc(n)||we(n)!=Yn)return!1;var t=El(n);if(null===t)return!0;var r=bl.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&dl.call(r)==jl}function yc(n){ +return ic(n)&&n>=-Wn&&n<=Wn}function dc(n){return"string"==typeof n||!bh(n)&&cc(n)&&we(n)==rt}function bc(n){return"symbol"==typeof n||cc(n)&&we(n)==et}function wc(n){return n===X}function mc(n){return cc(n)&&zs(n)==it}function xc(n){return cc(n)&&we(n)==ot}function jc(n){if(!n)return[];if(Hf(n))return dc(n)?G(n):Tu(n);if(Ul&&n[Ul])return D(n[Ul]());var t=zs(n);return(t==Gn?M:t==tt?P:ra)(n)}function Ac(n){if(!n)return 0===n?n:0;if(n=Ic(n),n===Sn||n===-Sn){return(n<0?-1:1)*Ln}return n===n?n:0}function kc(n){ +var t=Ac(n),r=t%1;return t===t?r?t-r:t:0}function Oc(n){return n?Mr(kc(n),0,Un):0}function Ic(n){if("number"==typeof n)return n;if(bc(n))return Cn;if(fc(n)){var t="function"==typeof n.valueOf?n.valueOf():n;n=fc(t)?t+"":t}if("string"!=typeof n)return 0===n?n:+n;n=R(n);var r=qt.test(n);return r||Kt.test(n)?Xr(n.slice(2),r?2:8):Pt.test(n)?Cn:+n}function Rc(n){return $u(n,qc(n))}function zc(n){return n?Mr(kc(n),-Wn,Wn):0===n?n:0}function Ec(n){return null==n?"":vu(n)}function Sc(n,t){var r=gs(n);return null==t?r:Cr(r,t); +}function Wc(n,t){return v(n,mi(t,3),ue)}function Lc(n,t){return v(n,mi(t,3),oe)}function Cc(n,t){return null==n?n:bs(n,mi(t,3),qc)}function Uc(n,t){return null==n?n:ws(n,mi(t,3),qc)}function Bc(n,t){return n&&ue(n,mi(t,3))}function Tc(n,t){return n&&oe(n,mi(t,3))}function $c(n){return null==n?[]:fe(n,Pc(n))}function Dc(n){return null==n?[]:fe(n,qc(n))}function Mc(n,t,r){var e=null==n?X:_e(n,t);return e===X?r:e}function Fc(n,t){return null!=n&&Ri(n,t,xe)}function Nc(n,t){return null!=n&&Ri(n,t,je); +}function Pc(n){return Hf(n)?Or(n):Me(n)}function qc(n){return Hf(n)?Or(n,!0):Fe(n)}function Zc(n,t){var r={};return t=mi(t,3),ue(n,function(n,e,u){Br(r,t(n,e,u),n)}),r}function Kc(n,t){var r={};return t=mi(t,3),ue(n,function(n,e,u){Br(r,e,t(n,e,u))}),r}function Vc(n,t){return Gc(n,Uf(mi(t)))}function Gc(n,t){if(null==n)return{};var r=c(di(n),function(n){return[n]});return t=mi(t),Ye(n,r,function(n,r){return t(n,r[0])})}function Hc(n,t,r){t=ku(t,n);var e=-1,u=t.length;for(u||(u=1,n=X);++et){ +var e=n;n=t,t=e}if(r||n%1||t%1){var u=Ql();return Hl(n+u*(t-n+Qr("1e-"+((u+"").length-1))),t)}return tu(n,t)}function fa(n){return Qh(Ec(n).toLowerCase())}function ca(n){return n=Ec(n),n&&n.replace(Gt,ve).replace(Dr,"")}function aa(n,t,r){n=Ec(n),t=vu(t);var e=n.length;r=r===X?e:Mr(kc(r),0,e);var u=r;return r-=t.length,r>=0&&n.slice(r,u)==t}function la(n){return n=Ec(n),n&&At.test(n)?n.replace(xt,ge):n}function sa(n){return n=Ec(n),n&&Wt.test(n)?n.replace(St,"\\$&"):n}function ha(n,t,r){n=Ec(n),t=kc(t); +var e=t?V(n):0;if(!t||e>=t)return n;var u=(t-e)/2;return ri(Nl(u),r)+n+ri(Fl(u),r)}function pa(n,t,r){n=Ec(n),t=kc(t);var e=t?V(n):0;return t&&e>>0)?(n=Ec(n),n&&("string"==typeof t||null!=t&&!Ah(t))&&(t=vu(t),!t&&T(n))?Ou(G(n),0,r):n.split(t,r)):[]}function ba(n,t,r){return n=Ec(n),r=null==r?0:Mr(kc(r),0,n.length),t=vu(t),n.slice(r,r+t.length)==t}function wa(n,t,r){var e=Z.templateSettings;r&&Ui(n,t,r)&&(t=X),n=Ec(n),t=Sh({},t,e,li);var u,i,o=Sh({},t.imports,e.imports,li),f=Pc(o),c=E(o,f),a=0,l=t.interpolate||Ht,s="__p += '",h=sl((t.escape||Ht).source+"|"+l.source+"|"+(l===It?Ft:Ht).source+"|"+(t.evaluate||Ht).source+"|$","g"),p="//# sourceURL="+(bl.call(t,"sourceURL")?(t.sourceURL+"").replace(/\s/g," "):"lodash.templateSources["+ ++Zr+"]")+"\n"; +n.replace(h,function(t,r,e,o,f,c){return e||(e=o),s+=n.slice(a,c).replace(Jt,U),r&&(u=!0,s+="' +\n__e("+r+") +\n'"),f&&(i=!0,s+="';\n"+f+";\n__p += '"),e&&(s+="' +\n((__t = ("+e+")) == null ? '' : __t) +\n'"),a=c+t.length,t}),s+="';\n";var _=bl.call(t,"variable")&&t.variable;if(_){if(Dt.test(_))throw new fl(un)}else s="with (obj) {\n"+s+"\n}\n";s=(i?s.replace(dt,""):s).replace(bt,"$1").replace(wt,"$1;"),s="function("+(_||"obj")+") {\n"+(_?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(u?", __e = _.escape":"")+(i?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":";\n")+s+"return __p\n}"; +var v=Xh(function(){return cl(f,p+"return "+s).apply(X,c)});if(v.source=s,rc(v))throw v;return v}function ma(n){return Ec(n).toLowerCase()}function xa(n){return Ec(n).toUpperCase()}function ja(n,t,r){if(n=Ec(n),n&&(r||t===X))return R(n);if(!n||!(t=vu(t)))return n;var e=G(n),u=G(t);return Ou(e,W(e,u),L(e,u)+1).join("")}function Aa(n,t,r){if(n=Ec(n),n&&(r||t===X))return n.slice(0,H(n)+1);if(!n||!(t=vu(t)))return n;var e=G(n);return Ou(e,0,L(e,G(t))+1).join("")}function ka(n,t,r){if(n=Ec(n),n&&(r||t===X))return n.replace(Lt,""); +if(!n||!(t=vu(t)))return n;var e=G(n);return Ou(e,W(e,G(t))).join("")}function Oa(n,t){var r=An,e=kn;if(fc(t)){var u="separator"in t?t.separator:u;r="length"in t?kc(t.length):r,e="omission"in t?vu(t.omission):e}n=Ec(n);var i=n.length;if(T(n)){var o=G(n);i=o.length}if(r>=i)return n;var f=r-V(e);if(f<1)return e;var c=o?Ou(o,0,f).join(""):n.slice(0,f);if(u===X)return c+e;if(o&&(f+=c.length-f),Ah(u)){if(n.slice(f).search(u)){var a,l=c;for(u.global||(u=sl(u.source,Ec(Nt.exec(u))+"g")),u.lastIndex=0;a=u.exec(l);)var s=a.index; +c=c.slice(0,s===X?f:s)}}else if(n.indexOf(vu(u),f)!=f){var h=c.lastIndexOf(u);h>-1&&(c=c.slice(0,h))}return c+e}function Ia(n){return n=Ec(n),n&&jt.test(n)?n.replace(mt,ye):n}function Ra(n,t,r){return n=Ec(n),t=r?X:t,t===X?$(n)?Q(n):_(n):n.match(t)||[]}function za(t){var r=null==t?0:t.length,e=mi();return t=r?c(t,function(n){if("function"!=typeof n[1])throw new pl(en);return[e(n[0]),n[1]]}):[],uu(function(e){for(var u=-1;++uWn)return[];var r=Un,e=Hl(n,Un);t=mi(t),n-=Un;for(var u=O(e,t);++r1?n[t-1]:X;return r="function"==typeof r?(n.pop(), +r):X,Ho(n,r)}),Qs=gi(function(n){var t=n.length,r=t?n[0]:0,e=this.__wrapped__,u=function(t){return Tr(t,n)};return!(t>1||this.__actions__.length)&&e instanceof Ct&&Ci(r)?(e=e.slice(r,+r+(t?1:0)),e.__actions__.push({func:nf,args:[u],thisArg:X}),new Y(e,this.__chain__).thru(function(n){return t&&!n.length&&n.push(X),n})):this.thru(u)}),Xs=Fu(function(n,t,r){bl.call(n,r)?++n[r]:Br(n,r,1)}),nh=Ju(ho),th=Ju(po),rh=Fu(function(n,t,r){bl.call(n,r)?n[r].push(t):Br(n,r,[t])}),eh=uu(function(t,r,e){var u=-1,i="function"==typeof r,o=Hf(t)?il(t.length):[]; +return ys(t,function(t){o[++u]=i?n(r,t,e):Ie(t,r,e)}),o}),uh=Fu(function(n,t,r){Br(n,r,t)}),ih=Fu(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),oh=uu(function(n,t){if(null==n)return[];var r=t.length;return r>1&&Ui(n,t[0],t[1])?t=[]:r>2&&Ui(t[0],t[1],t[2])&&(t=[t[0]]),He(n,ee(t,1),[])}),fh=Dl||function(){return re.Date.now()},ch=uu(function(n,t,r){var e=_n;if(r.length){var u=N(r,wi(ch));e|=bn}return ai(n,e,t,r,u)}),ah=uu(function(n,t,r){var e=_n|vn;if(r.length){var u=N(r,wi(ah));e|=bn; +}return ai(t,e,n,r,u)}),lh=uu(function(n,t){return Gr(n,1,t)}),sh=uu(function(n,t,r){return Gr(n,Ic(t)||0,r)});Cf.Cache=sr;var hh=js(function(t,r){r=1==r.length&&bh(r[0])?c(r[0],z(mi())):c(ee(r,1),z(mi()));var e=r.length;return uu(function(u){for(var i=-1,o=Hl(u.length,e);++i=t}),dh=Re(function(){return arguments}())?Re:function(n){return cc(n)&&bl.call(n,"callee")&&!Wl.call(n,"callee")},bh=il.isArray,wh=ce?z(ce):ze,mh=ql||qa,xh=ae?z(ae):Ee,jh=le?z(le):Le,Ah=se?z(se):Be,kh=he?z(he):Te,Oh=pe?z(pe):$e,Ih=ii(Ne),Rh=ii(function(n,t){return n<=t}),zh=Nu(function(n,t){if(Mi(t)||Hf(t))return $u(t,Pc(t),n),X;for(var r in t)bl.call(t,r)&&Sr(n,r,t[r])}),Eh=Nu(function(n,t){$u(t,qc(t),n)}),Sh=Nu(function(n,t,r,e){$u(t,qc(t),n,e)}),Wh=Nu(function(n,t,r,e){$u(t,Pc(t),n,e); +}),Lh=gi(Tr),Ch=uu(function(n,t){n=ll(n);var r=-1,e=t.length,u=e>2?t[2]:X;for(u&&Ui(t[0],t[1],u)&&(e=1);++r1),t}),$u(n,di(n),r),e&&(r=Fr(r,an|ln|sn,hi));for(var u=t.length;u--;)yu(r,t[u]);return r}),Nh=gi(function(n,t){return null==n?{}:Je(n,t)}),Ph=ci(Pc),qh=ci(qc),Zh=Vu(function(n,t,r){return t=t.toLowerCase(),n+(r?fa(t):t)}),Kh=Vu(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Vh=Vu(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Gh=Ku("toLowerCase"),Hh=Vu(function(n,t,r){ +return n+(r?"_":"")+t.toLowerCase()}),Jh=Vu(function(n,t,r){return n+(r?" ":"")+Qh(t)}),Yh=Vu(function(n,t,r){return n+(r?" ":"")+t.toUpperCase()}),Qh=Ku("toUpperCase"),Xh=uu(function(t,r){try{return n(t,X,r)}catch(n){return rc(n)?n:new fl(n)}}),np=gi(function(n,t){return r(t,function(t){t=no(t),Br(n,t,ch(n[t],n))}),n}),tp=Yu(),rp=Yu(!0),ep=uu(function(n,t){return function(r){return Ie(r,n,t)}}),up=uu(function(n,t){return function(r){return Ie(n,r,t)}}),ip=ti(c),op=ti(u),fp=ti(h),cp=ui(),ap=ui(!0),lp=ni(function(n,t){ +return n+t},0),sp=fi("ceil"),hp=ni(function(n,t){return n/t},1),pp=fi("floor"),_p=ni(function(n,t){return n*t},1),vp=fi("round"),gp=ni(function(n,t){return n-t},0);return Z.after=If,Z.ary=Rf,Z.assign=zh,Z.assignIn=Eh,Z.assignInWith=Sh,Z.assignWith=Wh,Z.at=Lh,Z.before=zf,Z.bind=ch,Z.bindAll=np,Z.bindKey=ah,Z.castArray=Nf,Z.chain=Qo,Z.chunk=uo,Z.compact=io,Z.concat=oo,Z.cond=za,Z.conforms=Ea,Z.constant=Sa,Z.countBy=Xs,Z.create=Sc,Z.curry=Ef,Z.curryRight=Sf,Z.debounce=Wf,Z.defaults=Ch,Z.defaultsDeep=Uh, +Z.defer=lh,Z.delay=sh,Z.difference=Us,Z.differenceBy=Bs,Z.differenceWith=Ts,Z.drop=fo,Z.dropRight=co,Z.dropRightWhile=ao,Z.dropWhile=lo,Z.fill=so,Z.filter=lf,Z.flatMap=sf,Z.flatMapDeep=hf,Z.flatMapDepth=pf,Z.flatten=_o,Z.flattenDeep=vo,Z.flattenDepth=go,Z.flip=Lf,Z.flow=tp,Z.flowRight=rp,Z.fromPairs=yo,Z.functions=$c,Z.functionsIn=Dc,Z.groupBy=rh,Z.initial=mo,Z.intersection=$s,Z.intersectionBy=Ds,Z.intersectionWith=Ms,Z.invert=Bh,Z.invertBy=Th,Z.invokeMap=eh,Z.iteratee=Ca,Z.keyBy=uh,Z.keys=Pc,Z.keysIn=qc, +Z.map=yf,Z.mapKeys=Zc,Z.mapValues=Kc,Z.matches=Ua,Z.matchesProperty=Ba,Z.memoize=Cf,Z.merge=Dh,Z.mergeWith=Mh,Z.method=ep,Z.methodOf=up,Z.mixin=Ta,Z.negate=Uf,Z.nthArg=Ma,Z.omit=Fh,Z.omitBy=Vc,Z.once=Bf,Z.orderBy=df,Z.over=ip,Z.overArgs=hh,Z.overEvery=op,Z.overSome=fp,Z.partial=ph,Z.partialRight=_h,Z.partition=ih,Z.pick=Nh,Z.pickBy=Gc,Z.property=Fa,Z.propertyOf=Na,Z.pull=Fs,Z.pullAll=Oo,Z.pullAllBy=Io,Z.pullAllWith=Ro,Z.pullAt=Ns,Z.range=cp,Z.rangeRight=ap,Z.rearg=vh,Z.reject=mf,Z.remove=zo,Z.rest=Tf, +Z.reverse=Eo,Z.sampleSize=jf,Z.set=Jc,Z.setWith=Yc,Z.shuffle=Af,Z.slice=So,Z.sortBy=oh,Z.sortedUniq=$o,Z.sortedUniqBy=Do,Z.split=da,Z.spread=$f,Z.tail=Mo,Z.take=Fo,Z.takeRight=No,Z.takeRightWhile=Po,Z.takeWhile=qo,Z.tap=Xo,Z.throttle=Df,Z.thru=nf,Z.toArray=jc,Z.toPairs=Ph,Z.toPairsIn=qh,Z.toPath=Ha,Z.toPlainObject=Rc,Z.transform=Qc,Z.unary=Mf,Z.union=Ps,Z.unionBy=qs,Z.unionWith=Zs,Z.uniq=Zo,Z.uniqBy=Ko,Z.uniqWith=Vo,Z.unset=Xc,Z.unzip=Go,Z.unzipWith=Ho,Z.update=na,Z.updateWith=ta,Z.values=ra,Z.valuesIn=ea, +Z.without=Ks,Z.words=Ra,Z.wrap=Ff,Z.xor=Vs,Z.xorBy=Gs,Z.xorWith=Hs,Z.zip=Js,Z.zipObject=Jo,Z.zipObjectDeep=Yo,Z.zipWith=Ys,Z.entries=Ph,Z.entriesIn=qh,Z.extend=Eh,Z.extendWith=Sh,Ta(Z,Z),Z.add=lp,Z.attempt=Xh,Z.camelCase=Zh,Z.capitalize=fa,Z.ceil=sp,Z.clamp=ua,Z.clone=Pf,Z.cloneDeep=Zf,Z.cloneDeepWith=Kf,Z.cloneWith=qf,Z.conformsTo=Vf,Z.deburr=ca,Z.defaultTo=Wa,Z.divide=hp,Z.endsWith=aa,Z.eq=Gf,Z.escape=la,Z.escapeRegExp=sa,Z.every=af,Z.find=nh,Z.findIndex=ho,Z.findKey=Wc,Z.findLast=th,Z.findLastIndex=po, +Z.findLastKey=Lc,Z.floor=pp,Z.forEach=_f,Z.forEachRight=vf,Z.forIn=Cc,Z.forInRight=Uc,Z.forOwn=Bc,Z.forOwnRight=Tc,Z.get=Mc,Z.gt=gh,Z.gte=yh,Z.has=Fc,Z.hasIn=Nc,Z.head=bo,Z.identity=La,Z.includes=gf,Z.indexOf=wo,Z.inRange=ia,Z.invoke=$h,Z.isArguments=dh,Z.isArray=bh,Z.isArrayBuffer=wh,Z.isArrayLike=Hf,Z.isArrayLikeObject=Jf,Z.isBoolean=Yf,Z.isBuffer=mh,Z.isDate=xh,Z.isElement=Qf,Z.isEmpty=Xf,Z.isEqual=nc,Z.isEqualWith=tc,Z.isError=rc,Z.isFinite=ec,Z.isFunction=uc,Z.isInteger=ic,Z.isLength=oc,Z.isMap=jh, +Z.isMatch=ac,Z.isMatchWith=lc,Z.isNaN=sc,Z.isNative=hc,Z.isNil=_c,Z.isNull=pc,Z.isNumber=vc,Z.isObject=fc,Z.isObjectLike=cc,Z.isPlainObject=gc,Z.isRegExp=Ah,Z.isSafeInteger=yc,Z.isSet=kh,Z.isString=dc,Z.isSymbol=bc,Z.isTypedArray=Oh,Z.isUndefined=wc,Z.isWeakMap=mc,Z.isWeakSet=xc,Z.join=xo,Z.kebabCase=Kh,Z.last=jo,Z.lastIndexOf=Ao,Z.lowerCase=Vh,Z.lowerFirst=Gh,Z.lt=Ih,Z.lte=Rh,Z.max=Ya,Z.maxBy=Qa,Z.mean=Xa,Z.meanBy=nl,Z.min=tl,Z.minBy=rl,Z.stubArray=Pa,Z.stubFalse=qa,Z.stubObject=Za,Z.stubString=Ka, +Z.stubTrue=Va,Z.multiply=_p,Z.nth=ko,Z.noConflict=$a,Z.noop=Da,Z.now=fh,Z.pad=ha,Z.padEnd=pa,Z.padStart=_a,Z.parseInt=va,Z.random=oa,Z.reduce=bf,Z.reduceRight=wf,Z.repeat=ga,Z.replace=ya,Z.result=Hc,Z.round=vp,Z.runInContext=p,Z.sample=xf,Z.size=kf,Z.snakeCase=Hh,Z.some=Of,Z.sortedIndex=Wo,Z.sortedIndexBy=Lo,Z.sortedIndexOf=Co,Z.sortedLastIndex=Uo,Z.sortedLastIndexBy=Bo,Z.sortedLastIndexOf=To,Z.startCase=Jh,Z.startsWith=ba,Z.subtract=gp,Z.sum=el,Z.sumBy=ul,Z.template=wa,Z.times=Ga,Z.toFinite=Ac,Z.toInteger=kc, +Z.toLength=Oc,Z.toLower=ma,Z.toNumber=Ic,Z.toSafeInteger=zc,Z.toString=Ec,Z.toUpper=xa,Z.trim=ja,Z.trimEnd=Aa,Z.trimStart=ka,Z.truncate=Oa,Z.unescape=Ia,Z.uniqueId=Ja,Z.upperCase=Yh,Z.upperFirst=Qh,Z.each=_f,Z.eachRight=vf,Z.first=bo,Ta(Z,function(){var n={};return ue(Z,function(t,r){bl.call(Z.prototype,r)||(n[r]=t)}),n}(),{chain:!1}),Z.VERSION=nn,r(["bind","bindKey","curry","curryRight","partial","partialRight"],function(n){Z[n].placeholder=Z}),r(["drop","take"],function(n,t){Ct.prototype[n]=function(r){ +r=r===X?1:Gl(kc(r),0);var e=this.__filtered__&&!t?new Ct(this):this.clone();return e.__filtered__?e.__takeCount__=Hl(r,e.__takeCount__):e.__views__.push({size:Hl(r,Un),type:n+(e.__dir__<0?"Right":"")}),e},Ct.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),r(["filter","map","takeWhile"],function(n,t){var r=t+1,e=r==Rn||r==En;Ct.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:mi(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),r(["head","last"],function(n,t){ +var r="take"+(t?"Right":"");Ct.prototype[n]=function(){return this[r](1).value()[0]}}),r(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Ct.prototype[n]=function(){return this.__filtered__?new Ct(this):this[r](1)}}),Ct.prototype.compact=function(){return this.filter(La)},Ct.prototype.find=function(n){return this.filter(n).head()},Ct.prototype.findLast=function(n){return this.reverse().find(n)},Ct.prototype.invokeMap=uu(function(n,t){return"function"==typeof n?new Ct(this):this.map(function(r){ +return Ie(r,n,t)})}),Ct.prototype.reject=function(n){return this.filter(Uf(mi(n)))},Ct.prototype.slice=function(n,t){n=kc(n);var r=this;return r.__filtered__&&(n>0||t<0)?new Ct(r):(n<0?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==X&&(t=kc(t),r=t<0?r.dropRight(-t):r.take(t-n)),r)},Ct.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Ct.prototype.toArray=function(){return this.take(Un)},ue(Ct.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=Z[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t); +u&&(Z.prototype[t]=function(){var t=this.__wrapped__,o=e?[1]:arguments,f=t instanceof Ct,c=o[0],l=f||bh(t),s=function(n){var t=u.apply(Z,a([n],o));return e&&h?t[0]:t};l&&r&&"function"==typeof c&&1!=c.length&&(f=l=!1);var h=this.__chain__,p=!!this.__actions__.length,_=i&&!h,v=f&&!p;if(!i&&l){t=v?t:new Ct(this);var g=n.apply(t,o);return g.__actions__.push({func:nf,args:[s],thisArg:X}),new Y(g,h)}return _&&v?n.apply(this,o):(g=this.thru(s),_?e?g.value()[0]:g.value():g)})}),r(["pop","push","shift","sort","splice","unshift"],function(n){ +var t=_l[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);Z.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(bh(u)?u:[],n)}return this[r](function(r){return t.apply(bh(r)?r:[],n)})}}),ue(Ct.prototype,function(n,t){var r=Z[t];if(r){var e=r.name+"";bl.call(fs,e)||(fs[e]=[]),fs[e].push({name:t,func:r})}}),fs[Qu(X,vn).name]=[{name:"wrapper",func:X}],Ct.prototype.clone=$t,Ct.prototype.reverse=Yt,Ct.prototype.value=Qt,Z.prototype.at=Qs, +Z.prototype.chain=tf,Z.prototype.commit=rf,Z.prototype.next=ef,Z.prototype.plant=of,Z.prototype.reverse=ff,Z.prototype.toJSON=Z.prototype.valueOf=Z.prototype.value=cf,Z.prototype.first=Z.prototype.head,Ul&&(Z.prototype[Ul]=uf),Z},be=de();"function"==typeof define&&"object"==typeof define.amd&&define.amd?(re._=be,define(function(){return be})):ue?((ue.exports=be)._=be,ee._=be):re._=be}).call(this); \ No newline at end of file diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index a41e09a3f4..b23a05ffff 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -6056,6 +6056,24 @@ "tavernSessionUsing": "In use", "tavernSessionLook": "Look", "tavernSessionMore": "More", + "tavernAudio": "Music and sound", + "tavernAudioBgm": "Music", + "tavernAudioAmbient": "Sound effects", + "tavernAudioNothing": "Not playing", + "tavernAudioPlay": "Play", + "tavernAudioPause": "Pause", + "tavernAudioMode": "Play mode", + "tavernAudioModeOne": "One", + "tavernAudioModeAll": "All", + "tavernAudioModeShuffle": "Shuffle", + "tavernAudioModeOnce": "Once", + "tavernAudioVolume": "Volume", + "tavernAudioAllow": "Let the card play", + "tavernAudioAdd": "Add background music", + "tavernAudioAddTitle": "Link to the music", + "tavernAudioAddNote": "The web address of an audio file (mp3, ogg…). It joins this chat's music list, with the card's own.", + "tavernAudioAddConfirm": "Add", + "tavernAudioAddInvalid": "Starts with http:// or https://", "tavernStageState": "State", "tavernStageDirector": "Director", "tavernStageTurn": "This turn", @@ -6242,6 +6260,8 @@ "tavernSessionUsage": "This chat's usage", "tavernSessionUsageNote": "{count, plural, =1{1 reply on the current line} other{{count} replies on the current line}}, added up; replaced alternatives aren't counted", "tavernSessionUsageNone": "The model hasn't reported any usage yet", + "tavernSessionUsageCost": "About {usd} at DeepSeek's list prices, peak and off-peak hours apart; China's public holidays are off-peak all day, so those days come out a little high here", + "tavernSessionUsageUnpriced": "{count, plural, =1{1 reply used a model not on the price list and isn't in the cost} other{{count} replies used a model not on the price list and aren't in the cost}}", "tavernComposerNoKey": "Add a key to begin", "tavernWriteOwn": "Write your own character", "tavernWriteOwnSubtitle": "Start from a blank card and fill in the name, setting and greeting", @@ -6324,10 +6344,12 @@ "tavernStTheme": "ST theme: {name}", "tavernStThemeNote": "With 「ST」 as the colours, quotes and *actions* take its colours; one too faint to read here is adjusted until it reads.", "tavernStThemeImport": "Import an ST theme", - "tavernStThemeImportNote": "Takes the quote and italics colours from a theme file exported from SillyTavern.", + "tavernStThemeImportNote": "Takes the quote and italics colours, and its custom CSS, from a theme file exported from SillyTavern.", + "tavernStThemeCss": "Use its custom CSS", + "tavernStThemeCssSubtitle": "With 「ST」 as the colours, it goes into the whole-story canvas and overrides the default look, as in ST. Turn it off if the page comes apart; the colours stay.", "tavernStThemeRemove": "Remove", "tavernStThemeImported": "Now using the colours of the ST theme “{name}”", - "tavernStThemeNoColors": "“{name}” has no quote or italics colour to use", + "tavernStThemeNoColors": "“{name}” has no quote or italics colour or custom CSS to use", "tavernPromptContent": "Content", "tavernPromptRole": "Spoken as", "tavernPromptRoleSystem": "System", @@ -6382,6 +6404,7 @@ "tavernRollSuccess": "Success", "tavernRollFailure": "Failure", "tavernGameHere": "A mini-game goes here; it can't be played here yet", + "tavernCanvasReplied": "{name} replied", "tavernRegexEdit": "Edit rule", "tavernRegexFieldName": "Name", "tavernRegexFieldFindHelp": "SillyTavern's form: /pattern/flags, e.g. /(hi|bye)/gi", @@ -6415,8 +6438,60 @@ "tavernRegexTryDraft": "Try it as filled in", "tavernRegexDepthAny": "Any", "tavernCanvas": "Whole-page canvas (trial)", - "tavernCanvasSubtitle": "The whole chat in one web page laid out as SillyTavern does, so a card's ST styles reach across messages. Not for visual novel; card scripts don't run here yet.", + "tavernCanvasSubtitle": "The whole chat in one web page laid out as SillyTavern does, so a card's ST styles reach across messages. Card scripts run here; turn on \"No card scripts in those pages\" to stop them. Not for visual novel.", "tavernCanvasReloaded": "The page reloaded", + "tavernGroupSpeaking": "{name} is speaking", + "tavernGroupThen": "then {names}", + "tavernGroupNoSpeakers": "No one took this turn. Tap a name above to have them speak.", + "tavernGroupSpeakNext": "Let them go on", + "tavernGroupNew": "New group chat", + "tavernGroupMembers": "Who's in it", + "tavernGroupMembersNote": "In the order picked; the first hosts it, and the story's shared state lives on that card.", + "tavernGroupHost": "Host", + "tavernGroupTitle": "Name", + "tavernGroupActivation": "Taking turns", + "tavernGroupNatural": "Natural", + "tavernGroupListOrder": "In order", + "tavernGroupRandom": "Random", + "tavernGroupManual": "Manual", + "tavernGroupDirector": "Director", + "tavernGroupNaturalNote": "As SillyTavern does: whoever is named speaks first, the rest by how talkative each is; the most per turn is in the group's settings.", + "tavernGroupListOrderNote": "Each turn goes down the list from the top (the muted are skipped).", + "tavernGroupRandomNote": "Each turn picks one who hasn't spoken since your last line.", + "tavernGroupManualNote": "No one speaks on their own; tap a name above the box to have them speak.", + "tavernGroupDirectorNote": "The director picks who speaks next, as the story goes.", + "tavernGroupPickTwo": "Pick at least two", + "tavernGroupCreate": "Create ({count})", + "tavernGroupCreateFailed": "Couldn't create the group: {reason}", + "tavernGroupTag": "Group", + "tavernGroupMemberCount": "{count} members", + "tavernGroupSettings": "Group settings", + "tavernGroupMaxSpeakers": "Most to speak each turn", + "tavernGroupSelfResponses": "May answer themselves", + "tavernGroupSelfResponsesNote": "Whoever spoke last may still go first this turn.", + "tavernGroupHostNote": "Hosted by {name}: the story's shared state, director and secrets live on that card; the host need not be among the speakers.", + "tavernGroupMuted": "Muted", + "tavernGroupMutedNote": "Won't speak on their own; still speaks when named.", + "tavernGroupTalkativeness": "Talkativeness", + "tavernGroupTalkativenessNote": "In natural turns, the higher, the likelier to speak.", + "tavernGroupHelperModel": "Helper model", + "tavernGroupHelperModelNote": "Speaks with the cheaper, faster model (DeepSeek flash, no thinking): for side characters.", + "tavernGroupMoveUp": "Move up", + "tavernGroupMoveDown": "Move down", + "tavernGroupRemove": "Remove from the group", + "tavernGroupAdd": "Add a character", + "tavernGroupNoOthers": "Everyone on the shelf is in it", + "tavernGroupUnavailable": "Card missing", + "tavernGroupSaveFailed": "Couldn't save: {reason}", + "tavernGroupErrorNotGroup": "This is no longer a group chat", + "tavernGroupErrorCharacterNotFound": "That character's card is gone", + "tavernGroupErrorMemberExists": "That character is already in the group", + "tavernGroupErrorMemberNotFound": "That character is no longer in the group", + "tavernGroupErrorInvalidMembers": "Members can't repeat, and a group needs at least one character", + "tavernGroupErrorInvalidTalkativeness": "Talkativeness goes from 0 to 1", + "tavernGroupErrorInvalidLimit": "At least one member speaks each round", + "tavernGroupErrorInvalidOrder": "That order doesn't match the members; open it again and reorder", + "tavernGroupErrorHostInUse": "This character hosts a group chat; delete that chat first", "updateCheck": "Check for updates", "updateAutoCheck": "Check automatically", "updateAutoCheckDaily": "Once a day", diff --git a/app/lib/l10n/app_zh.arb b/app/lib/l10n/app_zh.arb index befa56e624..da509cd64a 100644 --- a/app/lib/l10n/app_zh.arb +++ b/app/lib/l10n/app_zh.arb @@ -12198,6 +12198,24 @@ "tavernSessionUsing": "正在用", "tavernSessionLook": "外观", "tavernSessionMore": "更多", + "tavernAudio": "音乐和音效", + "tavernAudioBgm": "背景音乐", + "tavernAudioAmbient": "音效", + "tavernAudioNothing": "没在放", + "tavernAudioPlay": "播放", + "tavernAudioPause": "暂停", + "tavernAudioMode": "播放方式", + "tavernAudioModeOne": "单曲", + "tavernAudioModeAll": "列表", + "tavernAudioModeShuffle": "随机", + "tavernAudioModeOnce": "放一首", + "tavernAudioVolume": "音量", + "tavernAudioAllow": "让角色卡播放", + "tavernAudioAdd": "加一首背景音乐", + "tavernAudioAddTitle": "背景音乐的链接", + "tavernAudioAddNote": "填音频文件的网址(mp3、ogg 等),加进这段对话的背景音乐列表,和卡放的放在一起。", + "tavernAudioAddConfirm": "加入", + "tavernAudioAddInvalid": "要以 http:// 或 https:// 开头", "tavernStageState": "状态", "tavernStageDirector": "导演", "tavernStageTurn": "本回合", @@ -12641,6 +12659,22 @@ } }, "tavernSessionUsageNone": "还没有模型报告过用量", + "tavernSessionUsageCost": "约 {usd},按 DeepSeek 官方价,分高峰和低谷时段算;法定节假日全天是低谷价,那几天这里会算高一点", + "@tavernSessionUsageCost": { + "placeholders": { + "usd": { + "type": "String" + } + } + }, + "tavernSessionUsageUnpriced": "{count} 条回复用的模型不在价目表里,没算进费用", + "@tavernSessionUsageUnpriced": { + "placeholders": { + "count": { + "type": "int" + } + } + }, "tavernComposerNoKey": "填好 Key 就能开始", "tavernWriteOwn": "自己写一个角色", "tavernWriteOwnSubtitle": "从一张空白卡开始,名字、设定和开场白都自己填", @@ -12803,7 +12837,9 @@ }, "tavernStThemeNote": "配色选「ST」时,引号里的话和 *动作* 用它的颜色;太浅或太深看不清的,会调到看得清。", "tavernStThemeImport": "导入 ST 主题", - "tavernStThemeImportNote": "从 SillyTavern 导出的主题文件里取引号和斜体的颜色。", + "tavernStThemeImportNote": "从 SillyTavern 导出的主题文件里取引号和斜体的颜色,和它的自定义 CSS。", + "tavernStThemeCss": "用它的自定义 CSS", + "tavernStThemeCssSubtitle": "配色选「ST」时加进整页画布,和在 ST 里一样盖过默认样式。版面乱了就关掉,颜色照用。", "tavernStThemeRemove": "移除", "tavernStThemeImported": "换成了 ST 主题「{name}」的配色", "@tavernStThemeImported": { @@ -12813,7 +12849,7 @@ } } }, - "tavernStThemeNoColors": "「{name}」里没有能用的引号或斜体颜色", + "tavernStThemeNoColors": "「{name}」里没有能用的引号、斜体颜色或自定义 CSS", "@tavernStThemeNoColors": { "placeholders": { "name": { @@ -12917,6 +12953,14 @@ "tavernRollSuccess": "成功", "tavernRollFailure": "失败", "tavernGameHere": "这里有一个小游戏,现在还不能在这里玩", + "tavernCanvasReplied": "{name}回复了", + "@tavernCanvasReplied": { + "placeholders": { + "name": { + "type": "String" + } + } + }, "tavernRegexEdit": "编辑规则", "tavernRegexFieldName": "名字", "tavernRegexFieldFindHelp": "SillyTavern 的写法:/规则/标志,比如 /(早安|晚安)/gi", @@ -12950,8 +12994,109 @@ "tavernRegexTryDraft": "用现在填的试一下", "tavernRegexDepthAny": "不限", "tavernCanvas": "整页画布(试用)", - "tavernCanvasSubtitle": "整段对话放进一张网页,按 SillyTavern 的结构排,卡写给 ST 的样式能跨消息生效。视觉小说不用它;卡里的脚本暂时还不跑。", + "tavernCanvasSubtitle": "整段对话放进一张网页,按 SillyTavern 的结构排,卡写给 ST 的样式能跨消息生效。卡里的脚本会运行,不想让它跑就打开「网页里不运行卡的脚本」。视觉小说不用它。", "tavernCanvasReloaded": "画面重新加载了", + "tavernGroupSpeaking": "{name} 在说", + "@tavernGroupSpeaking": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "tavernGroupThen": "接着是 {names}", + "@tavernGroupThen": { + "placeholders": { + "names": { + "type": "String" + } + } + }, + "tavernGroupNoSpeakers": "这一轮没人接话。点上面的名字,让谁来说。", + "tavernGroupSpeakNext": "让大家接着说", + "tavernGroupNew": "新建群聊", + "tavernGroupMembers": "谁在里面", + "tavernGroupMembersNote": "按点选的顺序排;第一个是主持,故事共用的状态记在它的卡上。", + "tavernGroupHost": "主持", + "tavernGroupTitle": "群名", + "tavernGroupActivation": "怎么轮流说", + "tavernGroupNatural": "自然", + "tavernGroupListOrder": "按顺序", + "tavernGroupRandom": "随机", + "tavernGroupManual": "手动", + "tavernGroupDirector": "导演", + "tavernGroupNaturalNote": "像 SillyTavern:被点到名的先说,其余看各自的话多话少;每轮最多几个人说在群设置里改。", + "tavernGroupListOrderNote": "每轮按名单从头轮一遍(静音的跳过)。", + "tavernGroupRandomNote": "每轮随机挑一个这轮还没说过的人。", + "tavernGroupManualNote": "谁也不自己开口;点输入框上面的名字,让谁说谁说。", + "tavernGroupDirectorNote": "由导演按剧情点名下一位。", + "tavernGroupPickTwo": "至少选两个角色", + "tavernGroupCreate": "建群({count} 人)", + "@tavernGroupCreate": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "tavernGroupCreateFailed": "没能建群:{reason}", + "@tavernGroupCreateFailed": { + "placeholders": { + "reason": { + "type": "String" + } + } + }, + "tavernGroupTag": "群", + "tavernGroupMemberCount": "{count} 人", + "@tavernGroupMemberCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "tavernGroupSettings": "群聊设置", + "tavernGroupMaxSpeakers": "每轮最多几人说", + "tavernGroupSelfResponses": "可以接着自己说", + "tavernGroupSelfResponsesNote": "上一句是谁说的,这一轮还能先轮到他。", + "tavernGroupHostNote": "主持是「{name}」:故事共用的状态、导演和秘密记在它的卡上;它可以不在发言名单里。", + "@tavernGroupHostNote": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "tavernGroupMuted": "静音", + "tavernGroupMutedNote": "不会自己开口;点名时照样说。", + "tavernGroupTalkativeness": "话多话少", + "tavernGroupTalkativenessNote": "「自然」轮流时,越高越容易开口。", + "tavernGroupHelperModel": "用辅助模型", + "tavernGroupHelperModelNote": "用便宜快的模型说(DeepSeek 是 flash、不思考),适合配角。", + "tavernGroupMoveUp": "往前挪", + "tavernGroupMoveDown": "往后挪", + "tavernGroupRemove": "移出群聊", + "tavernGroupAdd": "加一个角色", + "tavernGroupNoOthers": "书架上的角色都在群里了", + "tavernGroupUnavailable": "角色卡不在了", + "tavernGroupSaveFailed": "没能保存:{reason}", + "@tavernGroupSaveFailed": { + "placeholders": { + "reason": { + "type": "String" + } + } + }, + "tavernGroupErrorNotGroup": "这已经不是群聊了", + "tavernGroupErrorCharacterNotFound": "这个角色的卡不在了", + "tavernGroupErrorMemberExists": "这个角色已经在群里了", + "tavernGroupErrorMemberNotFound": "这个角色已经不在群里了", + "tavernGroupErrorInvalidMembers": "成员不能重复,群里至少要有一个角色", + "tavernGroupErrorInvalidTalkativeness": "话多程度要在 0 到 1 之间", + "tavernGroupErrorInvalidLimit": "每轮至少要有一个人说话", + "tavernGroupErrorInvalidOrder": "排的顺序和群里的成员对不上,请重新打开再排", + "tavernGroupErrorHostInUse": "这个角色是一个群聊的群主,先删掉那个群聊", "updateCheck": "检查更新", "updateAutoCheck": "自动检查更新", "updateAutoCheckDaily": "每天检查一次", diff --git a/app/lib/l10n/gen/app_localizations.dart b/app/lib/l10n/gen/app_localizations.dart index 4d8e095bc4..c79f8d7721 100644 --- a/app/lib/l10n/gen/app_localizations.dart +++ b/app/lib/l10n/gen/app_localizations.dart @@ -36428,6 +36428,114 @@ abstract class L10n { /// **'更多'** String get tavernSessionMore; + /// No description provided for @tavernAudio. + /// + /// In zh, this message translates to: + /// **'音乐和音效'** + String get tavernAudio; + + /// No description provided for @tavernAudioBgm. + /// + /// In zh, this message translates to: + /// **'背景音乐'** + String get tavernAudioBgm; + + /// No description provided for @tavernAudioAmbient. + /// + /// In zh, this message translates to: + /// **'音效'** + String get tavernAudioAmbient; + + /// No description provided for @tavernAudioNothing. + /// + /// In zh, this message translates to: + /// **'没在放'** + String get tavernAudioNothing; + + /// No description provided for @tavernAudioPlay. + /// + /// In zh, this message translates to: + /// **'播放'** + String get tavernAudioPlay; + + /// No description provided for @tavernAudioPause. + /// + /// In zh, this message translates to: + /// **'暂停'** + String get tavernAudioPause; + + /// No description provided for @tavernAudioMode. + /// + /// In zh, this message translates to: + /// **'播放方式'** + String get tavernAudioMode; + + /// No description provided for @tavernAudioModeOne. + /// + /// In zh, this message translates to: + /// **'单曲'** + String get tavernAudioModeOne; + + /// No description provided for @tavernAudioModeAll. + /// + /// In zh, this message translates to: + /// **'列表'** + String get tavernAudioModeAll; + + /// No description provided for @tavernAudioModeShuffle. + /// + /// In zh, this message translates to: + /// **'随机'** + String get tavernAudioModeShuffle; + + /// No description provided for @tavernAudioModeOnce. + /// + /// In zh, this message translates to: + /// **'放一首'** + String get tavernAudioModeOnce; + + /// No description provided for @tavernAudioVolume. + /// + /// In zh, this message translates to: + /// **'音量'** + String get tavernAudioVolume; + + /// No description provided for @tavernAudioAllow. + /// + /// In zh, this message translates to: + /// **'让角色卡播放'** + String get tavernAudioAllow; + + /// No description provided for @tavernAudioAdd. + /// + /// In zh, this message translates to: + /// **'加一首背景音乐'** + String get tavernAudioAdd; + + /// No description provided for @tavernAudioAddTitle. + /// + /// In zh, this message translates to: + /// **'背景音乐的链接'** + String get tavernAudioAddTitle; + + /// No description provided for @tavernAudioAddNote. + /// + /// In zh, this message translates to: + /// **'填音频文件的网址(mp3、ogg 等),加进这段对话的背景音乐列表,和卡放的放在一起。'** + String get tavernAudioAddNote; + + /// No description provided for @tavernAudioAddConfirm. + /// + /// In zh, this message translates to: + /// **'加入'** + String get tavernAudioAddConfirm; + + /// No description provided for @tavernAudioAddInvalid. + /// + /// In zh, this message translates to: + /// **'要以 http:// 或 https:// 开头'** + String get tavernAudioAddInvalid; + /// No description provided for @tavernStageState. /// /// In zh, this message translates to: @@ -37544,6 +37652,18 @@ abstract class L10n { /// **'还没有模型报告过用量'** String get tavernSessionUsageNone; + /// No description provided for @tavernSessionUsageCost. + /// + /// In zh, this message translates to: + /// **'约 {usd},按 DeepSeek 官方价,分高峰和低谷时段算;法定节假日全天是低谷价,那几天这里会算高一点'** + String tavernSessionUsageCost(String usd); + + /// No description provided for @tavernSessionUsageUnpriced. + /// + /// In zh, this message translates to: + /// **'{count} 条回复用的模型不在价目表里,没算进费用'** + String tavernSessionUsageUnpriced(int count); + /// No description provided for @tavernComposerNoKey. /// /// In zh, this message translates to: @@ -38039,9 +38159,21 @@ abstract class L10n { /// No description provided for @tavernStThemeImportNote. /// /// In zh, this message translates to: - /// **'从 SillyTavern 导出的主题文件里取引号和斜体的颜色。'** + /// **'从 SillyTavern 导出的主题文件里取引号和斜体的颜色,和它的自定义 CSS。'** String get tavernStThemeImportNote; + /// No description provided for @tavernStThemeCss. + /// + /// In zh, this message translates to: + /// **'用它的自定义 CSS'** + String get tavernStThemeCss; + + /// No description provided for @tavernStThemeCssSubtitle. + /// + /// In zh, this message translates to: + /// **'配色选「ST」时加进整页画布,和在 ST 里一样盖过默认样式。版面乱了就关掉,颜色照用。'** + String get tavernStThemeCssSubtitle; + /// No description provided for @tavernStThemeRemove. /// /// In zh, this message translates to: @@ -38057,7 +38189,7 @@ abstract class L10n { /// No description provided for @tavernStThemeNoColors. /// /// In zh, this message translates to: - /// **'「{name}」里没有能用的引号或斜体颜色'** + /// **'「{name}」里没有能用的引号、斜体颜色或自定义 CSS'** String tavernStThemeNoColors(String name); /// No description provided for @tavernPromptContent. @@ -38384,6 +38516,12 @@ abstract class L10n { /// **'这里有一个小游戏,现在还不能在这里玩'** String get tavernGameHere; + /// No description provided for @tavernCanvasReplied. + /// + /// In zh, this message translates to: + /// **'{name}回复了'** + String tavernCanvasReplied(String name); + /// No description provided for @tavernRegexEdit. /// /// In zh, this message translates to: @@ -38585,7 +38723,7 @@ abstract class L10n { /// No description provided for @tavernCanvasSubtitle. /// /// In zh, this message translates to: - /// **'整段对话放进一张网页,按 SillyTavern 的结构排,卡写给 ST 的样式能跨消息生效。视觉小说不用它;卡里的脚本暂时还不跑。'** + /// **'整段对话放进一张网页,按 SillyTavern 的结构排,卡写给 ST 的样式能跨消息生效。卡里的脚本会运行,不想让它跑就打开「网页里不运行卡的脚本」。视觉小说不用它。'** String get tavernCanvasSubtitle; /// No description provided for @tavernCanvasReloaded. @@ -38594,6 +38732,318 @@ abstract class L10n { /// **'画面重新加载了'** String get tavernCanvasReloaded; + /// No description provided for @tavernGroupSpeaking. + /// + /// In zh, this message translates to: + /// **'{name} 在说'** + String tavernGroupSpeaking(String name); + + /// No description provided for @tavernGroupThen. + /// + /// In zh, this message translates to: + /// **'接着是 {names}'** + String tavernGroupThen(String names); + + /// No description provided for @tavernGroupNoSpeakers. + /// + /// In zh, this message translates to: + /// **'这一轮没人接话。点上面的名字,让谁来说。'** + String get tavernGroupNoSpeakers; + + /// No description provided for @tavernGroupSpeakNext. + /// + /// In zh, this message translates to: + /// **'让大家接着说'** + String get tavernGroupSpeakNext; + + /// No description provided for @tavernGroupNew. + /// + /// In zh, this message translates to: + /// **'新建群聊'** + String get tavernGroupNew; + + /// No description provided for @tavernGroupMembers. + /// + /// In zh, this message translates to: + /// **'谁在里面'** + String get tavernGroupMembers; + + /// No description provided for @tavernGroupMembersNote. + /// + /// In zh, this message translates to: + /// **'按点选的顺序排;第一个是主持,故事共用的状态记在它的卡上。'** + String get tavernGroupMembersNote; + + /// No description provided for @tavernGroupHost. + /// + /// In zh, this message translates to: + /// **'主持'** + String get tavernGroupHost; + + /// No description provided for @tavernGroupTitle. + /// + /// In zh, this message translates to: + /// **'群名'** + String get tavernGroupTitle; + + /// No description provided for @tavernGroupActivation. + /// + /// In zh, this message translates to: + /// **'怎么轮流说'** + String get tavernGroupActivation; + + /// No description provided for @tavernGroupNatural. + /// + /// In zh, this message translates to: + /// **'自然'** + String get tavernGroupNatural; + + /// No description provided for @tavernGroupListOrder. + /// + /// In zh, this message translates to: + /// **'按顺序'** + String get tavernGroupListOrder; + + /// No description provided for @tavernGroupRandom. + /// + /// In zh, this message translates to: + /// **'随机'** + String get tavernGroupRandom; + + /// No description provided for @tavernGroupManual. + /// + /// In zh, this message translates to: + /// **'手动'** + String get tavernGroupManual; + + /// No description provided for @tavernGroupDirector. + /// + /// In zh, this message translates to: + /// **'导演'** + String get tavernGroupDirector; + + /// No description provided for @tavernGroupNaturalNote. + /// + /// In zh, this message translates to: + /// **'像 SillyTavern:被点到名的先说,其余看各自的话多话少;每轮最多几个人说在群设置里改。'** + String get tavernGroupNaturalNote; + + /// No description provided for @tavernGroupListOrderNote. + /// + /// In zh, this message translates to: + /// **'每轮按名单从头轮一遍(静音的跳过)。'** + String get tavernGroupListOrderNote; + + /// No description provided for @tavernGroupRandomNote. + /// + /// In zh, this message translates to: + /// **'每轮随机挑一个这轮还没说过的人。'** + String get tavernGroupRandomNote; + + /// No description provided for @tavernGroupManualNote. + /// + /// In zh, this message translates to: + /// **'谁也不自己开口;点输入框上面的名字,让谁说谁说。'** + String get tavernGroupManualNote; + + /// No description provided for @tavernGroupDirectorNote. + /// + /// In zh, this message translates to: + /// **'由导演按剧情点名下一位。'** + String get tavernGroupDirectorNote; + + /// No description provided for @tavernGroupPickTwo. + /// + /// In zh, this message translates to: + /// **'至少选两个角色'** + String get tavernGroupPickTwo; + + /// No description provided for @tavernGroupCreate. + /// + /// In zh, this message translates to: + /// **'建群({count} 人)'** + String tavernGroupCreate(int count); + + /// No description provided for @tavernGroupCreateFailed. + /// + /// In zh, this message translates to: + /// **'没能建群:{reason}'** + String tavernGroupCreateFailed(String reason); + + /// No description provided for @tavernGroupTag. + /// + /// In zh, this message translates to: + /// **'群'** + String get tavernGroupTag; + + /// No description provided for @tavernGroupMemberCount. + /// + /// In zh, this message translates to: + /// **'{count} 人'** + String tavernGroupMemberCount(int count); + + /// No description provided for @tavernGroupSettings. + /// + /// In zh, this message translates to: + /// **'群聊设置'** + String get tavernGroupSettings; + + /// No description provided for @tavernGroupMaxSpeakers. + /// + /// In zh, this message translates to: + /// **'每轮最多几人说'** + String get tavernGroupMaxSpeakers; + + /// No description provided for @tavernGroupSelfResponses. + /// + /// In zh, this message translates to: + /// **'可以接着自己说'** + String get tavernGroupSelfResponses; + + /// No description provided for @tavernGroupSelfResponsesNote. + /// + /// In zh, this message translates to: + /// **'上一句是谁说的,这一轮还能先轮到他。'** + String get tavernGroupSelfResponsesNote; + + /// No description provided for @tavernGroupHostNote. + /// + /// In zh, this message translates to: + /// **'主持是「{name}」:故事共用的状态、导演和秘密记在它的卡上;它可以不在发言名单里。'** + String tavernGroupHostNote(String name); + + /// No description provided for @tavernGroupMuted. + /// + /// In zh, this message translates to: + /// **'静音'** + String get tavernGroupMuted; + + /// No description provided for @tavernGroupMutedNote. + /// + /// In zh, this message translates to: + /// **'不会自己开口;点名时照样说。'** + String get tavernGroupMutedNote; + + /// No description provided for @tavernGroupTalkativeness. + /// + /// In zh, this message translates to: + /// **'话多话少'** + String get tavernGroupTalkativeness; + + /// No description provided for @tavernGroupTalkativenessNote. + /// + /// In zh, this message translates to: + /// **'「自然」轮流时,越高越容易开口。'** + String get tavernGroupTalkativenessNote; + + /// No description provided for @tavernGroupHelperModel. + /// + /// In zh, this message translates to: + /// **'用辅助模型'** + String get tavernGroupHelperModel; + + /// No description provided for @tavernGroupHelperModelNote. + /// + /// In zh, this message translates to: + /// **'用便宜快的模型说(DeepSeek 是 flash、不思考),适合配角。'** + String get tavernGroupHelperModelNote; + + /// No description provided for @tavernGroupMoveUp. + /// + /// In zh, this message translates to: + /// **'往前挪'** + String get tavernGroupMoveUp; + + /// No description provided for @tavernGroupMoveDown. + /// + /// In zh, this message translates to: + /// **'往后挪'** + String get tavernGroupMoveDown; + + /// No description provided for @tavernGroupRemove. + /// + /// In zh, this message translates to: + /// **'移出群聊'** + String get tavernGroupRemove; + + /// No description provided for @tavernGroupAdd. + /// + /// In zh, this message translates to: + /// **'加一个角色'** + String get tavernGroupAdd; + + /// No description provided for @tavernGroupNoOthers. + /// + /// In zh, this message translates to: + /// **'书架上的角色都在群里了'** + String get tavernGroupNoOthers; + + /// No description provided for @tavernGroupUnavailable. + /// + /// In zh, this message translates to: + /// **'角色卡不在了'** + String get tavernGroupUnavailable; + + /// No description provided for @tavernGroupSaveFailed. + /// + /// In zh, this message translates to: + /// **'没能保存:{reason}'** + String tavernGroupSaveFailed(String reason); + + /// No description provided for @tavernGroupErrorNotGroup. + /// + /// In zh, this message translates to: + /// **'这已经不是群聊了'** + String get tavernGroupErrorNotGroup; + + /// No description provided for @tavernGroupErrorCharacterNotFound. + /// + /// In zh, this message translates to: + /// **'这个角色的卡不在了'** + String get tavernGroupErrorCharacterNotFound; + + /// No description provided for @tavernGroupErrorMemberExists. + /// + /// In zh, this message translates to: + /// **'这个角色已经在群里了'** + String get tavernGroupErrorMemberExists; + + /// No description provided for @tavernGroupErrorMemberNotFound. + /// + /// In zh, this message translates to: + /// **'这个角色已经不在群里了'** + String get tavernGroupErrorMemberNotFound; + + /// No description provided for @tavernGroupErrorInvalidMembers. + /// + /// In zh, this message translates to: + /// **'成员不能重复,群里至少要有一个角色'** + String get tavernGroupErrorInvalidMembers; + + /// No description provided for @tavernGroupErrorInvalidTalkativeness. + /// + /// In zh, this message translates to: + /// **'话多程度要在 0 到 1 之间'** + String get tavernGroupErrorInvalidTalkativeness; + + /// No description provided for @tavernGroupErrorInvalidLimit. + /// + /// In zh, this message translates to: + /// **'每轮至少要有一个人说话'** + String get tavernGroupErrorInvalidLimit; + + /// No description provided for @tavernGroupErrorInvalidOrder. + /// + /// In zh, this message translates to: + /// **'排的顺序和群里的成员对不上,请重新打开再排'** + String get tavernGroupErrorInvalidOrder; + + /// No description provided for @tavernGroupErrorHostInUse. + /// + /// In zh, this message translates to: + /// **'这个角色是一个群聊的群主,先删掉那个群聊'** + String get tavernGroupErrorHostInUse; + /// No description provided for @updateCheck. /// /// In zh, this message translates to: diff --git a/app/lib/l10n/gen/app_localizations_en.dart b/app/lib/l10n/gen/app_localizations_en.dart index 5654994e1a..1257beea73 100644 --- a/app/lib/l10n/gen/app_localizations_en.dart +++ b/app/lib/l10n/gen/app_localizations_en.dart @@ -21022,6 +21022,61 @@ class L10nEn extends L10n { @override String get tavernSessionMore => 'More'; + @override + String get tavernAudio => 'Music and sound'; + + @override + String get tavernAudioBgm => 'Music'; + + @override + String get tavernAudioAmbient => 'Sound effects'; + + @override + String get tavernAudioNothing => 'Not playing'; + + @override + String get tavernAudioPlay => 'Play'; + + @override + String get tavernAudioPause => 'Pause'; + + @override + String get tavernAudioMode => 'Play mode'; + + @override + String get tavernAudioModeOne => 'One'; + + @override + String get tavernAudioModeAll => 'All'; + + @override + String get tavernAudioModeShuffle => 'Shuffle'; + + @override + String get tavernAudioModeOnce => 'Once'; + + @override + String get tavernAudioVolume => 'Volume'; + + @override + String get tavernAudioAllow => 'Let the card play'; + + @override + String get tavernAudioAdd => 'Add background music'; + + @override + String get tavernAudioAddTitle => 'Link to the music'; + + @override + String get tavernAudioAddNote => + 'The web address of an audio file (mp3, ogg…). It joins this chat\'s music list, with the card\'s own.'; + + @override + String get tavernAudioAddConfirm => 'Add'; + + @override + String get tavernAudioAddInvalid => 'Starts with http:// or https://'; + @override String get tavernStageState => 'State'; @@ -21707,6 +21762,22 @@ class L10nEn extends L10n { @override String get tavernSessionUsageNone => 'The model hasn\'t reported any usage yet'; + @override + String tavernSessionUsageCost(String usd) { + return 'About $usd at DeepSeek\'s list prices, peak and off-peak hours apart; China\'s public holidays are off-peak all day, so those days come out a little high here'; + } + + @override + String tavernSessionUsageUnpriced(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count replies used a model not on the price list and aren\'t in the cost', + one: '1 reply used a model not on the price list and isn\'t in the cost', + ); + return '$_temp0'; + } + @override String get tavernComposerNoKey => 'Add a key to begin'; @@ -21984,7 +22055,14 @@ class L10nEn extends L10n { @override String get tavernStThemeImportNote => - 'Takes the quote and italics colours from a theme file exported from SillyTavern.'; + 'Takes the quote and italics colours, and its custom CSS, from a theme file exported from SillyTavern.'; + + @override + String get tavernStThemeCss => 'Use its custom CSS'; + + @override + String get tavernStThemeCssSubtitle => + 'With 「ST」 as the colours, it goes into the whole-story canvas and overrides the default look, as in ST. Turn it off if the page comes apart; the colours stay.'; @override String get tavernStThemeRemove => 'Remove'; @@ -21996,7 +22074,7 @@ class L10nEn extends L10n { @override String tavernStThemeNoColors(String name) { - return '“$name” has no quote or italics colour to use'; + return '“$name” has no quote or italics colour or custom CSS to use'; } @override @@ -22174,6 +22252,11 @@ class L10nEn extends L10n { @override String get tavernGameHere => 'A mini-game goes here; it can\'t be played here yet'; + @override + String tavernCanvasReplied(String name) { + return '$name replied'; + } + @override String get tavernRegexEdit => 'Edit rule'; @@ -22276,11 +22359,184 @@ class L10nEn extends L10n { @override String get tavernCanvasSubtitle => - 'The whole chat in one web page laid out as SillyTavern does, so a card\'s ST styles reach across messages. Not for visual novel; card scripts don\'t run here yet.'; + 'The whole chat in one web page laid out as SillyTavern does, so a card\'s ST styles reach across messages. Card scripts run here; turn on \"No card scripts in those pages\" to stop them. Not for visual novel.'; @override String get tavernCanvasReloaded => 'The page reloaded'; + @override + String tavernGroupSpeaking(String name) { + return '$name is speaking'; + } + + @override + String tavernGroupThen(String names) { + return 'then $names'; + } + + @override + String get tavernGroupNoSpeakers => 'No one took this turn. Tap a name above to have them speak.'; + + @override + String get tavernGroupSpeakNext => 'Let them go on'; + + @override + String get tavernGroupNew => 'New group chat'; + + @override + String get tavernGroupMembers => 'Who\'s in it'; + + @override + String get tavernGroupMembersNote => + 'In the order picked; the first hosts it, and the story\'s shared state lives on that card.'; + + @override + String get tavernGroupHost => 'Host'; + + @override + String get tavernGroupTitle => 'Name'; + + @override + String get tavernGroupActivation => 'Taking turns'; + + @override + String get tavernGroupNatural => 'Natural'; + + @override + String get tavernGroupListOrder => 'In order'; + + @override + String get tavernGroupRandom => 'Random'; + + @override + String get tavernGroupManual => 'Manual'; + + @override + String get tavernGroupDirector => 'Director'; + + @override + String get tavernGroupNaturalNote => + 'As SillyTavern does: whoever is named speaks first, the rest by how talkative each is; the most per turn is in the group\'s settings.'; + + @override + String get tavernGroupListOrderNote => 'Each turn goes down the list from the top (the muted are skipped).'; + + @override + String get tavernGroupRandomNote => 'Each turn picks one who hasn\'t spoken since your last line.'; + + @override + String get tavernGroupManualNote => 'No one speaks on their own; tap a name above the box to have them speak.'; + + @override + String get tavernGroupDirectorNote => 'The director picks who speaks next, as the story goes.'; + + @override + String get tavernGroupPickTwo => 'Pick at least two'; + + @override + String tavernGroupCreate(int count) { + return 'Create ($count)'; + } + + @override + String tavernGroupCreateFailed(String reason) { + return 'Couldn\'t create the group: $reason'; + } + + @override + String get tavernGroupTag => 'Group'; + + @override + String tavernGroupMemberCount(int count) { + return '$count members'; + } + + @override + String get tavernGroupSettings => 'Group settings'; + + @override + String get tavernGroupMaxSpeakers => 'Most to speak each turn'; + + @override + String get tavernGroupSelfResponses => 'May answer themselves'; + + @override + String get tavernGroupSelfResponsesNote => 'Whoever spoke last may still go first this turn.'; + + @override + String tavernGroupHostNote(String name) { + return 'Hosted by $name: the story\'s shared state, director and secrets live on that card; the host need not be among the speakers.'; + } + + @override + String get tavernGroupMuted => 'Muted'; + + @override + String get tavernGroupMutedNote => 'Won\'t speak on their own; still speaks when named.'; + + @override + String get tavernGroupTalkativeness => 'Talkativeness'; + + @override + String get tavernGroupTalkativenessNote => 'In natural turns, the higher, the likelier to speak.'; + + @override + String get tavernGroupHelperModel => 'Helper model'; + + @override + String get tavernGroupHelperModelNote => + 'Speaks with the cheaper, faster model (DeepSeek flash, no thinking): for side characters.'; + + @override + String get tavernGroupMoveUp => 'Move up'; + + @override + String get tavernGroupMoveDown => 'Move down'; + + @override + String get tavernGroupRemove => 'Remove from the group'; + + @override + String get tavernGroupAdd => 'Add a character'; + + @override + String get tavernGroupNoOthers => 'Everyone on the shelf is in it'; + + @override + String get tavernGroupUnavailable => 'Card missing'; + + @override + String tavernGroupSaveFailed(String reason) { + return 'Couldn\'t save: $reason'; + } + + @override + String get tavernGroupErrorNotGroup => 'This is no longer a group chat'; + + @override + String get tavernGroupErrorCharacterNotFound => 'That character\'s card is gone'; + + @override + String get tavernGroupErrorMemberExists => 'That character is already in the group'; + + @override + String get tavernGroupErrorMemberNotFound => 'That character is no longer in the group'; + + @override + String get tavernGroupErrorInvalidMembers => 'Members can\'t repeat, and a group needs at least one character'; + + @override + String get tavernGroupErrorInvalidTalkativeness => 'Talkativeness goes from 0 to 1'; + + @override + String get tavernGroupErrorInvalidLimit => 'At least one member speaks each round'; + + @override + String get tavernGroupErrorInvalidOrder => 'That order doesn\'t match the members; open it again and reorder'; + + @override + String get tavernGroupErrorHostInUse => 'This character hosts a group chat; delete that chat first'; + @override String get updateCheck => 'Check for updates'; diff --git a/app/lib/l10n/gen/app_localizations_zh.dart b/app/lib/l10n/gen/app_localizations_zh.dart index 9e4c22c5fc..187e891716 100644 --- a/app/lib/l10n/gen/app_localizations_zh.dart +++ b/app/lib/l10n/gen/app_localizations_zh.dart @@ -19915,6 +19915,60 @@ class L10nZh extends L10n { @override String get tavernSessionMore => '更多'; + @override + String get tavernAudio => '音乐和音效'; + + @override + String get tavernAudioBgm => '背景音乐'; + + @override + String get tavernAudioAmbient => '音效'; + + @override + String get tavernAudioNothing => '没在放'; + + @override + String get tavernAudioPlay => '播放'; + + @override + String get tavernAudioPause => '暂停'; + + @override + String get tavernAudioMode => '播放方式'; + + @override + String get tavernAudioModeOne => '单曲'; + + @override + String get tavernAudioModeAll => '列表'; + + @override + String get tavernAudioModeShuffle => '随机'; + + @override + String get tavernAudioModeOnce => '放一首'; + + @override + String get tavernAudioVolume => '音量'; + + @override + String get tavernAudioAllow => '让角色卡播放'; + + @override + String get tavernAudioAdd => '加一首背景音乐'; + + @override + String get tavernAudioAddTitle => '背景音乐的链接'; + + @override + String get tavernAudioAddNote => '填音频文件的网址(mp3、ogg 等),加进这段对话的背景音乐列表,和卡放的放在一起。'; + + @override + String get tavernAudioAddConfirm => '加入'; + + @override + String get tavernAudioAddInvalid => '要以 http:// 或 https:// 开头'; + @override String get tavernStageState => '状态'; @@ -20554,6 +20608,16 @@ class L10nZh extends L10n { @override String get tavernSessionUsageNone => '还没有模型报告过用量'; + @override + String tavernSessionUsageCost(String usd) { + return '约 $usd,按 DeepSeek 官方价,分高峰和低谷时段算;法定节假日全天是低谷价,那几天这里会算高一点'; + } + + @override + String tavernSessionUsageUnpriced(int count) { + return '$count 条回复用的模型不在价目表里,没算进费用'; + } + @override String get tavernComposerNoKey => '填好 Key 就能开始'; @@ -20823,7 +20887,13 @@ class L10nZh extends L10n { String get tavernStThemeImport => '导入 ST 主题'; @override - String get tavernStThemeImportNote => '从 SillyTavern 导出的主题文件里取引号和斜体的颜色。'; + String get tavernStThemeImportNote => '从 SillyTavern 导出的主题文件里取引号和斜体的颜色,和它的自定义 CSS。'; + + @override + String get tavernStThemeCss => '用它的自定义 CSS'; + + @override + String get tavernStThemeCssSubtitle => '配色选「ST」时加进整页画布,和在 ST 里一样盖过默认样式。版面乱了就关掉,颜色照用。'; @override String get tavernStThemeRemove => '移除'; @@ -20835,7 +20905,7 @@ class L10nZh extends L10n { @override String tavernStThemeNoColors(String name) { - return '「$name」里没有能用的引号或斜体颜色'; + return '「$name」里没有能用的引号、斜体颜色或自定义 CSS'; } @override @@ -21012,6 +21082,11 @@ class L10nZh extends L10n { @override String get tavernGameHere => '这里有一个小游戏,现在还不能在这里玩'; + @override + String tavernCanvasReplied(String name) { + return '$name回复了'; + } + @override String get tavernRegexEdit => '编辑规则'; @@ -21112,11 +21187,182 @@ class L10nZh extends L10n { String get tavernCanvas => '整页画布(试用)'; @override - String get tavernCanvasSubtitle => '整段对话放进一张网页,按 SillyTavern 的结构排,卡写给 ST 的样式能跨消息生效。视觉小说不用它;卡里的脚本暂时还不跑。'; + String get tavernCanvasSubtitle => + '整段对话放进一张网页,按 SillyTavern 的结构排,卡写给 ST 的样式能跨消息生效。卡里的脚本会运行,不想让它跑就打开「网页里不运行卡的脚本」。视觉小说不用它。'; @override String get tavernCanvasReloaded => '画面重新加载了'; + @override + String tavernGroupSpeaking(String name) { + return '$name 在说'; + } + + @override + String tavernGroupThen(String names) { + return '接着是 $names'; + } + + @override + String get tavernGroupNoSpeakers => '这一轮没人接话。点上面的名字,让谁来说。'; + + @override + String get tavernGroupSpeakNext => '让大家接着说'; + + @override + String get tavernGroupNew => '新建群聊'; + + @override + String get tavernGroupMembers => '谁在里面'; + + @override + String get tavernGroupMembersNote => '按点选的顺序排;第一个是主持,故事共用的状态记在它的卡上。'; + + @override + String get tavernGroupHost => '主持'; + + @override + String get tavernGroupTitle => '群名'; + + @override + String get tavernGroupActivation => '怎么轮流说'; + + @override + String get tavernGroupNatural => '自然'; + + @override + String get tavernGroupListOrder => '按顺序'; + + @override + String get tavernGroupRandom => '随机'; + + @override + String get tavernGroupManual => '手动'; + + @override + String get tavernGroupDirector => '导演'; + + @override + String get tavernGroupNaturalNote => '像 SillyTavern:被点到名的先说,其余看各自的话多话少;每轮最多几个人说在群设置里改。'; + + @override + String get tavernGroupListOrderNote => '每轮按名单从头轮一遍(静音的跳过)。'; + + @override + String get tavernGroupRandomNote => '每轮随机挑一个这轮还没说过的人。'; + + @override + String get tavernGroupManualNote => '谁也不自己开口;点输入框上面的名字,让谁说谁说。'; + + @override + String get tavernGroupDirectorNote => '由导演按剧情点名下一位。'; + + @override + String get tavernGroupPickTwo => '至少选两个角色'; + + @override + String tavernGroupCreate(int count) { + return '建群($count 人)'; + } + + @override + String tavernGroupCreateFailed(String reason) { + return '没能建群:$reason'; + } + + @override + String get tavernGroupTag => '群'; + + @override + String tavernGroupMemberCount(int count) { + return '$count 人'; + } + + @override + String get tavernGroupSettings => '群聊设置'; + + @override + String get tavernGroupMaxSpeakers => '每轮最多几人说'; + + @override + String get tavernGroupSelfResponses => '可以接着自己说'; + + @override + String get tavernGroupSelfResponsesNote => '上一句是谁说的,这一轮还能先轮到他。'; + + @override + String tavernGroupHostNote(String name) { + return '主持是「$name」:故事共用的状态、导演和秘密记在它的卡上;它可以不在发言名单里。'; + } + + @override + String get tavernGroupMuted => '静音'; + + @override + String get tavernGroupMutedNote => '不会自己开口;点名时照样说。'; + + @override + String get tavernGroupTalkativeness => '话多话少'; + + @override + String get tavernGroupTalkativenessNote => '「自然」轮流时,越高越容易开口。'; + + @override + String get tavernGroupHelperModel => '用辅助模型'; + + @override + String get tavernGroupHelperModelNote => '用便宜快的模型说(DeepSeek 是 flash、不思考),适合配角。'; + + @override + String get tavernGroupMoveUp => '往前挪'; + + @override + String get tavernGroupMoveDown => '往后挪'; + + @override + String get tavernGroupRemove => '移出群聊'; + + @override + String get tavernGroupAdd => '加一个角色'; + + @override + String get tavernGroupNoOthers => '书架上的角色都在群里了'; + + @override + String get tavernGroupUnavailable => '角色卡不在了'; + + @override + String tavernGroupSaveFailed(String reason) { + return '没能保存:$reason'; + } + + @override + String get tavernGroupErrorNotGroup => '这已经不是群聊了'; + + @override + String get tavernGroupErrorCharacterNotFound => '这个角色的卡不在了'; + + @override + String get tavernGroupErrorMemberExists => '这个角色已经在群里了'; + + @override + String get tavernGroupErrorMemberNotFound => '这个角色已经不在群里了'; + + @override + String get tavernGroupErrorInvalidMembers => '成员不能重复,群里至少要有一个角色'; + + @override + String get tavernGroupErrorInvalidTalkativeness => '话多程度要在 0 到 1 之间'; + + @override + String get tavernGroupErrorInvalidLimit => '每轮至少要有一个人说话'; + + @override + String get tavernGroupErrorInvalidOrder => '排的顺序和群里的成员对不上,请重新打开再排'; + + @override + String get tavernGroupErrorHostInUse => '这个角色是一个群聊的群主,先删掉那个群聊'; + @override String get updateCheck => '检查更新'; diff --git a/app/lib/l10n/parts/tavern_en.arb b/app/lib/l10n/parts/tavern_en.arb index 6002a510bd..ff66826d98 100644 --- a/app/lib/l10n/parts/tavern_en.arb +++ b/app/lib/l10n/parts/tavern_en.arb @@ -634,6 +634,24 @@ "tavernSessionUsing": "In use", "tavernSessionLook": "Look", "tavernSessionMore": "More", + "tavernAudio": "Music and sound", + "tavernAudioBgm": "Music", + "tavernAudioAmbient": "Sound effects", + "tavernAudioNothing": "Not playing", + "tavernAudioPlay": "Play", + "tavernAudioPause": "Pause", + "tavernAudioMode": "Play mode", + "tavernAudioModeOne": "One", + "tavernAudioModeAll": "All", + "tavernAudioModeShuffle": "Shuffle", + "tavernAudioModeOnce": "Once", + "tavernAudioVolume": "Volume", + "tavernAudioAllow": "Let the card play", + "tavernAudioAdd": "Add background music", + "tavernAudioAddTitle": "Link to the music", + "tavernAudioAddNote": "The web address of an audio file (mp3, ogg…). It joins this chat's music list, with the card's own.", + "tavernAudioAddConfirm": "Add", + "tavernAudioAddInvalid": "Starts with http:// or https://", "tavernStageState": "State", "tavernStageDirector": "Director", "tavernStageTurn": "This turn", @@ -853,6 +871,10 @@ "tavernSessionUsageNote": "{count, plural, =1{1 reply on the current line} other{{count} replies on the current line}}, added up; replaced alternatives aren't counted", "@tavernSessionUsageNote": {"placeholders": {"count": {"type": "int"}}}, "tavernSessionUsageNone": "The model hasn't reported any usage yet", + "tavernSessionUsageCost": "About {usd} at DeepSeek's list prices, peak and off-peak hours apart; China's public holidays are off-peak all day, so those days come out a little high here", + "@tavernSessionUsageCost": {"placeholders": {"usd": {"type": "String"}}}, + "tavernSessionUsageUnpriced": "{count, plural, =1{1 reply used a model not on the price list and isn't in the cost} other{{count} replies used a model not on the price list and aren't in the cost}}", + "@tavernSessionUsageUnpriced": {"placeholders": {"count": {"type": "int"}}}, "tavernComposerNoKey": "Add a key to begin", "tavernWriteOwn": "Write your own character", "tavernWriteOwnSubtitle": "Start from a blank card and fill in the name, setting and greeting", @@ -946,11 +968,13 @@ "@tavernStTheme": {"placeholders": {"name": {"type": "String"}}}, "tavernStThemeNote": "With 「ST」 as the colours, quotes and *actions* take its colours; one too faint to read here is adjusted until it reads.", "tavernStThemeImport": "Import an ST theme", - "tavernStThemeImportNote": "Takes the quote and italics colours from a theme file exported from SillyTavern.", + "tavernStThemeImportNote": "Takes the quote and italics colours, and its custom CSS, from a theme file exported from SillyTavern.", + "tavernStThemeCss": "Use its custom CSS", + "tavernStThemeCssSubtitle": "With 「ST」 as the colours, it goes into the whole-story canvas and overrides the default look, as in ST. Turn it off if the page comes apart; the colours stay.", "tavernStThemeRemove": "Remove", "tavernStThemeImported": "Now using the colours of the ST theme “{name}”", "@tavernStThemeImported": {"placeholders": {"name": {"type": "String"}}}, - "tavernStThemeNoColors": "“{name}” has no quote or italics colour to use", + "tavernStThemeNoColors": "“{name}” has no quote or italics colour or custom CSS to use", "@tavernStThemeNoColors": {"placeholders": {"name": {"type": "String"}}}, "tavernPromptContent": "Content", "tavernPromptRole": "Spoken as", @@ -1012,6 +1036,8 @@ "tavernRollSuccess": "Success", "tavernRollFailure": "Failure", "tavernGameHere": "A mini-game goes here; it can't be played here yet", + "tavernCanvasReplied": "{name} replied", + "@tavernCanvasReplied": {"placeholders": {"name": {"type": "String"}}}, "tavernRegexEdit": "Edit rule", "tavernRegexFieldName": "Name", "tavernRegexFieldFindHelp": "SillyTavern's form: /pattern/flags, e.g. /(hi|bye)/gi", @@ -1045,6 +1071,65 @@ "tavernRegexTryDraft": "Try it as filled in", "tavernRegexDepthAny": "Any", "tavernCanvas": "Whole-page canvas (trial)", - "tavernCanvasSubtitle": "The whole chat in one web page laid out as SillyTavern does, so a card's ST styles reach across messages. Not for visual novel; card scripts don't run here yet.", - "tavernCanvasReloaded": "The page reloaded" + "tavernCanvasSubtitle": "The whole chat in one web page laid out as SillyTavern does, so a card's ST styles reach across messages. Card scripts run here; turn on \"No card scripts in those pages\" to stop them. Not for visual novel.", + "tavernCanvasReloaded": "The page reloaded", + "tavernGroupSpeaking": "{name} is speaking", + "@tavernGroupSpeaking": {"placeholders": {"name": {"type": "String"}}}, + "tavernGroupThen": "then {names}", + "@tavernGroupThen": {"placeholders": {"names": {"type": "String"}}}, + "tavernGroupNoSpeakers": "No one took this turn. Tap a name above to have them speak.", + "tavernGroupSpeakNext": "Let them go on", + "tavernGroupNew": "New group chat", + "tavernGroupMembers": "Who's in it", + "tavernGroupMembersNote": "In the order picked; the first hosts it, and the story's shared state lives on that card.", + "tavernGroupHost": "Host", + "tavernGroupTitle": "Name", + "tavernGroupActivation": "Taking turns", + "tavernGroupNatural": "Natural", + "tavernGroupListOrder": "In order", + "tavernGroupRandom": "Random", + "tavernGroupManual": "Manual", + "tavernGroupDirector": "Director", + "tavernGroupNaturalNote": "As SillyTavern does: whoever is named speaks first, the rest by how talkative each is; the most per turn is in the group's settings.", + "tavernGroupListOrderNote": "Each turn goes down the list from the top (the muted are skipped).", + "tavernGroupRandomNote": "Each turn picks one who hasn't spoken since your last line.", + "tavernGroupManualNote": "No one speaks on their own; tap a name above the box to have them speak.", + "tavernGroupDirectorNote": "The director picks who speaks next, as the story goes.", + "tavernGroupPickTwo": "Pick at least two", + "tavernGroupCreate": "Create ({count})", + "@tavernGroupCreate": {"placeholders": {"count": {"type": "int"}}}, + "tavernGroupCreateFailed": "Couldn't create the group: {reason}", + "@tavernGroupCreateFailed": {"placeholders": {"reason": {"type": "String"}}}, + "tavernGroupTag": "Group", + "tavernGroupMemberCount": "{count} members", + "@tavernGroupMemberCount": {"placeholders": {"count": {"type": "int"}}}, + "tavernGroupSettings": "Group settings", + "tavernGroupMaxSpeakers": "Most to speak each turn", + "tavernGroupSelfResponses": "May answer themselves", + "tavernGroupSelfResponsesNote": "Whoever spoke last may still go first this turn.", + "tavernGroupHostNote": "Hosted by {name}: the story's shared state, director and secrets live on that card; the host need not be among the speakers.", + "@tavernGroupHostNote": {"placeholders": {"name": {"type": "String"}}}, + "tavernGroupMuted": "Muted", + "tavernGroupMutedNote": "Won't speak on their own; still speaks when named.", + "tavernGroupTalkativeness": "Talkativeness", + "tavernGroupTalkativenessNote": "In natural turns, the higher, the likelier to speak.", + "tavernGroupHelperModel": "Helper model", + "tavernGroupHelperModelNote": "Speaks with the cheaper, faster model (DeepSeek flash, no thinking): for side characters.", + "tavernGroupMoveUp": "Move up", + "tavernGroupMoveDown": "Move down", + "tavernGroupRemove": "Remove from the group", + "tavernGroupAdd": "Add a character", + "tavernGroupNoOthers": "Everyone on the shelf is in it", + "tavernGroupUnavailable": "Card missing", + "tavernGroupSaveFailed": "Couldn't save: {reason}", + "tavernGroupErrorNotGroup": "This is no longer a group chat", + "tavernGroupErrorCharacterNotFound": "That character's card is gone", + "tavernGroupErrorMemberExists": "That character is already in the group", + "tavernGroupErrorMemberNotFound": "That character is no longer in the group", + "tavernGroupErrorInvalidMembers": "Members can't repeat, and a group needs at least one character", + "tavernGroupErrorInvalidTalkativeness": "Talkativeness goes from 0 to 1", + "tavernGroupErrorInvalidLimit": "At least one member speaks each round", + "tavernGroupErrorInvalidOrder": "That order doesn't match the members; open it again and reorder", + "tavernGroupErrorHostInUse": "This character hosts a group chat; delete that chat first", + "@tavernGroupSaveFailed": {"placeholders": {"reason": {"type": "String"}}} } diff --git a/app/lib/l10n/parts/tavern_zh.arb b/app/lib/l10n/parts/tavern_zh.arb index a83e12eb6b..bd7b6c5cf2 100644 --- a/app/lib/l10n/parts/tavern_zh.arb +++ b/app/lib/l10n/parts/tavern_zh.arb @@ -634,6 +634,24 @@ "tavernSessionUsing": "正在用", "tavernSessionLook": "外观", "tavernSessionMore": "更多", + "tavernAudio": "音乐和音效", + "tavernAudioBgm": "背景音乐", + "tavernAudioAmbient": "音效", + "tavernAudioNothing": "没在放", + "tavernAudioPlay": "播放", + "tavernAudioPause": "暂停", + "tavernAudioMode": "播放方式", + "tavernAudioModeOne": "单曲", + "tavernAudioModeAll": "列表", + "tavernAudioModeShuffle": "随机", + "tavernAudioModeOnce": "放一首", + "tavernAudioVolume": "音量", + "tavernAudioAllow": "让角色卡播放", + "tavernAudioAdd": "加一首背景音乐", + "tavernAudioAddTitle": "背景音乐的链接", + "tavernAudioAddNote": "填音频文件的网址(mp3、ogg 等),加进这段对话的背景音乐列表,和卡放的放在一起。", + "tavernAudioAddConfirm": "加入", + "tavernAudioAddInvalid": "要以 http:// 或 https:// 开头", "tavernStageState": "状态", "tavernStageDirector": "导演", "tavernStageTurn": "本回合", @@ -853,6 +871,10 @@ "tavernSessionUsageNote": "当前这条线上 {count} 条回复的合计;换掉的备选不算", "@tavernSessionUsageNote": {"placeholders": {"count": {"type": "int"}}}, "tavernSessionUsageNone": "还没有模型报告过用量", + "tavernSessionUsageCost": "约 {usd},按 DeepSeek 官方价,分高峰和低谷时段算;法定节假日全天是低谷价,那几天这里会算高一点", + "@tavernSessionUsageCost": {"placeholders": {"usd": {"type": "String"}}}, + "tavernSessionUsageUnpriced": "{count} 条回复用的模型不在价目表里,没算进费用", + "@tavernSessionUsageUnpriced": {"placeholders": {"count": {"type": "int"}}}, "tavernComposerNoKey": "填好 Key 就能开始", "tavernWriteOwn": "自己写一个角色", "tavernWriteOwnSubtitle": "从一张空白卡开始,名字、设定和开场白都自己填", @@ -946,11 +968,13 @@ "@tavernStTheme": {"placeholders": {"name": {"type": "String"}}}, "tavernStThemeNote": "配色选「ST」时,引号里的话和 *动作* 用它的颜色;太浅或太深看不清的,会调到看得清。", "tavernStThemeImport": "导入 ST 主题", - "tavernStThemeImportNote": "从 SillyTavern 导出的主题文件里取引号和斜体的颜色。", + "tavernStThemeImportNote": "从 SillyTavern 导出的主题文件里取引号和斜体的颜色,和它的自定义 CSS。", + "tavernStThemeCss": "用它的自定义 CSS", + "tavernStThemeCssSubtitle": "配色选「ST」时加进整页画布,和在 ST 里一样盖过默认样式。版面乱了就关掉,颜色照用。", "tavernStThemeRemove": "移除", "tavernStThemeImported": "换成了 ST 主题「{name}」的配色", "@tavernStThemeImported": {"placeholders": {"name": {"type": "String"}}}, - "tavernStThemeNoColors": "「{name}」里没有能用的引号或斜体颜色", + "tavernStThemeNoColors": "「{name}」里没有能用的引号、斜体颜色或自定义 CSS", "@tavernStThemeNoColors": {"placeholders": {"name": {"type": "String"}}}, "tavernPromptContent": "内容", "tavernPromptRole": "谁说", @@ -1012,6 +1036,8 @@ "tavernRollSuccess": "成功", "tavernRollFailure": "失败", "tavernGameHere": "这里有一个小游戏,现在还不能在这里玩", + "tavernCanvasReplied": "{name}回复了", + "@tavernCanvasReplied": {"placeholders": {"name": {"type": "String"}}}, "tavernRegexEdit": "编辑规则", "tavernRegexFieldName": "名字", "tavernRegexFieldFindHelp": "SillyTavern 的写法:/规则/标志,比如 /(早安|晚安)/gi", @@ -1045,6 +1071,65 @@ "tavernRegexTryDraft": "用现在填的试一下", "tavernRegexDepthAny": "不限", "tavernCanvas": "整页画布(试用)", - "tavernCanvasSubtitle": "整段对话放进一张网页,按 SillyTavern 的结构排,卡写给 ST 的样式能跨消息生效。视觉小说不用它;卡里的脚本暂时还不跑。", - "tavernCanvasReloaded": "画面重新加载了" + "tavernCanvasSubtitle": "整段对话放进一张网页,按 SillyTavern 的结构排,卡写给 ST 的样式能跨消息生效。卡里的脚本会运行,不想让它跑就打开「网页里不运行卡的脚本」。视觉小说不用它。", + "tavernCanvasReloaded": "画面重新加载了", + "tavernGroupSpeaking": "{name} 在说", + "@tavernGroupSpeaking": {"placeholders": {"name": {"type": "String"}}}, + "tavernGroupThen": "接着是 {names}", + "@tavernGroupThen": {"placeholders": {"names": {"type": "String"}}}, + "tavernGroupNoSpeakers": "这一轮没人接话。点上面的名字,让谁来说。", + "tavernGroupSpeakNext": "让大家接着说", + "tavernGroupNew": "新建群聊", + "tavernGroupMembers": "谁在里面", + "tavernGroupMembersNote": "按点选的顺序排;第一个是主持,故事共用的状态记在它的卡上。", + "tavernGroupHost": "主持", + "tavernGroupTitle": "群名", + "tavernGroupActivation": "怎么轮流说", + "tavernGroupNatural": "自然", + "tavernGroupListOrder": "按顺序", + "tavernGroupRandom": "随机", + "tavernGroupManual": "手动", + "tavernGroupDirector": "导演", + "tavernGroupNaturalNote": "像 SillyTavern:被点到名的先说,其余看各自的话多话少;每轮最多几个人说在群设置里改。", + "tavernGroupListOrderNote": "每轮按名单从头轮一遍(静音的跳过)。", + "tavernGroupRandomNote": "每轮随机挑一个这轮还没说过的人。", + "tavernGroupManualNote": "谁也不自己开口;点输入框上面的名字,让谁说谁说。", + "tavernGroupDirectorNote": "由导演按剧情点名下一位。", + "tavernGroupPickTwo": "至少选两个角色", + "tavernGroupCreate": "建群({count} 人)", + "@tavernGroupCreate": {"placeholders": {"count": {"type": "int"}}}, + "tavernGroupCreateFailed": "没能建群:{reason}", + "@tavernGroupCreateFailed": {"placeholders": {"reason": {"type": "String"}}}, + "tavernGroupTag": "群", + "tavernGroupMemberCount": "{count} 人", + "@tavernGroupMemberCount": {"placeholders": {"count": {"type": "int"}}}, + "tavernGroupSettings": "群聊设置", + "tavernGroupMaxSpeakers": "每轮最多几人说", + "tavernGroupSelfResponses": "可以接着自己说", + "tavernGroupSelfResponsesNote": "上一句是谁说的,这一轮还能先轮到他。", + "tavernGroupHostNote": "主持是「{name}」:故事共用的状态、导演和秘密记在它的卡上;它可以不在发言名单里。", + "@tavernGroupHostNote": {"placeholders": {"name": {"type": "String"}}}, + "tavernGroupMuted": "静音", + "tavernGroupMutedNote": "不会自己开口;点名时照样说。", + "tavernGroupTalkativeness": "话多话少", + "tavernGroupTalkativenessNote": "「自然」轮流时,越高越容易开口。", + "tavernGroupHelperModel": "用辅助模型", + "tavernGroupHelperModelNote": "用便宜快的模型说(DeepSeek 是 flash、不思考),适合配角。", + "tavernGroupMoveUp": "往前挪", + "tavernGroupMoveDown": "往后挪", + "tavernGroupRemove": "移出群聊", + "tavernGroupAdd": "加一个角色", + "tavernGroupNoOthers": "书架上的角色都在群里了", + "tavernGroupUnavailable": "角色卡不在了", + "tavernGroupSaveFailed": "没能保存:{reason}", + "tavernGroupErrorNotGroup": "这已经不是群聊了", + "tavernGroupErrorCharacterNotFound": "这个角色的卡不在了", + "tavernGroupErrorMemberExists": "这个角色已经在群里了", + "tavernGroupErrorMemberNotFound": "这个角色已经不在群里了", + "tavernGroupErrorInvalidMembers": "成员不能重复,群里至少要有一个角色", + "tavernGroupErrorInvalidTalkativeness": "话多程度要在 0 到 1 之间", + "tavernGroupErrorInvalidLimit": "每轮至少要有一个人说话", + "tavernGroupErrorInvalidOrder": "排的顺序和群里的成员对不上,请重新打开再排", + "tavernGroupErrorHostInUse": "这个角色是一个群聊的群主,先删掉那个群聊", + "@tavernGroupSaveFailed": {"placeholders": {"reason": {"type": "String"}}} } diff --git a/app/lib/tavern/deferred_tavern.dart b/app/lib/tavern/deferred_tavern.dart index 0b10c49da0..f34a4f9fec 100644 --- a/app/lib/tavern/deferred_tavern.dart +++ b/app/lib/tavern/deferred_tavern.dart @@ -129,6 +129,101 @@ class DeferredTavern implements Tavern { @override Stream send(String chatId, String text) => _stream((t) => t.send(chatId, text)); + // Group chats (engine P2 125): the lists are copied as they are when called, + // before waiting for the engine. + + @override + Future createGroupChat( + List characterIds, { + String? hostCharacterId, + String? title, + TavernGroupActivation activation = TavernGroupActivation.natural, + int maxSpeakersPerTurn = 3, + bool allowSelfResponses = false, + bool addGreetings = true, + }) { + final ids = List.of(characterIds); + return _ready.then( + (t) => t.createGroupChat( + ids, + hostCharacterId: hostCharacterId, + title: title, + activation: activation, + maxSpeakersPerTurn: maxSpeakersPerTurn, + allowSelfResponses: allowSelfResponses, + addGreetings: addGreetings, + ), + ); + } + + @override + Future groupChat(String chatId) async => (await _ready).groupChat(chatId); + + @override + Stream watchGroupChat(String chatId) => _stream((t) => t.watchGroupChat(chatId)); + + @override + Future addGroupMember( + String chatId, + String characterId, { + double? talkativeness, + bool muted = false, + TavernGroupModelRole? modelRole, + }) async => (await _ready).addGroupMember( + chatId, + characterId, + talkativeness: talkativeness, + muted: muted, + modelRole: modelRole, + ); + + @override + Future updateGroupMember( + String chatId, + String characterId, { + double? talkativeness, + bool? muted, + TavernGroupModelRole? modelRole, + }) async => (await _ready).updateGroupMember( + chatId, + characterId, + talkativeness: talkativeness, + muted: muted, + modelRole: modelRole, + ); + + @override + Future removeGroupMember(String chatId, String characterId) async => + (await _ready).removeGroupMember(chatId, characterId); + + @override + Future reorderGroupMembers(String chatId, List characterIds) { + final ids = List.of(characterIds); + return _ready.then((t) => t.reorderGroupMembers(chatId, ids)); + } + + @override + Future updateGroupChat( + String chatId, { + String? title, + TavernGroupActivation? activation, + int? maxSpeakersPerTurn, + bool? allowSelfResponses, + }) async => (await _ready).updateGroupChat( + chatId, + title: title, + activation: activation, + maxSpeakersPerTurn: maxSpeakersPerTurn, + allowSelfResponses: allowSelfResponses, + ); + + @override + Stream sendGroup(String chatId, String text, {String? speakerCharacterId}) => + _stream((t) => t.sendGroup(chatId, text, speakerCharacterId: speakerCharacterId)); + + @override + Stream resumeGroupTurn(String chatId) => _stream((t) => t.resumeGroupTurn(chatId)); + @override Stream regenerate(String chatId, String nodeId) => _stream((t) => t.regenerate(chatId, nodeId)); @@ -392,6 +487,11 @@ class DeferredTavern implements Tavern { Future> readDirectorEvents(String chatId, {int limit = 50, String? beforeEventId}) async => (await _ready).readDirectorEvents(chatId, limit: limit, beforeEventId: beforeEventId); + /// One bridge for as long as this tavern lives (P2 B1): calls go straight + /// to the engine's once it is open, and wait for it before. + @override + late final TavernScriptBridge scripts = _DeferredScripts(this); + /// Nothing to close when it never opened. @override Future close() async { @@ -404,3 +504,313 @@ class DeferredTavern implements Tavern { await tavern.close(); } } + +/// [DeferredTavern.scripts]. Once the engine is open each call goes straight +/// to its bridge, so the bridge copies what it is given at the call, as its +/// contract asks; before that, lists and the one raw JSON object are copied +/// here and the call waits for the engine. The latest host reaches the +/// engine when it opens. Until then nothing is claimed: no methods, no +/// scopes. +class _DeferredScripts implements TavernScriptBridge { + _DeferredScripts(this._tavern) { + unawaited( + _tavern._ready.then((t) { + if (_pending) t.scripts.attachHost(_host); + _pending = false; + }, onError: (Object _) {}), + ); + } + + final DeferredTavern _tavern; + TavernScriptHost? _host; + + /// A host was set while the engine was still opening: it gets it on open. + bool _pending = false; + + Future _call(Future Function(TavernScriptBridge b) call) { + final open = _tavern._open; + return open != null ? call(open.scripts) : _tavern._ready.then((t) => call(t.scripts)); + } + + @override + TavernScriptCapabilities get capabilities => + _tavern._open?.scripts.capabilities ?? + TavernScriptCapabilities(adapterVersion: '', methods: const {}, scopes: const {}, hostAttached: _host != null); + + @override + void attachHost(TavernScriptHost? host) { + _host = host; + final open = _tavern._open; + _pending = open == null; + open?.scripts.attachHost(host); + } + + @override + Future openContext(TavernScriptContextSpec spec) => _call((b) => b.openContext(spec)); + + @override + Future snapshot(String contextId) => _call((b) => b.snapshot(contextId)); + + @override + Future closeContext(String contextId) => _call((b) => b.closeContext(contextId)); + + @override + Future activateChat(String contextId) => _call((b) => b.activateChat(contextId)); + + @override + Stream watchEvents({String? chatId}) => + _tavern._stream((t) => t.scripts.watchEvents(chatId: chatId)); + + @override + Future getChatMessages( + String contextId, + TavernMessageRange range, { + TavernScriptRole? role, + TavernHiddenFilter hidden = TavernHiddenFilter.all, + bool includeSwipes = false, + }) => _call((b) => b.getChatMessages(contextId, range, role: role, hidden: hidden, includeSwipes: includeSwipes)); + + @override + Future setChatMessages( + String contextId, + List patches, { + TavernScriptWriteOptions options = const TavernScriptWriteOptions(), + }) { + final held = List.of(patches); + return _call((b) => b.setChatMessages(contextId, held, options: options)); + } + + @override + Future createChatMessages( + String contextId, + List messages, { + int? insertBefore, + TavernScriptWriteOptions options = const TavernScriptWriteOptions(), + }) { + final held = List.of(messages); + return _call((b) => b.createChatMessages(contextId, held, insertBefore: insertBefore, options: options)); + } + + @override + Future deleteChatMessages( + String contextId, + List messageIds, { + TavernScriptWriteOptions options = const TavernScriptWriteOptions(), + }) { + final held = List.of(messageIds); + return _call((b) => b.deleteChatMessages(contextId, held, options: options)); + } + + @override + Future rotateChatMessages( + String contextId, + int begin, + int middle, + int end, { + TavernScriptWriteOptions options = const TavernScriptWriteOptions(), + }) => _call((b) => b.rotateChatMessages(contextId, begin, middle, end, options: options)); + + @override + Future selectSwipe( + String contextId, + TavernScriptMessageRef message, + int swipeId, { + TavernScriptWriteOptions options = const TavernScriptWriteOptions(), + }) => _call((b) => b.selectSwipe(contextId, message, swipeId, options: options)); + + @override + Future getVariables(String contextId, TavernVariableTarget target) => + _call((b) => b.getVariables(contextId, target)); + + @override + Future replaceVariables( + String contextId, + TavernVariableTarget target, + TavernJsonObject value, { + String? expectedVersion, + }) { + if (_tavern._open case final open?) { + return open.scripts.replaceVariables(contextId, target, value, expectedVersion: expectedVersion); + } + final TavernJsonObject held; + try { + held = _held(value, 'replaceVariables') as TavernJsonObject; + } on TavernScriptException catch (e) { + return Future.error(e); + } + return _call((b) => b.replaceVariables(contextId, target, held, expectedVersion: expectedVersion)); + } + + @override + Future updateVariablesWith( + String contextId, + TavernVariableTarget target, + FutureOr Function(TavernJsonObject value) updater, + ) => _call((b) => b.updateVariablesWith(contextId, target, updater)); + + @override + Future mutateVariables( + String contextId, + TavernVariableTarget target, + List changes, { + String? expectedVersion, + }) { + final held = List.of(changes); + return _call((b) => b.mutateVariables(contextId, target, held, expectedVersion: expectedVersion)); + } + + // B2: background generation, regex and injections. + + @override + Future generate(String contextId, TavernScriptGenerateRequest request) => + _call((b) => b.generate(contextId, request)); + + @override + Future generateRaw(String contextId, TavernScriptRawRequest request) => + _call((b) => b.generateRaw(contextId, request)); + + /// Nothing can be generating before the engine is open. + @override + bool stopGenerationById(String generationId) => _tavern._open?.scripts.stopGenerationById(generationId) ?? false; + + @override + Future> getTavernRegexes(String contextId, TavernScriptRegexQuery query) => + _call((b) => b.getTavernRegexes(contextId, query)); + + @override + Future formatAsTavernRegexedString( + String contextId, + String text, { + required int source, + required RegexPhase destination, + num? depth, + String? characterId, + }) => _call( + (b) => b.formatAsTavernRegexedString( + contextId, + text, + source: source, + destination: destination, + depth: depth, + characterId: characterId, + ), + ); + + @override + Future injectPrompts( + String contextId, + List prompts, { + bool once = false, + }) { + final held = List.of(prompts); + return _call((b) => b.injectPrompts(contextId, held, once: once)); + } + + @override + Future uninjectPrompts(String contextId, List ids) { + final held = List.of(ids); + return _call((b) => b.uninjectPrompts(contextId, held)); + } + + @override + Future releaseInjectionLease(String leaseId) => _call((b) => b.releaseInjectionLease(leaseId)); + + @override + Future> listInjections(String contextId) => _call((b) => b.listInjections(contextId)); + + // B3: Slash, macros, a character's raw data and the books in play. What + // the caller hands over is taken as it is now, not once the engine opens. + + @override + Future triggerSlash( + String contextId, + String source, { + String? executionId, + String pipe = '', + Map arguments = const {}, + }) { + if (_tavern._open case final open?) { + return open.scripts.triggerSlash(contextId, source, executionId: executionId, pipe: pipe, arguments: arguments); + } + final held = Map.of(arguments); + return _call((b) => b.triggerSlash(contextId, source, executionId: executionId, pipe: pipe, arguments: held)); + } + + /// Nothing can be running before the engine is open. + @override + bool stopScript(String executionId) => _tavern._open?.scripts.stopScript(executionId) ?? false; + + @override + Future substituteMacros(String contextId, String text) => _call((b) => b.substituteMacros(contextId, text)); + + @override + Future replaceCharacterData( + String contextId, + String characterId, + TavernJsonObject data, { + String? expectedVersion, + }) { + if (_tavern._open case final open?) { + return open.scripts.replaceCharacterData(contextId, characterId, data, expectedVersion: expectedVersion); + } + final TavernJsonObject held; + try { + held = _held(data, 'replaceCharacterData') as TavernJsonObject; + } on TavernScriptException catch (e) { + return Future.error(e); + } + return _call((b) => b.replaceCharacterData(contextId, characterId, held, expectedVersion: expectedVersion)); + } + + @override + Future> getContextBooks(String contextId) => _call((b) => b.getContextBooks(contextId)); + + @override + Future readBook(String contextId, TavernScriptBookRef ref) => + _call((b) => b.readBook(contextId, ref)); + + @override + Future replaceBook( + String contextId, + TavernScriptBookRef ref, + TavernJsonObject book, { + String? expectedVersion, + }) { + if (_tavern._open case final open?) { + return open.scripts.replaceBook(contextId, ref, book, expectedVersion: expectedVersion); + } + final TavernJsonObject held; + try { + held = _held(book, 'replaceBook') as TavernJsonObject; + } on TavernScriptException catch (e) { + return Future.error(e); + } + return _call((b) => b.replaceBook(contextId, ref, held, expectedVersion: expectedVersion)); + } +} + +/// [value] as it is now: maps and lists copied all the way down, the rest +/// kept for the engine to judge. A cycle is not JSON. +Object? _held(Object? value, String operation, [Set? seen]) { + if (value is! Map && value is! List) return value; + final active = seen ?? Set.identity(); + if (!active.add(value!)) { + throw TavernScriptException( + code: TavernScriptErrorCode.invalidJson, + operation: operation, + message: 'The value contains a cycle.', + ); + } + try { + return switch (value) { + Map() => { + for (final MapEntry(:key, value: v) in value.entries) key: _held(v, operation, active), + }, + Map() => {for (final MapEntry(:key, value: v) in value.entries) key: _held(v, operation, active)}, + List() => [for (final v in value) _held(v, operation, active)], + _ => value, + }; + } finally { + active.remove(value); + } +} diff --git a/app/lib/tavern/tavern_canvas.dart b/app/lib/tavern/tavern_canvas.dart index 16ec88da68..94dcf339b6 100644 --- a/app/lib/tavern/tavern_canvas.dart +++ b/app/lib/tavern/tavern_canvas.dart @@ -4,13 +4,16 @@ // card's CSS that reaches across messages (#chat .mes …) takes, and ST's own // colour variables (--SmartThemeQuoteColor …) carry the story's colours. // Messages arrive as JSON through our script; the page itself is loaded once. -// Card scripts don't run yet: they come with the helper bridge. Component +// A card's scripts run in it, with 酒馆助手's helpers (variables, messages, +// events, floors, toastr) answered through dsh.call. Component // tags (…, roleplay-ui §5) are Web Components here, with parts // and --dsh-* variables a card's CSS may restyle. import 'dart:convert'; import 'dart:ui' show Color; +import 'package:dsh_tavern/dsh_tavern.dart' show PersonaSelection, TavernPersona; + /// One message on the canvas. final class TavernCanvasEntry { const TavernCanvasEntry({ @@ -55,10 +58,14 @@ final class TavernCanvasLabels { this.success = 'Success', this.failure = 'Failure', this.game = "A mini-game goes here; it can't be played here yet", + this.replied = '{name} replied', }); final String letter, letterFrom, taken, clue, phone, phoneFrom, roll, rollCheck, success, failure, game; + /// Said to a screen reader when a reply that was being written is in. + final String replied; + Map toJson() => { 'letter': letter, 'letterFrom': letterFrom, @@ -71,10 +78,16 @@ final class TavernCanvasLabels { 'success': success, 'failure': failure, 'game': game, + 'replied': replied, }; } +/// [value] as JSON written inside our "']); + expect(withLibs, contains('')); + expect(withLibs, contains(r'var s="<\/script>"')); + expect(withLibs.indexOf('window.__lib=1'), lessThan(withLibs.indexOf('window.dsh={'))); + expect(page(noScripts: true, libraries: ['window.__lib=1']), isNot(contains('window.__lib=1'))); + // 酒馆助手's page API, from the page's own copy (checked in Edge). + for (final name in ['getVariables', 'replaceVariables', 'getChatMessages', 'eventOn', 'tavern_events']) { + expect(open, contains('if(!window.$name)window.$name=')); + } + for (final name in [ + 'setChatMessages', + 'createChatMessages', + 'deleteChatMessages', + 'rotateChatMessages', + 'updateVariablesWith', + 'insertOrAssignVariables', + 'insertVariables', + 'deleteVariable', + ]) { + expect(open, contains("['$name',$name]")); + } expect(open, contains('--SmartThemeQuoteColor:rgba(170,85,0,1.000)')); expect(open, contains('--SmartThemeEmColor:rgba(85,102,119,1.000)')); expect(open, contains('font:var(--mainFontSize)/1.65')); @@ -114,8 +227,122 @@ void main() { } expect(open, contains('"letterFrom":"{from}的信"')); expect(open, contains('"taken":"获得"')); + // To a screen reader (roleplay-ui §12; the tree itself was read in + // Edge): each message an article named by who speaks; a component a + // group named by its head, the head not said twice; the reply being + // written busy, and only its arrival said, in a live region. + expect(open, contains("m.setAttribute('role','article');")); + expect(open, contains("m.setAttribute('aria-label',nameText.textContent);")); + expect(open, contains('
')); + expect(open, contains("stream.setAttribute('aria-busy','true')")); + expect(open, contains("say(fill(L.replied,'name',last.getAttribute('aria-label')||''))")); + expect(open, contains('"replied":"{name} replied"')); + expect(open, contains("if(!this.hasAttribute('role'))this.setAttribute('role',a[0]);")); + expect('