Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging

6cca977d077f5b10b035321fc161ebd9315583bc

RossAscends <124905043+RossAscends@users.noreply.github.com>

36 files changed, +758 -322Ignore whitespace
default/config.yaml+13 -0
@@ -51,6 +51,19 @@ requestProxy:
51enableUserAccounts: false51enableUserAccounts: false
52# Enable discreet login mode: hides user list on the login screen52# Enable discreet login mode: hides user list on the login screen
53enableDiscreetLogin: false53enableDiscreetLogin: false
54# Enable's authlia based auto login. Only enable this if you
55# have setup and installed Authelia as a middle-ware on your
56# reverse proxy
57# https://www.authelia.com/
58# This will use auto login to an account with the same username
59# as that used for authlia. (Ensure the username in authlia
60# is an exact match with that in sillytavern)
61autheliaAuth: false
62# If `basicAuthMode` and this are enabled then
63# the username and passwords for basic auth are the same as those
64# for the individual accounts
65perUserBasicAuth: false
66
54# User session timeout *in seconds* (defaults to 24 hours).67# User session timeout *in seconds* (defaults to 24 hours).
55## Set to a positive number to expire session after a certain time of inactivity68## Set to a positive number to expire session after a certain time of inactivity
56## Set to 0 to expire session when the browser is closed69## Set to 0 to expire session when the browser is closed
package-lock.json+15 -14
@@ -2067,21 +2067,21 @@
2067 }2067 }
2068 },2068 },
2069 "node_modules/cookie": {2069 "node_modules/cookie": {
2070 "version": "0.4.1",2070 "version": "0.7.2",
2071 "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",2071 "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
2072 "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",2072 "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
2073 "license": "MIT",2073 "license": "MIT",
2074 "engines": {2074 "engines": {
2075 "node": ">= 0.6"2075 "node": ">= 0.6"
2076 }2076 }
2077 },2077 },
2078 "node_modules/cookie-parser": {2078 "node_modules/cookie-parser": {
2079 "version": "1.4.6",2079 "version": "1.4.7",
2080 "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",2080 "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
2081 "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",2081 "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
2082 "license": "MIT",2082 "license": "MIT",
2083 "dependencies": {2083 "dependencies": {
2084 "cookie": "0.4.1",2084 "cookie": "0.7.2",
2085 "cookie-signature": "1.0.6"2085 "cookie-signature": "1.0.6"
2086 },2086 },
2087 "engines": {2087 "engines": {
@@ -2894,9 +2894,9 @@
2894 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="2894 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
2895 },2895 },
2896 "node_modules/express": {2896 "node_modules/express": {
2897 "version": "4.21.0",2897 "version": "4.21.1",
2898 "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",2898 "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
2899 "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",2899 "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
2900 "license": "MIT",2900 "license": "MIT",
2901 "dependencies": {2901 "dependencies": {
2902 "accepts": "~1.3.8",2902 "accepts": "~1.3.8",
@@ -2904,7 +2904,7 @@
2904 "body-parser": "1.20.3",2904 "body-parser": "1.20.3",
2905 "content-disposition": "0.5.4",2905 "content-disposition": "0.5.4",
2906 "content-type": "~1.0.4",2906 "content-type": "~1.0.4",
2907 "cookie": "0.6.0",2907 "cookie": "0.7.1",
2908 "cookie-signature": "1.0.6",2908 "cookie-signature": "1.0.6",
2909 "debug": "2.6.9",2909 "debug": "2.6.9",
2910 "depd": "2.0.0",2910 "depd": "2.0.0",
@@ -2936,9 +2936,10 @@
2936 }2936 }
2937 },2937 },
2938 "node_modules/express/node_modules/cookie": {2938 "node_modules/express/node_modules/cookie": {
2939 "version": "0.6.0",2939 "version": "0.7.1",
2940 "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",2940 "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
2941 "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",2941 "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
2942 "license": "MIT",
2942 "engines": {2943 "engines": {
2943 "node": ">= 0.6"2944 "node": ">= 0.6"
2944 }2945 }
public/index.html+33 -14
@@ -264,10 +264,10 @@
264 </div>264 </div>
265 <div class="range-block-range-and-counter">265 <div class="range-block-range-and-counter">
266 <div class="range-block-range">266 <div class="range-block-range">
267 <input type="range" id="rep_pen_novel" name="volume" min="1" max="8" step="0.05">267 <input type="range" id="rep_pen_novel" name="volume" min="1" max="8" step="0.025">
268 </div>268 </div>
269 <div class="range-block-counter">269 <div class="range-block-counter">
270 <input type="number" min="1" max="8" step="0.05" data-for="rep_pen_novel" id="rep_pen_counter_novel">270 <input type="number" min="1" max="8" step="0.025" data-for="rep_pen_novel" id="rep_pen_counter_novel">
271 </div>271 </div>
272 </div>272 </div>
273 </div>273 </div>
@@ -595,17 +595,6 @@
595 </span>595 </span>
596 </div>596 </div>
597 </div>597 </div>
598 <div class="range-block" data-source="cohere">
599 <label for="websearch_toggle" title="Enable Cohere web-search connector" data-i18n="[title]Enable Cohere web-search connector" class="checkbox_label widthFreeExpand">
600 <input id="websearch_toggle" type="checkbox" />
601 <span data-i18n="Web-search">Web-search</span>
602 </label>
603 <div class="toggle-description justifyLeft">
604 <span data-i18n="Allow the model to use the web-search connector.">
605 Allow the model to use the web-search connector.
606 </span>
607 </div>
608 </div>
609 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">598 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
610 <div class="range-block-title" data-i18n="Temperature">599 <div class="range-block-title" data-i18n="Temperature">
611 Temperature600 Temperature
@@ -2271,7 +2260,37 @@
2271 <div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">2260 <div data-for="api_key_featherless" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
2272 For privacy reasons, your API key will be hidden after you reload the page.2261 For privacy reasons, your API key will be hidden after you reload the page.
2273 </div>2262 </div>
2274 <select id="featherless_model">2263 <hr>
2264 <h4 data-i18n="Featherless Model Selection">Featherless Model Selection</h4>
2265 <div class="flex-container wide100p flexGap10">
2266 <div class="flex1 overflowHidden wide100p">
2267 <div class="flex-container marginBot10 alignitemscenter">
2268 <input id="featherless_model_search_bar" class="text_pole width100p flex1 margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search...">
2269 <select id="featherless_model_sort_order" class="margin0 text_pole">
2270 <option value="search" data-i18n="Search" hidden>A-Z</option>
2271 <option value="asc">A-Z</option>
2272 <option value="desc">Z-A</option>
2273 <option value="date_asc">Date Asc</option>
2274 <option value="date_desc">Date Desc</option>
2275 </select>
2276 <select id="featherless_category_selection" class="text_pole">
2277 <option value="" disabled selected data-i18n="category">category</option>
2278 <!-- <option value="Favorite" data-i18n="Top">Favorite</option> -->
2279 <option value="Top" data-i18n="Top">Top</option>
2280 <option value="New" data-i18n="New">New</option>
2281 <option value="All" data-i18n="All">All</option>
2282 </select>
2283 <select id="featherless_class_selection" class="text_pole">
2284 <option value="" selected data-i18n="class">All Classes</option>
2285 </select>
2286 <div id="featherless_model_pagination_container" class="flex1"></div>
2287 <i id="featherless_model_grid_toggle" class="fa-solid fa-table-cells-large menu_button" data-i18n="[title]Toggle grid view" title="Toggle grid view"></i>
2288 </div>
2289 <div id="featherless_model_card_block" data-i18n="[no_desc_text]No model description" no_desc_text="[No description]"></div>
2290 </div>
2291 </div>
2292 <!-- Do not remove. Needed for the /model command to function -->
2293 <select id="featherless_model" class="displayNone">
2275 <option value="" data-i18n="-- Connect to the API --">2294 <option value="" data-i18n="-- Connect to the API --">
2276 -- Connect to the API --2295 -- Connect to the API --
2277 </option>2296 </option>
public/locales/ar-sa.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "الحد الأقصى لطول الاستجابة (الرموز,الحرف)",56 "Max Response Length (tokens)": "الحد الأقصى لطول الاستجابة (الرموز,الحرف)",
57 "Multiple swipes per generation": "الضربات الشديدة المتعددة لكل جيل",57 "Multiple swipes per generation": "الضربات الشديدة المتعددة لكل جيل",
58 "Enable OpenAI completion streaming": "تمكين بث الاكتمال من OpenAI",58 "Enable OpenAI completion streaming": "تمكين بث الاكتمال من OpenAI",
59 "Enable Cohere web-search connector": "تمكين موصل بحث الويب Cohere",
60 "Web-search": "البحث في الويب",
61 "Allow the model to use the web-search connector.": "اسمح للنموذج باستخدام موصل بحث الويب.",
62 "Frequency Penalty": "عقوبة التكرار",59 "Frequency Penalty": "عقوبة التكرار",
63 "Presence Penalty": "عقوبة الوجود",60 "Presence Penalty": "عقوبة الوجود",
64 "Count Penalty": "عد ضربة جزاء",61 "Count Penalty": "عد ضربة جزاء",
public/locales/de-de.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Maximale Antwortlänge (Tokens)",56 "Max Response Length (tokens)": "Maximale Antwortlänge (Tokens)",
57 "Multiple swipes per generation": "Mehrere Swipes pro Generation",57 "Multiple swipes per generation": "Mehrere Swipes pro Generation",
58 "Enable OpenAI completion streaming": "OpenAI-Vervollständigungsstreaming aktivieren",58 "Enable OpenAI completion streaming": "OpenAI-Vervollständigungsstreaming aktivieren",
59 "Enable Cohere web-search connector": "Cohere-Websuch-Connector aktivieren",
60 "Web-search": "Web-Suche",
61 "Allow the model to use the web-search connector.": "Erlauben Sie dem Modell, den Websuch-Connector zu verwenden.",
62 "Frequency Penalty": "Frequenzstrafe",59 "Frequency Penalty": "Frequenzstrafe",
63 "Presence Penalty": "Präsenzstrafe",60 "Presence Penalty": "Präsenzstrafe",
64 "Count Penalty": "Strafe zählen",61 "Count Penalty": "Strafe zählen",
public/locales/es-es.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Longitud máxima de respuesta (tokens)",56 "Max Response Length (tokens)": "Longitud máxima de respuesta (tokens)",
57 "Multiple swipes per generation": "Múltiples golpes por generación",57 "Multiple swipes per generation": "Múltiples golpes por generación",
58 "Enable OpenAI completion streaming": "Activar streaming de completado de OpenAI",58 "Enable OpenAI completion streaming": "Activar streaming de completado de OpenAI",
59 "Enable Cohere web-search connector": "Habilitar el conector de búsqueda web de Cohere",
60 "Web-search": "Búsqueda Web",
61 "Allow the model to use the web-search connector.": "Permita que el modelo utilice el conector de búsqueda web.",
62 "Frequency Penalty": "Penalización de frecuencia",59 "Frequency Penalty": "Penalización de frecuencia",
63 "Presence Penalty": "Penalización de presencia",60 "Presence Penalty": "Penalización de presencia",
64 "Count Penalty": "Penalización de conteo",61 "Count Penalty": "Penalización de conteo",
public/locales/fr-fr.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Longueur maximale de la réponse (tokens)",56 "Max Response Length (tokens)": "Longueur maximale de la réponse (tokens)",
57 "Multiple swipes per generation": "Plusieurs balayages par génération",57 "Multiple swipes per generation": "Plusieurs balayages par génération",
58 "Enable OpenAI completion streaming": "Activer le streaming de complétion OpenAI",58 "Enable OpenAI completion streaming": "Activer le streaming de complétion OpenAI",
59 "Enable Cohere web-search connector": "Activer le connecteur de recherche Web Cohere",
60 "Web-search": "Recherche Internet",
61 "Allow the model to use the web-search connector.": "Autorisez le modèle à utiliser le connecteur de recherche Web.",
62 "Frequency Penalty": "Pénalité de fréquence",59 "Frequency Penalty": "Pénalité de fréquence",
63 "Presence Penalty": "Pénalité de présence",60 "Presence Penalty": "Pénalité de présence",
64 "Count Penalty": "Pénalité de décompte",61 "Count Penalty": "Pénalité de décompte",
public/locales/is-is.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Hámarks lengd svörunar (í táknum)",56 "Max Response Length (tokens)": "Hámarks lengd svörunar (í táknum)",
57 "Multiple swipes per generation": "Mörg högg á hverja kynslóð",57 "Multiple swipes per generation": "Mörg högg á hverja kynslóð",
58 "Enable OpenAI completion streaming": "Virkja OpenAI klárastreymi",58 "Enable OpenAI completion streaming": "Virkja OpenAI klárastreymi",
59 "Enable Cohere web-search connector": "Virkja Cohere vefleitartengi",
60 "Web-search": "Vefleit",
61 "Allow the model to use the web-search connector.": "Leyfðu líkaninu að nota vefleitartengið.",
62 "Frequency Penalty": "Tíðnarefning",59 "Frequency Penalty": "Tíðnarefning",
63 "Presence Penalty": "Tilkoma refning",60 "Presence Penalty": "Tilkoma refning",
64 "Count Penalty": "Telja víti",61 "Count Penalty": "Telja víti",
public/locales/it-it.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Lunghezza massima della risposta (token)",56 "Max Response Length (tokens)": "Lunghezza massima della risposta (token)",
57 "Multiple swipes per generation": "Più passaggi per generazione",57 "Multiple swipes per generation": "Più passaggi per generazione",
58 "Enable OpenAI completion streaming": "Abilita lo streaming di completamento OpenAI",58 "Enable OpenAI completion streaming": "Abilita lo streaming di completamento OpenAI",
59 "Enable Cohere web-search connector": "Abilita il connettore di ricerca web Cohere",
60 "Web-search": "Ricerca sul web",
61 "Allow the model to use the web-search connector.": "Consenti al modello di utilizzare il connettore di ricerca web.",
62 "Frequency Penalty": "Penalità di frequenza",59 "Frequency Penalty": "Penalità di frequenza",
63 "Presence Penalty": "Penalità di presenza",60 "Presence Penalty": "Penalità di presenza",
64 "Count Penalty": "Conte Penalità",61 "Count Penalty": "Conte Penalità",
public/locales/ja-jp.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "最大応答長(トークン数)",56 "Max Response Length (tokens)": "最大応答長(トークン数)",
57 "Multiple swipes per generation": "世代ごとに複数のスワイプ",57 "Multiple swipes per generation": "世代ごとに複数のスワイプ",
58 "Enable OpenAI completion streaming": "OpenAIの完了ストリーミングを有効にする",58 "Enable OpenAI completion streaming": "OpenAIの完了ストリーミングを有効にする",
59 "Enable Cohere web-search connector": "Cohereウェブ検索コネクタを有効にする",
60 "Web-search": "ウェブ検索",
61 "Allow the model to use the web-search connector.": "モデルが Web 検索コネクタを使用できるようにします。",
62 "Frequency Penalty": "頻度ペナルティ",59 "Frequency Penalty": "頻度ペナルティ",
63 "Presence Penalty": "存在ペナルティ",60 "Presence Penalty": "存在ペナルティ",
64 "Count Penalty": "カウントペナルティ",61 "Count Penalty": "カウントペナルティ",
public/locales/ko-kr.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "최대 응답 길이 (토큰)",56 "Max Response Length (tokens)": "최대 응답 길이 (토큰)",
57 "Multiple swipes per generation": "세대당 다중 스와이프",57 "Multiple swipes per generation": "세대당 다중 스와이프",
58 "Enable OpenAI completion streaming": "OpenAI 완성 스트리밍 활성화",58 "Enable OpenAI completion streaming": "OpenAI 완성 스트리밍 활성화",
59 "Enable Cohere web-search connector": "Cohere 웹 검색 커넥터 활성화",
60 "Web-search": "웹 서핑",
61 "Allow the model to use the web-search connector.": "모델이 웹 검색 커넥터를 사용하도록 허용합니다.",
62 "Frequency Penalty": "빈도 패널티",59 "Frequency Penalty": "빈도 패널티",
63 "Presence Penalty": "존재 패널티",60 "Presence Penalty": "존재 패널티",
64 "Count Penalty": "카운트 페널티",61 "Count Penalty": "카운트 페널티",
public/locales/nl-nl.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Maximale lengte van het antwoord (tokens)",56 "Max Response Length (tokens)": "Maximale lengte van het antwoord (tokens)",
57 "Multiple swipes per generation": "Meerdere swipes per generatie",57 "Multiple swipes per generation": "Meerdere swipes per generatie",
58 "Enable OpenAI completion streaming": "OpenAI voltooiingsstreaming inschakelen",58 "Enable OpenAI completion streaming": "OpenAI voltooiingsstreaming inschakelen",
59 "Enable Cohere web-search connector": "Schakel de Cohere-webzoekconnector in",
60 "Web-search": "Web-zoeken",
61 "Allow the model to use the web-search connector.": "Sta toe dat het model de webzoekconnector gebruikt.",
62 "Frequency Penalty": "Frequentieboete",59 "Frequency Penalty": "Frequentieboete",
63 "Presence Penalty": "Aanwezigheidsboete",60 "Presence Penalty": "Aanwezigheidsboete",
64 "Count Penalty": "Tel straf",61 "Count Penalty": "Tel straf",
public/locales/pt-pt.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Comprimento Máximo da Resposta (tokens)",56 "Max Response Length (tokens)": "Comprimento Máximo da Resposta (tokens)",
57 "Multiple swipes per generation": "Vários furtos por geração",57 "Multiple swipes per generation": "Vários furtos por geração",
58 "Enable OpenAI completion streaming": "Ativar streaming de conclusão do OpenAI",58 "Enable OpenAI completion streaming": "Ativar streaming de conclusão do OpenAI",
59 "Enable Cohere web-search connector": "Ativar o conector de pesquisa na Web Cohere",
60 "Web-search": "Pesquisa na internet",
61 "Allow the model to use the web-search connector.": "Permita que o modelo use o conector de pesquisa na Web.",
62 "Frequency Penalty": "Pena de Frequência",59 "Frequency Penalty": "Pena de Frequência",
63 "Presence Penalty": "Pena de Presença",60 "Presence Penalty": "Pena de Presença",
64 "Count Penalty": "Contar penalidade",61 "Count Penalty": "Contar penalidade",
public/locales/ru-ru.json+0 -3
@@ -772,7 +772,6 @@
772 "Type a message, or /? for help": "Введите сообщение, или /? для получения справки",772 "Type a message, or /? for help": "Введите сообщение, или /? для получения справки",
773 "Welcome to SillyTavern!": "Добро пожаловать в SillyTavern!",773 "Welcome to SillyTavern!": "Добро пожаловать в SillyTavern!",
774 "Won't be shared with the character card on export.": "Не попадут в карточку персонажа при экспорте.",774 "Won't be shared with the character card on export.": "Не попадут в карточку персонажа при экспорте.",
775 "Web-search": "Веб-поиск",
776 "Persona Name:": "Имя персоны:",775 "Persona Name:": "Имя персоны:",
777 "User first message": "Первое сообщение пользователя",776 "User first message": "Первое сообщение пользователя",
778 "extension_token_counter": "Токенов:",777 "extension_token_counter": "Токенов:",
@@ -1200,8 +1199,6 @@
1200 "Streaming_desc": "Выводить текст последовательно по мере его генерации.\rЕсли параметр выключен, ответы будут отображаться сразу целиком, и только после полного завершения генерации.",1199 "Streaming_desc": "Выводить текст последовательно по мере его генерации.\rЕсли параметр выключен, ответы будут отображаться сразу целиком, и только после полного завершения генерации.",
1201 "Max prompt cost:": "Max prompt cost:",1200 "Max prompt cost:": "Max prompt cost:",
1202 "TFS": "TFS",1201 "TFS": "TFS",
1203 "Enable Cohere web-search connector": "Enable Cohere web-search connector",
1204 "Allow the model to use the web-search connector.": "Allow the model to use the web-search connector.",
1205 "Count Penalty": "Count Penalty",1202 "Count Penalty": "Count Penalty",
1206 "Min P": "Min P",1203 "Min P": "Min P",
1207 "NSFW": "NSFW",1204 "NSFW": "NSFW",
public/locales/uk-ua.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Довжина відповіді (токени)",56 "Max Response Length (tokens)": "Довжина відповіді (токени)",
57 "Multiple swipes per generation": "Кілька свайпів за покоління",57 "Multiple swipes per generation": "Кілька свайпів за покоління",
58 "Enable OpenAI completion streaming": "Увімкнути потокове завершення OpenAI",58 "Enable OpenAI completion streaming": "Увімкнути потокове завершення OpenAI",
59 "Enable Cohere web-search connector": "Увімкнути конектор веб-пошуку Cohere",
60 "Web-search": "Веб-пошук",
61 "Allow the model to use the web-search connector.": "Дозвольте моделі використовувати конектор веб-пошуку.",
62 "Frequency Penalty": "Штраф за частоту",59 "Frequency Penalty": "Штраф за частоту",
63 "Presence Penalty": "Штраф за наявність",60 "Presence Penalty": "Штраф за наявність",
64 "Count Penalty": "Рахувати пенальті",61 "Count Penalty": "Рахувати пенальті",
public/locales/vi-vn.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "Độ dài phản hồi tối đa (token)",56 "Max Response Length (tokens)": "Độ dài phản hồi tối đa (token)",
57 "Multiple swipes per generation": "Vuốt nhiều lần trong một lần tạo",57 "Multiple swipes per generation": "Vuốt nhiều lần trong một lần tạo",
58 "Enable OpenAI completion streaming": "Bật streaming của OpenAI",58 "Enable OpenAI completion streaming": "Bật streaming của OpenAI",
59 "Enable Cohere web-search connector": "Bật web tìm kiếm của Cohere",
60 "Web-search": "Tìm kiếm trên web",
61 "Allow the model to use the web-search connector.": "Cho phép model sử dụng trình kết nối tìm kiếm trên web.",
62 "Frequency Penalty": "Frequency Penalty",59 "Frequency Penalty": "Frequency Penalty",
63 "Presence Penalty": "Presence Penalty",60 "Presence Penalty": "Presence Penalty",
64 "Count Penalty": "Count Penalty",61 "Count Penalty": "Count Penalty",
public/locales/zh-cn.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "最大回复长度(以词符数计)",56 "Max Response Length (tokens)": "最大回复长度(以词符数计)",
57 "Multiple swipes per generation": "每次生成多个备选回复",57 "Multiple swipes per generation": "每次生成多个备选回复",
58 "Enable OpenAI completion streaming": "启用OpenAI文本补全流式传输",58 "Enable OpenAI completion streaming": "启用OpenAI文本补全流式传输",
59 "Enable Cohere web-search connector": "启用 Cohere 网络搜索连接器",
60 "Web-search": "联网搜索",
61 "Allow the model to use the web-search connector.": "允许模型使用联网搜索。",
62 "Frequency Penalty": "频率惩罚",59 "Frequency Penalty": "频率惩罚",
63 "Presence Penalty": "存在惩罚",60 "Presence Penalty": "存在惩罚",
64 "Count Penalty": "计数惩罚",61 "Count Penalty": "计数惩罚",
public/locales/zh-tw.json+0 -3
@@ -56,9 +56,6 @@
56 "Max Response Length (tokens)": "最大回應長度(符記數)",56 "Max Response Length (tokens)": "最大回應長度(符記數)",
57 "Multiple swipes per generation": "每次生成多次滑動",57 "Multiple swipes per generation": "每次生成多次滑動",
58 "Enable OpenAI completion streaming": "啟用 OpenAI 補充串流",58 "Enable OpenAI completion streaming": "啟用 OpenAI 補充串流",
59 "Enable Cohere web-search connector": "啟用 Cohere 網頁搜尋連接器",
60 "Web-search": "網頁搜尋",
61 "Allow the model to use the web-search connector.": "允許模型使用網頁搜尋連接器",
62 "Frequency Penalty": "頻率懲罰",59 "Frequency Penalty": "頻率懲罰",
63 "Presence Penalty": "存在懲罰",60 "Presence Penalty": "存在懲罰",
64 "Count Penalty": "計數懲罰",61 "Count Penalty": "計數懲罰",
public/script.js+1 -1
@@ -5436,7 +5436,7 @@ function extractMessageFromData(data) {
5436 case 'novel':5436 case 'novel':
5437 return data.output;5437 return data.output;
5438 case 'openai':5438 case 'openai':
5439 return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? '';5439 return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
5440 default:5440 default:
5441 return '';5441 return '';
5442 }5442 }
public/scripts/login.js+7 -1
@@ -180,7 +180,13 @@ function displayError(message) {
180 * Preserves the query string.180 * Preserves the query string.
181 */181 */
182function redirectToHome() {182function redirectToHome() {
183 window.location.href = '/' + window.location.search;183 // After a login theres no need to preserve the
184 // noauto (if present)
185 const urlParams = new URLSearchParams(window.location.search);
186
187 urlParams.delete('noauto');
188
189 window.location.href = '/' + urlParams.toString();
184}190}
185191
186/**192/**
public/scripts/nai-settings.js+3 -3
@@ -245,7 +245,7 @@ function loadNovelSettingsUi(ui_settings) {
245 $('#temp_novel').val(ui_settings.temperature);245 $('#temp_novel').val(ui_settings.temperature);
246 $('#temp_counter_novel').val(Number(ui_settings.temperature).toFixed(2));246 $('#temp_counter_novel').val(Number(ui_settings.temperature).toFixed(2));
247 $('#rep_pen_novel').val(ui_settings.repetition_penalty);247 $('#rep_pen_novel').val(ui_settings.repetition_penalty);
248 $('#rep_pen_counter_novel').val(Number(ui_settings.repetition_penalty).toFixed(2));248 $('#rep_pen_counter_novel').val(Number(ui_settings.repetition_penalty).toFixed(3));
249 $('#rep_pen_size_novel').val(ui_settings.repetition_penalty_range);249 $('#rep_pen_size_novel').val(ui_settings.repetition_penalty_range);
250 $('#rep_pen_size_counter_novel').val(Number(ui_settings.repetition_penalty_range).toFixed(0));250 $('#rep_pen_size_counter_novel').val(Number(ui_settings.repetition_penalty_range).toFixed(0));
251 $('#rep_pen_slope_novel').val(ui_settings.repetition_penalty_slope);251 $('#rep_pen_slope_novel').val(ui_settings.repetition_penalty_slope);
@@ -298,8 +298,8 @@ const sliders = [
298 {298 {
299 sliderId: '#rep_pen_novel',299 sliderId: '#rep_pen_novel',
300 counterId: '#rep_pen_counter_novel',300 counterId: '#rep_pen_counter_novel',
301 format: (val) => Number(val).toFixed(2),301 format: (val) => Number(val).toFixed(3),
302 setValue: (val) => { nai_settings.repetition_penalty = Number(val).toFixed(2); },302 setValue: (val) => { nai_settings.repetition_penalty = Number(val).toFixed(3); },
303 },303 },
304 {304 {
305 sliderId: '#rep_pen_size_novel',305 sliderId: '#rep_pen_size_novel',
public/scripts/openai.js+5 -15
@@ -225,7 +225,6 @@ const default_settings = {
225 top_a_openai: 0,225 top_a_openai: 0,
226 repetition_penalty_openai: 1,226 repetition_penalty_openai: 1,
227 stream_openai: false,227 stream_openai: false,
228 websearch_cohere: false,
229 openai_max_context: max_4k,228 openai_max_context: max_4k,
230 openai_max_tokens: 300,229 openai_max_tokens: 300,
231 wrap_in_quotes: false,230 wrap_in_quotes: false,
@@ -302,7 +301,6 @@ const oai_settings = {
302 top_a_openai: 0,301 top_a_openai: 0,
303 repetition_penalty_openai: 1,302 repetition_penalty_openai: 1,
304 stream_openai: false,303 stream_openai: false,
305 websearch_cohere: false,
306 openai_max_context: max_4k,304 openai_max_context: max_4k,
307 openai_max_tokens: 300,305 openai_max_tokens: 300,
308 wrap_in_quotes: false,306 wrap_in_quotes: false,
@@ -1847,7 +1845,6 @@ async function sendOpenAIRequest(type, messages, signal) {
1847 generate_data['frequency_penalty'] = Math.min(Math.max(Number(oai_settings.freq_pen_openai), 0), 1);1845 generate_data['frequency_penalty'] = Math.min(Math.max(Number(oai_settings.freq_pen_openai), 0), 1);
1848 generate_data['presence_penalty'] = Math.min(Math.max(Number(oai_settings.pres_pen_openai), 0), 1);1846 generate_data['presence_penalty'] = Math.min(Math.max(Number(oai_settings.pres_pen_openai), 0), 1);
1849 generate_data['stop'] = getCustomStoppingStrings(5);1847 generate_data['stop'] = getCustomStoppingStrings(5);
1850 generate_data['websearch'] = oai_settings.websearch_cohere;
1851 }1848 }
18521849
1853 if (isPerplexity) {1850 if (isPerplexity) {
@@ -1976,12 +1973,14 @@ async function sendOpenAIRequest(type, messages, signal) {
1976}1973}
19771974
1978function getStreamingReply(data) {1975function getStreamingReply(data) {
1979 if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {1976 if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
1980 return data?.delta?.text || '';1977 return data?.delta?.text || '';
1981 } else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {1978 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
1982 return data?.candidates?.[0]?.content?.parts?.[0]?.text || '';1979 return data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
1980 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
1981 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
1983 } else {1982 } else {
1984 return data.choices[0]?.delta?.content ?? data.choices[0]?.message?.content ?? data.choices[0]?.text ?? '';1983 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
1985 }1984 }
1986}1985}
19871986
@@ -2857,7 +2856,6 @@ function loadOpenAISettings(data, settings) {
2857 oai_settings.min_p_openai = settings.min_p_openai ?? default_settings.min_p_openai;2856 oai_settings.min_p_openai = settings.min_p_openai ?? default_settings.min_p_openai;
2858 oai_settings.repetition_penalty_openai = settings.repetition_penalty_openai ?? default_settings.repetition_penalty_openai;2857 oai_settings.repetition_penalty_openai = settings.repetition_penalty_openai ?? default_settings.repetition_penalty_openai;
2859 oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai;2858 oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai;
2860 oai_settings.websearch_cohere = settings.websearch_cohere ?? default_settings.websearch_cohere;
2861 oai_settings.openai_max_context = settings.openai_max_context ?? default_settings.openai_max_context;2859 oai_settings.openai_max_context = settings.openai_max_context ?? default_settings.openai_max_context;
2862 oai_settings.openai_max_tokens = settings.openai_max_tokens ?? default_settings.openai_max_tokens;2860 oai_settings.openai_max_tokens = settings.openai_max_tokens ?? default_settings.openai_max_tokens;
2863 oai_settings.bias_preset_selected = settings.bias_preset_selected ?? default_settings.bias_preset_selected;2861 oai_settings.bias_preset_selected = settings.bias_preset_selected ?? default_settings.bias_preset_selected;
@@ -2931,7 +2929,6 @@ function loadOpenAISettings(data, settings) {
2931 if (settings.use_makersuite_sysprompt !== undefined) oai_settings.use_makersuite_sysprompt = !!settings.use_makersuite_sysprompt;2929 if (settings.use_makersuite_sysprompt !== undefined) oai_settings.use_makersuite_sysprompt = !!settings.use_makersuite_sysprompt;
2932 if (settings.use_alt_scale !== undefined) { oai_settings.use_alt_scale = !!settings.use_alt_scale; updateScaleForm(); }2930 if (settings.use_alt_scale !== undefined) { oai_settings.use_alt_scale = !!settings.use_alt_scale; updateScaleForm(); }
2933 $('#stream_toggle').prop('checked', oai_settings.stream_openai);2931 $('#stream_toggle').prop('checked', oai_settings.stream_openai);
2934 $('#websearch_toggle').prop('checked', oai_settings.websearch_cohere);
2935 $('#api_url_scale').val(oai_settings.api_url_scale);2932 $('#api_url_scale').val(oai_settings.api_url_scale);
2936 $('#openai_proxy_password').val(oai_settings.proxy_password);2933 $('#openai_proxy_password').val(oai_settings.proxy_password);
2937 $('#claude_assistant_prefill').val(oai_settings.assistant_prefill);2934 $('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
@@ -3258,7 +3255,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
3258 personality_format: settings.personality_format,3255 personality_format: settings.personality_format,
3259 group_nudge_prompt: settings.group_nudge_prompt,3256 group_nudge_prompt: settings.group_nudge_prompt,
3260 stream_openai: settings.stream_openai,3257 stream_openai: settings.stream_openai,
3261 websearch_cohere: settings.websearch_cohere,
3262 prompts: settings.prompts,3258 prompts: settings.prompts,
3263 prompt_order: settings.prompt_order,3259 prompt_order: settings.prompt_order,
3264 api_url_scale: settings.api_url_scale,3260 api_url_scale: settings.api_url_scale,
@@ -3682,7 +3678,6 @@ function onSettingsPresetChange() {
3682 personality_format: ['#personality_format_textarea', 'personality_format', false],3678 personality_format: ['#personality_format_textarea', 'personality_format', false],
3683 group_nudge_prompt: ['#group_nudge_prompt_textarea', 'group_nudge_prompt', false],3679 group_nudge_prompt: ['#group_nudge_prompt_textarea', 'group_nudge_prompt', false],
3684 stream_openai: ['#stream_toggle', 'stream_openai', true],3680 stream_openai: ['#stream_toggle', 'stream_openai', true],
3685 websearch_cohere: ['#websearch_toggle', 'websearch_cohere', true],
3686 prompts: ['', 'prompts', false],3681 prompts: ['', 'prompts', false],
3687 prompt_order: ['', 'prompt_order', false],3682 prompt_order: ['', 'prompt_order', false],
3688 api_url_scale: ['#api_url_scale', 'api_url_scale', false],3683 api_url_scale: ['#api_url_scale', 'api_url_scale', false],
@@ -4846,11 +4841,6 @@ export function initOpenAI() {
4846 saveSettingsDebounced();4841 saveSettingsDebounced();
4847 });4842 });
48484843
4849 $('#websearch_toggle').on('change', function () {
4850 oai_settings.websearch_cohere = !!$('#websearch_toggle').prop('checked');
4851 saveSettingsDebounced();
4852 });
4853
4854 $('#wrap_in_quotes').on('change', function () {4844 $('#wrap_in_quotes').on('change', function () {
4855 oai_settings.wrap_in_quotes = !!$('#wrap_in_quotes').prop('checked');4845 oai_settings.wrap_in_quotes = !!$('#wrap_in_quotes').prop('checked');
4856 saveSettingsDebounced();4846 saveSettingsDebounced();
public/scripts/sse-stream.js+14 -2
@@ -108,9 +108,21 @@ function getDelay(s) {
108 * @returns {AsyncGenerator<{data: object, chunk: string}>} The parsed data and the chunk to be sent.108 * @returns {AsyncGenerator<{data: object, chunk: string}>} The parsed data and the chunk to be sent.
109 */109 */
110async function* parseStreamData(json) {110async function* parseStreamData(json) {
111 // Cohere
112 if (typeof json.delta.message === 'object' && ['tool-plan-delta', 'content-delta'].includes(json.type)) {
113 const text = json?.delta?.message?.content?.text ?? '';
114 for (let i = 0; i < text.length; i++) {
115 const str = json.delta.message.content.text[i];
116 yield {
117 data: { ...json, delta: { message: { content: { text: str } } } },
118 chunk: str,
119 };
120 }
121 return;
122 }
111 // Claude123 // Claude
112 if (typeof json.delta === 'object') {124 if (typeof json.delta === 'object' && typeof json.delta.text === 'string') {
113 if (typeof json.delta.text === 'string' && json.delta.text.length > 0) {125 if (json.delta.text.length > 0) {
114 for (let i = 0; i < json.delta.text.length; i++) {126 for (let i = 0; i < json.delta.text.length; i++) {
115 const str = json.delta.text[i];127 const str = json.delta.text[i];
116 yield {128 yield {
public/scripts/textgen-models.js+234 -27
@@ -4,6 +4,7 @@ import { textgenerationwebui_settings as textgen_settings, textgen_types } from
4import { tokenizers } from './tokenizers.js';4import { tokenizers } from './tokenizers.js';
5import { renderTemplateAsync } from './templates.js';5import { renderTemplateAsync } from './templates.js';
6import { POPUP_TYPE, callGenericPopup } from './popup.js';6import { POPUP_TYPE, callGenericPopup } from './popup.js';
7import { PAGINATION_TEMPLATE } from './utils.js';
78
8let mancerModels = [];9let mancerModels = [];
9let togetherModels = [];10let togetherModels = [];
@@ -265,19 +266,212 @@ export async function loadAphroditeModels(data) {
265 }266 }
266}267}
267268
269let featherlessCurrentPage = 1;
268export async function loadFeatherlessModels(data) {270export async function loadFeatherlessModels(data) {
271 const searchBar = document.getElementById('featherless_model_search_bar');
272 const modelCardBlock = document.getElementById('featherless_model_card_block');
273 const paginationContainer = $('#featherless_model_pagination_container');
274 const sortOrderSelect = document.getElementById('featherless_model_sort_order');
275 const classSelect = document.getElementById('featherless_class_selection');
276 const categoriesSelect = document.getElementById('featherless_category_selection');
277 const storageKey = 'FeatherlessModels_PerPage';
278
279 // Store the original models data for search and filtering
280 let originalModels = [];
281
269 if (!Array.isArray(data)) {282 if (!Array.isArray(data)) {
270 console.error('Invalid Featherless models data', data);283 console.error('Invalid Featherless models data', data);
271 return;284 return;
272 }285 }
273286
287 // Sort the data by model id (default A-Z)
274 data.sort((a, b) => a.id.localeCompare(b.id));288 data.sort((a, b) => a.id.localeCompare(b.id));
289 originalModels = data; // Store the original data for search
275 featherlessModels = data;290 featherlessModels = data;
276291
277 if (!data.find(x => x.id === textgen_settings.featherless_model)) {292 // Populate class select options with unique classes
278 textgen_settings.featherless_model = data[0]?.id || '';293 populateClassSelection(data);
294
295 // Retrieve the stored number of items per page or default to 5
296 const perPage = Number(localStorage.getItem(storageKey)) || 10;
297
298 // Initialize pagination with the full set of models
299 const selectedModelPage = (data.findIndex(x => x.id === textgen_settings.featherless_model) / perPage) + 1;
300 featherlessCurrentPage = selectedModelPage > 0 ? selectedModelPage : 1;
301 setupPagination(originalModels, perPage);
302
303 // Function to set up pagination (also used for filtered results)
304 function setupPagination(models, perPage, pageNumber = featherlessCurrentPage) {
305 paginationContainer.pagination({
306 dataSource: models,
307 pageSize: perPage,
308 pageNumber: pageNumber,
309 sizeChangerOptions: [6, 10, 26, 50, 100, 250, 500, 1000],
310 pageRange: 1,
311 showPageNumbers: true,
312 showSizeChanger: false,
313 prevText: '<',
314 nextText: '>',
315 formatNavigator: function (currentPage, totalPage) {
316 return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ' of ' + totalPage * perPage;
317 },
318 showNavigator: true,
319 callback: function (modelsOnPage, pagination) {
320 modelCardBlock.innerHTML = '';
321
322 modelsOnPage.forEach(model => {
323 const card = document.createElement('div');
324 card.classList.add('model-card');
325
326 const modelNameContainer = document.createElement('div');
327 modelNameContainer.classList.add('model-name-container');
328
329 const modelTitle = document.createElement('div');
330 modelTitle.classList.add('model-title');
331 modelTitle.textContent = model.id.replace(/_/g, '_\u200B');
332 modelNameContainer.appendChild(modelTitle);
333
334 const detailsContainer = document.createElement('div');
335 detailsContainer.classList.add('details-container');
336
337 const modelClassDiv = document.createElement('div');
338 modelClassDiv.classList.add('model-class');
339 modelClassDiv.textContent = `Class: ${model.model_class || 'N/A'}`;
340
341 const contextLengthDiv = document.createElement('div');
342 contextLengthDiv.classList.add('model-context-length');
343 contextLengthDiv.textContent = `Context Length: ${model.context_length}`;
344
345 const dateAddedDiv = document.createElement('div');
346 dateAddedDiv.classList.add('model-date-added');
347 dateAddedDiv.textContent = `Added On: ${new Date(model.updated_at).toLocaleDateString()}`;
348
349 detailsContainer.appendChild(modelClassDiv);
350 detailsContainer.appendChild(contextLengthDiv);
351 detailsContainer.appendChild(dateAddedDiv);
352
353 card.appendChild(modelNameContainer);
354 card.appendChild(detailsContainer);
355
356 modelCardBlock.appendChild(card);
357
358 if (model.id === textgen_settings.featherless_model) {
359 card.classList.add('selected');
360 }
361
362 card.addEventListener('click', function () {
363 document.querySelectorAll('.model-card').forEach(c => c.classList.remove('selected'));
364 card.classList.add('selected');
365 onFeatherlessModelSelect(model.id);
366 });
367 });
368
369 // Update the current page value whenever the page changes
370 featherlessCurrentPage = pagination.pageNumber;
371 },
372 afterSizeSelectorChange: function (e) {
373 const newPerPage = e.target.value;
374 localStorage.setItem('Models_PerPage', newPerPage);
375 setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number
376 },
377 });
378 }
379
380 // Unset previously added listeners
381 $(searchBar).off('input');
382 $(sortOrderSelect).off('change');
383 $(classSelect).off('change');
384 $(categoriesSelect).off('change');
385
386 // Add event listener for input on the search bar
387 searchBar.addEventListener('input', function () {
388 applyFiltersAndSort();
389 });
390
391 // Add event listener for the sort order select
392 sortOrderSelect.addEventListener('change', function () {
393 applyFiltersAndSort();
394 });
395
396 // Add event listener for the class select
397 classSelect.addEventListener('change', function () {
398 applyFiltersAndSort();
399 });
400
401 categoriesSelect.addEventListener('change', function () {
402 applyFiltersAndSort();
403 });
404
405 // Function to populate class selection dropdown
406 function populateClassSelection(models) {
407 const uniqueClasses = [...new Set(models.map(model => model.model_class).filter(Boolean))]; // Get unique class names
408 uniqueClasses.sort((a, b) => a.localeCompare(b));
409 uniqueClasses.forEach(className => {
410 const option = document.createElement('option');
411 option.value = className;
412 option.textContent = className;
413 classSelect.appendChild(option);
414 });
279 }415 }
280416
417 // Function to apply sorting and filtering based on user input
418 async function applyFiltersAndSort() {
419 if (!(searchBar instanceof HTMLInputElement) ||
420 !(sortOrderSelect instanceof HTMLSelectElement) ||
421 !(classSelect instanceof HTMLSelectElement) ||
422 !(categoriesSelect instanceof HTMLSelectElement)) {
423 return;
424 }
425 const searchQuery = searchBar.value.toLowerCase();
426 const selectedSortOrder = sortOrderSelect.value;
427 const selectedClass = classSelect.value;
428 const selectedCategory = categoriesSelect.value;
429 let featherlessTop = [];
430 let featherlessNew = [];
431
432 if (selectedCategory === 'Top') {
433 featherlessTop = await fetchFeatherlessStats();
434 }
435 const featherlessIds = featherlessTop.map(stat => stat.id);
436 if (selectedCategory === 'New') {
437 featherlessNew = await fetchFeatherlessNew();
438 }
439 const featherlessNewIds = featherlessNew.map(stat => stat.id);
440
441 let filteredModels = originalModels.filter(model => {
442 const matchesSearch = model.id.toLowerCase().includes(searchQuery);
443 const matchesClass = selectedClass ? model.model_class === selectedClass : true;
444 const matchesTop = featherlessIds.includes(model.id);
445 const matchesNew = featherlessNewIds.includes(model.id);
446
447 if (selectedCategory === 'All') {
448 return matchesSearch && matchesClass;
449 }
450 else if (selectedCategory === 'Top') {
451 return matchesSearch && matchesClass && matchesTop;
452 }
453 else if (selectedCategory === 'New') {
454 return matchesSearch && matchesClass && matchesNew;
455 }
456 else {
457 return matchesSearch;
458 }
459 });
460
461 if (selectedSortOrder === 'asc') {
462 filteredModels.sort((a, b) => a.id.localeCompare(b.id));
463 } else if (selectedSortOrder === 'desc') {
464 filteredModels.sort((a, b) => b.id.localeCompare(a.id));
465 } else if (selectedSortOrder === 'date_asc') {
466 filteredModels.sort((a, b) => a.updated_at.localeCompare(b.updated_at));
467 } else if (selectedSortOrder === 'date_desc') {
468 filteredModels.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
469 }
470
471 setupPagination(filteredModels, Number(localStorage.getItem(storageKey)) || perPage, featherlessCurrentPage);
472 }
473
474 // Required to keep the /model command function
281 $('#featherless_model').empty();475 $('#featherless_model').empty();
282 for (const model of data) {476 for (const model of data) {
283 const option = document.createElement('option');477 const option = document.createElement('option');
@@ -288,15 +482,49 @@ export async function loadFeatherlessModels(data) {
288 }482 }
289}483}
290484
291function onFeatherlessModelSelect() {485async function fetchFeatherlessStats() {
292 const modelId = String($('#featherless_model').val());486 const response = await fetch('https://api.featherless.ai/feather/popular');
487 const data = await response.json();
488 return data.popular;
489}
490
491async function fetchFeatherlessNew() {
492 const response = await fetch('https://api.featherless.ai/feather/models?sort=-created_at&perPage=10');
493 const data = await response.json();
494 return data.items;
495}
496
497function onFeatherlessModelSelect(modelId) {
498 const model = featherlessModels.find(x => x.id === modelId);
293 textgen_settings.featherless_model = modelId;499 textgen_settings.featherless_model = modelId;
500 $('#featherless_model').val(modelId);
294 $('#api_button_textgenerationwebui').trigger('click');501 $('#api_button_textgenerationwebui').trigger('click');
295 const model = featherlessModels.find(x => x.id === modelId);
296 setGenerationParamsFromPreset({ max_length: model.context_length });502 setGenerationParamsFromPreset({ max_length: model.context_length });
297}503}
298504
505let featherlessIsGridView = false; // Default state set to grid view
506
507// Ensure the correct initial view is applied when the page loads
508document.addEventListener('DOMContentLoaded', function () {
509 const modelCardBlock = document.getElementById('featherless_model_card_block');
510 modelCardBlock.classList.add('list-view');
511
512 const toggleButton = document.getElementById('featherless_model_grid_toggle');
513 toggleButton.addEventListener('click', function () {
514 // Toggle between grid and list view
515 if (featherlessIsGridView) {
516 modelCardBlock.classList.remove('grid-view');
517 modelCardBlock.classList.add('list-view');
518 this.title = 'Toggle to grid view';
519 } else {
520 modelCardBlock.classList.remove('list-view');
521 modelCardBlock.classList.add('grid-view');
522 this.title = 'Toggle to list view';
523 }
299524
525 featherlessIsGridView = !featherlessIsGridView;
526 });
527});
300function onMancerModelSelect() {528function onMancerModelSelect() {
301 const modelId = String($('#mancer_model').val());529 const modelId = String($('#mancer_model').val());
302 textgen_settings.mancer_model = modelId;530 textgen_settings.mancer_model = modelId;
@@ -469,20 +697,6 @@ function getAphroditeModelTemplate(option) {
469 `));697 `));
470}698}
471699
472function getFeatherlessModelTemplate(option) {
473 const model = featherlessModels.find(x => x.id === option?.element?.value);
474
475 if (!option.id || !model) {
476 return option.text;
477 }
478
479 return $((`
480 <div class="flex-container flexFlowColumn">
481 <div><strong>${DOMPurify.sanitize(model.name)}</strong> | <span>${model.context_length || '???'} tokens</span></div>
482 </div>
483 `));
484}
485
486async function downloadOllamaModel() {700async function downloadOllamaModel() {
487 try {701 try {
488 const serverUrl = textgen_settings.server_urls[textgen_types.OLLAMA];702 const serverUrl = textgen_settings.server_urls[textgen_types.OLLAMA];
@@ -670,9 +884,9 @@ export function initTextGenModels() {
670 $('#ollama_download_model').on('click', downloadOllamaModel);884 $('#ollama_download_model').on('click', downloadOllamaModel);
671 $('#vllm_model').on('change', onVllmModelSelect);885 $('#vllm_model').on('change', onVllmModelSelect);
672 $('#aphrodite_model').on('change', onAphroditeModelSelect);886 $('#aphrodite_model').on('change', onAphroditeModelSelect);
673 $('#featherless_model').on('change', onFeatherlessModelSelect);
674 $('#tabby_download_model').on('click', downloadTabbyModel);887 $('#tabby_download_model').on('click', downloadTabbyModel);
675 $('#tabby_model').on('change', onTabbyModelSelect);888 $('#tabby_model').on('change', onTabbyModelSelect);
889 $('#featherless_model').on('change', () => onFeatherlessModelSelect(String($('#featherless_model').val())));
676890
677 const providersSelect = $('.openrouter_providers');891 const providersSelect = $('.openrouter_providers');
678 for (const provider of OPENROUTER_PROVIDERS) {892 for (const provider of OPENROUTER_PROVIDERS) {
@@ -745,13 +959,6 @@ export function initTextGenModels() {
745 width: '100%',959 width: '100%',
746 templateResult: getAphroditeModelTemplate,960 templateResult: getAphroditeModelTemplate,
747 });961 });
748 $('#featherless_model').select2({
749 placeholder: 'Select a model',
750 searchInputPlaceholder: 'Search models...',
751 searchInputCssClass: 'text_pole',
752 width: '100%',
753 templateResult: getFeatherlessModelTemplate,
754 });
755 providersSelect.select2({962 providersSelect.select2({
756 sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),963 sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
757 placeholder: 'Select providers. No selection = all providers.',964 placeholder: 'Select providers. No selection = all providers.',
public/scripts/textgen-settings.js+1 -0
@@ -193,6 +193,7 @@ const settings = {
193 openrouter_allow_fallbacks: true,193 openrouter_allow_fallbacks: true,
194 xtc_threshold: 0.1,194 xtc_threshold: 0.1,
195 xtc_probability: 0,195 xtc_probability: 0,
196 featherless_model: '',
196};197};
197198
198export let textgenerationwebui_banned_in_macros = [];199export let textgenerationwebui_banned_in_macros = [];
public/scripts/tool-calling.js+36 -3
@@ -292,10 +292,10 @@ export class ToolManager {
292292
293 if (error instanceof Error) {293 if (error instanceof Error) {
294 error.cause = name;294 error.cause = name;
295 return error;295 return error.toString();
296 }296 }
297297
298 return new Error('Unknown error occurred while invoking the tool.', { cause: name });298 return new Error('Unknown error occurred while invoking the tool.', { cause: name }).toString();
299 }299 }
300 }300 }
301301
@@ -365,6 +365,9 @@ export class ToolManager {
365 * @returns {void}365 * @returns {void}
366 */366 */
367 static parseToolCalls(toolCalls, parsed) {367 static parseToolCalls(toolCalls, parsed) {
368 if (!this.isToolCallingSupported()) {
369 return;
370 }
368 if (Array.isArray(parsed?.choices)) {371 if (Array.isArray(parsed?.choices)) {
369 for (const choice of parsed.choices) {372 for (const choice of parsed.choices) {
370 const choiceIndex = (typeof choice.index === 'number') ? choice.index : null;373 const choiceIndex = (typeof choice.index === 'number') ? choice.index : null;
@@ -401,6 +404,22 @@ export class ToolManager {
401 }404 }
402 }405 }
403 }406 }
407 const cohereToolEvents = ['message-start', 'tool-call-start', 'tool-call-delta', 'tool-call-end'];
408 if (cohereToolEvents.includes(parsed?.type) && typeof parsed?.delta?.message === 'object') {
409 const choiceIndex = 0;
410 const toolCallIndex = parsed?.index ?? 0;
411
412 if (!Array.isArray(toolCalls[choiceIndex])) {
413 toolCalls[choiceIndex] = [];
414 }
415
416 if (toolCalls[choiceIndex][toolCallIndex] === undefined) {
417 toolCalls[choiceIndex][toolCallIndex] = {};
418 }
419
420 const targetToolCall = toolCalls[choiceIndex][toolCallIndex];
421 ToolManager.#applyToolCallDelta(targetToolCall, parsed.delta.message);
422 }
404 if (typeof parsed?.content_block === 'object') {423 if (typeof parsed?.content_block === 'object') {
405 const choiceIndex = 0;424 const choiceIndex = 0;
406 const toolCallIndex = parsed?.index ?? 0;425 const toolCallIndex = parsed?.index ?? 0;
@@ -503,6 +522,7 @@ export class ToolManager {
503 chat_completion_sources.CLAUDE,522 chat_completion_sources.CLAUDE,
504 chat_completion_sources.OPENROUTER,523 chat_completion_sources.OPENROUTER,
505 chat_completion_sources.GROQ,524 chat_completion_sources.GROQ,
525 chat_completion_sources.COHERE,
506 ];526 ];
507 return supportedSources.includes(oai_settings.chat_completion_source);527 return supportedSources.includes(oai_settings.chat_completion_source);
508 }528 }
@@ -529,7 +549,15 @@ export class ToolManager {
529549
530 // Parsed tool calls from streaming data550 // Parsed tool calls from streaming data
531 if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) {551 if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) {
532 return isClaudeToolCall(data[0]) ? data[0].filter(x => x).map(convertClaudeToolCall) : data[0];552 if (isClaudeToolCall(data[0])) {
553 return data[0].filter(x => x).map(convertClaudeToolCall);
554 }
555
556 if (typeof data[0]?.[0]?.tool_calls === 'object') {
557 return Array.isArray(data[0]?.[0]?.tool_calls) ? data[0][0].tool_calls : [data[0][0].tool_calls];
558 }
559
560 return data[0];
533 }561 }
534562
535 // Parsed tool calls from non-streaming data563 // Parsed tool calls from non-streaming data
@@ -550,6 +578,11 @@ export class ToolManager {
550 return content;578 return content;
551 }579 }
552 }580 }
581
582 // Cohere tool calls
583 if (typeof data?.message?.tool_calls === 'object') {
584 return Array.isArray(data?.message?.tool_calls) ? data.message.tool_calls : [data.message.tool_calls];
585 }
553 }586 }
554587
555 /**588 /**
public/scripts/user.js+8 -1
@@ -848,7 +848,14 @@ async function logout() {
848 headers: getRequestHeaders(),848 headers: getRequestHeaders(),
849 });849 });
850850
851 window.location.reload();851 // On an explicit logout stop auto login
852 // to allow user to change username even
853 // when auto auth (such as authelia or basic)
854 // would be valid
855 const urlParams = new URLSearchParams(window.location.search);
856 urlParams.set('noauto', 'true');
857
858 window.location.search = urlParams.toString();
852}859}
853860
854/**861/**
public/scripts/variables.js+79 -0
@@ -836,6 +836,40 @@ function randValuesCallback(from, to, args) {
836 return value;836 return value;
837}837}
838838
839function customSortComparitor(a, b) {
840 if (typeof a != typeof b) {
841 a = typeof a;
842 b = typeof b;
843 }
844 return a > b ? 1 : a < b ? -1 : 0;
845}
846
847function sortArrayObjectCallback(args, value) {
848 let parsedValue;
849 if (typeof value == 'string') {
850 try {
851 parsedValue = JSON.parse(value);
852 } catch {
853 // return the original input if it was invalid
854 return value;
855 }
856 } else {
857 parsedValue = value;
858 }
859 if (Array.isArray(parsedValue)) {
860 // always sort lists by value
861 parsedValue.sort(customSortComparitor);
862 } else if (typeof parsedValue == 'object') {
863 let keysort = args.keysort;
864 if (isFalseBoolean(keysort)) {
865 parsedValue = Object.keys(parsedValue).sort(function (a, b) { return customSortComparitor(parsedValue[a], parsedValue[b]); });
866 } else {
867 parsedValue = Object.keys(parsedValue).sort(customSortComparitor);
868 }
869 }
870 return JSON.stringify(parsedValue);
871}
872
839/**873/**
840 * Declare a new variable in the current scope.874 * Declare a new variable in the current scope.
841 * @param {NamedArguments} args Named arguments.875 * @param {NamedArguments} args Named arguments.
@@ -2110,6 +2144,51 @@ export function registerVariableCommands() {
2110 `,2144 `,
2111 }));2145 }));
2112 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2146 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2147 name: 'sort',
2148 callback: sortArrayObjectCallback,
2149 returns: 'the sorted list or dictionary keys',
2150 namedArgumentList: [
2151 SlashCommandNamedArgument.fromProps({ name: 'keysort',
2152 description: 'whether to sort by key or value; ignored for lists',
2153 typeList: [ARGUMENT_TYPE.BOOLEAN],
2154 enumList: ['true', 'false'],
2155 defaultValue: 'true',
2156 }),
2157 ],
2158 unnamedArgumentList: [
2159 SlashCommandArgument.fromProps({
2160 description: 'value',
2161 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY],
2162 isRequired: true,
2163 forceEnum: false,
2164 }),
2165 ],
2166 helpString: `
2167 <div>
2168 Sorts a list or dictionary in ascending order and passes the result down the pipe.
2169 <ul>
2170 <li>
2171 For lists, returns the list sorted by value.
2172 </li>
2173 <li>
2174 For dictionaries, returns the ordered list of keys after sorting. Setting keysort=false means keys are sorted by associated value.
2175 </li>
2176 </ul>
2177 </div>
2178 <div>
2179 <strong>Examples:</strong>
2180 <ul>
2181 <li>
2182 <pre><code class="language-stscript">/sort [5,3,4,1,2] | /echo</code></pre>
2183 </li>
2184 <li>
2185 <pre><code class="language-stscript">/sort keysort=false {"a": 1, "d": 3, "c": 2, "b": 5} | /echo</code></pre>
2186 </li>
2187 </ul>
2188 </div>
2189 `,
2190 }));
2191 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2113 name: 'rand',2192 name: 'rand',
2114 callback: (args, value) => String(randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value ? value : 1)), args)),2193 callback: (args, value) => String(randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value ? value : 1)), args)),
2115 returns: 'random number',2194 returns: 'random number',
public/style.css+126 -0
@@ -5547,3 +5547,129 @@ body:not(.movingUI) .drawer-content.maximized {
5547#InstructSequencesColumn details:not(:last-of-type) {5547#InstructSequencesColumn details:not(:last-of-type) {
5548 margin-bottom: 5px;5548 margin-bottom: 5px;
5549}5549}
5550
5551#user_avatar_block {
5552 display: flex;
5553 flex-wrap: wrap;
5554 gap: 10px;
5555}
5556
5557/* Main structure for the model cards */
5558.model-card {
5559 display: flex;
5560 justify-content: space-between;
5561 align-items: center;
5562 padding: 5px;
5563 border: 1px solid #333;
5564 border-radius: 8px;
5565 background-color: #222;
5566 color: #fff;
5567 margin: 7px;
5568 width: calc(100% - 7px);
5569 box-sizing: border-box;
5570 transition: transform 0.2s ease-in-out, background-color 0.2s ease-in-out, border 0.2s ease-in-out;
5571}
5572
5573.model-card .details-container {
5574 text-align: right;
5575}
5576
5577.model-card:hover {
5578 transform: scale(1.01);
5579 background-color: #444;
5580 transition: transform 0.2s ease-in-out, background-color 0.2s ease-in-out; /* Smooth transition */
5581}
5582
5583.model-card.selected {
5584 border: 2px solid var(--okGreen70a);
5585 background-color: var(--okGreen70a);
5586}
5587
5588.model-info {
5589 flex: 1;
5590 white-space: nowrap;
5591 overflow: hidden;
5592 text-overflow: ellipsis;
5593}
5594
5595.model-title {
5596 font-size: 13px;
5597 font-weight: bold;
5598 overflow: hidden;
5599}
5600
5601.model-details {
5602 display: flex;
5603 flex-direction: column;
5604 align-items: flex-end;
5605 text-align: right;
5606 min-width: 120px;
5607}
5608
5609.model-class, .model-context-length, .model-date-added {
5610 font-size: 10px;
5611}
5612
5613.model-class, .model-context-length {
5614 margin-bottom: 5px;
5615}
5616
5617#featherless_model_pagination_container .paginationjs-nav {
5618 min-width: max-content;
5619}
5620
5621#featherless_model_card_block.grid-view {
5622 grid-template-columns: repeat(2, 1fr);
5623 display: flex;
5624 flex-wrap: wrap;
5625 /* gap: 3px; */
5626 justify-content: flex-start;
5627}
5628
5629/* Grid-view card */
5630#featherless_model_card_block.grid-view .model-card {
5631 flex-direction: column;
5632 flex: 1 1 calc(50% - 30px);
5633}
5634
5635#featherless_model_search_bar {
5636 width: 15ch;
5637 flex-grow: 0;
5638 align-self: center;
5639}
5640
5641#featherless_model_sort_order {
5642 width: auto;
5643 flex-shrink: 0;
5644 align-self: center;
5645}
5646
5647#featherless_model_grid_toggle {
5648 flex-shrink: 0;
5649 width: auto;
5650 cursor: pointer;
5651}
5652
5653#featherless_category_selection,
5654#featherless_class_selection {
5655 display: flex;
5656 width: auto;
5657 align-self: center;
5658}
5659
5660@media (max-width: 768px) {
5661 .model-card {
5662 flex-direction: column;
5663 align-items: stretch;
5664 }
5665
5666 .model-info, .model-details, .model-date-added {
5667 width: 100%;
5668 text-align: left;
5669 }
5670
5671 #featherless_model_search_bar {
5672 width: 100%;
5673 }
5674
5675}
server.js+10 -4
@@ -65,6 +65,7 @@ const DEFAULT_WHITELIST = true;
65const DEFAULT_ACCOUNTS = false;65const DEFAULT_ACCOUNTS = false;
66const DEFAULT_CSRF_DISABLED = false;66const DEFAULT_CSRF_DISABLED = false;
67const DEFAULT_BASIC_AUTH = false;67const DEFAULT_BASIC_AUTH = false;
68const DEFAULT_PER_USER_BASIC_AUTH = false;
6869
69const DEFAULT_ENABLE_IPV6 = false;70const DEFAULT_ENABLE_IPV6 = false;
70const DEFAULT_ENABLE_IPV4 = true;71const DEFAULT_ENABLE_IPV4 = true;
@@ -184,6 +185,7 @@ const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode'
184const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');185const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
185const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);186const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
186const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);187const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
188const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);
187const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);189const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
188190
189const uploadsPath = path.join(dataRoot, require('./src/constants').UPLOADS_DIRECTORY);191const uploadsPath = path.join(dataRoot, require('./src/constants').UPLOADS_DIRECTORY);
@@ -361,7 +363,7 @@ app.get('/login', async (request, response) => {
361 }363 }
362364
363 try {365 try {
364 const autoLogin = await userModule.tryAutoLogin(request);366 const autoLogin = await userModule.tryAutoLogin(request, basicAuthMode);
365367
366 if (autoLogin) {368 if (autoLogin) {
367 return response.redirect('/');369 return response.redirect('/');
@@ -756,9 +758,13 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
756 }758 }
757759
758 if (basicAuthMode) {760 if (basicAuthMode) {
759 const basicAuthUser = getConfigValue('basicAuthUser', {});761 if (perUserBasicAuth && !enableAccounts) {
760 if (!basicAuthUser?.username || !basicAuthUser?.password) {762 console.error(color.red('Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.'));
761 console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));763 } else if (!perUserBasicAuth) {
764 const basicAuthUser = getConfigValue('basicAuthUser', {});
765 if (!basicAuthUser?.username || !basicAuthUser?.password) {
766 console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));
767 }
762 }768 }
763 }769 }
764};770};
src/cohere-stream.js+0 -126
@@ -1,126 +0,0 @@
1const DATA_PREFIX = 'data:';
2
3/**
4 * Borrowed from Cohere SDK (MIT License)
5 * https://github.com/cohere-ai/cohere-typescript/blob/main/src/core/streaming-fetcher/Stream.ts
6 * Copyright (c) 2021 Cohere
7 */
8class CohereStream {
9 /** @type {ReadableStream} */
10 stream;
11 /** @type {string} */
12 prefix;
13 /** @type {string} */
14 messageTerminator;
15 /** @type {string|undefined} */
16 streamTerminator;
17 /** @type {AbortController} */
18 controller = new AbortController();
19
20 constructor({ stream, eventShape }) {
21 this.stream = stream;
22 if (eventShape.type === 'sse') {
23 this.prefix = DATA_PREFIX;
24 this.messageTerminator = '\n';
25 this.streamTerminator = eventShape.streamTerminator;
26 } else {
27 this.messageTerminator = eventShape.messageTerminator;
28 }
29 }
30
31 async *iterMessages() {
32 const stream = readableStreamAsyncIterable(this.stream);
33 let buf = '';
34 let prefixSeen = false;
35 let parsedAnyMessages = false;
36 for await (const chunk of stream) {
37 buf += this.decodeChunk(chunk);
38
39 let terminatorIndex;
40 // Parse the chunk into as many messages as possible
41 while ((terminatorIndex = buf.indexOf(this.messageTerminator)) >= 0) {
42 // Extract the line from the buffer
43 let line = buf.slice(0, terminatorIndex + 1);
44 buf = buf.slice(terminatorIndex + 1);
45
46 // Skip empty lines
47 if (line.length === 0) {
48 continue;
49 }
50
51 // Skip the chunk until the prefix is found
52 if (!prefixSeen && this.prefix != null) {
53 const prefixIndex = line.indexOf(this.prefix);
54 if (prefixIndex === -1) {
55 continue;
56 }
57 prefixSeen = true;
58 line = line.slice(prefixIndex + this.prefix.length);
59 }
60
61 // If the stream terminator is present, return
62 if (this.streamTerminator != null && line.includes(this.streamTerminator)) {
63 return;
64 }
65
66 // Otherwise, yield message from the prefix to the terminator
67 const message = JSON.parse(line);
68 yield message;
69 prefixSeen = false;
70 parsedAnyMessages = true;
71 }
72 }
73
74 if (!parsedAnyMessages && buf.length > 0) {
75 try {
76 yield JSON.parse(buf);
77 } catch (e) {
78 console.error('Error parsing message:', e);
79 }
80 }
81 }
82
83 async *[Symbol.asyncIterator]() {
84 for await (const message of this.iterMessages()) {
85 yield message;
86 }
87 }
88
89 decodeChunk(chunk) {
90 const decoder = new TextDecoder('utf8');
91 return decoder.decode(chunk);
92 }
93}
94
95function readableStreamAsyncIterable(stream) {
96 if (stream[Symbol.asyncIterator]) {
97 return stream;
98 }
99
100 const reader = stream.getReader();
101 return {
102 async next() {
103 try {
104 const result = await reader.read();
105 if (result?.done) {
106 reader.releaseLock();
107 } // release lock when stream becomes closed
108 return result;
109 } catch (e) {
110 reader.releaseLock(); // release lock when stream becomes errored
111 throw e;
112 }
113 },
114 async return() {
115 const cancelPromise = reader.cancel();
116 reader.releaseLock();
117 await cancelPromise;
118 return { done: true, value: undefined };
119 },
120 [Symbol.asyncIterator]() {
121 return this;
122 },
123 };
124}
125
126module.exports = CohereStream;
src/endpoints/backends/chat-completions.js+14 -18
@@ -5,7 +5,6 @@ const { jsonParser } = require('../../express-common');
5const { CHAT_COMPLETION_SOURCES, GEMINI_SAFETY, BISON_SAFETY, OPENROUTER_HEADERS } = require('../../constants');5const { CHAT_COMPLETION_SOURCES, GEMINI_SAFETY, BISON_SAFETY, OPENROUTER_HEADERS } = require('../../constants');
6const { forwardFetchResponse, getConfigValue, tryParse, uuidv4, mergeObjectWithYaml, excludeKeysByYaml, color } = require('../../util');6const { forwardFetchResponse, getConfigValue, tryParse, uuidv4, mergeObjectWithYaml, excludeKeysByYaml, color } = require('../../util');
7const { convertClaudeMessages, convertGooglePrompt, convertTextCompletionPrompt, convertCohereMessages, convertMistralMessages, convertAI21Messages, mergeMessages } = require('../../prompt-converters');7const { convertClaudeMessages, convertGooglePrompt, convertTextCompletionPrompt, convertCohereMessages, convertMistralMessages, convertAI21Messages, mergeMessages } = require('../../prompt-converters');
8const CohereStream = require('../../cohere-stream');
98
10const { readSecret, SECRET_KEYS } = require('../secrets');9const { readSecret, SECRET_KEYS } = require('../secrets');
11const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sentencepieceTokenizers, TEXT_COMPLETION_MODELS } = require('../tokenizers');10const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sentencepieceTokenizers, TEXT_COMPLETION_MODELS } = require('../tokenizers');
@@ -13,7 +12,8 @@ const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sente
13const API_OPENAI = 'https://api.openai.com/v1';12const API_OPENAI = 'https://api.openai.com/v1';
14const API_CLAUDE = 'https://api.anthropic.com/v1';13const API_CLAUDE = 'https://api.anthropic.com/v1';
15const API_MISTRAL = 'https://api.mistral.ai/v1';14const API_MISTRAL = 'https://api.mistral.ai/v1';
16const API_COHERE = 'https://api.cohere.ai/v1';15const API_COHERE_V1 = 'https://api.cohere.ai/v1';
16const API_COHERE_V2 = 'https://api.cohere.ai/v2';
17const API_PERPLEXITY = 'https://api.perplexity.ai';17const API_PERPLEXITY = 'https://api.perplexity.ai';
18const API_GROQ = 'https://api.groq.com/openai/v1';18const API_GROQ = 'https://api.groq.com/openai/v1';
19const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';19const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
@@ -553,13 +553,14 @@ async function sendCohereRequest(request, response) {
553553
554 try {554 try {
555 const convertedHistory = convertCohereMessages(request.body.messages, request.body.char_name, request.body.user_name);555 const convertedHistory = convertCohereMessages(request.body.messages, request.body.char_name, request.body.user_name);
556 const connectors = [];
557 const tools = [];556 const tools = [];
558557
559 const canDoWebSearch = !String(request.body.model).includes('c4ai-aya');558 if (Array.isArray(request.body.tools) && request.body.tools.length > 0) {
560 if (request.body.websearch && canDoWebSearch) {559 tools.push(...request.body.tools);
561 connectors.push({560 tools.forEach(tool => {
562 id: 'web-search',561 if (tool?.function?.parameters?.$schema) {
562 delete tool.function.parameters.$schema;
563 }
563 });564 });
564 }565 }
565566
@@ -567,9 +568,7 @@ async function sendCohereRequest(request, response) {
567 const requestBody = {568 const requestBody = {
568 stream: Boolean(request.body.stream),569 stream: Boolean(request.body.stream),
569 model: request.body.model,570 model: request.body.model,
570 message: convertedHistory.userPrompt,571 messages: convertedHistory.chatHistory,
571 preamble: convertedHistory.systemPrompt,
572 chat_history: convertedHistory.chatHistory,
573 temperature: request.body.temperature,572 temperature: request.body.temperature,
574 max_tokens: request.body.max_tokens,573 max_tokens: request.body.max_tokens,
575 k: request.body.top_k,574 k: request.body.top_k,
@@ -578,16 +577,13 @@ async function sendCohereRequest(request, response) {
578 stop_sequences: request.body.stop,577 stop_sequences: request.body.stop,
579 frequency_penalty: request.body.frequency_penalty,578 frequency_penalty: request.body.frequency_penalty,
580 presence_penalty: request.body.presence_penalty,579 presence_penalty: request.body.presence_penalty,
581 prompt_truncation: 'AUTO_PRESERVE_ORDER',
582 connectors: connectors,
583 documents: [],580 documents: [],
584 tools: tools,581 tools: tools,
585 search_queries_only: false,
586 };582 };
587583
588 const canDoSafetyMode = String(request.body.model).endsWith('08-2024');584 const canDoSafetyMode = String(request.body.model).endsWith('08-2024');
589 if (canDoSafetyMode) {585 if (canDoSafetyMode) {
590 requestBody.safety_mode = 'NONE';586 requestBody.safety_mode = 'OFF';
591 }587 }
592588
593 console.log('Cohere request:', requestBody);589 console.log('Cohere request:', requestBody);
@@ -603,11 +599,11 @@ async function sendCohereRequest(request, response) {
603 timeout: 0,599 timeout: 0,
604 };600 };
605601
606 const apiUrl = API_COHERE + '/chat';602 const apiUrl = API_COHERE_V2 + '/chat';
607603
608 if (request.body.stream) {604 if (request.body.stream) {
609 const stream = await global.fetch(apiUrl, config);605 const stream = await fetch(apiUrl, config);
610 parseCohereStream(stream, request, response);606 forwardFetchResponse(stream, response);
611 } else {607 } else {
612 const generateResponse = await fetch(apiUrl, config);608 const generateResponse = await fetch(apiUrl, config);
613 if (!generateResponse.ok) {609 if (!generateResponse.ok) {
@@ -658,7 +654,7 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
658 headers = {};654 headers = {};
659 mergeObjectWithYaml(headers, request.body.custom_include_headers);655 mergeObjectWithYaml(headers, request.body.custom_include_headers);
660 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COHERE) {656 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COHERE) {
661 api_url = API_COHERE;657 api_url = API_COHERE_V1;
662 api_key_openai = readSecret(request.user.directories, SECRET_KEYS.COHERE);658 api_key_openai = readSecret(request.user.directories, SECRET_KEYS.COHERE);
663 headers = {};659 headers = {};
664 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZEROONEAI) {660 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZEROONEAI) {
src/middleware/basicAuth.js+20 -5
@@ -2,14 +2,19 @@
2 * When applied, this middleware will ensure the request contains the required header for basic authentication and only2 * When applied, this middleware will ensure the request contains the required header for basic authentication and only
3 * allow access to the endpoint after successful authentication.3 * allow access to the endpoint after successful authentication.
4 */4 */
5const { getConfig } = require('../util.js');5const { getAllUserHandles, toKey, getPasswordHash } = require('../users.js');
6const { getConfig, getConfigValue } = require('../util.js');
7const storage = require('node-persist');
8
9const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
10const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
611
7const unauthorizedResponse = (res) => {12const unauthorizedResponse = (res) => {
8 res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');13 res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');
9 return res.status(401).send('Authentication required');14 return res.status(401).send('Authentication required');
10};15};
1116
12const basicAuthMiddleware = function (request, response, callback) {17const basicAuthMiddleware = async function (request, response, callback) {
13 const config = getConfig();18 const config = getConfig();
14 const authHeader = request.headers.authorization;19 const authHeader = request.headers.authorization;
1520
@@ -23,15 +28,25 @@ const basicAuthMiddleware = function (request, response, callback) {
23 return unauthorizedResponse(response);28 return unauthorizedResponse(response);
24 }29 }
2530
31 const usePerUserAuth = PER_USER_BASIC_AUTH && ENABLE_ACCOUNTS;
26 const [username, password] = Buffer.from(credentials, 'base64')32 const [username, password] = Buffer.from(credentials, 'base64')
27 .toString('utf8')33 .toString('utf8')
28 .split(':');34 .split(':');
2935
30 if (username === config.basicAuthUser.username && password === config.basicAuthUser.password) {36 if (!usePerUserAuth && username === config.basicAuthUser.username && password === config.basicAuthUser.password) {
31 return callback();37 return callback();
32 } else {38 } else if (usePerUserAuth) {
33 return unauthorizedResponse(response);39 const userHandles = await getAllUserHandles();
40 for (const userHandle of userHandles) {
41 if (username === userHandle) {
42 const user = await storage.getItem(toKey(userHandle));
43 if (user && user.enabled && (user.password && user.password === getPasswordHash(password, user.salt))) {
44 return callback();
45 }
46 }
47 }
34 }48 }
49 return unauthorizedResponse(response);
35};50};
3651
37module.exports = basicAuthMiddleware;52module.exports = basicAuthMiddleware;
src/prompt-converters.js+34 -39
@@ -277,38 +277,9 @@ function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools,
277 * @param {object[]} messages Array of messages277 * @param {object[]} messages Array of messages
278 * @param {string} charName Character name278 * @param {string} charName Character name
279 * @param {string} userName User name279 * @param {string} userName User name
280 * @returns {{systemPrompt: string, chatHistory: object[], userPrompt: string}} Prompt for Cohere280 * @returns {{chatHistory: object[]}} Prompt for Cohere
281 */281 */
282function convertCohereMessages(messages, charName = '', userName = '') {282function convertCohereMessages(messages, charName = '', userName = '') {
283 const roleMap = {
284 'system': 'SYSTEM',
285 'user': 'USER',
286 'assistant': 'CHATBOT',
287 };
288 let systemPrompt = '';
289
290 // Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array.
291 let i;
292 for (i = 0; i < messages.length; i++) {
293 if (messages[i].role !== 'system') {
294 break;
295 }
296 // Append example names if not already done by the frontend (e.g. for group chats).
297 if (userName && messages[i].name === 'example_user') {
298 if (!messages[i].content.startsWith(`${userName}: `)) {
299 messages[i].content = `${userName}: ${messages[i].content}`;
300 }
301 }
302 if (charName && messages[i].name === 'example_assistant') {
303 if (!messages[i].content.startsWith(`${charName}: `)) {
304 messages[i].content = `${charName}: ${messages[i].content}`;
305 }
306 }
307 systemPrompt += `${messages[i].content}\n\n`;
308 }
309
310 messages.splice(0, i);
311
312 if (messages.length === 0) {283 if (messages.length === 0) {
313 messages.unshift({284 messages.unshift({
314 role: 'user',285 role: 'user',
@@ -316,17 +287,41 @@ function convertCohereMessages(messages, charName = '', userName = '') {
316 });287 });
317 }288 }
318289
319 const lastNonSystemMessageIndex = messages.findLastIndex(msg => msg.role === 'user' || msg.role === 'assistant');290 messages.forEach((msg, index) => {
320 const userPrompt = messages.slice(lastNonSystemMessageIndex).map(msg => msg.content).join('\n\n') || PROMPT_PLACEHOLDER;291 // Tool calls require an assistent primer
321292 if (Array.isArray(msg.tool_calls)) {
322 const chatHistory = messages.slice(0, lastNonSystemMessageIndex).map(msg => {293 if (index > 0 && messages[index - 1].role === 'assistant') {
323 return {294 msg.content = messages[index - 1].content;
324 role: roleMap[msg.role] || 'USER',295 messages.splice(index - 1, 1);
325 message: msg.content,296 } else {
326 };297 msg.content = `I'm going to call a tool for that: ${msg.tool_calls.map(tc => tc?.function?.name).join(', ')}`;
298 }
299 }
300 // No names support (who would've thought)
301 if (msg.name) {
302 if (msg.role == 'system' && msg.name == 'example_assistant') {
303 if (charName && !msg.content.startsWith(`${charName}: `)) {
304 msg.content = `${charName}: ${msg.content}`;
305 }
306 }
307 if (msg.role == 'system' && msg.name == 'example_user') {
308 if (userName && !msg.content.startsWith(`${userName}: `)) {
309 msg.content = `${userName}: ${msg.content}`;
310 }
311 }
312 if (msg.role !== 'system' && !msg.content.startsWith(`${msg.name}: `)) {
313 msg.content = `${msg.name}: ${msg.content}`;
314 }
315 delete msg.name;
316 }
327 });317 });
328318
329 return { systemPrompt: systemPrompt.trim(), chatHistory, userPrompt };319 // A prompt should end with a user/tool message
320 if (messages.length && !['user', 'tool'].includes(messages[messages.length - 1].role)) {
321 messages[messages.length - 1].role = 'user';
322 }
323
324 return { chatHistory: messages };
330}325}
331326
332/**327/**
src/users.js+101 -1
@@ -19,6 +19,8 @@ const { readSecret, writeSecret } = require('./endpoints/secrets');
19const KEY_PREFIX = 'user:';19const KEY_PREFIX = 'user:';
20const AVATAR_PREFIX = 'avatar:';20const AVATAR_PREFIX = 'avatar:';
21const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);21const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
22const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false);
23const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
22const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');24const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
2325
24/**26/**
@@ -567,14 +569,43 @@ function shouldRedirectToLogin(request) {
567569
568/**570/**
569 * Tries auto-login if there is only one user and it's not password protected.571 * Tries auto-login if there is only one user and it's not password protected.
572 * or another configured method such authlia or basic
570 * @param {import('express').Request} request Request object573 * @param {import('express').Request} request Request object
574 * @param {boolean} basicAuthMode If Basic auth mode is enabled
571 * @returns {Promise<boolean>} Whether auto-login was performed575 * @returns {Promise<boolean>} Whether auto-login was performed
572 */576 */
573async function tryAutoLogin(request) {577async function tryAutoLogin(request, basicAuthMode) {
574 if (!ENABLE_ACCOUNTS || request.user || !request.session) {578 if (!ENABLE_ACCOUNTS || request.user || !request.session) {
575 return false;579 return false;
576 }580 }
577581
582 if (!request.query.noauto) {
583 if (await singleUserLogin(request)) {
584 return true;
585 }
586
587 if (AUTHELIA_AUTH && await autheliaUserLogin(request)) {
588 return true;
589 }
590
591 if (basicAuthMode && PER_USER_BASIC_AUTH && await basicUserLogin(request)) {
592 return true;
593 }
594 }
595
596 return false;
597}
598
599/**
600 * Tries auto-login if there is only one user and it's not password protected.
601 * @param {import('express').Request} request Request object
602 * @returns {Promise<boolean>} Whether auto-login was performed
603 */
604async function singleUserLogin(request) {
605 if (!request.session) {
606 return false;
607 }
608
578 const userHandles = await getAllUserHandles();609 const userHandles = await getAllUserHandles();
579 if (userHandles.length === 1) {610 if (userHandles.length === 1) {
580 const user = await storage.getItem(toKey(userHandles[0]));611 const user = await storage.getItem(toKey(userHandles[0]));
@@ -583,6 +614,75 @@ async function tryAutoLogin(request) {
583 return true;614 return true;
584 }615 }
585 }616 }
617 return false;
618}
619
620/**
621 * Tries auto-login with authlia trusted headers.
622 * https://www.authelia.com/integration/trusted-header-sso/introduction/
623 * @param {import('express').Request} request Request object
624 * @returns {Promise<boolean>} Whether auto-login was performed
625 */
626async function autheliaUserLogin(request) {
627 if (!request.session) {
628 return false;
629 }
630
631 const remoteUser = request.get('Remote-User');
632 if (!remoteUser) {
633 return false;
634 }
635
636 const userHandles = await getAllUserHandles();
637 for (const userHandle of userHandles) {
638 if (remoteUser === userHandle) {
639 const user = await storage.getItem(toKey(userHandle));
640 if (user && user.enabled) {
641 request.session.handle = userHandle;
642 return true;
643 }
644 }
645 }
646 return false;
647}
648
649/**
650 * Tries auto-login with basic auth username.
651 * @param {import('express').Request} request Request object
652 * @returns {Promise<boolean>} Whether auto-login was performed
653 */
654async function basicUserLogin(request) {
655 if (!request.session) {
656 return false;
657 }
658
659 const authHeader = request.headers.authorization;
660
661 if (!authHeader) {
662 return false;
663 }
664
665 const [scheme, credentials] = authHeader.split(' ');
666
667 if (scheme !== 'Basic' || !credentials) {
668 return false;
669 }
670
671 const [username, password] = Buffer.from(credentials, 'base64')
672 .toString('utf8')
673 .split(':');
674
675 const userHandles = await getAllUserHandles();
676 for (const userHandle of userHandles) {
677 if (username === userHandle) {
678 const user = await storage.getItem(toKey(userHandle));
679 // Verify pass again here just to be sure
680 if (user && user.enabled && user.password && user.password === getPasswordHash(password, user.salt)) {
681 request.session.handle = userHandle;
682 return true;
683 }
684 }
685 }
586686
587 return false;687 return false;
588}688}
src/vectors/cohere-vectors.js+4 -3
@@ -17,7 +17,7 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
17 throw new Error('No API key found');17 throw new Error('No API key found');
18 }18 }
1919
20 const response = await fetch('https://api.cohere.ai/v1/embed', {20 const response = await fetch('https://api.cohere.ai/v2/embed', {
21 method: 'POST',21 method: 'POST',
22 headers: {22 headers: {
23 'Content-Type': 'application/json',23 'Content-Type': 'application/json',
@@ -26,6 +26,7 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
26 body: JSON.stringify({26 body: JSON.stringify({
27 texts: texts,27 texts: texts,
28 model: model,28 model: model,
29 embedding_types: ['float'],
29 input_type: isQuery ? 'search_query' : 'search_document',30 input_type: isQuery ? 'search_query' : 'search_document',
30 truncate: 'END',31 truncate: 'END',
31 }),32 }),
@@ -38,12 +39,12 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
38 }39 }
3940
40 const data = await response.json();41 const data = await response.json();
41 if (!Array.isArray(data?.embeddings)) {42 if (!Array.isArray(data?.embeddings?.float)) {
42 console.log('API response was not an array');43 console.log('API response was not an array');
43 throw new Error('API response was not an array');44 throw new Error('API response was not an array');
44 }45 }
4546
46 return data.embeddings;47 return data.embeddings.float;
47}48}
4849
49/**50/**