mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-12 18:10:39 +01:00
Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging
This commit is contained in:
commit
6cca977d07
@ -51,6 +51,19 @@ requestProxy:
|
||||
enableUserAccounts: false
|
||||
# Enable discreet login mode: hides user list on the login screen
|
||||
enableDiscreetLogin: false
|
||||
# Enable's authlia based auto login. Only enable this if you
|
||||
# have setup and installed Authelia as a middle-ware on your
|
||||
# reverse proxy
|
||||
# https://www.authelia.com/
|
||||
# This will use auto login to an account with the same username
|
||||
# as that used for authlia. (Ensure the username in authlia
|
||||
# is an exact match with that in sillytavern)
|
||||
autheliaAuth: false
|
||||
# If `basicAuthMode` and this are enabled then
|
||||
# the username and passwords for basic auth are the same as those
|
||||
# for the individual accounts
|
||||
perUserBasicAuth: false
|
||||
|
||||
# User session timeout *in seconds* (defaults to 24 hours).
|
||||
## Set to a positive number to expire session after a certain time of inactivity
|
||||
## Set to 0 to expire session when the browser is closed
|
||||
|
29
package-lock.json
generated
29
package-lock.json
generated
@ -2067,21 +2067,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
@ -2894,9 +2894,9 @@
|
||||
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
|
||||
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
|
||||
"version": "4.21.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
@ -2904,7 +2904,7 @@
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.6.0",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
@ -2936,9 +2936,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
@ -264,10 +264,10 @@
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="rep_pen_novel" name="volume" min="1" max="8" step="0.05">
|
||||
<input type="range" id="rep_pen_novel" name="volume" min="1" max="8" step="0.025">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<input type="number" min="1" max="8" step="0.05" data-for="rep_pen_novel" id="rep_pen_counter_novel">
|
||||
<input type="number" min="1" max="8" step="0.025" data-for="rep_pen_novel" id="rep_pen_counter_novel">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -595,17 +595,6 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="cohere">
|
||||
<label for="websearch_toggle" title="Enable Cohere web-search connector" data-i18n="[title]Enable Cohere web-search connector" class="checkbox_label widthFreeExpand">
|
||||
<input id="websearch_toggle" type="checkbox" />
|
||||
<span data-i18n="Web-search">Web-search</span>
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<span data-i18n="Allow the model to use the web-search connector.">
|
||||
Allow the model to use the web-search connector.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
@ -2271,7 +2260,37 @@
|
||||
<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.">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<select id="featherless_model">
|
||||
<hr>
|
||||
<h4 data-i18n="Featherless Model Selection">Featherless Model Selection</h4>
|
||||
<div class="flex-container wide100p flexGap10">
|
||||
<div class="flex1 overflowHidden wide100p">
|
||||
<div class="flex-container marginBot10 alignitemscenter">
|
||||
<input id="featherless_model_search_bar" class="text_pole width100p flex1 margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search...">
|
||||
<select id="featherless_model_sort_order" class="margin0 text_pole">
|
||||
<option value="search" data-i18n="Search" hidden>A-Z</option>
|
||||
<option value="asc">A-Z</option>
|
||||
<option value="desc">Z-A</option>
|
||||
<option value="date_asc">Date Asc</option>
|
||||
<option value="date_desc">Date Desc</option>
|
||||
</select>
|
||||
<select id="featherless_category_selection" class="text_pole">
|
||||
<option value="" disabled selected data-i18n="category">category</option>
|
||||
<!-- <option value="Favorite" data-i18n="Top">Favorite</option> -->
|
||||
<option value="Top" data-i18n="Top">Top</option>
|
||||
<option value="New" data-i18n="New">New</option>
|
||||
<option value="All" data-i18n="All">All</option>
|
||||
</select>
|
||||
<select id="featherless_class_selection" class="text_pole">
|
||||
<option value="" selected data-i18n="class">All Classes</option>
|
||||
</select>
|
||||
<div id="featherless_model_pagination_container" class="flex1"></div>
|
||||
<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>
|
||||
</div>
|
||||
<div id="featherless_model_card_block" data-i18n="[no_desc_text]No model description" no_desc_text="[No description]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Do not remove. Needed for the /model command to function -->
|
||||
<select id="featherless_model" class="displayNone">
|
||||
<option value="" data-i18n="-- Connect to the API --">
|
||||
-- Connect to the API --
|
||||
</option>
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "الحد الأقصى لطول الاستجابة (الرموز,الحرف)",
|
||||
"Multiple swipes per generation": "الضربات الشديدة المتعددة لكل جيل",
|
||||
"Enable OpenAI completion streaming": "تمكين بث الاكتمال من OpenAI",
|
||||
"Enable Cohere web-search connector": "تمكين موصل بحث الويب Cohere",
|
||||
"Web-search": "البحث في الويب",
|
||||
"Allow the model to use the web-search connector.": "اسمح للنموذج باستخدام موصل بحث الويب.",
|
||||
"Frequency Penalty": "عقوبة التكرار",
|
||||
"Presence Penalty": "عقوبة الوجود",
|
||||
"Count Penalty": "عد ضربة جزاء",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Maximale Antwortlänge (Tokens)",
|
||||
"Multiple swipes per generation": "Mehrere Swipes pro Generation",
|
||||
"Enable OpenAI completion streaming": "OpenAI-Vervollständigungsstreaming aktivieren",
|
||||
"Enable Cohere web-search connector": "Cohere-Websuch-Connector aktivieren",
|
||||
"Web-search": "Web-Suche",
|
||||
"Allow the model to use the web-search connector.": "Erlauben Sie dem Modell, den Websuch-Connector zu verwenden.",
|
||||
"Frequency Penalty": "Frequenzstrafe",
|
||||
"Presence Penalty": "Präsenzstrafe",
|
||||
"Count Penalty": "Strafe zählen",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Longitud máxima de respuesta (tokens)",
|
||||
"Multiple swipes per generation": "Múltiples golpes por generación",
|
||||
"Enable OpenAI completion streaming": "Activar streaming de completado de OpenAI",
|
||||
"Enable Cohere web-search connector": "Habilitar el conector de búsqueda web de Cohere",
|
||||
"Web-search": "Búsqueda Web",
|
||||
"Allow the model to use the web-search connector.": "Permita que el modelo utilice el conector de búsqueda web.",
|
||||
"Frequency Penalty": "Penalización de frecuencia",
|
||||
"Presence Penalty": "Penalización de presencia",
|
||||
"Count Penalty": "Penalización de conteo",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Longueur maximale de la réponse (tokens)",
|
||||
"Multiple swipes per generation": "Plusieurs balayages par génération",
|
||||
"Enable OpenAI completion streaming": "Activer le streaming de complétion OpenAI",
|
||||
"Enable Cohere web-search connector": "Activer le connecteur de recherche Web Cohere",
|
||||
"Web-search": "Recherche Internet",
|
||||
"Allow the model to use the web-search connector.": "Autorisez le modèle à utiliser le connecteur de recherche Web.",
|
||||
"Frequency Penalty": "Pénalité de fréquence",
|
||||
"Presence Penalty": "Pénalité de présence",
|
||||
"Count Penalty": "Pénalité de décompte",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Hámarks lengd svörunar (í táknum)",
|
||||
"Multiple swipes per generation": "Mörg högg á hverja kynslóð",
|
||||
"Enable OpenAI completion streaming": "Virkja OpenAI klárastreymi",
|
||||
"Enable Cohere web-search connector": "Virkja Cohere vefleitartengi",
|
||||
"Web-search": "Vefleit",
|
||||
"Allow the model to use the web-search connector.": "Leyfðu líkaninu að nota vefleitartengið.",
|
||||
"Frequency Penalty": "Tíðnarefning",
|
||||
"Presence Penalty": "Tilkoma refning",
|
||||
"Count Penalty": "Telja víti",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Lunghezza massima della risposta (token)",
|
||||
"Multiple swipes per generation": "Più passaggi per generazione",
|
||||
"Enable OpenAI completion streaming": "Abilita lo streaming di completamento OpenAI",
|
||||
"Enable Cohere web-search connector": "Abilita il connettore di ricerca web Cohere",
|
||||
"Web-search": "Ricerca sul web",
|
||||
"Allow the model to use the web-search connector.": "Consenti al modello di utilizzare il connettore di ricerca web.",
|
||||
"Frequency Penalty": "Penalità di frequenza",
|
||||
"Presence Penalty": "Penalità di presenza",
|
||||
"Count Penalty": "Conte Penalità",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "最大応答長(トークン数)",
|
||||
"Multiple swipes per generation": "世代ごとに複数のスワイプ",
|
||||
"Enable OpenAI completion streaming": "OpenAIの完了ストリーミングを有効にする",
|
||||
"Enable Cohere web-search connector": "Cohereウェブ検索コネクタを有効にする",
|
||||
"Web-search": "ウェブ検索",
|
||||
"Allow the model to use the web-search connector.": "モデルが Web 検索コネクタを使用できるようにします。",
|
||||
"Frequency Penalty": "頻度ペナルティ",
|
||||
"Presence Penalty": "存在ペナルティ",
|
||||
"Count Penalty": "カウントペナルティ",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "최대 응답 길이 (토큰)",
|
||||
"Multiple swipes per generation": "세대당 다중 스와이프",
|
||||
"Enable OpenAI completion streaming": "OpenAI 완성 스트리밍 활성화",
|
||||
"Enable Cohere web-search connector": "Cohere 웹 검색 커넥터 활성화",
|
||||
"Web-search": "웹 서핑",
|
||||
"Allow the model to use the web-search connector.": "모델이 웹 검색 커넥터를 사용하도록 허용합니다.",
|
||||
"Frequency Penalty": "빈도 패널티",
|
||||
"Presence Penalty": "존재 패널티",
|
||||
"Count Penalty": "카운트 페널티",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Maximale lengte van het antwoord (tokens)",
|
||||
"Multiple swipes per generation": "Meerdere swipes per generatie",
|
||||
"Enable OpenAI completion streaming": "OpenAI voltooiingsstreaming inschakelen",
|
||||
"Enable Cohere web-search connector": "Schakel de Cohere-webzoekconnector in",
|
||||
"Web-search": "Web-zoeken",
|
||||
"Allow the model to use the web-search connector.": "Sta toe dat het model de webzoekconnector gebruikt.",
|
||||
"Frequency Penalty": "Frequentieboete",
|
||||
"Presence Penalty": "Aanwezigheidsboete",
|
||||
"Count Penalty": "Tel straf",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Comprimento Máximo da Resposta (tokens)",
|
||||
"Multiple swipes per generation": "Vários furtos por geração",
|
||||
"Enable OpenAI completion streaming": "Ativar streaming de conclusão do OpenAI",
|
||||
"Enable Cohere web-search connector": "Ativar o conector de pesquisa na Web Cohere",
|
||||
"Web-search": "Pesquisa na internet",
|
||||
"Allow the model to use the web-search connector.": "Permita que o modelo use o conector de pesquisa na Web.",
|
||||
"Frequency Penalty": "Pena de Frequência",
|
||||
"Presence Penalty": "Pena de Presença",
|
||||
"Count Penalty": "Contar penalidade",
|
||||
|
@ -772,7 +772,6 @@
|
||||
"Type a message, or /? for help": "Введите сообщение, или /? для получения справки",
|
||||
"Welcome to SillyTavern!": "Добро пожаловать в SillyTavern!",
|
||||
"Won't be shared with the character card on export.": "Не попадут в карточку персонажа при экспорте.",
|
||||
"Web-search": "Веб-поиск",
|
||||
"Persona Name:": "Имя персоны:",
|
||||
"User first message": "Первое сообщение пользователя",
|
||||
"extension_token_counter": "Токенов:",
|
||||
@ -1200,8 +1199,6 @@
|
||||
"Streaming_desc": "Выводить текст последовательно по мере его генерации.\rЕсли параметр выключен, ответы будут отображаться сразу целиком, и только после полного завершения генерации.",
|
||||
"Max prompt cost:": "Max prompt cost:",
|
||||
"TFS": "TFS",
|
||||
"Enable Cohere web-search connector": "Enable Cohere web-search connector",
|
||||
"Allow the model to use the web-search connector.": "Allow the model to use the web-search connector.",
|
||||
"Count Penalty": "Count Penalty",
|
||||
"Min P": "Min P",
|
||||
"NSFW": "NSFW",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Довжина відповіді (токени)",
|
||||
"Multiple swipes per generation": "Кілька свайпів за покоління",
|
||||
"Enable OpenAI completion streaming": "Увімкнути потокове завершення OpenAI",
|
||||
"Enable Cohere web-search connector": "Увімкнути конектор веб-пошуку Cohere",
|
||||
"Web-search": "Веб-пошук",
|
||||
"Allow the model to use the web-search connector.": "Дозвольте моделі використовувати конектор веб-пошуку.",
|
||||
"Frequency Penalty": "Штраф за частоту",
|
||||
"Presence Penalty": "Штраф за наявність",
|
||||
"Count Penalty": "Рахувати пенальті",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "Độ dài phản hồi tối đa (token)",
|
||||
"Multiple swipes per generation": "Vuốt nhiều lần trong một lần tạo",
|
||||
"Enable OpenAI completion streaming": "Bật streaming của OpenAI",
|
||||
"Enable Cohere web-search connector": "Bật web tìm kiếm của Cohere",
|
||||
"Web-search": "Tìm kiếm trên web",
|
||||
"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.",
|
||||
"Frequency Penalty": "Frequency Penalty",
|
||||
"Presence Penalty": "Presence Penalty",
|
||||
"Count Penalty": "Count Penalty",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "最大回复长度(以词符数计)",
|
||||
"Multiple swipes per generation": "每次生成多个备选回复",
|
||||
"Enable OpenAI completion streaming": "启用OpenAI文本补全流式传输",
|
||||
"Enable Cohere web-search connector": "启用 Cohere 网络搜索连接器",
|
||||
"Web-search": "联网搜索",
|
||||
"Allow the model to use the web-search connector.": "允许模型使用联网搜索。",
|
||||
"Frequency Penalty": "频率惩罚",
|
||||
"Presence Penalty": "存在惩罚",
|
||||
"Count Penalty": "计数惩罚",
|
||||
|
@ -56,9 +56,6 @@
|
||||
"Max Response Length (tokens)": "最大回應長度(符記數)",
|
||||
"Multiple swipes per generation": "每次生成多次滑動",
|
||||
"Enable OpenAI completion streaming": "啟用 OpenAI 補充串流",
|
||||
"Enable Cohere web-search connector": "啟用 Cohere 網頁搜尋連接器",
|
||||
"Web-search": "網頁搜尋",
|
||||
"Allow the model to use the web-search connector.": "允許模型使用網頁搜尋連接器",
|
||||
"Frequency Penalty": "頻率懲罰",
|
||||
"Presence Penalty": "存在懲罰",
|
||||
"Count Penalty": "計數懲罰",
|
||||
|
@ -5436,7 +5436,7 @@ function extractMessageFromData(data) {
|
||||
case 'novel':
|
||||
return data.output;
|
||||
case 'openai':
|
||||
return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? '';
|
||||
return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -180,7 +180,13 @@ function displayError(message) {
|
||||
* Preserves the query string.
|
||||
*/
|
||||
function redirectToHome() {
|
||||
window.location.href = '/' + window.location.search;
|
||||
// After a login theres no need to preserve the
|
||||
// noauto (if present)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
urlParams.delete('noauto');
|
||||
|
||||
window.location.href = '/' + urlParams.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,7 +245,7 @@ function loadNovelSettingsUi(ui_settings) {
|
||||
$('#temp_novel').val(ui_settings.temperature);
|
||||
$('#temp_counter_novel').val(Number(ui_settings.temperature).toFixed(2));
|
||||
$('#rep_pen_novel').val(ui_settings.repetition_penalty);
|
||||
$('#rep_pen_counter_novel').val(Number(ui_settings.repetition_penalty).toFixed(2));
|
||||
$('#rep_pen_counter_novel').val(Number(ui_settings.repetition_penalty).toFixed(3));
|
||||
$('#rep_pen_size_novel').val(ui_settings.repetition_penalty_range);
|
||||
$('#rep_pen_size_counter_novel').val(Number(ui_settings.repetition_penalty_range).toFixed(0));
|
||||
$('#rep_pen_slope_novel').val(ui_settings.repetition_penalty_slope);
|
||||
@ -298,8 +298,8 @@ const sliders = [
|
||||
{
|
||||
sliderId: '#rep_pen_novel',
|
||||
counterId: '#rep_pen_counter_novel',
|
||||
format: (val) => Number(val).toFixed(2),
|
||||
setValue: (val) => { nai_settings.repetition_penalty = Number(val).toFixed(2); },
|
||||
format: (val) => Number(val).toFixed(3),
|
||||
setValue: (val) => { nai_settings.repetition_penalty = Number(val).toFixed(3); },
|
||||
},
|
||||
{
|
||||
sliderId: '#rep_pen_size_novel',
|
||||
|
@ -225,7 +225,6 @@ const default_settings = {
|
||||
top_a_openai: 0,
|
||||
repetition_penalty_openai: 1,
|
||||
stream_openai: false,
|
||||
websearch_cohere: false,
|
||||
openai_max_context: max_4k,
|
||||
openai_max_tokens: 300,
|
||||
wrap_in_quotes: false,
|
||||
@ -302,7 +301,6 @@ const oai_settings = {
|
||||
top_a_openai: 0,
|
||||
repetition_penalty_openai: 1,
|
||||
stream_openai: false,
|
||||
websearch_cohere: false,
|
||||
openai_max_context: max_4k,
|
||||
openai_max_tokens: 300,
|
||||
wrap_in_quotes: false,
|
||||
@ -1847,7 +1845,6 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
generate_data['frequency_penalty'] = Math.min(Math.max(Number(oai_settings.freq_pen_openai), 0), 1);
|
||||
generate_data['presence_penalty'] = Math.min(Math.max(Number(oai_settings.pres_pen_openai), 0), 1);
|
||||
generate_data['stop'] = getCustomStoppingStrings(5);
|
||||
generate_data['websearch'] = oai_settings.websearch_cohere;
|
||||
}
|
||||
|
||||
if (isPerplexity) {
|
||||
@ -1976,12 +1973,14 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
}
|
||||
|
||||
function getStreamingReply(data) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
|
||||
return data?.delta?.text || '';
|
||||
} else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
|
||||
return data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
|
||||
return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
|
||||
} else {
|
||||
return data.choices[0]?.delta?.content ?? data.choices[0]?.message?.content ?? data.choices[0]?.text ?? '';
|
||||
return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -2857,7 +2856,6 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.min_p_openai = settings.min_p_openai ?? default_settings.min_p_openai;
|
||||
oai_settings.repetition_penalty_openai = settings.repetition_penalty_openai ?? default_settings.repetition_penalty_openai;
|
||||
oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai;
|
||||
oai_settings.websearch_cohere = settings.websearch_cohere ?? default_settings.websearch_cohere;
|
||||
oai_settings.openai_max_context = settings.openai_max_context ?? default_settings.openai_max_context;
|
||||
oai_settings.openai_max_tokens = settings.openai_max_tokens ?? default_settings.openai_max_tokens;
|
||||
oai_settings.bias_preset_selected = settings.bias_preset_selected ?? default_settings.bias_preset_selected;
|
||||
@ -2931,7 +2929,6 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.use_makersuite_sysprompt !== undefined) oai_settings.use_makersuite_sysprompt = !!settings.use_makersuite_sysprompt;
|
||||
if (settings.use_alt_scale !== undefined) { oai_settings.use_alt_scale = !!settings.use_alt_scale; updateScaleForm(); }
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
$('#websearch_toggle').prop('checked', oai_settings.websearch_cohere);
|
||||
$('#api_url_scale').val(oai_settings.api_url_scale);
|
||||
$('#openai_proxy_password').val(oai_settings.proxy_password);
|
||||
$('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
|
||||
@ -3258,7 +3255,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
personality_format: settings.personality_format,
|
||||
group_nudge_prompt: settings.group_nudge_prompt,
|
||||
stream_openai: settings.stream_openai,
|
||||
websearch_cohere: settings.websearch_cohere,
|
||||
prompts: settings.prompts,
|
||||
prompt_order: settings.prompt_order,
|
||||
api_url_scale: settings.api_url_scale,
|
||||
@ -3682,7 +3678,6 @@ function onSettingsPresetChange() {
|
||||
personality_format: ['#personality_format_textarea', 'personality_format', false],
|
||||
group_nudge_prompt: ['#group_nudge_prompt_textarea', 'group_nudge_prompt', false],
|
||||
stream_openai: ['#stream_toggle', 'stream_openai', true],
|
||||
websearch_cohere: ['#websearch_toggle', 'websearch_cohere', true],
|
||||
prompts: ['', 'prompts', false],
|
||||
prompt_order: ['', 'prompt_order', false],
|
||||
api_url_scale: ['#api_url_scale', 'api_url_scale', false],
|
||||
@ -4846,11 +4841,6 @@ export function initOpenAI() {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#websearch_toggle').on('change', function () {
|
||||
oai_settings.websearch_cohere = !!$('#websearch_toggle').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#wrap_in_quotes').on('change', function () {
|
||||
oai_settings.wrap_in_quotes = !!$('#wrap_in_quotes').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
|
@ -108,9 +108,21 @@ function getDelay(s) {
|
||||
* @returns {AsyncGenerator<{data: object, chunk: string}>} The parsed data and the chunk to be sent.
|
||||
*/
|
||||
async function* parseStreamData(json) {
|
||||
// Cohere
|
||||
if (typeof json.delta.message === 'object' && ['tool-plan-delta', 'content-delta'].includes(json.type)) {
|
||||
const text = json?.delta?.message?.content?.text ?? '';
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const str = json.delta.message.content.text[i];
|
||||
yield {
|
||||
data: { ...json, delta: { message: { content: { text: str } } } },
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Claude
|
||||
if (typeof json.delta === 'object') {
|
||||
if (typeof json.delta.text === 'string' && json.delta.text.length > 0) {
|
||||
if (typeof json.delta === 'object' && typeof json.delta.text === 'string') {
|
||||
if (json.delta.text.length > 0) {
|
||||
for (let i = 0; i < json.delta.text.length; i++) {
|
||||
const str = json.delta.text[i];
|
||||
yield {
|
||||
|
@ -4,6 +4,7 @@ import { textgenerationwebui_settings as textgen_settings, textgen_types } from
|
||||
import { tokenizers } from './tokenizers.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||
import { PAGINATION_TEMPLATE } from './utils.js';
|
||||
|
||||
let mancerModels = [];
|
||||
let togetherModels = [];
|
||||
@ -265,19 +266,212 @@ export async function loadAphroditeModels(data) {
|
||||
}
|
||||
}
|
||||
|
||||
let featherlessCurrentPage = 1;
|
||||
export async function loadFeatherlessModels(data) {
|
||||
const searchBar = document.getElementById('featherless_model_search_bar');
|
||||
const modelCardBlock = document.getElementById('featherless_model_card_block');
|
||||
const paginationContainer = $('#featherless_model_pagination_container');
|
||||
const sortOrderSelect = document.getElementById('featherless_model_sort_order');
|
||||
const classSelect = document.getElementById('featherless_class_selection');
|
||||
const categoriesSelect = document.getElementById('featherless_category_selection');
|
||||
const storageKey = 'FeatherlessModels_PerPage';
|
||||
|
||||
// Store the original models data for search and filtering
|
||||
let originalModels = [];
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
console.error('Invalid Featherless models data', data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort the data by model id (default A-Z)
|
||||
data.sort((a, b) => a.id.localeCompare(b.id));
|
||||
originalModels = data; // Store the original data for search
|
||||
featherlessModels = data;
|
||||
|
||||
if (!data.find(x => x.id === textgen_settings.featherless_model)) {
|
||||
textgen_settings.featherless_model = data[0]?.id || '';
|
||||
// Populate class select options with unique classes
|
||||
populateClassSelection(data);
|
||||
|
||||
// Retrieve the stored number of items per page or default to 5
|
||||
const perPage = Number(localStorage.getItem(storageKey)) || 10;
|
||||
|
||||
// Initialize pagination with the full set of models
|
||||
const selectedModelPage = (data.findIndex(x => x.id === textgen_settings.featherless_model) / perPage) + 1;
|
||||
featherlessCurrentPage = selectedModelPage > 0 ? selectedModelPage : 1;
|
||||
setupPagination(originalModels, perPage);
|
||||
|
||||
// Function to set up pagination (also used for filtered results)
|
||||
function setupPagination(models, perPage, pageNumber = featherlessCurrentPage) {
|
||||
paginationContainer.pagination({
|
||||
dataSource: models,
|
||||
pageSize: perPage,
|
||||
pageNumber: pageNumber,
|
||||
sizeChangerOptions: [6, 10, 26, 50, 100, 250, 500, 1000],
|
||||
pageRange: 1,
|
||||
showPageNumbers: true,
|
||||
showSizeChanger: false,
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
formatNavigator: function (currentPage, totalPage) {
|
||||
return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ' of ' + totalPage * perPage;
|
||||
},
|
||||
showNavigator: true,
|
||||
callback: function (modelsOnPage, pagination) {
|
||||
modelCardBlock.innerHTML = '';
|
||||
|
||||
modelsOnPage.forEach(model => {
|
||||
const card = document.createElement('div');
|
||||
card.classList.add('model-card');
|
||||
|
||||
const modelNameContainer = document.createElement('div');
|
||||
modelNameContainer.classList.add('model-name-container');
|
||||
|
||||
const modelTitle = document.createElement('div');
|
||||
modelTitle.classList.add('model-title');
|
||||
modelTitle.textContent = model.id.replace(/_/g, '_\u200B');
|
||||
modelNameContainer.appendChild(modelTitle);
|
||||
|
||||
const detailsContainer = document.createElement('div');
|
||||
detailsContainer.classList.add('details-container');
|
||||
|
||||
const modelClassDiv = document.createElement('div');
|
||||
modelClassDiv.classList.add('model-class');
|
||||
modelClassDiv.textContent = `Class: ${model.model_class || 'N/A'}`;
|
||||
|
||||
const contextLengthDiv = document.createElement('div');
|
||||
contextLengthDiv.classList.add('model-context-length');
|
||||
contextLengthDiv.textContent = `Context Length: ${model.context_length}`;
|
||||
|
||||
const dateAddedDiv = document.createElement('div');
|
||||
dateAddedDiv.classList.add('model-date-added');
|
||||
dateAddedDiv.textContent = `Added On: ${new Date(model.updated_at).toLocaleDateString()}`;
|
||||
|
||||
detailsContainer.appendChild(modelClassDiv);
|
||||
detailsContainer.appendChild(contextLengthDiv);
|
||||
detailsContainer.appendChild(dateAddedDiv);
|
||||
|
||||
card.appendChild(modelNameContainer);
|
||||
card.appendChild(detailsContainer);
|
||||
|
||||
modelCardBlock.appendChild(card);
|
||||
|
||||
if (model.id === textgen_settings.featherless_model) {
|
||||
card.classList.add('selected');
|
||||
}
|
||||
|
||||
card.addEventListener('click', function () {
|
||||
document.querySelectorAll('.model-card').forEach(c => c.classList.remove('selected'));
|
||||
card.classList.add('selected');
|
||||
onFeatherlessModelSelect(model.id);
|
||||
});
|
||||
});
|
||||
|
||||
// Update the current page value whenever the page changes
|
||||
featherlessCurrentPage = pagination.pageNumber;
|
||||
},
|
||||
afterSizeSelectorChange: function (e) {
|
||||
const newPerPage = e.target.value;
|
||||
localStorage.setItem('Models_PerPage', newPerPage);
|
||||
setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Unset previously added listeners
|
||||
$(searchBar).off('input');
|
||||
$(sortOrderSelect).off('change');
|
||||
$(classSelect).off('change');
|
||||
$(categoriesSelect).off('change');
|
||||
|
||||
// Add event listener for input on the search bar
|
||||
searchBar.addEventListener('input', function () {
|
||||
applyFiltersAndSort();
|
||||
});
|
||||
|
||||
// Add event listener for the sort order select
|
||||
sortOrderSelect.addEventListener('change', function () {
|
||||
applyFiltersAndSort();
|
||||
});
|
||||
|
||||
// Add event listener for the class select
|
||||
classSelect.addEventListener('change', function () {
|
||||
applyFiltersAndSort();
|
||||
});
|
||||
|
||||
categoriesSelect.addEventListener('change', function () {
|
||||
applyFiltersAndSort();
|
||||
});
|
||||
|
||||
// Function to populate class selection dropdown
|
||||
function populateClassSelection(models) {
|
||||
const uniqueClasses = [...new Set(models.map(model => model.model_class).filter(Boolean))]; // Get unique class names
|
||||
uniqueClasses.sort((a, b) => a.localeCompare(b));
|
||||
uniqueClasses.forEach(className => {
|
||||
const option = document.createElement('option');
|
||||
option.value = className;
|
||||
option.textContent = className;
|
||||
classSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to apply sorting and filtering based on user input
|
||||
async function applyFiltersAndSort() {
|
||||
if (!(searchBar instanceof HTMLInputElement) ||
|
||||
!(sortOrderSelect instanceof HTMLSelectElement) ||
|
||||
!(classSelect instanceof HTMLSelectElement) ||
|
||||
!(categoriesSelect instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
const searchQuery = searchBar.value.toLowerCase();
|
||||
const selectedSortOrder = sortOrderSelect.value;
|
||||
const selectedClass = classSelect.value;
|
||||
const selectedCategory = categoriesSelect.value;
|
||||
let featherlessTop = [];
|
||||
let featherlessNew = [];
|
||||
|
||||
if (selectedCategory === 'Top') {
|
||||
featherlessTop = await fetchFeatherlessStats();
|
||||
}
|
||||
const featherlessIds = featherlessTop.map(stat => stat.id);
|
||||
if (selectedCategory === 'New') {
|
||||
featherlessNew = await fetchFeatherlessNew();
|
||||
}
|
||||
const featherlessNewIds = featherlessNew.map(stat => stat.id);
|
||||
|
||||
let filteredModels = originalModels.filter(model => {
|
||||
const matchesSearch = model.id.toLowerCase().includes(searchQuery);
|
||||
const matchesClass = selectedClass ? model.model_class === selectedClass : true;
|
||||
const matchesTop = featherlessIds.includes(model.id);
|
||||
const matchesNew = featherlessNewIds.includes(model.id);
|
||||
|
||||
if (selectedCategory === 'All') {
|
||||
return matchesSearch && matchesClass;
|
||||
}
|
||||
else if (selectedCategory === 'Top') {
|
||||
return matchesSearch && matchesClass && matchesTop;
|
||||
}
|
||||
else if (selectedCategory === 'New') {
|
||||
return matchesSearch && matchesClass && matchesNew;
|
||||
}
|
||||
else {
|
||||
return matchesSearch;
|
||||
}
|
||||
});
|
||||
|
||||
if (selectedSortOrder === 'asc') {
|
||||
filteredModels.sort((a, b) => a.id.localeCompare(b.id));
|
||||
} else if (selectedSortOrder === 'desc') {
|
||||
filteredModels.sort((a, b) => b.id.localeCompare(a.id));
|
||||
} else if (selectedSortOrder === 'date_asc') {
|
||||
filteredModels.sort((a, b) => a.updated_at.localeCompare(b.updated_at));
|
||||
} else if (selectedSortOrder === 'date_desc') {
|
||||
filteredModels.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
||||
}
|
||||
|
||||
setupPagination(filteredModels, Number(localStorage.getItem(storageKey)) || perPage, featherlessCurrentPage);
|
||||
}
|
||||
|
||||
// Required to keep the /model command function
|
||||
$('#featherless_model').empty();
|
||||
for (const model of data) {
|
||||
const option = document.createElement('option');
|
||||
@ -288,15 +482,49 @@ export async function loadFeatherlessModels(data) {
|
||||
}
|
||||
}
|
||||
|
||||
function onFeatherlessModelSelect() {
|
||||
const modelId = String($('#featherless_model').val());
|
||||
textgen_settings.featherless_model = modelId;
|
||||
$('#api_button_textgenerationwebui').trigger('click');
|
||||
async function fetchFeatherlessStats() {
|
||||
const response = await fetch('https://api.featherless.ai/feather/popular');
|
||||
const data = await response.json();
|
||||
return data.popular;
|
||||
}
|
||||
|
||||
async function fetchFeatherlessNew() {
|
||||
const response = await fetch('https://api.featherless.ai/feather/models?sort=-created_at&perPage=10');
|
||||
const data = await response.json();
|
||||
return data.items;
|
||||
}
|
||||
|
||||
function onFeatherlessModelSelect(modelId) {
|
||||
const model = featherlessModels.find(x => x.id === modelId);
|
||||
textgen_settings.featherless_model = modelId;
|
||||
$('#featherless_model').val(modelId);
|
||||
$('#api_button_textgenerationwebui').trigger('click');
|
||||
setGenerationParamsFromPreset({ max_length: model.context_length });
|
||||
}
|
||||
|
||||
let featherlessIsGridView = false; // Default state set to grid view
|
||||
|
||||
// Ensure the correct initial view is applied when the page loads
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const modelCardBlock = document.getElementById('featherless_model_card_block');
|
||||
modelCardBlock.classList.add('list-view');
|
||||
|
||||
const toggleButton = document.getElementById('featherless_model_grid_toggle');
|
||||
toggleButton.addEventListener('click', function () {
|
||||
// Toggle between grid and list view
|
||||
if (featherlessIsGridView) {
|
||||
modelCardBlock.classList.remove('grid-view');
|
||||
modelCardBlock.classList.add('list-view');
|
||||
this.title = 'Toggle to grid view';
|
||||
} else {
|
||||
modelCardBlock.classList.remove('list-view');
|
||||
modelCardBlock.classList.add('grid-view');
|
||||
this.title = 'Toggle to list view';
|
||||
}
|
||||
|
||||
featherlessIsGridView = !featherlessIsGridView;
|
||||
});
|
||||
});
|
||||
function onMancerModelSelect() {
|
||||
const modelId = String($('#mancer_model').val());
|
||||
textgen_settings.mancer_model = modelId;
|
||||
@ -469,20 +697,6 @@ function getAphroditeModelTemplate(option) {
|
||||
`));
|
||||
}
|
||||
|
||||
function getFeatherlessModelTemplate(option) {
|
||||
const model = featherlessModels.find(x => x.id === option?.element?.value);
|
||||
|
||||
if (!option.id || !model) {
|
||||
return option.text;
|
||||
}
|
||||
|
||||
return $((`
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<div><strong>${DOMPurify.sanitize(model.name)}</strong> | <span>${model.context_length || '???'} tokens</span></div>
|
||||
</div>
|
||||
`));
|
||||
}
|
||||
|
||||
async function downloadOllamaModel() {
|
||||
try {
|
||||
const serverUrl = textgen_settings.server_urls[textgen_types.OLLAMA];
|
||||
@ -670,9 +884,9 @@ export function initTextGenModels() {
|
||||
$('#ollama_download_model').on('click', downloadOllamaModel);
|
||||
$('#vllm_model').on('change', onVllmModelSelect);
|
||||
$('#aphrodite_model').on('change', onAphroditeModelSelect);
|
||||
$('#featherless_model').on('change', onFeatherlessModelSelect);
|
||||
$('#tabby_download_model').on('click', downloadTabbyModel);
|
||||
$('#tabby_model').on('change', onTabbyModelSelect);
|
||||
$('#featherless_model').on('change', () => onFeatherlessModelSelect(String($('#featherless_model').val())));
|
||||
|
||||
const providersSelect = $('.openrouter_providers');
|
||||
for (const provider of OPENROUTER_PROVIDERS) {
|
||||
@ -745,13 +959,6 @@ export function initTextGenModels() {
|
||||
width: '100%',
|
||||
templateResult: getAphroditeModelTemplate,
|
||||
});
|
||||
$('#featherless_model').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getFeatherlessModelTemplate,
|
||||
});
|
||||
providersSelect.select2({
|
||||
sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
|
||||
placeholder: 'Select providers. No selection = all providers.',
|
||||
|
@ -193,6 +193,7 @@ const settings = {
|
||||
openrouter_allow_fallbacks: true,
|
||||
xtc_threshold: 0.1,
|
||||
xtc_probability: 0,
|
||||
featherless_model: '',
|
||||
};
|
||||
|
||||
export let textgenerationwebui_banned_in_macros = [];
|
||||
|
@ -292,10 +292,10 @@ export class ToolManager {
|
||||
|
||||
if (error instanceof Error) {
|
||||
error.cause = name;
|
||||
return error;
|
||||
return error.toString();
|
||||
}
|
||||
|
||||
return new Error('Unknown error occurred while invoking the tool.', { cause: name });
|
||||
return new Error('Unknown error occurred while invoking the tool.', { cause: name }).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,6 +365,9 @@ export class ToolManager {
|
||||
* @returns {void}
|
||||
*/
|
||||
static parseToolCalls(toolCalls, parsed) {
|
||||
if (!this.isToolCallingSupported()) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(parsed?.choices)) {
|
||||
for (const choice of parsed.choices) {
|
||||
const choiceIndex = (typeof choice.index === 'number') ? choice.index : null;
|
||||
@ -401,6 +404,22 @@ export class ToolManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
const cohereToolEvents = ['message-start', 'tool-call-start', 'tool-call-delta', 'tool-call-end'];
|
||||
if (cohereToolEvents.includes(parsed?.type) && typeof parsed?.delta?.message === 'object') {
|
||||
const choiceIndex = 0;
|
||||
const toolCallIndex = parsed?.index ?? 0;
|
||||
|
||||
if (!Array.isArray(toolCalls[choiceIndex])) {
|
||||
toolCalls[choiceIndex] = [];
|
||||
}
|
||||
|
||||
if (toolCalls[choiceIndex][toolCallIndex] === undefined) {
|
||||
toolCalls[choiceIndex][toolCallIndex] = {};
|
||||
}
|
||||
|
||||
const targetToolCall = toolCalls[choiceIndex][toolCallIndex];
|
||||
ToolManager.#applyToolCallDelta(targetToolCall, parsed.delta.message);
|
||||
}
|
||||
if (typeof parsed?.content_block === 'object') {
|
||||
const choiceIndex = 0;
|
||||
const toolCallIndex = parsed?.index ?? 0;
|
||||
@ -503,6 +522,7 @@ export class ToolManager {
|
||||
chat_completion_sources.CLAUDE,
|
||||
chat_completion_sources.OPENROUTER,
|
||||
chat_completion_sources.GROQ,
|
||||
chat_completion_sources.COHERE,
|
||||
];
|
||||
return supportedSources.includes(oai_settings.chat_completion_source);
|
||||
}
|
||||
@ -529,7 +549,15 @@ export class ToolManager {
|
||||
|
||||
// Parsed tool calls from streaming data
|
||||
if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) {
|
||||
return isClaudeToolCall(data[0]) ? data[0].filter(x => x).map(convertClaudeToolCall) : data[0];
|
||||
if (isClaudeToolCall(data[0])) {
|
||||
return data[0].filter(x => x).map(convertClaudeToolCall);
|
||||
}
|
||||
|
||||
if (typeof data[0]?.[0]?.tool_calls === 'object') {
|
||||
return Array.isArray(data[0]?.[0]?.tool_calls) ? data[0][0].tool_calls : [data[0][0].tool_calls];
|
||||
}
|
||||
|
||||
return data[0];
|
||||
}
|
||||
|
||||
// Parsed tool calls from non-streaming data
|
||||
@ -550,6 +578,11 @@ export class ToolManager {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
// Cohere tool calls
|
||||
if (typeof data?.message?.tool_calls === 'object') {
|
||||
return Array.isArray(data?.message?.tool_calls) ? data.message.tool_calls : [data.message.tool_calls];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -848,7 +848,14 @@ async function logout() {
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
window.location.reload();
|
||||
// On an explicit logout stop auto login
|
||||
// to allow user to change username even
|
||||
// when auto auth (such as authelia or basic)
|
||||
// would be valid
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
urlParams.set('noauto', 'true');
|
||||
|
||||
window.location.search = urlParams.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -836,6 +836,40 @@ function randValuesCallback(from, to, args) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function customSortComparitor(a, b) {
|
||||
if (typeof a != typeof b) {
|
||||
a = typeof a;
|
||||
b = typeof b;
|
||||
}
|
||||
return a > b ? 1 : a < b ? -1 : 0;
|
||||
}
|
||||
|
||||
function sortArrayObjectCallback(args, value) {
|
||||
let parsedValue;
|
||||
if (typeof value == 'string') {
|
||||
try {
|
||||
parsedValue = JSON.parse(value);
|
||||
} catch {
|
||||
// return the original input if it was invalid
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
parsedValue = value;
|
||||
}
|
||||
if (Array.isArray(parsedValue)) {
|
||||
// always sort lists by value
|
||||
parsedValue.sort(customSortComparitor);
|
||||
} else if (typeof parsedValue == 'object') {
|
||||
let keysort = args.keysort;
|
||||
if (isFalseBoolean(keysort)) {
|
||||
parsedValue = Object.keys(parsedValue).sort(function (a, b) { return customSortComparitor(parsedValue[a], parsedValue[b]); });
|
||||
} else {
|
||||
parsedValue = Object.keys(parsedValue).sort(customSortComparitor);
|
||||
}
|
||||
}
|
||||
return JSON.stringify(parsedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a new variable in the current scope.
|
||||
* @param {NamedArguments} args Named arguments.
|
||||
@ -2109,6 +2143,51 @@ export function registerVariableCommands() {
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'sort',
|
||||
callback: sortArrayObjectCallback,
|
||||
returns: 'the sorted list or dictionary keys',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({ name: 'keysort',
|
||||
description: 'whether to sort by key or value; ignored for lists',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
enumList: ['true', 'false'],
|
||||
defaultValue: 'true',
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'value',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY],
|
||||
isRequired: true,
|
||||
forceEnum: false,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Sorts a list or dictionary in ascending order and passes the result down the pipe.
|
||||
<ul>
|
||||
<li>
|
||||
For lists, returns the list sorted by value.
|
||||
</li>
|
||||
<li>
|
||||
For dictionaries, returns the ordered list of keys after sorting. Setting keysort=false means keys are sorted by associated value.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Examples:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/sort [5,3,4,1,2] | /echo</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/sort keysort=false {"a": 1, "d": 3, "c": 2, "b": 5} | /echo</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'rand',
|
||||
callback: (args, value) => String(randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value ? value : 1)), args)),
|
||||
|
126
public/style.css
126
public/style.css
@ -5547,3 +5547,129 @@ body:not(.movingUI) .drawer-content.maximized {
|
||||
#InstructSequencesColumn details:not(:last-of-type) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#user_avatar_block {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Main structure for the model cards */
|
||||
.model-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
border: 1px solid #333;
|
||||
border-radius: 8px;
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
margin: 7px;
|
||||
width: calc(100% - 7px);
|
||||
box-sizing: border-box;
|
||||
transition: transform 0.2s ease-in-out, background-color 0.2s ease-in-out, border 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.model-card .details-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.model-card:hover {
|
||||
transform: scale(1.01);
|
||||
background-color: #444;
|
||||
transition: transform 0.2s ease-in-out, background-color 0.2s ease-in-out; /* Smooth transition */
|
||||
}
|
||||
|
||||
.model-card.selected {
|
||||
border: 2px solid var(--okGreen70a);
|
||||
background-color: var(--okGreen70a);
|
||||
}
|
||||
|
||||
.model-info {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.model-title {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.model-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
text-align: right;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.model-class, .model-context-length, .model-date-added {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.model-class, .model-context-length {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#featherless_model_pagination_container .paginationjs-nav {
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
#featherless_model_card_block.grid-view {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* gap: 3px; */
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Grid-view card */
|
||||
#featherless_model_card_block.grid-view .model-card {
|
||||
flex-direction: column;
|
||||
flex: 1 1 calc(50% - 30px);
|
||||
}
|
||||
|
||||
#featherless_model_search_bar {
|
||||
width: 15ch;
|
||||
flex-grow: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#featherless_model_sort_order {
|
||||
width: auto;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#featherless_model_grid_toggle {
|
||||
flex-shrink: 0;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#featherless_category_selection,
|
||||
#featherless_class_selection {
|
||||
display: flex;
|
||||
width: auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.model-card {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.model-info, .model-details, .model-date-added {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#featherless_model_search_bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
14
server.js
14
server.js
@ -65,6 +65,7 @@ const DEFAULT_WHITELIST = true;
|
||||
const DEFAULT_ACCOUNTS = false;
|
||||
const DEFAULT_CSRF_DISABLED = false;
|
||||
const DEFAULT_BASIC_AUTH = false;
|
||||
const DEFAULT_PER_USER_BASIC_AUTH = false;
|
||||
|
||||
const DEFAULT_ENABLE_IPV6 = false;
|
||||
const DEFAULT_ENABLE_IPV4 = true;
|
||||
@ -184,6 +185,7 @@ const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode'
|
||||
const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
|
||||
const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
|
||||
const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
|
||||
const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);
|
||||
const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
|
||||
|
||||
const uploadsPath = path.join(dataRoot, require('./src/constants').UPLOADS_DIRECTORY);
|
||||
@ -361,7 +363,7 @@ app.get('/login', async (request, response) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const autoLogin = await userModule.tryAutoLogin(request);
|
||||
const autoLogin = await userModule.tryAutoLogin(request, basicAuthMode);
|
||||
|
||||
if (autoLogin) {
|
||||
return response.redirect('/');
|
||||
@ -756,9 +758,13 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
|
||||
}
|
||||
|
||||
if (basicAuthMode) {
|
||||
const basicAuthUser = getConfigValue('basicAuthUser', {});
|
||||
if (!basicAuthUser?.username || !basicAuthUser?.password) {
|
||||
console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));
|
||||
if (perUserBasicAuth && !enableAccounts) {
|
||||
console.error(color.red('Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.'));
|
||||
} else if (!perUserBasicAuth) {
|
||||
const basicAuthUser = getConfigValue('basicAuthUser', {});
|
||||
if (!basicAuthUser?.username || !basicAuthUser?.password) {
|
||||
console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,126 +0,0 @@
|
||||
const DATA_PREFIX = 'data:';
|
||||
|
||||
/**
|
||||
* Borrowed from Cohere SDK (MIT License)
|
||||
* https://github.com/cohere-ai/cohere-typescript/blob/main/src/core/streaming-fetcher/Stream.ts
|
||||
* Copyright (c) 2021 Cohere
|
||||
*/
|
||||
class CohereStream {
|
||||
/** @type {ReadableStream} */
|
||||
stream;
|
||||
/** @type {string} */
|
||||
prefix;
|
||||
/** @type {string} */
|
||||
messageTerminator;
|
||||
/** @type {string|undefined} */
|
||||
streamTerminator;
|
||||
/** @type {AbortController} */
|
||||
controller = new AbortController();
|
||||
|
||||
constructor({ stream, eventShape }) {
|
||||
this.stream = stream;
|
||||
if (eventShape.type === 'sse') {
|
||||
this.prefix = DATA_PREFIX;
|
||||
this.messageTerminator = '\n';
|
||||
this.streamTerminator = eventShape.streamTerminator;
|
||||
} else {
|
||||
this.messageTerminator = eventShape.messageTerminator;
|
||||
}
|
||||
}
|
||||
|
||||
async *iterMessages() {
|
||||
const stream = readableStreamAsyncIterable(this.stream);
|
||||
let buf = '';
|
||||
let prefixSeen = false;
|
||||
let parsedAnyMessages = false;
|
||||
for await (const chunk of stream) {
|
||||
buf += this.decodeChunk(chunk);
|
||||
|
||||
let terminatorIndex;
|
||||
// Parse the chunk into as many messages as possible
|
||||
while ((terminatorIndex = buf.indexOf(this.messageTerminator)) >= 0) {
|
||||
// Extract the line from the buffer
|
||||
let line = buf.slice(0, terminatorIndex + 1);
|
||||
buf = buf.slice(terminatorIndex + 1);
|
||||
|
||||
// Skip empty lines
|
||||
if (line.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip the chunk until the prefix is found
|
||||
if (!prefixSeen && this.prefix != null) {
|
||||
const prefixIndex = line.indexOf(this.prefix);
|
||||
if (prefixIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
prefixSeen = true;
|
||||
line = line.slice(prefixIndex + this.prefix.length);
|
||||
}
|
||||
|
||||
// If the stream terminator is present, return
|
||||
if (this.streamTerminator != null && line.includes(this.streamTerminator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, yield message from the prefix to the terminator
|
||||
const message = JSON.parse(line);
|
||||
yield message;
|
||||
prefixSeen = false;
|
||||
parsedAnyMessages = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsedAnyMessages && buf.length > 0) {
|
||||
try {
|
||||
yield JSON.parse(buf);
|
||||
} catch (e) {
|
||||
console.error('Error parsing message:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async *[Symbol.asyncIterator]() {
|
||||
for await (const message of this.iterMessages()) {
|
||||
yield message;
|
||||
}
|
||||
}
|
||||
|
||||
decodeChunk(chunk) {
|
||||
const decoder = new TextDecoder('utf8');
|
||||
return decoder.decode(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
function readableStreamAsyncIterable(stream) {
|
||||
if (stream[Symbol.asyncIterator]) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
const reader = stream.getReader();
|
||||
return {
|
||||
async next() {
|
||||
try {
|
||||
const result = await reader.read();
|
||||
if (result?.done) {
|
||||
reader.releaseLock();
|
||||
} // release lock when stream becomes closed
|
||||
return result;
|
||||
} catch (e) {
|
||||
reader.releaseLock(); // release lock when stream becomes errored
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
async return() {
|
||||
const cancelPromise = reader.cancel();
|
||||
reader.releaseLock();
|
||||
await cancelPromise;
|
||||
return { done: true, value: undefined };
|
||||
},
|
||||
[Symbol.asyncIterator]() {
|
||||
return this;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = CohereStream;
|
@ -5,7 +5,6 @@ const { jsonParser } = require('../../express-common');
|
||||
const { CHAT_COMPLETION_SOURCES, GEMINI_SAFETY, BISON_SAFETY, OPENROUTER_HEADERS } = require('../../constants');
|
||||
const { forwardFetchResponse, getConfigValue, tryParse, uuidv4, mergeObjectWithYaml, excludeKeysByYaml, color } = require('../../util');
|
||||
const { convertClaudeMessages, convertGooglePrompt, convertTextCompletionPrompt, convertCohereMessages, convertMistralMessages, convertAI21Messages, mergeMessages } = require('../../prompt-converters');
|
||||
const CohereStream = require('../../cohere-stream');
|
||||
|
||||
const { readSecret, SECRET_KEYS } = require('../secrets');
|
||||
const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sentencepieceTokenizers, TEXT_COMPLETION_MODELS } = require('../tokenizers');
|
||||
@ -13,7 +12,8 @@ const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sente
|
||||
const API_OPENAI = 'https://api.openai.com/v1';
|
||||
const API_CLAUDE = 'https://api.anthropic.com/v1';
|
||||
const API_MISTRAL = 'https://api.mistral.ai/v1';
|
||||
const API_COHERE = 'https://api.cohere.ai/v1';
|
||||
const API_COHERE_V1 = 'https://api.cohere.ai/v1';
|
||||
const API_COHERE_V2 = 'https://api.cohere.ai/v2';
|
||||
const API_PERPLEXITY = 'https://api.perplexity.ai';
|
||||
const API_GROQ = 'https://api.groq.com/openai/v1';
|
||||
const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
|
||||
@ -553,13 +553,14 @@ async function sendCohereRequest(request, response) {
|
||||
|
||||
try {
|
||||
const convertedHistory = convertCohereMessages(request.body.messages, request.body.char_name, request.body.user_name);
|
||||
const connectors = [];
|
||||
const tools = [];
|
||||
|
||||
const canDoWebSearch = !String(request.body.model).includes('c4ai-aya');
|
||||
if (request.body.websearch && canDoWebSearch) {
|
||||
connectors.push({
|
||||
id: 'web-search',
|
||||
if (Array.isArray(request.body.tools) && request.body.tools.length > 0) {
|
||||
tools.push(...request.body.tools);
|
||||
tools.forEach(tool => {
|
||||
if (tool?.function?.parameters?.$schema) {
|
||||
delete tool.function.parameters.$schema;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -567,9 +568,7 @@ async function sendCohereRequest(request, response) {
|
||||
const requestBody = {
|
||||
stream: Boolean(request.body.stream),
|
||||
model: request.body.model,
|
||||
message: convertedHistory.userPrompt,
|
||||
preamble: convertedHistory.systemPrompt,
|
||||
chat_history: convertedHistory.chatHistory,
|
||||
messages: convertedHistory.chatHistory,
|
||||
temperature: request.body.temperature,
|
||||
max_tokens: request.body.max_tokens,
|
||||
k: request.body.top_k,
|
||||
@ -578,16 +577,13 @@ async function sendCohereRequest(request, response) {
|
||||
stop_sequences: request.body.stop,
|
||||
frequency_penalty: request.body.frequency_penalty,
|
||||
presence_penalty: request.body.presence_penalty,
|
||||
prompt_truncation: 'AUTO_PRESERVE_ORDER',
|
||||
connectors: connectors,
|
||||
documents: [],
|
||||
tools: tools,
|
||||
search_queries_only: false,
|
||||
};
|
||||
|
||||
const canDoSafetyMode = String(request.body.model).endsWith('08-2024');
|
||||
if (canDoSafetyMode) {
|
||||
requestBody.safety_mode = 'NONE';
|
||||
requestBody.safety_mode = 'OFF';
|
||||
}
|
||||
|
||||
console.log('Cohere request:', requestBody);
|
||||
@ -603,11 +599,11 @@ async function sendCohereRequest(request, response) {
|
||||
timeout: 0,
|
||||
};
|
||||
|
||||
const apiUrl = API_COHERE + '/chat';
|
||||
const apiUrl = API_COHERE_V2 + '/chat';
|
||||
|
||||
if (request.body.stream) {
|
||||
const stream = await global.fetch(apiUrl, config);
|
||||
parseCohereStream(stream, request, response);
|
||||
const stream = await fetch(apiUrl, config);
|
||||
forwardFetchResponse(stream, response);
|
||||
} else {
|
||||
const generateResponse = await fetch(apiUrl, config);
|
||||
if (!generateResponse.ok) {
|
||||
@ -658,7 +654,7 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
|
||||
headers = {};
|
||||
mergeObjectWithYaml(headers, request.body.custom_include_headers);
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COHERE) {
|
||||
api_url = API_COHERE;
|
||||
api_url = API_COHERE_V1;
|
||||
api_key_openai = readSecret(request.user.directories, SECRET_KEYS.COHERE);
|
||||
headers = {};
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZEROONEAI) {
|
||||
|
@ -2,14 +2,19 @@
|
||||
* When applied, this middleware will ensure the request contains the required header for basic authentication and only
|
||||
* allow access to the endpoint after successful authentication.
|
||||
*/
|
||||
const { getConfig } = require('../util.js');
|
||||
const { getAllUserHandles, toKey, getPasswordHash } = require('../users.js');
|
||||
const { getConfig, getConfigValue } = require('../util.js');
|
||||
const storage = require('node-persist');
|
||||
|
||||
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
|
||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
||||
|
||||
const unauthorizedResponse = (res) => {
|
||||
res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');
|
||||
return res.status(401).send('Authentication required');
|
||||
};
|
||||
|
||||
const basicAuthMiddleware = function (request, response, callback) {
|
||||
const basicAuthMiddleware = async function (request, response, callback) {
|
||||
const config = getConfig();
|
||||
const authHeader = request.headers.authorization;
|
||||
|
||||
@ -23,15 +28,25 @@ const basicAuthMiddleware = function (request, response, callback) {
|
||||
return unauthorizedResponse(response);
|
||||
}
|
||||
|
||||
const usePerUserAuth = PER_USER_BASIC_AUTH && ENABLE_ACCOUNTS;
|
||||
const [username, password] = Buffer.from(credentials, 'base64')
|
||||
.toString('utf8')
|
||||
.split(':');
|
||||
|
||||
if (username === config.basicAuthUser.username && password === config.basicAuthUser.password) {
|
||||
if (!usePerUserAuth && username === config.basicAuthUser.username && password === config.basicAuthUser.password) {
|
||||
return callback();
|
||||
} else {
|
||||
return unauthorizedResponse(response);
|
||||
} else if (usePerUserAuth) {
|
||||
const userHandles = await getAllUserHandles();
|
||||
for (const userHandle of userHandles) {
|
||||
if (username === userHandle) {
|
||||
const user = await storage.getItem(toKey(userHandle));
|
||||
if (user && user.enabled && (user.password && user.password === getPasswordHash(password, user.salt))) {
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return unauthorizedResponse(response);
|
||||
};
|
||||
|
||||
module.exports = basicAuthMiddleware;
|
||||
|
@ -277,38 +277,9 @@ function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools,
|
||||
* @param {object[]} messages Array of messages
|
||||
* @param {string} charName Character name
|
||||
* @param {string} userName User name
|
||||
* @returns {{systemPrompt: string, chatHistory: object[], userPrompt: string}} Prompt for Cohere
|
||||
* @returns {{chatHistory: object[]}} Prompt for Cohere
|
||||
*/
|
||||
function convertCohereMessages(messages, charName = '', userName = '') {
|
||||
const roleMap = {
|
||||
'system': 'SYSTEM',
|
||||
'user': 'USER',
|
||||
'assistant': 'CHATBOT',
|
||||
};
|
||||
let systemPrompt = '';
|
||||
|
||||
// Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array.
|
||||
let i;
|
||||
for (i = 0; i < messages.length; i++) {
|
||||
if (messages[i].role !== 'system') {
|
||||
break;
|
||||
}
|
||||
// Append example names if not already done by the frontend (e.g. for group chats).
|
||||
if (userName && messages[i].name === 'example_user') {
|
||||
if (!messages[i].content.startsWith(`${userName}: `)) {
|
||||
messages[i].content = `${userName}: ${messages[i].content}`;
|
||||
}
|
||||
}
|
||||
if (charName && messages[i].name === 'example_assistant') {
|
||||
if (!messages[i].content.startsWith(`${charName}: `)) {
|
||||
messages[i].content = `${charName}: ${messages[i].content}`;
|
||||
}
|
||||
}
|
||||
systemPrompt += `${messages[i].content}\n\n`;
|
||||
}
|
||||
|
||||
messages.splice(0, i);
|
||||
|
||||
if (messages.length === 0) {
|
||||
messages.unshift({
|
||||
role: 'user',
|
||||
@ -316,17 +287,41 @@ function convertCohereMessages(messages, charName = '', userName = '') {
|
||||
});
|
||||
}
|
||||
|
||||
const lastNonSystemMessageIndex = messages.findLastIndex(msg => msg.role === 'user' || msg.role === 'assistant');
|
||||
const userPrompt = messages.slice(lastNonSystemMessageIndex).map(msg => msg.content).join('\n\n') || PROMPT_PLACEHOLDER;
|
||||
|
||||
const chatHistory = messages.slice(0, lastNonSystemMessageIndex).map(msg => {
|
||||
return {
|
||||
role: roleMap[msg.role] || 'USER',
|
||||
message: msg.content,
|
||||
};
|
||||
messages.forEach((msg, index) => {
|
||||
// Tool calls require an assistent primer
|
||||
if (Array.isArray(msg.tool_calls)) {
|
||||
if (index > 0 && messages[index - 1].role === 'assistant') {
|
||||
msg.content = messages[index - 1].content;
|
||||
messages.splice(index - 1, 1);
|
||||
} else {
|
||||
msg.content = `I'm going to call a tool for that: ${msg.tool_calls.map(tc => tc?.function?.name).join(', ')}`;
|
||||
}
|
||||
}
|
||||
// No names support (who would've thought)
|
||||
if (msg.name) {
|
||||
if (msg.role == 'system' && msg.name == 'example_assistant') {
|
||||
if (charName && !msg.content.startsWith(`${charName}: `)) {
|
||||
msg.content = `${charName}: ${msg.content}`;
|
||||
}
|
||||
}
|
||||
if (msg.role == 'system' && msg.name == 'example_user') {
|
||||
if (userName && !msg.content.startsWith(`${userName}: `)) {
|
||||
msg.content = `${userName}: ${msg.content}`;
|
||||
}
|
||||
}
|
||||
if (msg.role !== 'system' && !msg.content.startsWith(`${msg.name}: `)) {
|
||||
msg.content = `${msg.name}: ${msg.content}`;
|
||||
}
|
||||
delete msg.name;
|
||||
}
|
||||
});
|
||||
|
||||
return { systemPrompt: systemPrompt.trim(), chatHistory, userPrompt };
|
||||
// A prompt should end with a user/tool message
|
||||
if (messages.length && !['user', 'tool'].includes(messages[messages.length - 1].role)) {
|
||||
messages[messages.length - 1].role = 'user';
|
||||
}
|
||||
|
||||
return { chatHistory: messages };
|
||||
}
|
||||
|
||||
/**
|
||||
|
104
src/users.js
104
src/users.js
@ -19,6 +19,8 @@ const { readSecret, writeSecret } = require('./endpoints/secrets');
|
||||
const KEY_PREFIX = 'user:';
|
||||
const AVATAR_PREFIX = 'avatar:';
|
||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
||||
const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false);
|
||||
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
|
||||
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
||||
|
||||
/**
|
||||
@ -565,13 +567,42 @@ function shouldRedirectToLogin(request) {
|
||||
return ENABLE_ACCOUNTS && !request.user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries auto-login if there is only one user and it's not password protected.
|
||||
* or another configured method such authlia or basic
|
||||
* @param {import('express').Request} request Request object
|
||||
* @param {boolean} basicAuthMode If Basic auth mode is enabled
|
||||
* @returns {Promise<boolean>} Whether auto-login was performed
|
||||
*/
|
||||
async function tryAutoLogin(request, basicAuthMode) {
|
||||
if (!ENABLE_ACCOUNTS || request.user || !request.session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!request.query.noauto) {
|
||||
if (await singleUserLogin(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AUTHELIA_AUTH && await autheliaUserLogin(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (basicAuthMode && PER_USER_BASIC_AUTH && await basicUserLogin(request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries auto-login if there is only one user and it's not password protected.
|
||||
* @param {import('express').Request} request Request object
|
||||
* @returns {Promise<boolean>} Whether auto-login was performed
|
||||
*/
|
||||
async function tryAutoLogin(request) {
|
||||
if (!ENABLE_ACCOUNTS || request.user || !request.session) {
|
||||
async function singleUserLogin(request) {
|
||||
if (!request.session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -583,6 +614,75 @@ async function tryAutoLogin(request) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries auto-login with authlia trusted headers.
|
||||
* https://www.authelia.com/integration/trusted-header-sso/introduction/
|
||||
* @param {import('express').Request} request Request object
|
||||
* @returns {Promise<boolean>} Whether auto-login was performed
|
||||
*/
|
||||
async function autheliaUserLogin(request) {
|
||||
if (!request.session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const remoteUser = request.get('Remote-User');
|
||||
if (!remoteUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userHandles = await getAllUserHandles();
|
||||
for (const userHandle of userHandles) {
|
||||
if (remoteUser === userHandle) {
|
||||
const user = await storage.getItem(toKey(userHandle));
|
||||
if (user && user.enabled) {
|
||||
request.session.handle = userHandle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries auto-login with basic auth username.
|
||||
* @param {import('express').Request} request Request object
|
||||
* @returns {Promise<boolean>} Whether auto-login was performed
|
||||
*/
|
||||
async function basicUserLogin(request) {
|
||||
if (!request.session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const authHeader = request.headers.authorization;
|
||||
|
||||
if (!authHeader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [scheme, credentials] = authHeader.split(' ');
|
||||
|
||||
if (scheme !== 'Basic' || !credentials) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [username, password] = Buffer.from(credentials, 'base64')
|
||||
.toString('utf8')
|
||||
.split(':');
|
||||
|
||||
const userHandles = await getAllUserHandles();
|
||||
for (const userHandle of userHandles) {
|
||||
if (username === userHandle) {
|
||||
const user = await storage.getItem(toKey(userHandle));
|
||||
// Verify pass again here just to be sure
|
||||
if (user && user.enabled && user.password && user.password === getPasswordHash(password, user.salt)) {
|
||||
request.session.handle = userHandle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
|
||||
throw new Error('No API key found');
|
||||
}
|
||||
|
||||
const response = await fetch('https://api.cohere.ai/v1/embed', {
|
||||
const response = await fetch('https://api.cohere.ai/v2/embed', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -26,6 +26,7 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
|
||||
body: JSON.stringify({
|
||||
texts: texts,
|
||||
model: model,
|
||||
embedding_types: ['float'],
|
||||
input_type: isQuery ? 'search_query' : 'search_document',
|
||||
truncate: 'END',
|
||||
}),
|
||||
@ -38,12 +39,12 @@ async function getCohereBatchVector(texts, isQuery, directories, model) {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!Array.isArray(data?.embeddings)) {
|
||||
if (!Array.isArray(data?.embeddings?.float)) {
|
||||
console.log('API response was not an array');
|
||||
throw new Error('API response was not an array');
|
||||
}
|
||||
|
||||
return data.embeddings;
|
||||
return data.embeddings.float;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user