Merge branch 'staging' into small-bookmark-updates

This commit is contained in:
Wolfsblvt 2024-09-10 19:05:05 +02:00
commit 894b95679c
32 changed files with 1624 additions and 594 deletions

276
package-lock.json generated
View File

@ -1461,9 +1461,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.2", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
@ -1474,7 +1474,7 @@
"http-errors": "2.0.0", "http-errors": "2.0.0",
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.13.0",
"raw-body": "2.5.2", "raw-body": "2.5.2",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
@ -1505,6 +1505,21 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/body-parser/node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/boolbase": { "node_modules/boolbase": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@ -1618,13 +1633,19 @@
} }
}, },
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.2", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.1", "es-define-property": "^1.0.0",
"get-intrinsic": "^1.0.2" "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -2269,6 +2290,23 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": { "node_modules/define-lazy-prop": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@ -2445,6 +2483,27 @@
"url": "https://github.com/fb55/entities?sponsor=1" "url": "https://github.com/fb55/entities?sponsor=1"
} }
}, },
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
@ -2702,36 +2761,37 @@
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.19.2", "version": "4.20.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==",
"license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.2", "body-parser": "1.20.3",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.6.0", "cookie": "0.6.0",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"etag": "~1.8.1", "etag": "~1.8.1",
"finalhandler": "1.2.0", "finalhandler": "1.2.0",
"fresh": "0.5.2", "fresh": "0.5.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"merge-descriptors": "1.0.1", "merge-descriptors": "1.0.3",
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7", "path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.11.0", "qs": "6.11.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "0.18.0", "send": "0.19.0",
"serve-static": "1.15.0", "serve-static": "1.16.0",
"setprototypeof": "1.2.0", "setprototypeof": "1.2.0",
"statuses": "2.0.1", "statuses": "2.0.1",
"type-is": "~1.6.18", "type-is": "~1.6.18",
@ -2750,6 +2810,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/express/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/safe-buffer": { "node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -3028,15 +3097,19 @@
} }
}, },
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.2.1", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.1", "es-errors": "^1.3.0",
"has": "^1.0.3", "function-bind": "^1.1.2",
"has-proto": "^1.0.1", "has-proto": "^1.0.1",
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -3120,6 +3193,18 @@
"integrity": "sha512-KTLodkyGBWMK9IW6QIeJ2zCuju4Z0CLpbkADKo+yLhbSTD4l+CXXpQ/xaynGVAzeBezzJG6qn8MLeqOq3SmW0A==", "integrity": "sha512-KTLodkyGBWMK9IW6QIeJ2zCuju4Z0CLpbkADKo+yLhbSTD4l+CXXpQ/xaynGVAzeBezzJG6qn8MLeqOq3SmW0A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/got": { "node_modules/got": {
"version": "11.8.6", "version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
@ -3169,18 +3254,6 @@
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -3191,10 +3264,22 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": { "node_modules/has-proto": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -3215,6 +3300,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/he": { "node_modules/he": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@ -3804,10 +3901,13 @@
} }
}, },
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT" "license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}, },
"node_modules/methods": { "node_modules/methods": {
"version": "1.1.2", "version": "1.1.2",
@ -4042,10 +4142,13 @@
} }
}, },
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.12.3", "version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
"license": "MIT", "license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@ -4346,9 +4449,9 @@
} }
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.7", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/peek-readable": { "node_modules/peek-readable": {
@ -4859,9 +4962,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/send": { "node_modules/send": {
"version": "0.18.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
@ -4889,9 +4992,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.15.0", "version": "1.16.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"encodeurl": "~1.0.2", "encodeurl": "~1.0.2",
@ -4903,6 +5006,53 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/serve-static/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/serve-static/node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -4931,14 +5081,18 @@
} }
}, },
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.0.4", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind": "^1.0.0", "call-bind": "^1.0.7",
"get-intrinsic": "^1.0.2", "es-errors": "^1.3.0",
"object-inspect": "^1.9.0" "get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"

View File

@ -15,8 +15,13 @@
z-index: 40000; z-index: 40000;
} }
.select2-selection__clear { .select2-container .select2-selection .select2-selection__clear {
color: var(--SmartThemeBodyColor); color: var(--SmartThemeBodyColor);
font-size: 24px;
padding: 0;
position: absolute;
right: 5px;
top: 0;
} }
.select2-container .select2-search__field { .select2-container .select2-search__field {

View File

@ -65,7 +65,7 @@
<!-- background selection menu --> <!-- background selection menu -->
<div id="ai-config-button" class="drawer"> <div id="ai-config-button" class="drawer">
<div class="drawer-toggle drawer-header"> <div class="drawer-toggle drawer-header">
<div id="leftNavDrawerIcon" class="drawer-icon fa-solid fa-sliders closedIcon" title="AI Response Configuration" data-i18n="[title]AI Response Configuration"></div> <div id="leftNavDrawerIcon" class="drawer-icon fa-solid fa-sliders fa-fw closedIcon" title="AI Response Configuration" data-i18n="[title]AI Response Configuration"></div>
</div> </div>
<div id="left-nav-panel" class="drawer-content fillLeft closedDrawer"> <div id="left-nav-panel" class="drawer-content fillLeft closedDrawer">
<div id="left-nav-panelheader" class="fa-solid fa-grip drag-grabber"></div> <div id="left-nav-panelheader" class="fa-solid fa-grip drag-grabber"></div>
@ -1898,18 +1898,18 @@
</div> </div>
<div id="sys-settings-button" class="drawer"> <div id="sys-settings-button" class="drawer">
<div class="drawer-toggle drawer-header"> <div class="drawer-toggle drawer-header">
<div id="API-status-top" class="drawer-icon fa-solid fa-plug-circle-exclamation closedIcon" title="API Connections" data-i18n="[title]API Connections;[no_connection_text]api_no_connection" no_connection_text="No connection..."></div> <div id="API-status-top" class="drawer-icon fa-solid fa-plug-circle-exclamation fa-fw closedIcon" title="API Connections" data-i18n="[title]API Connections;[no_connection_text]api_no_connection" no_connection_text="No connection..."></div>
</div> </div>
<div id="rm_api_block" class="drawer-content closedDrawer"> <div id="rm_api_block" class="drawer-content closedDrawer">
<h3 class="margin0" id="title_api">API</h3> <h3 class="margin0" id="title_api">API</h3>
<div class="flex-container flexFlowColumn"> <div class="flex-container flexFlowColumn">
<div id="main-API-selector-block"> <div id="main-API-selector-block">
<select id="main_api"> <select id="main_api">
<option value="textgenerationwebui"><span data-i18n="Text Completion">Text Completion</span></option> <option value="textgenerationwebui" data-i18n="Text Completion">Text Completion</option>
<option value="openai"><span data-i18n="Chat Completion">Chat Completion</span></option> <option value="openai" data-i18n="Chat Completion">Chat Completion</option>
<option value="novel"><span data-i18n="NovelAI">NovelAI</span></option> <option value="novel" data-i18n="NovelAI">NovelAI</option>
<option value="koboldhorde"><span data-i18n="KoboldAI Horde">KoboldAI Horde</span></option> <option value="koboldhorde" data-i18n="KoboldAI Horde">KoboldAI Horde</option>
<option value="kobold"><span data-i18n="KoboldAI">KoboldAI Classic</span></option> <option value="kobold" data-i18n="KoboldAI">KoboldAI Classic</option>
</select> </select>
</div> </div>
<div id="kobold_horde" style="position: relative;"> <!-- shows the kobold settings --> <div id="kobold_horde" style="position: relative;"> <!-- shows the kobold settings -->
@ -2071,7 +2071,7 @@
<div> <div>
<h4 data-i18n="TogetherAI Model">TogetherAI Model</h4> <h4 data-i18n="TogetherAI Model">TogetherAI Model</h4>
<select id="model_togetherai_select"> <select id="model_togetherai_select">
<option data-i18n="-- Connect to the API --"> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2095,7 +2095,7 @@
<div> <div>
<h4 data-i18n="OpenRouter Model">OpenRouter Model</h4> <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>
<select id="openrouter_model"> <select id="openrouter_model">
<option data-i18n="-- Connect to the API --"> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2122,7 +2122,7 @@
<div> <div>
<h4 data-i18n="InfermaticAI Model">InfermaticAI Model</h4> <h4 data-i18n="InfermaticAI Model">InfermaticAI Model</h4>
<select id="model_infermaticai_select"> <select id="model_infermaticai_select">
<option> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2145,7 +2145,7 @@
<div> <div>
<h4 data-i18n="DreamGen Model">DreamGen Model</h4> <h4 data-i18n="DreamGen Model">DreamGen Model</h4>
<select id="model_dreamgen_select"> <select id="model_dreamgen_select">
<option> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2171,7 +2171,7 @@
<div class="flex1"> <div class="flex1">
<h4 data-i18n="Mancer Model">Mancer Model</h4> <h4 data-i18n="Mancer Model">Mancer Model</h4>
<select id="mancer_model"> <select id="mancer_model">
<option data-i18n="-- Connect to the API --"> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2221,7 +2221,7 @@
For privacy reasons, your API key will be hidden after you reload the page. For privacy reasons, your API key will be hidden after you reload the page.
</div> </div>
<select id="featherless_model"> <select id="featherless_model">
<option data-i18n="-- Connect to the API --"> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2249,7 +2249,7 @@
<div> <div>
<h4 data-i18n="vLLM Model">vLLM Model</h4> <h4 data-i18n="vLLM Model">vLLM Model</h4>
<select id="vllm_model"> <select id="vllm_model">
<option data-i18n="-- Connect to the API --"> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2295,7 +2295,7 @@
<div> <div>
<h4 data-i18n="Aphrodite Model">Aphrodite Model</h4> <h4 data-i18n="Aphrodite Model">Aphrodite Model</h4>
<select id="aphrodite_model"> <select id="aphrodite_model">
<option data-i18n="-- Connect to the API --"> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2335,11 +2335,10 @@
</div> </div>
<div class="flex1"> <div class="flex1">
<h4> <h4>
<span data-i18n="Ollama Model">Ollama Model <span data-i18n="Ollama Model">Ollama Model</span>
</h4>
</h4> </h4>
<select id="ollama_model"> <select id="ollama_model">
<option data-i18n="-- Connect to the API --"> <option value="" data-i18n="-- Connect to the API --">
-- Connect to the API -- -- Connect to the API --
</option> </option>
</select> </select>
@ -2373,7 +2372,27 @@
<h4> <h4>
<span data-i18n="Tabby Model">Tabby Model</span> <span data-i18n="Tabby Model">Tabby Model</span>
</h4> </h4>
</h4> <b>
EXPERIMENTAL FEATURE. USE AT YOUR OWN RISK. DON'T ASK FOR SUPPORT ON THIS.
</b>
<select id="tabby_model">
<option value="" data-i18n="-- Connect to the API --">
-- Connect to the API --
</option>
</select>
<div class="marginTopBot5">
<small>
<i class="fa-solid fa-lightbulb"></i>
&nbsp;
<code>inline_model_loading: True</code>
<span data-i18n="must be set in Tabby's config.yml to switch models.">
must be set in Tabby's config.yml to switch models.
</span>
<b data-i18n="Use an admin API key.">
Use an admin API key.
</b>
</small>
</div>
<div id="tabby_download_model" class="menu_button menu_button_icon"> <div id="tabby_download_model" class="menu_button menu_button_icon">
<i class="fa-solid fa-download"></i> <i class="fa-solid fa-download"></i>
<span data-i18n="Download">Download</span> <span data-i18n="Download">Download</span>
@ -2685,7 +2704,7 @@
<div> <div>
<h4 data-i18n="OpenRouter Model">OpenRouter Model</h4> <h4 data-i18n="OpenRouter Model">OpenRouter Model</h4>
<select id="model_openrouter_select"> <select id="model_openrouter_select">
<option data-i18n="-- Connect to the API --">-- Connect to the API --</option> <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
</select> </select>
</div> </div>
<label for="openrouter_use_fallback" class="checkbox_label marginTopBot5" data-i18n="[title]Allow fallback routes Description" title="Automatically chooses an alternative model if the chosen model can't serve your request."> <label for="openrouter_use_fallback" class="checkbox_label marginTopBot5" data-i18n="[title]Allow fallback routes Description" title="Automatically chooses an alternative model if the chosen model can't serve your request.">
@ -3060,7 +3079,7 @@
</div> </div>
<div id="advanced-formatting-button" class="drawer"> <div id="advanced-formatting-button" class="drawer">
<div class="drawer-toggle"> <div class="drawer-toggle">
<div class="drawer-icon fa-solid fa-font closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div> <div class="drawer-icon fa-solid fa-font fa-fw closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div>
</div> </div>
<div id="AdvancedFormatting" class="drawer-content"> <div id="AdvancedFormatting" class="drawer-content">
<h3 class="margin0" data-i18n="Advanced Formatting"> <h3 class="margin0" data-i18n="Advanced Formatting">
@ -3240,7 +3259,7 @@
</small> </small>
</label> </label>
<div> <div>
<input type="text" id="instruct_activation_regex" class="text_pole textarea_compact" placeholder="e.g. /llama(-)?[3|3.1]/i"></input> <input type="text" id="instruct_activation_regex" class="text_pole textarea_compact" placeholder="e.g. /llama(-)?[3|3.1]/i">
</div> </div>
<div> <div>
<label for="instruct_system_prompt" class="flex-container"> <label for="instruct_system_prompt" class="flex-container">
@ -3456,7 +3475,7 @@
</div> </div>
<div id="WI-SP-button" class="drawer"> <div id="WI-SP-button" class="drawer">
<div class="drawer-toggle drawer-header"> <div class="drawer-toggle drawer-header">
<div id="WIDrawerIcon" class="drawer-icon fa-solid fa-book-atlas closedIcon " title="World Info" data-i18n="[title]World Info"></div> <div id="WIDrawerIcon" class="drawer-icon fa-solid fa-book-atlas fa-fw closedIcon " title="World Info" data-i18n="[title]World Info"></div>
</div> </div>
<div id="WorldInfo" class="drawer-content closedDrawer"> <div id="WorldInfo" class="drawer-content closedDrawer">
<div id="WorldInfoheader" class="fa-solid fa-grip drag-grabber"></div> <div id="WorldInfoheader" class="fa-solid fa-grip drag-grabber"></div>
@ -3657,7 +3676,7 @@
</div> </div>
<div id="user-settings-button" class="drawer"> <div id="user-settings-button" class="drawer">
<div class="drawer-toggle"> <div class="drawer-toggle">
<div class="drawer-icon fa-solid fa-user-cog closedIcon" title="User Settings" data-i18n="[title]User Settings"></div> <div class="drawer-icon fa-solid fa-user-cog fa-fw closedIcon" title="User Settings" data-i18n="[title]User Settings"></div>
</div> </div>
<div id="user-settings-block" class="drawer-content closedDrawer"> <div id="user-settings-block" class="drawer-content closedDrawer">
<div class="flex-container flexFlowColumn"> <div class="flex-container flexFlowColumn">
@ -3726,7 +3745,7 @@
<div name="themeElements" class="flex-container flexFlowColumn flexNoGap"> <div name="themeElements" class="flex-container flexFlowColumn flexNoGap">
<!-- <h4><span data-i18n="UI Colors">Theme Settings</span></h4> --> <!-- <h4><span data-i18n="UI Colors">Theme Settings</span></h4> -->
<div name="AvatarAndChatDisplay" class="flex-container flexFlowColumn"> <div name="AvatarAndChatDisplay" class="flex-container flexFlowColumn">
<div class="flex-container"> <div class="flex-container alignItemsBaseline">
<span data-i18n="Avatar Style">Avatars:</span> <span data-i18n="Avatar Style">Avatars:</span>
<select id="avatar_style" class="widthNatural flex1 margin0"> <select id="avatar_style" class="widthNatural flex1 margin0">
<option value="0" data-i18n="Circle">Circle</option> <option value="0" data-i18n="Circle">Circle</option>
@ -3734,10 +3753,10 @@
<option value="1" data-i18n="Rectangle">Rectangle</option> <option value="1" data-i18n="Rectangle">Rectangle</option>
</select> </select>
</div> </div>
<div class="flex-container"> <div class="flex-container alignItemsBaseline">
<span data-i18n="Chat Style:">Chat Style:</span><br> <span data-i18n="Chat Style:">Chat Style:</span><br>
<select id="chat_display" class="widthNatural flex1 margin0"> <select id="chat_display" class="widthNatural flex1 margin0">
<option value="0" data-i18n="Flat">Flat</span> <option value="0" data-i18n="Flat">Flat</option>
<option value="1" data-i18n="Bubbles">Bubbles</option> <option value="1" data-i18n="Bubbles">Bubbles</option>
<option value="2" data-i18n="Document">Document</option> <option value="2" data-i18n="Document">Document</option>
</select> </select>
@ -4337,7 +4356,7 @@
</div> </div>
<div id="logo_block" class="drawer"> <div id="logo_block" class="drawer">
<div id="site_logo" class="drawer-toggle drawer-header" title="Change Background Image" data-i18n="[title]Change Background Image"> <div id="site_logo" class="drawer-toggle drawer-header" title="Change Background Image" data-i18n="[title]Change Background Image">
<div class="drawer-icon fa-solid fa-panorama closedIcon"></div> <div class="drawer-icon fa-solid fa-panorama fa-fw closedIcon"></div>
</div> </div>
<div id="Backgrounds" class="drawer-content closedDrawer"> <div id="Backgrounds" class="drawer-content closedDrawer">
<div class="flex-container"> <div class="flex-container">
@ -4375,7 +4394,7 @@
</div> </div>
<div id="extensions-settings-button" class="drawer"> <div id="extensions-settings-button" class="drawer">
<div class="drawer-toggle"> <div class="drawer-toggle">
<div class="drawer-icon fa-solid fa-cubes closedIcon" title="Extensions" data-i18n="[title]Extensions"></div> <div class="drawer-icon fa-solid fa-cubes fa-fw closedIcon" title="Extensions" data-i18n="[title]Extensions"></div>
</div> </div>
<div id="rm_extensions_block" class="drawer-content closedDrawer"> <div id="rm_extensions_block" class="drawer-content closedDrawer">
<div class="extensions_block flex-container"> <div class="extensions_block flex-container">
@ -4448,7 +4467,7 @@
</div> </div>
<div id="persona-management-button" class="drawer"> <div id="persona-management-button" class="drawer">
<div class="drawer-toggle"> <div class="drawer-toggle">
<div class="drawer-icon fa-solid fa-face-smile closedIcon" title="Persona Management" data-i18n="[title]Persona Management"></div> <div class="drawer-icon fa-solid fa-face-smile fa-fw closedIcon" title="Persona Management" data-i18n="[title]Persona Management"></div>
</div> </div>
<div class="drawer-content closedDrawer"> <div class="drawer-content closedDrawer">
<div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap"> <div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap">
@ -4556,7 +4575,7 @@
</div> </div>
<div id="rightNavHolder" class="drawer"> <div id="rightNavHolder" class="drawer">
<div id="unimportantYes" class="drawer-toggle drawer-header"> <div id="unimportantYes" class="drawer-toggle drawer-header">
<div id="rightNavDrawerIcon" class="drawer-icon fa-solid fa-address-card closedIcon" title="Character Management" data-i18n="[title]Character Management"> <div id="rightNavDrawerIcon" class="drawer-icon fa-solid fa-address-card fa-fw closedIcon" title="Character Management" data-i18n="[title]Character Management">
</div> </div>
</div> </div>
<nav id="right-nav-panel" class="drawer-content closedDrawer fillRight"> <nav id="right-nav-panel" class="drawer-content closedDrawer fillRight">
@ -5586,8 +5605,8 @@
</div> </div>
<div class="range-block-range"> <div class="range-block-range">
<select name="characterFilter" class="select2_multi_sameline" multiple> <select name="characterFilter" class="select2_multi_sameline" multiple>
<option value=""> <option value="" data-i18n="-- Characters not found --">
<span data-i18n="-- Characters not found --">-- Characters not found --</span> -- Characters not found --
</option> </option>
</select> </select>
</div> </div>
@ -5644,7 +5663,6 @@
<input class="openai_logit_bias_text text_pole" data-i18n="[placeholder]Text or token ids" placeholder="Text or [token ids]" /> <input class="openai_logit_bias_text text_pole" data-i18n="[placeholder]Text or token ids" placeholder="Text or [token ids]" />
<input class="openai_logit_bias_value text_pole" type="number" min="-100" value="0" max="100" /> <input class="openai_logit_bias_value text_pole" type="number" min="-100" value="0" max="100" />
<i class="menu_button fa-solid fa-xmark openai_logit_bias_remove"></i> <i class="menu_button fa-solid fa-xmark openai_logit_bias_remove"></i>
</form>
</div> </div>
</div> </div>
<div id="logit_bias_template" class="template_element"> <div id="logit_bias_template" class="template_element">

File diff suppressed because it is too large Load Diff

View File

@ -224,7 +224,7 @@ import {
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
import { hideLoader, showLoader } from './scripts/loader.js'; import { hideLoader, showLoader } from './scripts/loader.js';
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels } from './scripts/textgen-models.js'; import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js'; import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
import { initPresetManager } from './scripts/preset-manager.js'; import { initPresetManager } from './scripts/preset-manager.js';
import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros.js'; import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros.js';
@ -242,6 +242,7 @@ import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js'
import { initDynamicStyles } from './scripts/dynamic-styles.js'; import { initDynamicStyles } from './scripts/dynamic-styles.js';
import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js';
import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';
import { AbortReason } from './scripts/util/AbortReason.js';
//exporting functions and vars for mods //exporting functions and vars for mods
export { export {
@ -462,6 +463,7 @@ export const event_types = {
LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call', LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call',
ONLINE_STATUS_CHANGED: 'online_status_changed', ONLINE_STATUS_CHANGED: 'online_status_changed',
IMAGE_SWIPED: 'image_swiped', IMAGE_SWIPED: 'image_swiped',
CONNECTION_PROFILE_LOADED: 'connection_profile_loaded',
}; };
export const eventSource = new EventEmitter(); export const eventSource = new EventEmitter();
@ -962,8 +964,8 @@ async function fixViewport() {
document.body.style.position = ''; document.body.style.position = '';
} }
function cancelStatusCheck() { function cancelStatusCheck(reason = 'Manually cancelled status check') {
abortStatusCheck?.abort(); abortStatusCheck?.abort(new AbortReason(reason));
abortStatusCheck = new AbortController(); abortStatusCheck = new AbortController();
setOnlineStatus('no_connection'); setOnlineStatus('no_connection');
} }
@ -1197,6 +1199,9 @@ async function getStatusTextgen() {
} else if (textgen_settings.type === FEATHERLESS) { } else if (textgen_settings.type === FEATHERLESS) {
loadFeatherlessModels(data?.data); loadFeatherlessModels(data?.data);
setOnlineStatus(textgen_settings.featherless_model); setOnlineStatus(textgen_settings.featherless_model);
} else if (textgen_settings.type === TABBY) {
loadTabbyModels(data?.data);
setOnlineStatus(textgen_settings.tabby_model || data?.result);
} else { } else {
setOnlineStatus(data?.result); setOnlineStatus(data?.result);
} }
@ -1213,7 +1218,12 @@ async function getStatusTextgen() {
toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true }); toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true });
} }
} catch (err) { } catch (err) {
console.error('Error getting status', err); if (err instanceof AbortReason) {
console.info('Status check aborted.', err.reason);
} else {
console.error('Error getting status', err);
}
setOnlineStatus('no_connection'); setOnlineStatus('no_connection');
} }
@ -1867,6 +1877,7 @@ export async function sendTextareaMessage() {
// message was sent from a character (not the user or the system). // message was sent from a character (not the user or the system).
const textareaText = String($('#send_textarea').val()); const textareaText = String($('#send_textarea').val());
if (power_user.continue_on_send && if (power_user.continue_on_send &&
!hasPendingFileAttachment() &&
!textareaText && !textareaText &&
!selected_group && !selected_group &&
chat.length && chat.length &&
@ -3080,6 +3091,12 @@ class StreamingProcessor {
this.hideMessageButtons(this.messageId); this.hideMessageButtons(this.messageId);
generatedPromptCache = ''; generatedPromptCache = '';
unblockGeneration(); unblockGeneration();
const noEmitTypes = ['swipe', 'impersonate', 'continue'];
if (!noEmitTypes.includes(this.type)) {
eventSource.emit(event_types.MESSAGE_RECEIVED, this.messageId);
eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, this.messageId);
}
} }
setFirstSwipe(messageId) { setFirstSwipe(messageId) {
@ -3310,7 +3327,7 @@ function removeLastMessage() {
*/ */
export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops, quietName } = {}, dryRun = false) { export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops, quietName } = {}, dryRun = false) {
console.log('Generate entered'); console.log('Generate entered');
eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops }, dryRun); await eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops }, dryRun);
setGenerationProgress(0); setGenerationProgress(0);
generation_started = new Date(); generation_started = new Date();
@ -3390,7 +3407,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
.filter((index) => index !== undefined && index !== null); .filter((index) => index !== undefined && index !== null);
if (memberIds.length > 0) { if (memberIds.length > 0) {
setCharacterId(memberIds[0]); if (menu_type != 'character_edit') setCharacterId(memberIds[0]);
setCharacterName(''); setCharacterName('');
} else { } else {
console.log('No enabled members found'); console.log('No enabled members found');
@ -3456,8 +3473,17 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
//PRE FORMATING STRING //PRE FORMATING STRING
//********************************* //*********************************
// These generation types should not attach pending files to the chat
const noAttachTypes = [
'regenerate',
'swipe',
'impersonate',
'quiet',
'continue',
'ask_command',
];
//for normal messages sent from user.. //for normal messages sent from user..
if ((textareaText != '' || hasPendingFileAttachment()) && !automatic_trigger && type !== 'quiet' && !dryRun) { if ((textareaText != '' || (hasPendingFileAttachment() && !noAttachTypes.includes(type))) && !automatic_trigger && type !== 'quiet' && !dryRun) {
// If user message contains no text other than bias - send as a system message // If user message contains no text other than bias - send as a system message
if (messageBias && !removeMacros(textareaText)) { if (messageBias && !removeMacros(textareaText)) {
sendSystemMessage(system_message_types.GENERIC, ' ', { bias: messageBias }); sendSystemMessage(system_message_types.GENERIC, ' ', { bias: messageBias });
@ -4910,7 +4936,7 @@ async function duplicateCharacter() {
return ''; return '';
} }
export async function itemizedParams(itemizedPrompts, thisPromptSet) { export async function itemizedParams(itemizedPrompts, thisPromptSet, incomingMesId) {
const params = { const params = {
charDescriptionTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].charDescription), charDescriptionTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].charDescription),
charPersonalityTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].charPersonality), charPersonalityTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].charPersonality),
@ -4929,8 +4955,20 @@ export async function itemizedParams(itemizedPrompts, thisPromptSet) {
chatInjects: await getTokenCountAsync(itemizedPrompts[thisPromptSet].chatInjects), chatInjects: await getTokenCountAsync(itemizedPrompts[thisPromptSet].chatInjects),
chatVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].chatVectorsString), chatVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].chatVectorsString),
dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString), dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString),
modelUsed: chat[incomingMesId]?.extra?.model,
apiUsed: chat[incomingMesId]?.extra?.api,
}; };
const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value;
if (params.apiUsed) {
params.apiUsed = getFriendlyName(params.apiUsed);
}
if (params.this_main_api) {
params.mainApiFriendlyName = getFriendlyName(params.this_main_api);
}
if (params.chatInjects) { if (params.chatInjects) {
params.ActualChatHistoryTokens = params.ActualChatHistoryTokens - params.chatInjects; params.ActualChatHistoryTokens = params.ActualChatHistoryTokens - params.chatInjects;
} }
@ -5045,7 +5083,7 @@ async function promptItemize(itemizedPrompts, requestedMesId) {
return null; return null;
} }
const params = await itemizedParams(itemizedPrompts, thisPromptSet); const params = await itemizedParams(itemizedPrompts, thisPromptSet, incomingMesId);
const flatten = (rawPrompt) => Array.isArray(rawPrompt) ? rawPrompt.map(x => x.content).join('\n') : rawPrompt; const flatten = (rawPrompt) => Array.isArray(rawPrompt) ? rawPrompt.map(x => x.content).join('\n') : rawPrompt;
const template = params.this_main_api == 'openai' const template = params.this_main_api == 'openai'
@ -8482,7 +8520,7 @@ async function selectContextCallback(args, name) {
} }
const foundName = result[0].item; const foundName = result[0].item;
selectContextPreset(foundName, quiet); selectContextPreset(foundName, { quiet: quiet });
return foundName; return foundName;
} }
@ -8502,7 +8540,7 @@ async function selectInstructCallback(args, name) {
} }
const foundName = result[0].item; const foundName = result[0].item;
selectInstructPreset(foundName, quiet); selectInstructPreset(foundName, { quiet: quiet });
return foundName; return foundName;
} }
@ -9279,7 +9317,7 @@ jQuery(async function () {
$('#groupCurrentMemberListToggle .inline-drawer-icon').trigger('click'); $('#groupCurrentMemberListToggle .inline-drawer-icon').trigger('click');
}, 200); }, 200);
$(document).on('click', '.api_loading', cancelStatusCheck); $(document).on('click', '.api_loading', () => cancelStatusCheck('Canceled because connecting was manually canceled'));
//////////INPUT BAR FOCUS-KEEPING LOGIC///////////// //////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
let S_TAPreviouslyFocused = false; let S_TAPreviouslyFocused = false;
@ -10038,7 +10076,7 @@ jQuery(async function () {
}); });
$('#main_api').change(function () { $('#main_api').change(function () {
cancelStatusCheck(); cancelStatusCheck('Canceled because main api changed');
changeMainAPI(); changeMainAPI();
saveSettingsDebounced(); saveSettingsDebounced();
}); });
@ -10655,7 +10693,11 @@ jQuery(async function () {
var icon = $(this).find('.inline-drawer-icon'); var icon = $(this).find('.inline-drawer-icon');
icon.toggleClass('down up'); icon.toggleClass('down up');
icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up'); icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
$(this).closest('.inline-drawer').find('.inline-drawer-content').stop().slideToggle(); $(this).closest('.inline-drawer').find('.inline-drawer-content').stop().slideToggle({
complete: () => {
$(this).css('height', '');
},
});
// Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height // Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height
if (!CSS.supports('field-sizing', 'content')) { if (!CSS.supports('field-sizing', 'content')) {

View File

@ -123,6 +123,11 @@ const extension_settings = {
/** @type {string[]} */ /** @type {string[]} */
custom: [], custom: [],
}, },
connectionManager: {
selectedProfile: '',
/** @type {import('./extensions/connection-manager/index.js').ConnectionProfile[]} */
profiles: [],
},
dice: {}, dice: {},
/** @type {import('./char-data.js').RegexScriptData[]} */ /** @type {import('./char-data.js').RegexScriptData[]} */
regex: [], regex: [],

View File

@ -0,0 +1,612 @@
import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDebugController.js';
import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';
import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4 } from '../../utils.js';
const MODULE_NAME = 'connection-manager';
const NONE = '<None>';
const DEFAULT_SETTINGS = {
profiles: [],
selectedProfile: null,
};
const COMMON_COMMANDS = [
'api',
'preset',
'api-url',
'model',
];
const CC_COMMANDS = [
...COMMON_COMMANDS,
'proxy',
];
const TC_COMMANDS = [
...COMMON_COMMANDS,
'instruct',
'context',
'instruct-state',
'tokenizer',
];
const FANCY_NAMES = {
'api': 'API',
'api-url': 'Server URL',
'preset': 'Settings Preset',
'model': 'Model',
'proxy': 'Proxy Preset',
'instruct-state': 'Instruct Mode',
'instruct': 'Instruct Template',
'context': 'Context Template',
'tokenizer': 'Tokenizer',
};
/**
* A wrapper for the connection manager spinner.
*/
class ConnectionManagerSpinner {
/**
* @type {AbortController[]}
*/
static abortControllers = [];
/** @type {HTMLElement} */
spinnerElement;
/** @type {AbortController} */
abortController = new AbortController();
constructor() {
// @ts-ignore
this.spinnerElement = document.getElementById('connection_profile_spinner');
this.abortController = new AbortController();
}
start() {
ConnectionManagerSpinner.abortControllers.push(this.abortController);
this.spinnerElement.classList.remove('hidden');
}
stop() {
this.spinnerElement.classList.add('hidden');
}
isAborted() {
return this.abortController.signal.aborted;
}
static abort() {
for (const controller of ConnectionManagerSpinner.abortControllers) {
controller.abort();
}
ConnectionManagerSpinner.abortControllers = [];
}
}
/**
* Get named arguments for the command callback.
* @param {object} [args] Additional named arguments
* @returns {object} Named arguments
*/
function getNamedArguments(args = {}) {
// None of the commands here use underscored args, but better safe than sorry
return {
_scope: new SlashCommandScope(),
_abortController: new SlashCommandAbortController(),
_debugController: new SlashCommandDebugController(),
_parserFlags: {},
_hasUnnamedArgument: false,
quiet: 'true',
...args,
};
}
/** @type {() => SlashCommandEnumValue[]} */
const profilesProvider = () => [
new SlashCommandEnumValue(NONE),
...extension_settings.connectionManager.profiles.map(p => new SlashCommandEnumValue(p.name, null, enumTypes.name, enumIcons.server)),
];
/**
* @typedef {Object} ConnectionProfile
* @property {string} id Unique identifier
* @property {string} mode Mode of the connection profile
* @property {string} [name] Name of the connection profile
* @property {string} [api] API
* @property {string} [preset] Settings Preset
* @property {string} [model] Model
* @property {string} [proxy] Proxy Preset
* @property {string} [instruct] Instruct Template
* @property {string} [context] Context Template
* @property {string} [instruct-state] Instruct Mode
* @property {string} [tokenizer] Tokenizer
*/
/**
* Finds the best match for the search value.
* @param {string} value Search value
* @returns {ConnectionProfile|null} Best match or null
*/
function findProfileByName(value) {
// Try to find exact match
const profile = extension_settings.connectionManager.profiles.find(p => p.name === value);
if (profile) {
return profile;
}
// Try to find fuzzy match
const fuse = new Fuse(extension_settings.connectionManager.profiles, { keys: ['name'] });
const results = fuse.search(value);
if (results.length === 0) {
return null;
}
const bestMatch = results[0];
return bestMatch.item;
}
/**
* Reads the connection profile from the commands.
* @param {string} mode Mode of the connection profile
* @param {ConnectionProfile} profile Connection profile
* @param {boolean} [cleanUp] Whether to clean up the profile
*/
async function readProfileFromCommands(mode, profile, cleanUp = false) {
const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS;
const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS;
for (const command of commands) {
try {
const args = getNamedArguments();
const result = await SlashCommandParser.commands[command].callback(args, '');
if (result) {
profile[command] = result;
continue;
}
} catch (error) {
console.error(`Failed to execute command: ${command}`, error);
}
}
if (cleanUp) {
for (const command of opposingCommands) {
if (commands.includes(command)) {
continue;
}
delete profile[command];
}
}
}
/**
* Creates a new connection profile.
* @param {string} [forceName] Name of the connection profile
* @returns {Promise<ConnectionProfile>} Created connection profile
*/
async function createConnectionProfile(forceName = null) {
const mode = main_api === 'openai' ? 'cc' : 'tc';
const id = uuidv4();
const profile = {
id,
mode,
};
await readProfileFromCommands(mode, profile);
const profileForDisplay = makeFancyProfile(profile);
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay });
const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);
const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);
const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
if (!name) {
return null;
}
if (isNameTaken(name) || name === NONE) {
toastr.error('A profile with the same name already exists.');
return null;
}
profile.name = name;
return profile;
}
/**
* Deletes the selected connection profile.
* @returns {Promise<void>}
*/
async function deleteConnectionProfile() {
const selectedProfile = extension_settings.connectionManager.selectedProfile;
if (!selectedProfile) {
return;
}
const index = extension_settings.connectionManager.profiles.findIndex(p => p.id === selectedProfile);
if (index === -1) {
return;
}
const name = extension_settings.connectionManager.profiles[index].name;
const confirm = await Popup.show.confirm('Are you sure you want to delete the selected profile?', name);
if (!confirm) {
return;
}
extension_settings.connectionManager.profiles.splice(index, 1);
extension_settings.connectionManager.selectedProfile = null;
saveSettingsDebounced();
}
/**
* Formats the connection profile for display.
* @param {ConnectionProfile} profile Connection profile
* @returns {Object} Fancy profile
*/
function makeFancyProfile(profile) {
return Object.entries(FANCY_NAMES).reduce((acc, [key, value]) => {
if (!profile[key]) return acc;
acc[value] = profile[key];
return acc;
}, {});
}
/**
* Applies the connection profile.
* @param {ConnectionProfile} profile Connection profile
* @returns {Promise<void>}
*/
async function applyConnectionProfile(profile) {
if (!profile) {
return;
}
// Abort any ongoing profile application
ConnectionManagerSpinner.abort();
const mode = profile.mode;
const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS;
const spinner = new ConnectionManagerSpinner();
spinner.start();
for (const command of commands) {
if (spinner.isAborted()) {
throw new Error('Profile application aborted');
}
const argument = profile[command];
if (!argument) {
continue;
}
try {
const args = getNamedArguments();
await SlashCommandParser.commands[command].callback(args, argument);
} catch (error) {
console.error(`Failed to execute command: ${command} ${argument}`, error);
}
}
spinner.stop();
}
/**
* Updates the selected connection profile.
* @param {ConnectionProfile} profile Connection profile
* @returns {Promise<void>}
*/
async function updateConnectionProfile(profile) {
profile.mode = main_api === 'openai' ? 'cc' : 'tc';
await readProfileFromCommands(profile.mode, profile, true);
}
/**
* Renders the connection profile details.
* @param {HTMLSelectElement} profiles Select element containing connection profiles
*/
function renderConnectionProfiles(profiles) {
profiles.innerHTML = '';
const noneOption = document.createElement('option');
noneOption.value = '';
noneOption.textContent = NONE;
noneOption.selected = !extension_settings.connectionManager.selectedProfile;
profiles.appendChild(noneOption);
for (const profile of extension_settings.connectionManager.profiles) {
const option = document.createElement('option');
option.value = profile.id;
option.textContent = profile.name;
option.selected = profile.id === extension_settings.connectionManager.selectedProfile;
profiles.appendChild(option);
}
}
/**
* Renders the content of the details element.
* @param {HTMLElement} detailsContent Content element of the details
*/
async function renderDetailsContent(detailsContent) {
detailsContent.innerHTML = '';
if (detailsContent.classList.contains('hidden')) {
return;
}
const selectedProfile = extension_settings.connectionManager.selectedProfile;
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
if (profile) {
const profileForDisplay = makeFancyProfile(profile);
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', { profile: profileForDisplay });
detailsContent.innerHTML = template;
} else {
detailsContent.textContent = 'No profile selected';
}
}
(async function () {
extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS);
for (const key of Object.keys(DEFAULT_SETTINGS)) {
if (extension_settings.connectionManager[key] === undefined) {
extension_settings.connectionManager[key] = DEFAULT_SETTINGS[key];
}
}
const container = document.getElementById('rm_api_block');
const settings = await renderExtensionTemplateAsync(MODULE_NAME, 'settings');
container.insertAdjacentHTML('afterbegin', settings);
/** @type {HTMLSelectElement} */
// @ts-ignore
const profiles = document.getElementById('connection_profiles');
renderConnectionProfiles(profiles);
function toggleProfileSpecificButtons() {
const profileId = extension_settings.connectionManager.selectedProfile;
const profileSpecificButtons = ['update_connection_profile', 'reload_connection_profile', 'delete_connection_profile'];
profileSpecificButtons.forEach(id => document.getElementById(id).classList.toggle('disabled', !profileId));
}
toggleProfileSpecificButtons();
profiles.addEventListener('change', async function () {
const selectedProfile = profiles.selectedOptions[0];
if (!selectedProfile) {
// Safety net for preventing the command getting stuck
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);
return;
}
const profileId = selectedProfile.value;
extension_settings.connectionManager.selectedProfile = profileId;
saveSettingsDebounced();
await renderDetailsContent(detailsContent);
toggleProfileSpecificButtons();
// None option selected
if (!profileId) {
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);
return;
}
const profile = extension_settings.connectionManager.profiles.find(p => p.id === profileId);
if (!profile) {
console.log(`Profile not found: ${profileId}`);
return;
}
await applyConnectionProfile(profile);
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
});
const reloadButton = document.getElementById('reload_connection_profile');
reloadButton.addEventListener('click', async () => {
const selectedProfile = extension_settings.connectionManager.selectedProfile;
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
if (!profile) {
console.log('No profile selected');
return;
}
await applyConnectionProfile(profile);
await renderDetailsContent(detailsContent);
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
toastr.success('Connection profile reloaded', '', { timeOut: 1500 });
});
const createButton = document.getElementById('create_connection_profile');
createButton.addEventListener('click', async () => {
const profile = await createConnectionProfile();
if (!profile) {
return;
}
extension_settings.connectionManager.profiles.push(profile);
extension_settings.connectionManager.selectedProfile = profile.id;
saveSettingsDebounced();
renderConnectionProfiles(profiles);
await renderDetailsContent(detailsContent);
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
});
const updateButton = document.getElementById('update_connection_profile');
updateButton.addEventListener('click', async () => {
const selectedProfile = extension_settings.connectionManager.selectedProfile;
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
if (!profile) {
console.log('No profile selected');
return;
}
await updateConnectionProfile(profile);
await renderDetailsContent(detailsContent);
saveSettingsDebounced();
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
toastr.success('Connection profile updated', '', { timeOut: 1500 });
});
const deleteButton = document.getElementById('delete_connection_profile');
deleteButton.addEventListener('click', async () => {
await deleteConnectionProfile();
renderConnectionProfiles(profiles);
await renderDetailsContent(detailsContent);
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);
});
/** @type {HTMLElement} */
const viewDetails = document.getElementById('view_connection_profile');
const detailsContent = document.getElementById('connection_profile_details_content');
viewDetails.addEventListener('click', async () => {
viewDetails.classList.toggle('active');
detailsContent.classList.toggle('hidden');
await renderDetailsContent(detailsContent);
});
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'profile',
helpString: 'Switch to a connection profile or return the name of the current profile in no argument is provided. Use <code>&lt;None&gt;</code> to switch to no profile.',
returns: 'name of the profile',
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Name of the connection profile',
enumProvider: profilesProvider,
isRequired: false,
}),
],
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'await',
description: 'Wait for the connection profile to be applied before returning.',
isRequired: false,
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'true',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
callback: async (args, value) => {
if (!value || typeof value !== 'string') {
const selectedProfile = extension_settings.connectionManager.selectedProfile;
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
if (!profile) {
return NONE;
}
return profile.name;
}
if (value === NONE) {
profiles.selectedIndex = 0;
profiles.dispatchEvent(new Event('change'));
return NONE;
}
const profile = findProfileByName(value);
if (!profile) {
return '';
}
const shouldAwait = !isFalseBoolean(String(args?.await));
const awaitPromise = new Promise((resolve) => eventSource.once(event_types.CONNECTION_PROFILE_LOADED, resolve));
profiles.selectedIndex = Array.from(profiles.options).findIndex(o => o.value === profile.id);
profiles.dispatchEvent(new Event('change'));
if (shouldAwait) {
await awaitPromise;
}
return profile.name;
},
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'profile-list',
helpString: 'List all connection profile names.',
returns: 'list of profile names',
callback: () => JSON.stringify(extension_settings.connectionManager.profiles.map(p => p.name)),
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'profile-create',
returns: 'name of the new profile',
helpString: 'Create a new connection profile using the current settings.',
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'name of the new connection profile',
isRequired: true,
typeList: [ARGUMENT_TYPE.STRING],
}),
],
callback: async (_args, name) => {
if (!name || typeof name !== 'string') {
toastr.warning('Please provide a name for the new connection profile.');
return '';
}
const profile = await createConnectionProfile(name);
if (!profile) {
return '';
}
extension_settings.connectionManager.profiles.push(profile);
extension_settings.connectionManager.selectedProfile = profile.id;
saveSettingsDebounced();
renderConnectionProfiles(profiles);
await renderDetailsContent(detailsContent);
return profile.name;
},
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'profile-update',
helpString: 'Update the selected connection profile.',
callback: async () => {
const selectedProfile = extension_settings.connectionManager.selectedProfile;
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
if (!profile) {
toastr.warning('No profile selected.');
return '';
}
await updateConnectionProfile(profile);
await renderDetailsContent(detailsContent);
saveSettingsDebounced();
return profile.name;
},
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'profile-get',
helpString: 'Get the details of the connection profile. Returns the selected profile if no argument is provided.',
returns: 'object of the selected profile',
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Name of the connection profile',
enumProvider: profilesProvider,
isRequired: false,
}),
],
callback: async (_args, value) => {
if (!value || typeof value !== 'string') {
const selectedProfile = extension_settings.connectionManager.selectedProfile;
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
if (!profile) {
return '';
}
return JSON.stringify(profile);
}
const profile = findProfileByName(value);
if (!profile) {
return '';
}
return JSON.stringify(profile);
},
}));
})();

View File

@ -0,0 +1,11 @@
{
"display_name": "Connection Profiles",
"loading_order": 1,
"requires": [],
"optional": [],
"js": "index.js",
"css": "style.css",
"author": "Cohee1207",
"version": "1.0.0",
"homePage": "https://github.com/SillyTavern/SillyTavern"
}

View File

@ -0,0 +1,13 @@
<div>
<h2 data-i18n="Creating a Connection Profile">
Creating a Connection Profile
</h2>
<ul class="justifyLeft">
{{#each profile}}
<li><strong data-i18n="{{@key}}">{{@key}}:</strong>&nbsp;{{this}}</li>
{{/each}}
</ul>
<h3 data-i18n="Enter a name:">
Enter a name:
</h3>
</div>

View File

@ -0,0 +1,20 @@
<div class="wide100p">
<div class="flex-container alignItemsBaseline">
<h3 class="margin0">
<span data-i18n="Connection Profile">Connection Profile</span>
<a href="https://docs.sillytavern.app/usage/core-concepts/connection-profiles" target="_blank" class="notes-link">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</h3>
<i id="connection_profile_spinner" class="fa-solid fa-spinner fa-spin hidden"></i>
</div>
<div class="flex-container">
<select class="text_pole flex1" id="connection_profiles"></select>
<i id="view_connection_profile" class="menu_button fa-solid fa-info-circle" title="View connection profile details" data-i18n="[title]View connection profile details"></i>
<i id="create_connection_profile" class="menu_button fa-solid fa-file-circle-plus" title="Create a new connection profile" data-i18n="[title]Create a new connection profile"></i>
<i id="update_connection_profile" class="menu_button fa-solid fa-save" title="Update a connection profile" data-i18n="[title]Update a connection profile"></i>
<i id="reload_connection_profile" class="menu_button fa-solid fa-recycle" title="Reload a connection profile" data-i18n="[title]Reload a connection profile"></i>
<i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i>
</div>
<div id="connection_profile_details_content" class="hidden"></div>
</div>

View File

@ -0,0 +1,11 @@
#connection_profile_details_content {
margin: 5px 0;
}
#connection_profile_details_content ul {
margin: 0;
}
#connection_profile_spinner {
margin-left: 5px;
}

View File

@ -0,0 +1,5 @@
<ul>
{{#each profile}}
<li><strong data-i18n="{{@key}}">{{@key}}:</strong>&nbsp;{{this}}</li>
{{/each}}
</ul>

View File

@ -20,7 +20,7 @@ export async function getMultimodalCaption(base64Img, prompt) {
throwIfInvalidModel(useReverseProxy); throwIfInvalidModel(useReverseProxy);
const noPrefix = ['google', 'ollama', 'llamacpp'].includes(extension_settings.caption.multimodal_api); const noPrefix = ['ollama', 'llamacpp'].includes(extension_settings.caption.multimodal_api);
if (noPrefix && base64Img.startsWith('data:image/')) { if (noPrefix && base64Img.startsWith('data:image/')) {
base64Img = base64Img.split(',')[1]; base64Img = base64Img.split(',')[1];
@ -28,7 +28,6 @@ export async function getMultimodalCaption(base64Img, prompt) {
// OpenRouter has a payload limit of ~2MB. Google is 4MB, but we love democracy. // OpenRouter has a payload limit of ~2MB. Google is 4MB, but we love democracy.
// Ooba requires all images to be JPEGs. Koboldcpp just asked nicely. // Ooba requires all images to be JPEGs. Koboldcpp just asked nicely.
const isGoogle = extension_settings.caption.multimodal_api === 'google';
const isOllama = extension_settings.caption.multimodal_api === 'ollama'; const isOllama = extension_settings.caption.multimodal_api === 'ollama';
const isLlamaCpp = extension_settings.caption.multimodal_api === 'llamacpp'; const isLlamaCpp = extension_settings.caption.multimodal_api === 'llamacpp';
const isCustom = extension_settings.caption.multimodal_api === 'custom'; const isCustom = extension_settings.caption.multimodal_api === 'custom';
@ -40,10 +39,6 @@ export async function getMultimodalCaption(base64Img, prompt) {
if ((['google', 'openrouter'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) { if ((['google', 'openrouter'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {
const maxSide = 1024; const maxSide = 1024;
base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg'); base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg');
if (isGoogle) {
base64Img = base64Img.split(',')[1];
}
} }
const proxyUrl = useReverseProxy ? oai_settings.reverse_proxy : ''; const proxyUrl = useReverseProxy ? oai_settings.reverse_proxy : '';

View File

@ -283,7 +283,6 @@ const defaultSettings = {
// Pollinations settings // Pollinations settings
pollinations_enhance: false, pollinations_enhance: false,
pollinations_refine: false,
// Visibility toggles // Visibility toggles
wand_visible: false, wand_visible: false,
@ -425,7 +424,6 @@ async function loadSettings() {
$('#sd_novel_sm_dyn').prop('disabled', !extension_settings.sd.novel_sm); $('#sd_novel_sm_dyn').prop('disabled', !extension_settings.sd.novel_sm);
$('#sd_novel_decrisper').prop('checked', extension_settings.sd.novel_decrisper); $('#sd_novel_decrisper').prop('checked', extension_settings.sd.novel_decrisper);
$('#sd_pollinations_enhance').prop('checked', extension_settings.sd.pollinations_enhance); $('#sd_pollinations_enhance').prop('checked', extension_settings.sd.pollinations_enhance);
$('#sd_pollinations_refine').prop('checked', extension_settings.sd.pollinations_refine);
$('#sd_horde').prop('checked', extension_settings.sd.horde); $('#sd_horde').prop('checked', extension_settings.sd.horde);
$('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw); $('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw);
$('#sd_horde_karras').prop('checked', extension_settings.sd.horde_karras); $('#sd_horde_karras').prop('checked', extension_settings.sd.horde_karras);
@ -1009,11 +1007,6 @@ function onPollinationsEnhanceInput() {
saveSettingsDebounced(); saveSettingsDebounced();
} }
function onPollinationsRefineInput() {
extension_settings.sd.pollinations_refine = !!$('#sd_pollinations_refine').prop('checked');
saveSettingsDebounced();
}
function onHordeNsfwInput() { function onHordeNsfwInput() {
extension_settings.sd.horde_nsfw = !!$(this).prop('checked'); extension_settings.sd.horde_nsfw = !!$(this).prop('checked');
saveSettingsDebounced(); saveSettingsDebounced();
@ -1692,16 +1685,17 @@ async function loadStabilityModels() {
} }
async function loadPollinationsModels() { async function loadPollinationsModels() {
return [ const result = await fetch('/api/sd/pollinations/models', {
{ method: 'POST',
value: 'flux', headers: getRequestHeaders(),
text: 'FLUX.1 [schnell]', });
},
{ if (result.ok) {
value: 'turbo', const data = await result.json();
text: 'SDXL Turbo', return data;
}, }
];
return [];
} }
async function loadTogetherAIModels() { async function loadTogetherAIModels() {
@ -2739,7 +2733,6 @@ async function generatePollinationsImage(prompt, negativePrompt, signal) {
width: extension_settings.sd.width, width: extension_settings.sd.width,
height: extension_settings.sd.height, height: extension_settings.sd.height,
enhance: extension_settings.sd.pollinations_enhance, enhance: extension_settings.sd.pollinations_enhance,
refine: extension_settings.sd.pollinations_refine,
seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined, seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
}), }),
}); });
@ -3886,7 +3879,6 @@ jQuery(async () => {
$('#sd_novel_sm_dyn').on('input', onNovelSmDynInput); $('#sd_novel_sm_dyn').on('input', onNovelSmDynInput);
$('#sd_novel_decrisper').on('input', onNovelDecrisperInput); $('#sd_novel_decrisper').on('input', onNovelDecrisperInput);
$('#sd_pollinations_enhance').on('input', onPollinationsEnhanceInput); $('#sd_pollinations_enhance').on('input', onPollinationsEnhanceInput);
$('#sd_pollinations_refine').on('input', onPollinationsRefineInput);
$('#sd_comfy_validate').on('click', validateComfyUrl); $('#sd_comfy_validate').on('click', validateComfyUrl);
$('#sd_comfy_url').on('input', onComfyUrlInput); $('#sd_comfy_url').on('input', onComfyUrlInput);
$('#sd_comfy_workflow').on('change', onComfyWorkflowChange); $('#sd_comfy_workflow').on('change', onComfyWorkflowChange);

View File

@ -184,18 +184,12 @@
<a href="https://pollinations.ai">Pollinations.ai</a> <a href="https://pollinations.ai">Pollinations.ai</a>
</p> </p>
<div class="flex-container"> <div class="flex-container">
<label class="flex1 checkbox_label" for="sd_pollinations_enhance"> <label class="flex1 checkbox_label" for="sd_pollinations_enhance" title="Enables prompt enhancing (passes prompts through an LLM to add detail).">
<input id="sd_pollinations_enhance" type="checkbox" /> <input id="sd_pollinations_enhance" type="checkbox" />
<span data-i18n="Enhance"> <span data-i18n="Enhance">
Enhance Enhance
</span> </span>
</label> </label>
<label class="flex1 checkbox_label" for="sd_pollinations_refine">
<input id="sd_pollinations_refine" type="checkbox" />
<span data-i18n="Refine">
Refine
</span>
</label>
</div> </div>
</div> </div>
<div data-sd-source="stability"> <div data-sd-source="stability">

View File

@ -130,13 +130,15 @@ function highlightDefaultPreset() {
/** /**
* Select context template if not already selected. * Select context template if not already selected.
* @param {string} preset Preset name. * @param {string} preset Preset name.
* @param {boolean} quiet Suppress info message. * @param {object} [options={}] Optional arguments.
* @param {boolean} [options.quiet=false] Suppress toast messages.
* @param {boolean} [options.isAuto=false] Is auto-select.
*/ */
export function selectContextPreset(preset, quiet) { export function selectContextPreset(preset, { quiet = false, isAuto = false } = {}) {
// If context template is not already selected, select it // If context template is not already selected, select it
if (preset !== power_user.context.preset) { if (preset !== power_user.context.preset) {
$('#context_presets').val(preset).trigger('change'); $('#context_presets').val(preset).trigger('change');
!quiet && toastr.info(`Context Template: preset "${preset}" auto-selected`); !quiet && toastr.info(`Context Template: "${preset}" ${isAuto ? 'auto-' : ''}selected`);
} }
// If instruct mode is disabled, enable it, except for default context template // If instruct mode is disabled, enable it, except for default context template
@ -152,13 +154,15 @@ export function selectContextPreset(preset, quiet) {
/** /**
* Select instruct preset if not already selected. * Select instruct preset if not already selected.
* @param {string} preset Preset name. * @param {string} preset Preset name.
* @param {boolean} quiet Suppress info message. * @param {object} [options={}] Optional arguments.
* @param {boolean} [options.quiet=false] Suppress toast messages.
* @param {boolean} [options.isAuto=false] Is auto-select.
*/ */
export function selectInstructPreset(preset, quiet) { export function selectInstructPreset(preset, { quiet = false, isAuto = false } = {}) {
// If instruct preset is not already selected, select it // If instruct preset is not already selected, select it
if (preset !== power_user.instruct.preset) { if (preset !== power_user.instruct.preset) {
$('#instruct_presets').val(preset).trigger('change'); $('#instruct_presets').val(preset).trigger('change');
!quiet && toastr.info(`Instruct Mode: template "${preset}" auto-selected`); !quiet && toastr.info(`Instruct Template: "${preset}" ${isAuto ? 'auto-' : ''}selected`);
} }
// If instruct mode is disabled, enable it // If instruct mode is disabled, enable it
@ -189,7 +193,7 @@ export function autoSelectInstructPreset(modelId) {
// If instruct preset matches the context template // If instruct preset matches the context template
if (power_user.instruct.bind_to_context && instruct_preset.name === power_user.context.preset) { if (power_user.instruct.bind_to_context && instruct_preset.name === power_user.context.preset) {
foundMatch = true; foundMatch = true;
selectInstructPreset(instruct_preset.name); selectInstructPreset(instruct_preset.name, { isAuto: true });
break; break;
} }
} }
@ -203,7 +207,7 @@ export function autoSelectInstructPreset(modelId) {
// Stop on first match so it won't cycle back and forth between presets if multiple regexes match // Stop on first match so it won't cycle back and forth between presets if multiple regexes match
if (regex instanceof RegExp && regex.test(modelId)) { if (regex instanceof RegExp && regex.test(modelId)) {
selectInstructPreset(preset.name); selectInstructPreset(preset.name, { isAuto: true });
return true; return true;
} }
@ -541,13 +545,13 @@ function selectMatchingContextTemplate(name) {
// If context template matches the instruct preset // If context template matches the instruct preset
if (context_preset.name === name) { if (context_preset.name === name) {
foundMatch = true; foundMatch = true;
selectContextPreset(context_preset.name); selectContextPreset(context_preset.name, { isAuto: true });
break; break;
} }
} }
if (!foundMatch) { if (!foundMatch) {
// If no match was found, select default context preset // If no match was found, select default context preset
selectContextPreset(power_user.default_context); selectContextPreset(power_user.default_context, { isAuto: true });
} }
} }

View File

@ -47,6 +47,7 @@ import { SECRET_KEYS, secret_state, writeSecret } from './secrets.js';
import { getEventSourceStream } from './sse-stream.js'; import { getEventSourceStream } from './sse-stream.js';
import { import {
createThumbnail,
delay, delay,
download, download,
getBase64Async, getBase64Async,
@ -2440,25 +2441,52 @@ class Message {
if (!response.ok) throw new Error('Failed to fetch image'); if (!response.ok) throw new Error('Failed to fetch image');
const blob = await response.blob(); const blob = await response.blob();
image = await getBase64Async(blob); image = await getBase64Async(blob);
if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
image = image.split(',')[1];
}
} catch (error) { } catch (error) {
console.error('Image adding skipped', error); console.error('Image adding skipped', error);
return; return;
} }
} }
image = await this.compressImage(image);
const quality = oai_settings.inline_image_quality || default_settings.inline_image_quality; const quality = oai_settings.inline_image_quality || default_settings.inline_image_quality;
this.content = [ this.content = [
{ type: 'text', text: textContent }, { type: 'text', text: textContent },
{ type: 'image_url', image_url: { 'url': image, 'detail': quality } }, { type: 'image_url', image_url: { 'url': image, 'detail': quality } },
]; ];
const tokens = await this.getImageTokenCost(image, quality); try {
this.tokens += tokens; const tokens = await this.getImageTokenCost(image, quality);
this.tokens += tokens;
} catch (error) {
this.tokens += Message.tokensPerImage;
console.error('Failed to get image token cost', error);
}
} }
/**
* Compress an image if it exceeds the size threshold for the current chat completion source.
* @param {string} image Data URL of the image.
* @returns {Promise<string>} Compressed image as a Data URL.
*/
async compressImage(image) {
if ([chat_completion_sources.OPENROUTER, chat_completion_sources.MAKERSUITE].includes(oai_settings.chat_completion_source)) {
const sizeThreshold = 2 * 1024 * 1024;
const dataSize = image.length * 0.75;
const maxSide = 1024;
if (dataSize > sizeThreshold) {
image = await createThumbnail(image, maxSide);
}
}
return image;
}
/**
* Get the token cost of an image.
* @param {string} dataUrl Data URL of the image.
* @param {string} quality String representing the quality of the image. Can be 'low', 'auto', or 'high'.
* @returns {Promise<number>} The token cost of the image.
*/
async getImageTokenCost(dataUrl, quality) { async getImageTokenCost(dataUrl, quality) {
if (quality === 'low') { if (quality === 'low') {
return Message.tokensPerImage; return Message.tokensPerImage;

View File

@ -1745,7 +1745,7 @@ async function loadContextSettings() {
power_user.context[control.property] = value; power_user.context[control.property] = value;
} }
console.log(`Setting ${$element.prop('id')} to ${value}`); console.log(`Setting ${$element.prop('id')} to ${value}`);
if (!CSS.supports('field-sizing', 'content')) { if (!CSS.supports('field-sizing', 'content') && $(this).is('textarea')) {
await resetScrollHeight($(this)); await resetScrollHeight($(this));
} }
saveSettingsDebounced(); saveSettingsDebounced();
@ -1798,7 +1798,7 @@ async function loadContextSettings() {
for (const instruct_preset of instruct_presets) { for (const instruct_preset of instruct_presets) {
// If instruct preset matches the context template // If instruct preset matches the context template
if (instruct_preset.name === name) { if (instruct_preset.name === name) {
selectInstructPreset(instruct_preset.name); selectInstructPreset(instruct_preset.name, { isAuto: true });
break; break;
} }
} }

View File

@ -338,6 +338,7 @@ class PresetManager {
'max_tokens_second', 'max_tokens_second',
'openrouter_providers', 'openrouter_providers',
'openrouter_allow_fallbacks', 'openrouter_allow_fallbacks',
'tabby_model',
]; ];
const settings = Object.assign({}, getSettingsByApiId(this.apiId)); const settings = Object.assign({}, getSettingsByApiId(this.apiId));

View File

@ -13,8 +13,8 @@ import { setting_names } from './textgen-settings.js';
const TGsamplerNames = setting_names; const TGsamplerNames = setting_names;
const forcedOnColoring = 'filter: sepia(1) hue-rotate(59deg) contrast(1.5) saturate(3.5)'; const forcedOnColoring = 'color: #89db35;';
const forcedOffColoring = 'filter: sepia(1) hue-rotate(308deg) contrast(0.7) saturate(10)'; const forcedOffColoring = 'color: #e84f62;';
let userDisabledSamplers, userShownSamplers; let userDisabledSamplers, userShownSamplers;

View File

@ -1485,7 +1485,22 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'listinjects', name: 'listinjects',
callback: listInjectsCallback, callback: listInjectsCallback,
helpString: 'Lists all script injections for the current chat.', helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>format</code> argument to change the output format.',
returns: 'JSON object of script injections',
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'format',
description: 'output format',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
forceEnum: true,
enumList: [
new SlashCommandEnumValue('popup', 'Show injects in a popup.', enumTypes.enum, enumIcons.default),
new SlashCommandEnumValue('chat', 'Post a system message to the chat.', enumTypes.enum, enumIcons.default),
new SlashCommandEnumValue('none', 'Just return the injects as a JSON object.', enumTypes.enum, enumIcons.default),
],
}),
],
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'flushinject', name: 'flushinject',
@ -1743,10 +1758,11 @@ function injectCallback(args, value) {
return ''; return '';
} }
function listInjectsCallback() { async function listInjectsCallback(args) {
const type = String(args?.format).toLowerCase().trim();
if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) { if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) {
toastr.info('No script injections for the current chat'); type !== 'none' && toastr.info('No script injections for the current chat');
return ''; return JSON.stringify({});
} }
const injects = Object.entries(chat_metadata.script_injects) const injects = Object.entries(chat_metadata.script_injects)
@ -1761,7 +1777,19 @@ function listInjectsCallback() {
const messageText = `### Script injections:\n${injects}`; const messageText = `### Script injections:\n${injects}`;
const htmlMessage = DOMPurify.sanitize(converter.makeHtml(messageText)); const htmlMessage = DOMPurify.sanitize(converter.makeHtml(messageText));
sendSystemMessage(system_message_types.GENERIC, htmlMessage); switch (type) {
case 'none':
break;
case 'chat':
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
break;
case 'popup':
default:
await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
break;
}
return JSON.stringify(chat_metadata.script_injects);
} }
/** /**
@ -3398,6 +3426,7 @@ function getModelOptions(quiet) {
{ id: 'vllm_model', api: 'textgenerationwebui', type: textgen_types.VLLM }, { id: 'vllm_model', api: 'textgenerationwebui', type: textgen_types.VLLM },
{ id: 'aphrodite_model', api: 'textgenerationwebui', type: textgen_types.APHRODITE }, { id: 'aphrodite_model', api: 'textgenerationwebui', type: textgen_types.APHRODITE },
{ id: 'ollama_model', api: 'textgenerationwebui', type: textgen_types.OLLAMA }, { id: 'ollama_model', api: 'textgenerationwebui', type: textgen_types.OLLAMA },
{ id: 'tabby_model', api: 'textgenerationwebui', type: textgen_types.TABBY },
{ id: 'model_openai_select', api: 'openai', type: chat_completion_sources.OPENAI }, { id: 'model_openai_select', api: 'openai', type: chat_completion_sources.OPENAI },
{ id: 'model_claude_select', api: 'openai', type: chat_completion_sources.CLAUDE }, { id: 'model_claude_select', api: 'openai', type: chat_completion_sources.CLAUDE },
{ id: 'model_windowai_select', api: 'openai', type: chat_completion_sources.WINDOWAI }, { id: 'model_windowai_select', api: 'openai', type: chat_completion_sources.WINDOWAI },
@ -3441,7 +3470,7 @@ function getModelOptions(quiet) {
return nullResult; return nullResult;
} }
const options = Array.from(modelSelectControl.options); const options = Array.from(modelSelectControl.options).filter(x => x.value);
return { control: modelSelectControl, options }; return { control: modelSelectControl, options };
} }

View File

@ -33,6 +33,7 @@ export const enumIcons = {
file: '📄', file: '📄',
message: '💬', message: '💬',
voice: '🎤', voice: '🎤',
server: '🖥️',
true: '✔️', true: '✔️',
false: '❌', false: '❌',

View File

@ -4,8 +4,8 @@
<div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div> <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
<div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div> <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
</h3> </h3>
API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}<br>
Tokenizer: {{selectedTokenizer}}<br> Tokenizer: {{selectedTokenizer}}<br>
API Used: {{this_main_api}}<br>
<span class="tokenItemizingSubclass"> <span class="tokenItemizingSubclass">
Only the white numbers really matter. All numbers are estimates. Only the white numbers really matter. All numbers are estimates.
Grey color items may not have been included in the context due to certain prompt format settings. Grey color items may not have been included in the context due to certain prompt format settings.

View File

@ -4,8 +4,8 @@
<div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div> <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
<div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div> <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
</h3> </h3>
API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}<br>
Tokenizer: {{selectedTokenizer}}<br> Tokenizer: {{selectedTokenizer}}<br>
API Used: {{this_main_api}}<br>
<span class="tokenItemizingSubclass"> <span class="tokenItemizingSubclass">
Only the white numbers really matter. All numbers are estimates. Only the white numbers really matter. All numbers are estimates.
Grey color items may not have been included in the context due to certain prompt format settings. Grey color items may not have been included in the context due to certain prompt format settings.

View File

@ -12,6 +12,7 @@ let dreamGenModels = [];
let vllmModels = []; let vllmModels = [];
let aphroditeModels = []; let aphroditeModels = [];
let featherlessModels = []; let featherlessModels = [];
let tabbyModels = [];
export let openRouterModels = []; export let openRouterModels = [];
/** /**
@ -66,6 +67,30 @@ export async function loadOllamaModels(data) {
} }
} }
export async function loadTabbyModels(data) {
if (!Array.isArray(data)) {
console.error('Invalid Tabby models data', data);
return;
}
tabbyModels = data;
tabbyModels.sort((a, b) => a.id.localeCompare(b.id));
tabbyModels.unshift({ id: '' });
if (!tabbyModels.find(x => x.id === textgen_settings.tabby_model)) {
textgen_settings.tabby_model = tabbyModels[0]?.id || '';
}
$('#tabby_model').empty();
for (const model of tabbyModels) {
const option = document.createElement('option');
option.value = model.id;
option.text = model.id;
option.selected = model.id === textgen_settings.tabby_model;
$('#tabby_model').append(option);
}
}
export async function loadTogetherAIModels(data) { export async function loadTogetherAIModels(data) {
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
console.error('Invalid Together AI models data', data); console.error('Invalid Together AI models data', data);
@ -310,6 +335,12 @@ function onOllamaModelSelect() {
$('#api_button_textgenerationwebui').trigger('click'); $('#api_button_textgenerationwebui').trigger('click');
} }
function onTabbyModelSelect() {
const modelId = String($('#tabby_model').val());
textgen_settings.tabby_model = modelId;
$('#api_button_textgenerationwebui').trigger('click');
}
function onOpenRouterModelSelect() { function onOpenRouterModelSelect() {
const modelId = String($('#openrouter_model').val()); const modelId = String($('#openrouter_model').val());
textgen_settings.openrouter_model = modelId; textgen_settings.openrouter_model = modelId;
@ -641,6 +672,7 @@ export function initTextGenModels() {
$('#aphrodite_model').on('change', onAphroditeModelSelect); $('#aphrodite_model').on('change', onAphroditeModelSelect);
$('#featherless_model').on('change', onFeatherlessModelSelect); $('#featherless_model').on('change', onFeatherlessModelSelect);
$('#tabby_download_model').on('click', downloadTabbyModel); $('#tabby_download_model').on('click', downloadTabbyModel);
$('#tabby_model').on('change', onTabbyModelSelect);
const providersSelect = $('.openrouter_providers'); const providersSelect = $('.openrouter_providers');
for (const provider of OPENROUTER_PROVIDERS) { for (const provider of OPENROUTER_PROVIDERS) {
@ -671,6 +703,13 @@ export function initTextGenModels() {
searchInputCssClass: 'text_pole', searchInputCssClass: 'text_pole',
width: '100%', width: '100%',
}); });
$('#tabby_model').select2({
placeholder: '[Currently loaded]',
searchInputPlaceholder: 'Search models...',
searchInputCssClass: 'text_pole',
width: '100%',
allowClear: true,
});
$('#model_infermaticai_select').select2({ $('#model_infermaticai_select').select2({
placeholder: 'Select a model', placeholder: 'Select a model',
searchInputPlaceholder: 'Search models...', searchInputPlaceholder: 'Search models...',

View File

@ -180,6 +180,7 @@ const settings = {
vllm_model: '', vllm_model: '',
aphrodite_model: '', aphrodite_model: '',
dreamgen_model: 'opus-v1-xl/text', dreamgen_model: 'opus-v1-xl/text',
tabby_model: '',
legacy_api: false, legacy_api: false,
sampler_order: KOBOLDCPP_ORDER, sampler_order: KOBOLDCPP_ORDER,
logit_bias: [], logit_bias: [],
@ -995,7 +996,7 @@ function tryParseStreamingError(response, decoded) {
// No JSON. Do nothing. // No JSON. Do nothing.
} }
const message = data?.error?.message || data?.message; const message = data?.error?.message || data?.message || data?.detail;
if (message) { if (message) {
toastr.error(message, 'Text Completion API'); toastr.error(message, 'Text Completion API');
@ -1047,6 +1048,11 @@ export function getTextGenModel() {
return settings.featherless_model; return settings.featherless_model;
case HUGGINGFACE: case HUGGINGFACE:
return 'tgi'; return 'tgi';
case TABBY:
if (settings.tabby_model) {
return settings.tabby_model;
}
break;
default: default:
return undefined; return undefined;
} }

View File

@ -0,0 +1,9 @@
export class AbortReason {
constructor(reason) {
this.reason = reason;
}
toString() {
return this.reason;
}
}

View File

@ -1436,6 +1436,15 @@ export function uuidv4() {
}); });
} }
/**
* Collapses multiple spaces in a strings into one.
* @param {string} s String to process
* @returns {string} String with collapsed spaces
*/
export function collapseSpaces(s) {
return s.replace(/\s+/g, ' ').trim();
}
function postProcessText(text, collapse = true) { function postProcessText(text, collapse = true) {
// Remove carriage returns // Remove carriage returns
text = text.replace(/\r/g, ''); text = text.replace(/\r/g, '');
@ -2041,7 +2050,7 @@ export async function fetchFaFile(name) {
style.remove(); style.remove();
return [...sheet.cssRules] return [...sheet.cssRules]
.filter(rule => rule.style?.content) .filter(rule => rule.style?.content)
.map(rule => rule.selectorText.split(/,\s*/).map(selector=>selector.split('::').shift().slice(1))) .map(rule => rule.selectorText.split(/,\s*/).map(selector => selector.split('::').shift().slice(1)))
; ;
} }
export async function fetchFa() { export async function fetchFa() {
@ -2068,7 +2077,7 @@ export async function showFontAwesomePicker(customList = null) {
qry.placeholder = 'Filter icons'; qry.placeholder = 'Filter icons';
qry.autofocus = true; qry.autofocus = true;
const qryDebounced = debounce(() => { const qryDebounced = debounce(() => {
const result = faList.filter(fa => fa.find(className=>className.includes(qry.value.toLowerCase()))); const result = faList.filter(fa => fa.find(className => className.includes(qry.value.toLowerCase())));
for (const fa of faList) { for (const fa of faList) {
if (!result.includes(fa)) { if (!result.includes(fa)) {
fas[fa].classList.add('hidden'); fas[fa].classList.add('hidden');
@ -2090,7 +2099,7 @@ export async function showFontAwesomePicker(customList = null) {
opt.classList.add('menu_button'); opt.classList.add('menu_button');
opt.classList.add('fa-solid'); opt.classList.add('fa-solid');
opt.classList.add(fa[0]); opt.classList.add(fa[0]);
opt.title = fa.map(it=>it.slice(3)).join(', '); opt.title = fa.map(it => it.slice(3)).join(', ');
opt.dataset.result = POPUP_RESULT.AFFIRMATIVE.toString(); opt.dataset.result = POPUP_RESULT.AFFIRMATIVE.toString();
opt.addEventListener('click', () => value = fa[0]); opt.addEventListener('click', () => value = fa[0]);
grid.append(opt); grid.append(opt);

View File

@ -993,6 +993,7 @@ body .panelControlBar {
padding: 0; padding: 0;
font-family: var(--mainFontFamily); font-family: var(--mainFontFamily);
font-weight: 400; font-weight: 400;
align-self: self-end;
} }
.swipe_left { .swipe_left {
@ -5462,4 +5463,4 @@ body:not(.movingUI) .drawer-content.maximized {
#AdvancedFormatting .autoSetHeight { #AdvancedFormatting .autoSetHeight {
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }

View File

@ -22,8 +22,8 @@ router.post('/caption-image', jsonParser, async (request, response) => {
{ text: request.body.prompt }, { text: request.body.prompt },
{ {
inlineData: { inlineData: {
mimeType: 'image/png', // It needs to specify a MIME type in data if it's not a PNG mimeType: mimeType,
data: mimeType === 'image/png' ? base64Data : request.body.image, data: base64Data,
}, },
}], }],
}], }],

View File

@ -811,6 +811,31 @@ drawthings.post('/generate', jsonParser, async (request, response) => {
const pollinations = express.Router(); const pollinations = express.Router();
pollinations.post('/models', jsonParser, async (_request, response) => {
try {
const modelsUrl = new URL('https://image.pollinations.ai/models');
const result = await fetch(modelsUrl);
if (!result.ok) {
console.log('Pollinations returned an error.', result.status, result.statusText);
throw new Error('Pollinations request failed.');
}
const data = await result.json();
if (!Array.isArray(data)) {
console.log('Pollinations returned invalid data.');
throw new Error('Pollinations request failed.');
}
const models = data.map(x => ({ value: x, text: x }));
return response.send(models);
} catch (error) {
console.log(error);
return response.sendStatus(500);
}
});
pollinations.post('/generate', jsonParser, async (request, response) => { pollinations.post('/generate', jsonParser, async (request, response) => {
try { try {
const promptUrl = new URL(`https://image.pollinations.ai/prompt/${encodeURIComponent(request.body.prompt)}`); const promptUrl = new URL(`https://image.pollinations.ai/prompt/${encodeURIComponent(request.body.prompt)}`);
@ -819,7 +844,6 @@ pollinations.post('/generate', jsonParser, async (request, response) => {
negative_prompt: String(request.body.negative_prompt), negative_prompt: String(request.body.negative_prompt),
seed: String(request.body.seed >= 0 ? request.body.seed : Math.floor(Math.random() * 10_000_000)), seed: String(request.body.seed >= 0 ? request.body.seed : Math.floor(Math.random() * 10_000_000)),
enhance: String(request.body.enhance ?? false), enhance: String(request.body.enhance ?? false),
refine: String(request.body.refine ?? false),
width: String(request.body.width ?? 1024), width: String(request.body.width ?? 1024),
height: String(request.body.height ?? 1024), height: String(request.body.height ?? 1024),
nologo: String(true), nologo: String(true),

View File

@ -335,10 +335,12 @@ function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '
if (part.type === 'text') { if (part.type === 'text') {
parts.push({ text: part.text }); parts.push({ text: part.text });
} else if (part.type === 'image_url' && isMultimodal) { } else if (part.type === 'image_url' && isMultimodal) {
const mimeType = part.image_url.url.split(';')[0].split(':')[1];
const base64Data = part.image_url.url.split(',')[1];
parts.push({ parts.push({
inlineData: { inlineData: {
mimeType: 'image/png', mimeType: mimeType,
data: part.image_url.url, data: base64Data,
}, },
}); });
hasImage = true; hasImage = true;