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:
5151enableUserAccounts: false
5252# Enable discreet login mode: hides user list on the login screen
5353enableDiscreetLogin: 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)
61+autheliaAuth: 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
65+perUserBasicAuth: false
66+
5467# User session timeout *in seconds* (defaults to 24 hours).
5568## Set to a positive number to expire session after a certain time of inactivity
5669## Set to 0 to expire session when the browser is closed
package-lock.json+15 -14
@@ -2067,21 +2067,21 @@
20672067 }
20682068 },
20692069 "node_modules/cookie": {
20702070 "version": "0.47.12",
20712071 "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.47.12.tgz",
20722072 "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNlyki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/kxkUAw==",
20732073 "license": "MIT",
20742074 "engines": {
20752075 "node": ">= 0.6"
20762076 }
20772077 },
20782078 "node_modules/cookie-parser": {
20792079 "version": "1.4.67",
20802080 "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.67.tgz",
20812081 "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeAnGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
20822082 "license": "MIT",
20832083 "dependencies": {
20842084 "cookie": "0.47.12",
20852085 "cookie-signature": "1.0.6"
20862086 },
20872087 "engines": {
@@ -2894,9 +2894,9 @@
28942894 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
28952895 },
28962896 "node_modules/express": {
28972897 "version": "4.21.01",
28982898 "resolved": "https://registry.npmjs.org/express/-/express-4.21.01.tgz",
28992899 "integrity": "sha512-VqcNGcj/Id5ZT1LZYSFlK1Ee0/cfihi3ttTn+NJmkli2eZADigjq29qTlWiGC8QaO91tHcDxJiE/hAQ43tX4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/VLPq8TzUfZMpc+UX06FCEx3ByOYet6ZFblngBwuM1IPw8fmQBiQ==",
29002900 "license": "MIT",
29012901 "dependencies": {
29022902 "accepts": "~1.3.8",
@@ -2904,7 +2904,7 @@
29042904 "body-parser": "1.20.3",
29052905 "content-disposition": "0.5.4",
29062906 "content-type": "~1.0.4",
29072907 "cookie": "0.67.01",
29082908 "cookie-signature": "1.0.6",
29092909 "debug": "2.6.9",
29102910 "depd": "2.0.0",
@@ -2936,9 +2936,10 @@
29362936 }
29372937 },
29382938 "node_modules/express/node_modules/cookie": {
29392939 "version": "0.67.01",
29402940 "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.67.01.tgz",
29412941 "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz6DnInpx7SJ2AK3+CTUE/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdwZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
2942+ "license": "MIT",
29422943 "engines": {
29432944 "node": ">= 0.6"
29442945 }
public/index.html+33 -14
@@ -264,10 +264,10 @@
264264 </div>
265265 <div class="range-block-range-and-counter">
266266 <div class="range-block-range">
267267 <input type="range" id="rep_pen_novel" name="volume" min="1" max="8" step="0.05025">
268268 </div>
269269 <div class="range-block-counter">
270270 <input type="number" min="1" max="8" step="0.05025" data-for="rep_pen_novel" id="rep_pen_counter_novel">
271271 </div>
272272 </div>
273273 </div>
@@ -595,17 +595,6 @@
595595 </span>
596596 </div>
597597 </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>
609598 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
610599 <div class="range-block-title" data-i18n="Temperature">
611600 Temperature
@@ -2271,7 +2260,37 @@
22712260 <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.">
22722261 For privacy reasons, your API key will be hidden after you reload the page.
22732262 </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">
22752294 <option value="" data-i18n="-- Connect to the API --">
22762295 -- Connect to the API --
22772296 </option>
public/locales/ar-sa.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "الحد الأقصى لطول الاستجابة (الرموز,الحرف)",
5757 "Multiple swipes per generation": "الضربات الشديدة المتعددة لكل جيل",
5858 "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.": "اسمح للنموذج باستخدام موصل بحث الويب.",
6259 "Frequency Penalty": "عقوبة التكرار",
6360 "Presence Penalty": "عقوبة الوجود",
6461 "Count Penalty": "عد ضربة جزاء",
public/locales/de-de.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Maximale Antwortlänge (Tokens)",
5757 "Multiple swipes per generation": "Mehrere Swipes pro Generation",
5858 "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.",
6259 "Frequency Penalty": "Frequenzstrafe",
6360 "Presence Penalty": "Präsenzstrafe",
6461 "Count Penalty": "Strafe zählen",
public/locales/es-es.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Longitud máxima de respuesta (tokens)",
5757 "Multiple swipes per generation": "Múltiples golpes por generación",
5858 "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.",
6259 "Frequency Penalty": "Penalización de frecuencia",
6360 "Presence Penalty": "Penalización de presencia",
6461 "Count Penalty": "Penalización de conteo",
public/locales/fr-fr.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Longueur maximale de la réponse (tokens)",
5757 "Multiple swipes per generation": "Plusieurs balayages par génération",
5858 "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.",
6259 "Frequency Penalty": "Pénalité de fréquence",
6360 "Presence Penalty": "Pénalité de présence",
6461 "Count Penalty": "Pénalité de décompte",
public/locales/is-is.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Hámarks lengd svörunar (í táknum)",
5757 "Multiple swipes per generation": "Mörg högg á hverja kynslóð",
5858 "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ð.",
6259 "Frequency Penalty": "Tíðnarefning",
6360 "Presence Penalty": "Tilkoma refning",
6461 "Count Penalty": "Telja víti",
public/locales/it-it.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Lunghezza massima della risposta (token)",
5757 "Multiple swipes per generation": "Più passaggi per generazione",
5858 "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.",
6259 "Frequency Penalty": "Penalità di frequenza",
6360 "Presence Penalty": "Penalità di presenza",
6461 "Count Penalty": "Conte Penalità",
public/locales/ja-jp.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "最大応答長(トークン数)",
5757 "Multiple swipes per generation": "世代ごとに複数のスワイプ",
5858 "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 検索コネクタを使用できるようにします。",
6259 "Frequency Penalty": "頻度ペナルティ",
6360 "Presence Penalty": "存在ペナルティ",
6461 "Count Penalty": "カウントペナルティ",
public/locales/ko-kr.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "최대 응답 길이 (토큰)",
5757 "Multiple swipes per generation": "세대당 다중 스와이프",
5858 "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.": "모델이 웹 검색 커넥터를 사용하도록 허용합니다.",
6259 "Frequency Penalty": "빈도 패널티",
6360 "Presence Penalty": "존재 패널티",
6461 "Count Penalty": "카운트 페널티",
public/locales/nl-nl.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Maximale lengte van het antwoord (tokens)",
5757 "Multiple swipes per generation": "Meerdere swipes per generatie",
5858 "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.",
6259 "Frequency Penalty": "Frequentieboete",
6360 "Presence Penalty": "Aanwezigheidsboete",
6461 "Count Penalty": "Tel straf",
public/locales/pt-pt.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Comprimento Máximo da Resposta (tokens)",
5757 "Multiple swipes per generation": "Vários furtos por geração",
5858 "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.",
6259 "Frequency Penalty": "Pena de Frequência",
6360 "Presence Penalty": "Pena de Presença",
6461 "Count Penalty": "Contar penalidade",
public/locales/ru-ru.json+0 -3
@@ -772,7 +772,6 @@
772772 "Type a message, or /? for help": "Введите сообщение, или /? для получения справки",
773773 "Welcome to SillyTavern!": "Добро пожаловать в SillyTavern!",
774774 "Won't be shared with the character card on export.": "Не попадут в карточку персонажа при экспорте.",
775- "Web-search": "Веб-поиск",
776775 "Persona Name:": "Имя персоны:",
777776 "User first message": "Первое сообщение пользователя",
778777 "extension_token_counter": "Токенов:",
@@ -1200,8 +1199,6 @@
12001199 "Streaming_desc": "Выводить текст последовательно по мере его генерации.\rЕсли параметр выключен, ответы будут отображаться сразу целиком, и только после полного завершения генерации.",
12011200 "Max prompt cost:": "Max prompt cost:",
12021201 "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.",
12051202 "Count Penalty": "Count Penalty",
12061203 "Min P": "Min P",
12071204 "NSFW": "NSFW",
public/locales/uk-ua.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Довжина відповіді (токени)",
5757 "Multiple swipes per generation": "Кілька свайпів за покоління",
5858 "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.": "Дозвольте моделі використовувати конектор веб-пошуку.",
6259 "Frequency Penalty": "Штраф за частоту",
6360 "Presence Penalty": "Штраф за наявність",
6461 "Count Penalty": "Рахувати пенальті",
public/locales/vi-vn.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "Độ dài phản hồi tối đa (token)",
5757 "Multiple swipes per generation": "Vuốt nhiều lần trong một lần tạo",
5858 "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.",
6259 "Frequency Penalty": "Frequency Penalty",
6360 "Presence Penalty": "Presence Penalty",
6461 "Count Penalty": "Count Penalty",
public/locales/zh-cn.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "最大回复长度(以词符数计)",
5757 "Multiple swipes per generation": "每次生成多个备选回复",
5858 "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.": "允许模型使用联网搜索。",
6259 "Frequency Penalty": "频率惩罚",
6360 "Presence Penalty": "存在惩罚",
6461 "Count Penalty": "计数惩罚",
public/locales/zh-tw.json+0 -3
@@ -56,9 +56,6 @@
5656 "Max Response Length (tokens)": "最大回應長度(符記數)",
5757 "Multiple swipes per generation": "每次生成多次滑動",
5858 "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.": "允許模型使用網頁搜尋連接器",
6259 "Frequency Penalty": "頻率懲罰",
6360 "Presence Penalty": "存在懲罰",
6461 "Count Penalty": "計數懲罰",
public/script.js+1 -1
@@ -5436,7 +5436,7 @@ function extractMessageFromData(data) {
54365436 case 'novel':
54375437 return data.output;
54385438 case 'openai':
54395439 return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
54405440 default:
54415441 return '';
54425442 }
public/scripts/login.js+7 -1
@@ -180,7 +180,13 @@ function displayError(message) {
180180 * Preserves the query string.
181181 */
182182function 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();
184190}
185191
186192/**
public/scripts/nai-settings.js+3 -3
@@ -245,7 +245,7 @@ function loadNovelSettingsUi(ui_settings) {
245245 $('#temp_novel').val(ui_settings.temperature);
246246 $('#temp_counter_novel').val(Number(ui_settings.temperature).toFixed(2));
247247 $('#rep_pen_novel').val(ui_settings.repetition_penalty);
248248 $('#rep_pen_counter_novel').val(Number(ui_settings.repetition_penalty).toFixed(23));
249249 $('#rep_pen_size_novel').val(ui_settings.repetition_penalty_range);
250250 $('#rep_pen_size_counter_novel').val(Number(ui_settings.repetition_penalty_range).toFixed(0));
251251 $('#rep_pen_slope_novel').val(ui_settings.repetition_penalty_slope);
@@ -298,8 +298,8 @@ const sliders = [
298298 {
299299 sliderId: '#rep_pen_novel',
300300 counterId: '#rep_pen_counter_novel',
301301 format: (val) => Number(val).toFixed(23),
302302 setValue: (val) => { nai_settings.repetition_penalty = Number(val).toFixed(23); },
303303 },
304304 {
305305 sliderId: '#rep_pen_size_novel',
public/scripts/openai.js+5 -15
@@ -225,7 +225,6 @@ const default_settings = {
225225 top_a_openai: 0,
226226 repetition_penalty_openai: 1,
227227 stream_openai: false,
228- websearch_cohere: false,
229228 openai_max_context: max_4k,
230229 openai_max_tokens: 300,
231230 wrap_in_quotes: false,
@@ -302,7 +301,6 @@ const oai_settings = {
302301 top_a_openai: 0,
303302 repetition_penalty_openai: 1,
304303 stream_openai: false,
305- websearch_cohere: false,
306304 openai_max_context: max_4k,
307305 openai_max_tokens: 300,
308306 wrap_in_quotes: false,
@@ -1847,7 +1845,6 @@ async function sendOpenAIRequest(type, messages, signal) {
18471845 generate_data['frequency_penalty'] = Math.min(Math.max(Number(oai_settings.freq_pen_openai), 0), 1);
18481846 generate_data['presence_penalty'] = Math.min(Math.max(Number(oai_settings.pres_pen_openai), 0), 1);
18491847 generate_data['stop'] = getCustomStoppingStrings(5);
1850- generate_data['websearch'] = oai_settings.websearch_cohere;
18511848 }
18521849
18531850 if (isPerplexity) {
@@ -1976,12 +1973,14 @@ async function sendOpenAIRequest(type, messages, signal) {
19761973}
19771974
19781975function getStreamingReply(data) {
19791976 if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
19801977 return data?.delta?.text || '';
19811978 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
19821979 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 || '';
19831982 } else {
19841983 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
19851984 }
19861985}
19871986
@@ -2857,7 +2856,6 @@ function loadOpenAISettings(data, settings) {
28572856 oai_settings.min_p_openai = settings.min_p_openai ?? default_settings.min_p_openai;
28582857 oai_settings.repetition_penalty_openai = settings.repetition_penalty_openai ?? default_settings.repetition_penalty_openai;
28592858 oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai;
2860- oai_settings.websearch_cohere = settings.websearch_cohere ?? default_settings.websearch_cohere;
28612859 oai_settings.openai_max_context = settings.openai_max_context ?? default_settings.openai_max_context;
28622860 oai_settings.openai_max_tokens = settings.openai_max_tokens ?? default_settings.openai_max_tokens;
28632861 oai_settings.bias_preset_selected = settings.bias_preset_selected ?? default_settings.bias_preset_selected;
@@ -2931,7 +2929,6 @@ function loadOpenAISettings(data, settings) {
29312929 if (settings.use_makersuite_sysprompt !== undefined) oai_settings.use_makersuite_sysprompt = !!settings.use_makersuite_sysprompt;
29322930 if (settings.use_alt_scale !== undefined) { oai_settings.use_alt_scale = !!settings.use_alt_scale; updateScaleForm(); }
29332931 $('#stream_toggle').prop('checked', oai_settings.stream_openai);
2934- $('#websearch_toggle').prop('checked', oai_settings.websearch_cohere);
29352932 $('#api_url_scale').val(oai_settings.api_url_scale);
29362933 $('#openai_proxy_password').val(oai_settings.proxy_password);
29372934 $('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
@@ -3258,7 +3255,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
32583255 personality_format: settings.personality_format,
32593256 group_nudge_prompt: settings.group_nudge_prompt,
32603257 stream_openai: settings.stream_openai,
3261- websearch_cohere: settings.websearch_cohere,
32623258 prompts: settings.prompts,
32633259 prompt_order: settings.prompt_order,
32643260 api_url_scale: settings.api_url_scale,
@@ -3682,7 +3678,6 @@ function onSettingsPresetChange() {
36823678 personality_format: ['#personality_format_textarea', 'personality_format', false],
36833679 group_nudge_prompt: ['#group_nudge_prompt_textarea', 'group_nudge_prompt', false],
36843680 stream_openai: ['#stream_toggle', 'stream_openai', true],
3685- websearch_cohere: ['#websearch_toggle', 'websearch_cohere', true],
36863681 prompts: ['', 'prompts', false],
36873682 prompt_order: ['', 'prompt_order', false],
36883683 api_url_scale: ['#api_url_scale', 'api_url_scale', false],
@@ -4846,11 +4841,6 @@ export function initOpenAI() {
48464841 saveSettingsDebounced();
48474842 });
48484843
4849- $('#websearch_toggle').on('change', function () {
4850- oai_settings.websearch_cohere = !!$('#websearch_toggle').prop('checked');
4851- saveSettingsDebounced();
4852- });
4853-
48544844 $('#wrap_in_quotes').on('change', function () {
48554845 oai_settings.wrap_in_quotes = !!$('#wrap_in_quotes').prop('checked');
48564846 saveSettingsDebounced();
public/scripts/sse-stream.js+14 -2
@@ -108,9 +108,21 @@ function getDelay(s) {
108108 * @returns {AsyncGenerator<{data: object, chunk: string}>} The parsed data and the chunk to be sent.
109109 */
110110async 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+ }
111123 // Claude
112124 if (typeof json.delta === 'object' && typeof json.delta.text === 'string') {
113125 if (typeof json.delta.text === 'string' && json.delta.text.length > 0) {
114126 for (let i = 0; i < json.delta.text.length; i++) {
115127 const str = json.delta.text[i];
116128 yield {
public/scripts/textgen-models.js+234 -27
@@ -4,6 +4,7 @@ import { textgenerationwebui_settings as textgen_settings, textgen_types } from
44import { tokenizers } from './tokenizers.js';
55import { renderTemplateAsync } from './templates.js';
66import { POPUP_TYPE, callGenericPopup } from './popup.js';
7+import { PAGINATION_TEMPLATE } from './utils.js';
78
89let mancerModels = [];
910let togetherModels = [];
@@ -265,19 +266,212 @@ export async function loadAphroditeModels(data) {
265266 }
266267}
267268
269+let featherlessCurrentPage = 1;
268270export 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+
269282 if (!Array.isArray(data)) {
270283 console.error('Invalid Featherless models data', data);
271284 return;
272285 }
273286
287+ // Sort the data by model id (default A-Z)
274288 data.sort((a, b) => a.id.localeCompare(b.id));
289+ originalModels = data; // Store the original data for search
275290 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+ });
279415 }
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
281475 $('#featherless_model').empty();
282476 for (const model of data) {
283477 const option = document.createElement('option');
@@ -288,15 +482,49 @@ export async function loadFeatherlessModels(data) {
288482 }
289483}
290484
291485async function onFeatherlessModelSelectfetchFeatherlessStats() {
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+
491+async 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+
497+function onFeatherlessModelSelect(modelId) {
498+ const model = featherlessModels.find(x => x.id === modelId);
293499 textgen_settings.featherless_model = modelId;
500+ $('#featherless_model').val(modelId);
294501 $('#api_button_textgenerationwebui').trigger('click');
295- const model = featherlessModels.find(x => x.id === modelId);
296502 setGenerationParamsFromPreset({ max_length: model.context_length });
297503}
298504
505+let featherlessIsGridView = false; // Default state set to grid view
506+
507+// Ensure the correct initial view is applied when the page loads
508+document.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+});
300528function onMancerModelSelect() {
301529 const modelId = String($('#mancer_model').val());
302530 textgen_settings.mancer_model = modelId;
@@ -469,20 +697,6 @@ function getAphroditeModelTemplate(option) {
469697 `));
470698}
471699
472-function 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-
486700async function downloadOllamaModel() {
487701 try {
488702 const serverUrl = textgen_settings.server_urls[textgen_types.OLLAMA];
@@ -670,9 +884,9 @@ export function initTextGenModels() {
670884 $('#ollama_download_model').on('click', downloadOllamaModel);
671885 $('#vllm_model').on('change', onVllmModelSelect);
672886 $('#aphrodite_model').on('change', onAphroditeModelSelect);
673- $('#featherless_model').on('change', onFeatherlessModelSelect);
674887 $('#tabby_download_model').on('click', downloadTabbyModel);
675888 $('#tabby_model').on('change', onTabbyModelSelect);
889+ $('#featherless_model').on('change', () => onFeatherlessModelSelect(String($('#featherless_model').val())));
676890
677891 const providersSelect = $('.openrouter_providers');
678892 for (const provider of OPENROUTER_PROVIDERS) {
@@ -745,13 +959,6 @@ export function initTextGenModels() {
745959 width: '100%',
746960 templateResult: getAphroditeModelTemplate,
747961 });
748- $('#featherless_model').select2({
749- placeholder: 'Select a model',
750- searchInputPlaceholder: 'Search models...',
751- searchInputCssClass: 'text_pole',
752- width: '100%',
753- templateResult: getFeatherlessModelTemplate,
754- });
755962 providersSelect.select2({
756963 sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
757964 placeholder: 'Select providers. No selection = all providers.',
public/scripts/textgen-settings.js+1 -0
@@ -193,6 +193,7 @@ const settings = {
193193 openrouter_allow_fallbacks: true,
194194 xtc_threshold: 0.1,
195195 xtc_probability: 0,
196+ featherless_model: '',
196197};
197198
198199export let textgenerationwebui_banned_in_macros = [];
public/scripts/tool-calling.js+36 -3
@@ -292,10 +292,10 @@ export class ToolManager {
292292
293293 if (error instanceof Error) {
294294 error.cause = name;
295295 return error.toString();
296296 }
297297
298298 return new Error('Unknown error occurred while invoking the tool.', { cause: name }).toString();
299299 }
300300 }
301301
@@ -365,6 +365,9 @@ export class ToolManager {
365365 * @returns {void}
366366 */
367367 static parseToolCalls(toolCalls, parsed) {
368+ if (!this.isToolCallingSupported()) {
369+ return;
370+ }
368371 if (Array.isArray(parsed?.choices)) {
369372 for (const choice of parsed.choices) {
370373 const choiceIndex = (typeof choice.index === 'number') ? choice.index : null;
@@ -401,6 +404,22 @@ export class ToolManager {
401404 }
402405 }
403406 }
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+ }
404423 if (typeof parsed?.content_block === 'object') {
405424 const choiceIndex = 0;
406425 const toolCallIndex = parsed?.index ?? 0;
@@ -503,6 +522,7 @@ export class ToolManager {
503522 chat_completion_sources.CLAUDE,
504523 chat_completion_sources.OPENROUTER,
505524 chat_completion_sources.GROQ,
525+ chat_completion_sources.COHERE,
506526 ];
507527 return supportedSources.includes(oai_settings.chat_completion_source);
508528 }
@@ -529,7 +549,15 @@ export class ToolManager {
529549
530550 // Parsed tool calls from streaming data
531551 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];
533561 }
534562
535563 // Parsed tool calls from non-streaming data
@@ -550,6 +578,11 @@ export class ToolManager {
550578 return content;
551579 }
552580 }
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+ }
553586 }
554587
555588 /**
public/scripts/user.js+8 -1
@@ -848,7 +848,14 @@ async function logout() {
848848 headers: getRequestHeaders(),
849849 });
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();
852859}
853860
854861/**
public/scripts/variables.js+79 -0
@@ -836,6 +836,40 @@ function randValuesCallback(from, to, args) {
836836 return value;
837837}
838838
839+function 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+
847+function 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+
839873/**
840874 * Declare a new variable in the current scope.
841875 * @param {NamedArguments} args Named arguments.
@@ -2110,6 +2144,51 @@ export function registerVariableCommands() {
21102144 `,
21112145 }));
21122146 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({
21132192 name: 'rand',
21142193 callback: (args, value) => String(randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value ? value : 1)), args)),
21152194 returns: 'random number',
public/style.css+126 -0
@@ -5547,3 +5547,129 @@ body:not(.movingUI) .drawer-content.maximized {
55475547#InstructSequencesColumn details:not(:last-of-type) {
55485548 margin-bottom: 5px;
55495549}
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;
6565const DEFAULT_ACCOUNTS = false;
6666const DEFAULT_CSRF_DISABLED = false;
6767const DEFAULT_BASIC_AUTH = false;
68+const DEFAULT_PER_USER_BASIC_AUTH = false;
6869
6970const DEFAULT_ENABLE_IPV6 = false;
7071const DEFAULT_ENABLE_IPV4 = true;
@@ -184,6 +185,7 @@ const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode'
184185const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
185186const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
186187const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
188+const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);
187189const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
188190
189191const uploadsPath = path.join(dataRoot, require('./src/constants').UPLOADS_DIRECTORY);
@@ -361,7 +363,7 @@ app.get('/login', async (request, response) => {
361363 }
362364
363365 try {
364366 const autoLogin = await userModule.tryAutoLogin(request, basicAuthMode);
365367
366368 if (autoLogin) {
367369 return response.redirect('/');
@@ -756,9 +758,13 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
756758 }
757759
758760 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+ }
762768 }
763769 }
764770};
src/cohere-stream.js+0 -126
@@ -1,126 +0,0 @@
1-const 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- */
8-class 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-
95-function 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-
126-module.exports = CohereStream;
src/endpoints/backends/chat-completions.js+14 -18
@@ -5,7 +5,6 @@ const { jsonParser } = require('../../express-common');
55const { CHAT_COMPLETION_SOURCES, GEMINI_SAFETY, BISON_SAFETY, OPENROUTER_HEADERS } = require('../../constants');
66const { forwardFetchResponse, getConfigValue, tryParse, uuidv4, mergeObjectWithYaml, excludeKeysByYaml, color } = require('../../util');
77const { convertClaudeMessages, convertGooglePrompt, convertTextCompletionPrompt, convertCohereMessages, convertMistralMessages, convertAI21Messages, mergeMessages } = require('../../prompt-converters');
8-const CohereStream = require('../../cohere-stream');
98
109const { readSecret, SECRET_KEYS } = require('../secrets');
1110const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sentencepieceTokenizers, TEXT_COMPLETION_MODELS } = require('../tokenizers');
@@ -13,7 +12,8 @@ const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sente
1312const API_OPENAI = 'https://api.openai.com/v1';
1413const API_CLAUDE = 'https://api.anthropic.com/v1';
1514const API_MISTRAL = 'https://api.mistral.ai/v1';
1615const API_COHEREAPI_COHERE_V1 = 'https://api.cohere.ai/v1';
16+const API_COHERE_V2 = 'https://api.cohere.ai/v2';
1717const API_PERPLEXITY = 'https://api.perplexity.ai';
1818const API_GROQ = 'https://api.groq.com/openai/v1';
1919const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
@@ -553,13 +553,14 @@ async function sendCohereRequest(request, response) {
553553
554554 try {
555555 const convertedHistory = convertCohereMessages(request.body.messages, request.body.char_name, request.body.user_name);
556- const connectors = [];
557556 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+ }
563564 });
564565 }
565566
@@ -567,9 +568,7 @@ async function sendCohereRequest(request, response) {
567568 const requestBody = {
568569 stream: Boolean(request.body.stream),
569570 model: request.body.model,
570571 messagemessages: convertedHistory.userPromptchatHistory,
571- preamble: convertedHistory.systemPrompt,
572- chat_history: convertedHistory.chatHistory,
573572 temperature: request.body.temperature,
574573 max_tokens: request.body.max_tokens,
575574 k: request.body.top_k,
@@ -578,16 +577,13 @@ async function sendCohereRequest(request, response) {
578577 stop_sequences: request.body.stop,
579578 frequency_penalty: request.body.frequency_penalty,
580579 presence_penalty: request.body.presence_penalty,
581- prompt_truncation: 'AUTO_PRESERVE_ORDER',
582- connectors: connectors,
583580 documents: [],
584581 tools: tools,
585- search_queries_only: false,
586582 };
587583
588584 const canDoSafetyMode = String(request.body.model).endsWith('08-2024');
589585 if (canDoSafetyMode) {
590586 requestBody.safety_mode = 'NONEOFF';
591587 }
592588
593589 console.log('Cohere request:', requestBody);
@@ -603,11 +599,11 @@ async function sendCohereRequest(request, response) {
603599 timeout: 0,
604600 };
605601
606602 const apiUrl = API_COHEREAPI_COHERE_V2 + '/chat';
607603
608604 if (request.body.stream) {
609605 const stream = await global.fetch(apiUrl, config);
610606 parseCohereStreamforwardFetchResponse(stream, request, response);
611607 } else {
612608 const generateResponse = await fetch(apiUrl, config);
613609 if (!generateResponse.ok) {
@@ -658,7 +654,7 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
658654 headers = {};
659655 mergeObjectWithYaml(headers, request.body.custom_include_headers);
660656 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COHERE) {
661657 api_url = API_COHEREAPI_COHERE_V1;
662658 api_key_openai = readSecret(request.user.directories, SECRET_KEYS.COHERE);
663659 headers = {};
664660 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZEROONEAI) {
src/middleware/basicAuth.js+20 -5
@@ -2,14 +2,19 @@
22 * When applied, this middleware will ensure the request contains the required header for basic authentication and only
33 * allow access to the endpoint after successful authentication.
44 */
55const { getConfiggetAllUserHandles, toKey, getPasswordHash } = require('../utilusers.js');
6+const { getConfig, getConfigValue } = require('../util.js');
7+const storage = require('node-persist');
8+
9+const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
10+const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
611
712const unauthorizedResponse = (res) => {
813 res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');
914 return res.status(401).send('Authentication required');
1015};
1116
1217const basicAuthMiddleware = async function (request, response, callback) {
1318 const config = getConfig();
1419 const authHeader = request.headers.authorization;
1520
@@ -23,15 +28,25 @@ const basicAuthMiddleware = function (request, response, callback) {
2328 return unauthorizedResponse(response);
2429 }
2530
31+ const usePerUserAuth = PER_USER_BASIC_AUTH && ENABLE_ACCOUNTS;
2632 const [username, password] = Buffer.from(credentials, 'base64')
2733 .toString('utf8')
2834 .split(':');
2935
3036 if (!usePerUserAuth && username === config.basicAuthUser.username && password === config.basicAuthUser.password) {
3137 return callback();
3238 } 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+ }
3448 }
49+ return unauthorizedResponse(response);
3550};
3651
3752module.exports = basicAuthMiddleware;
src/prompt-converters.js+34 -39
@@ -277,38 +277,9 @@ function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools,
277277 * @param {object[]} messages Array of messages
278278 * @param {string} charName Character name
279279 * @param {string} userName User name
280280 * @returns {{systemPrompt: string, chatHistory: object[], userPrompt: string}} Prompt for Cohere
281281 */
282282function 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-
312283 if (messages.length === 0) {
313284 messages.unshift({
314285 role: 'user',
@@ -316,17 +287,41 @@ function convertCohereMessages(messages, charName = '', userName = '') {
316287 });
317288 }
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
321-
292+ 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+ }
327317 });
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 };
330325}
331326
332327/**
src/users.js+101 -1
@@ -19,6 +19,8 @@ const { readSecret, writeSecret } = require('./endpoints/secrets');
1919const KEY_PREFIX = 'user:';
2020const AVATAR_PREFIX = 'avatar:';
2121const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
22+const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false);
23+const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
2224const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
2325
2426/**
@@ -567,14 +569,43 @@ function shouldRedirectToLogin(request) {
567569
568570/**
569571 * Tries auto-login if there is only one user and it's not password protected.
572+ * or another configured method such authlia or basic
570573 * @param {import('express').Request} request Request object
574+ * @param {boolean} basicAuthMode If Basic auth mode is enabled
571575 * @returns {Promise<boolean>} Whether auto-login was performed
572576 */
573577async function tryAutoLogin(request, basicAuthMode) {
574578 if (!ENABLE_ACCOUNTS || request.user || !request.session) {
575579 return false;
576580 }
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+ */
604+async function singleUserLogin(request) {
605+ if (!request.session) {
606+ return false;
607+ }
608+
578609 const userHandles = await getAllUserHandles();
579610 if (userHandles.length === 1) {
580611 const user = await storage.getItem(toKey(userHandles[0]));
@@ -583,6 +614,75 @@ async function tryAutoLogin(request) {
583614 return true;
584615 }
585616 }
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+ */
626+async 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+ */
654+async 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
587687 return false;
588688}
src/vectors/cohere-vectors.js+4 -3
@@ -17,7 +17,7 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
1717 throw new Error('No API key found');
1818 }
1919
2020 const response = await fetch('https://api.cohere.ai/v1v2/embed', {
2121 method: 'POST',
2222 headers: {
2323 'Content-Type': 'application/json',
@@ -26,6 +26,7 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
2626 body: JSON.stringify({
2727 texts: texts,
2828 model: model,
29+ embedding_types: ['float'],
2930 input_type: isQuery ? 'search_query' : 'search_document',
3031 truncate: 'END',
3132 }),
@@ -38,12 +39,12 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
3839 }
3940
4041 const data = await response.json();
4142 if (!Array.isArray(data?.embeddings?.float)) {
4243 console.log('API response was not an array');
4344 throw new Error('API response was not an array');
4445 }
4546
4647 return data.embeddings.float;
4748}
4849
4950/**