>>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 jsonEncode(value).replaceAll('<', r'\u003c');
+
/// [entries] as our script's render() takes them.
+
String tavernCanvasJson(List entries) => jsonEncode([for (final e in entries) e.toJson()]);
String _escape(String text) =>
@@ -167,11 +180,61 @@ String tavernStoryHtml(String text) {
String _css(Color c) =>
'rgba(${(c.r * 255).round()},${(c.g * 255).round()},${(c.b * 255).round()},${c.a.toStringAsFixed(3)})';
+/// How many of the latest messages the canvas always lays out; older ones
+/// are skipped while off screen (CSS content-visibility).
+const tavernCanvasNear = 20;
+
+/// The user's personas as the page is told them (dsh.setPersonas): each one's
+/// id, name, description and its position (ST's own numbers); which this
+/// chat uses, and which is the default.
+Map tavernCanvasPersonas(List personas, PersonaSelection? selection) => {
+ 'list': [
+ for (final p in personas)
+ {'id': p.id, 'name': p.name, 'description': p.description, 'position': p.descriptionPosition},
+ ],
+ 'current': selection?.persona?.id,
+ 'default': selection?.defaultPersonaId,
+};
+
+/// The most of a card's toastr notice shown, in characters: its title, its
+/// text.
+const tavernToastTitleMax = 60;
+const tavernToastMax = 300;
+
+/// What a card script's toastr call shows (the page posts {type:'toast',
+/// level, title, message}, already plain text): the title over the text, each
+/// cut when long, up longer the more there is to read and longer still for a
+/// warning or an error. Null when there is nothing to show.
+({String title, String message, Duration duration})? tavernToast(Map data) {
+ String field(String key, int max) {
+ final text = switch (data[key]) {
+ final String s => s.trim(),
+ _ => '',
+ };
+ final runes = text.runes.toList();
+ return runes.length > max ? '${String.fromCharCodes(runes.take(max))}…' : text;
+ }
+
+ final title = field('title', tavernToastTitleMax);
+ final message = field('message', tavernToastMax);
+ if (title.isEmpty && message.isEmpty) return null;
+ final serious = data['level'] == 'warning' || data['level'] == 'error';
+ final length = title.runes.length + message.runes.length;
+ final ms = (1500 + length * 60).clamp(serious ? 4000 : 2000, serious ? 8000 : 6000);
+ return (title: title, message: message, duration: Duration(milliseconds: ms));
+}
+
/// The page the canvas loads once: ST's structure and colour variables, the
/// story's size and colours, and our script (allowed by [nonce]) that draws
/// what render()/stream() hand it and reports a long press on a message to
/// the `DshTavern` channel. [noScripts] and [offline] are 酒馆设置 › 安全's
-/// choices, as for islands.
+/// choices, as for islands. [libraries] are put in first, for card scripts.
+/// [themeCss] (an ST theme's custom CSS) comes after our styles, so it wins
+/// where both say something, as it does in ST. [cardScripts] ({id, name,
+/// content}, tavern_card_scripts.dart) are the card's own scripts, each run
+/// in a hidden frame once the story is drawn; not when scripts do not run.
+/// [card] (its fields) and [characterId] are the chat's character, for the
+/// scripts that ask about it; none for a group.
String tavernCanvasDocument({
required Color text,
required Color accent,
@@ -186,7 +249,22 @@ String tavernCanvasDocument({
TavernCanvasLabels labels = const TavernCanvasLabels(),
bool noScripts = false,
bool offline = false,
+ List libraries = const [],
+ String themeCss = '',
+ List> cardScripts = const [],
+ Map? card,
+ String? characterId,
}) {
+ // The card and its scripts go in as JSON (see [_inScript]).
+ final cardJson = noScripts || card == null ? 'null' : _inScript(card);
+ final cardIdJson = noScripts ? 'null' : _inScript(characterId);
+ final cardScriptsJson = noScripts ? '[]' : _inScript(cardScripts);
+ // The libraries card scripts expect on the page (jQuery, lodash), ours and
+ // so under the page's nonce; not there when card scripts do not run.
+ final preload = noScripts
+ ? ''
+ : [for (final code in libraries) '']
+ .join('\n');
final policy = noScripts || offline
? [
if (offline) "default-src 'none'",
@@ -194,7 +272,9 @@ String tavernCanvasDocument({
if (offline) 'media-src data: blob:',
if (offline) 'font-src data:',
if (offline) "style-src 'unsafe-inline' data:",
- if (noScripts) "script-src 'nonce-$nonce'" else "script-src 'unsafe-inline' 'unsafe-eval' 'nonce-$nonce'",
+ // With scripts on, no nonce: next to one, 'unsafe-inline' counts for
+ // nothing and a card's own scripts and handlers would be refused.
+ if (noScripts) "script-src 'nonce-$nonce'" else "script-src 'unsafe-inline' 'unsafe-eval'",
if (noScripts) "script-src-attr 'none'",
].join('; ')
: null;
@@ -213,6 +293,7 @@ html,body{margin:0;padding:0;background:transparent;}
body{color:var(--dsh-text);font:var(--mainFontSize)/$lineHeight system-ui,-apple-system,"Noto Sans CJK SC",sans-serif;overflow-wrap:anywhere;-webkit-text-size-adjust:100%;}
#chat{display:flex;flex-direction:column;gap:14px;padding:12px 16px 24px;}
.mes{display:flex;flex-direction:column;}
+.mes.far{content-visibility:auto;contain-intrinsic-size:auto 160px;}
.mes .ch_name{font-size:.82em;font-weight:600;color:var(--dsh-accent);margin-bottom:2px;}
.mes[is_user="true"] .ch_name{color:var(--dsh-muted);}
.mes[is_user="true"] .mes_text{background:var(--dsh-user);border-radius:16px;padding:8px 12px;align-self:flex-end;max-width:85%;box-sizing:border-box;}
@@ -227,15 +308,51 @@ body{color:var(--dsh-text);font:var(--mainFontSize)/$lineHeight system-ui,-apple
.mes_text a{color:var(--dsh-accent);}
.mes_text img,.mes_text video,.mes_text svg{max-width:100%;height:auto;}
.mes_text pre{white-space:pre-wrap;}
-.mes[data-streaming] .mes_text>:last-child::after,.mes[data-streaming] .mes_text:empty::after{content:"\\25CF";color:var(--dsh-muted);margin-left:3px;font-size:.6em;vertical-align:middle;}
-
-
+.mes[data-streaming] .mes_text>:last-child::after,.mes[data-streaming] .mes_text:empty::after{content:"\\25CF" / "";color:var(--dsh-muted);margin-left:3px;font-size:.6em;vertical-align:middle;}
+#dsh-live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;}
+
+${themeCss.trim().isEmpty ? '' : ''}
+
+
+$preload