Merge remote-tracking branch 'upstream/staging' into staging
This commit is contained in:
commit
10fb83ee53
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="590.000000pt" height="589.000000pt" viewBox="0 0 590.000000 589.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,589.000000) scale(0.100000,-0.100000)" stroke="none">
|
||||
<path d="M4436 5263 c-22 -2 -49 -11 -60 -19 -11 -8 -30 -14 -43 -14 -12 0
|
||||
-32 -4 -45 -9 -42 -16 -113 -43 -133 -51 -61 -23 -191 -92 -267 -143 -181
|
||||
-122 -327 -258 -472 -441 -71 -88 -216 -297 -216 -309 0 -4 -15 -30 -33 -59
|
||||
-73 -115 -212 -419 -256 -558 -6 -19 -21 -64 -32 -100 -12 -36 -25 -81 -30
|
||||
-100 -22 -96 -28 -122 -47 -215 -46 -228 -66 -517 -52 -743 15 -221 48 -473
|
||||
71 -530 5 -13 9 -33 9 -44 0 -12 4 -35 9 -52 6 -17 17 -58 27 -91 73 -263 189
|
||||
-540 299 -714 31 -50 136 -145 217 -197 38 -24 70 -44 73 -44 3 0 22 -10 43
|
||||
-23 42 -26 202 -92 247 -102 16 -4 33 -12 37 -18 5 -8 58 -12 170 -12 l163 0
|
||||
97 48 c76 37 111 62 169 120 71 70 179 221 179 249 0 8 4 18 9 23 30 33 61
|
||||
177 61 282 0 95 -4 121 -41 241 -60 197 -199 365 -390 475 -24 14 -80 45 -124
|
||||
70 -327 184 -411 248 -422 319 -9 60 2 80 81 145 148 124 337 231 541 308 55
|
||||
20 108 41 117 46 10 5 27 9 37 9 11 0 23 5 26 10 3 6 14 10 22 10 9 0 55 12
|
||||
102 26 47 14 102 29 121 34 19 4 62 15 95 24 69 19 117 32 178 47 82 20 263
|
||||
81 317 107 187 88 391 273 468 427 41 81 63 136 78 197 9 35 21 66 27 70 15 9
|
||||
14 341 -1 346 -6 2 -14 20 -18 40 -7 40 -28 113 -44 150 -6 12 -15 34 -21 50
|
||||
-118 283 -358 521 -639 634 -82 32 -117 45 -155 53 -16 4 -33 12 -37 18 -7 12
|
||||
-425 20 -512 10z"/>
|
||||
<path d="M740 4521 c-14 -5 -50 -14 -80 -21 -157 -36 -350 -175 -454 -325 -70
|
||||
-102 -145 -276 -166 -385 -12 -59 -12 -365 -1 -410 18 -69 57 -166 96 -235 40
|
||||
-71 183 -225 209 -225 7 0 19 -6 25 -14 15 -17 110 -64 171 -83 59 -19 136
|
||||
-33 235 -43 44 -5 114 -13 155 -19 41 -6 109 -16 150 -21 121 -17 191 -30 250
|
||||
-46 30 -8 73 -20 95 -26 98 -25 297 -115 335 -150 25 -23 34 -74 27 -154 -6
|
||||
-81 -77 -230 -173 -363 -86 -121 -93 -129 -191 -231 -84 -88 -139 -172 -162
|
||||
-250 -7 -25 -17 -55 -22 -68 -13 -32 -11 -208 2 -251 50 -164 174 -284 347
|
||||
-337 122 -37 290 6 442 112 127 89 252 228 352 396 15 24 57 143 97 273 17 55
|
||||
40 159 51 235 6 41 16 107 23 145 16 94 16 485 -1 600 -46 318 -109 525 -241
|
||||
795 -55 114 -184 322 -243 395 -168 206 -299 341 -413 425 -33 25 -62 48 -65
|
||||
51 -3 3 -30 21 -60 41 -139 88 -288 150 -453 187 -54 12 -299 13 -337 2z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -444,7 +444,7 @@
|
|||
complete.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,palm">
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
</div>
|
||||
|
@ -496,7 +496,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-newbie-hidden class="range-block" data-source="claude,openrouter,ai21,palm">
|
||||
<div data-newbie-hidden class="range-block" data-source="claude,openrouter,ai21,makersuite">
|
||||
<div class="range-block-title" data-i18n="Top K">
|
||||
Top K
|
||||
</div>
|
||||
|
@ -509,7 +509,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-newbie-hidden class="range-block" data-source="openai,claude,openrouter,ai21,scale,palm">
|
||||
<div data-newbie-hidden class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite">
|
||||
<div class="range-block-title" data-i18n="Top-p">
|
||||
Top P
|
||||
</div>
|
||||
|
@ -1493,7 +1493,7 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter">
|
||||
<div class="range-block" data-source="openai,openrouter,makersuite">
|
||||
<label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
|
||||
<input id="openai_image_inlining" type="checkbox" />
|
||||
<span data-i18n="Send inline images">Send inline images</span>
|
||||
|
@ -1512,6 +1512,14 @@
|
|||
<span data-i18n="Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.">Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="makersuite">
|
||||
<label for="use_google_tokenizer" title="Use Google Tokenizer" class="checkbox_label widthFreeExpand">
|
||||
<input id="use_google_tokenizer" type="checkbox" /><span data-i18n="Use Google Tokenizer">Use Google Tokenizer</span>
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<span data-i18n="Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.">Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-newbie-hidden class="range-block" data-source="claude">
|
||||
<label for="exclude_assistant" title="Exclude Assistant suffix" class="checkbox_label widthFreeExpand">
|
||||
<input id="exclude_assistant" type="checkbox" /><span data-i18n="Exclude Assistant suffix">Exclude Assistant suffix</span>
|
||||
|
@ -1595,7 +1603,7 @@
|
|||
<option value="koboldhorde"><span data-i18n="KoboldAI Horde">KoboldAI Horde</span></option>
|
||||
<option value="novel"><span data-i18n="NovelAI">NovelAI</span></option>
|
||||
<option value="textgenerationwebui"><span data-i18n="Text Completion">Text Completion (ooba, Mancer, Aphrodite, TabbyAPI, KoboldCpp)</span></option>
|
||||
<option value="openai"><span data-i18n="Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale, AI21)">Chat Completion (OpenAI, Claude, Window, OpenRouter, Scale, AI21, PaLM)</span></option>
|
||||
<option value="openai"><span data-i18n="Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale, AI21)">Chat Completion (OpenAI, Claude, Window, OpenRouter, Scale, AI21, Google MakerSuite)</span></option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="kobold_horde" style="position: relative;"> <!-- shows the kobold settings -->
|
||||
|
@ -1843,7 +1851,7 @@
|
|||
<option value="claude">Claude</option>
|
||||
<option value="scale">Scale</option>
|
||||
<option value="ai21">AI21</option>
|
||||
<option value="palm">Google PaLM 2</option>
|
||||
<option value="makersuite">Google MakerSuite</option>
|
||||
</select>
|
||||
<form id="openai_form" data-source="openai" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4><span data-i18n="OpenAI API key">OpenAI API key</span></h4>
|
||||
|
@ -2109,17 +2117,24 @@
|
|||
</select>
|
||||
</div>
|
||||
</form>
|
||||
<form id="palm_form" data-source="palm" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4 data-i18n="PaLM API Key">PaLM API Key</h4>
|
||||
<form id="makersuite_form" data-source="makersuite" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4 data-i18n="MakerSuite API Key">MakerSuite API Key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_palm" name="api_key_palm" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_palm"></div>
|
||||
<input id="api_key_makersuite" name="api_key_makersuite" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_makersuite"></div>
|
||||
</div>
|
||||
<div data-for="api_key_palm" class="neutral_warning">
|
||||
<div data-for="api_key_makersuite" class="neutral_warning">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<!-- Its only purpose is to trigger max context size check -->
|
||||
<select id="model_palm_select" class="displayNone"></select>
|
||||
<div>
|
||||
<h4 data-i18n="Google Model">Google Model</h4>
|
||||
<select id="model_google_select">
|
||||
<option value="gemini-pro">Gemini Pro</option>
|
||||
<option value="gemini-pro-vision">Gemini Pro Vision</option>
|
||||
<option value="text-bison-001">Bison Text</option>
|
||||
<option value="chat-bison-001">Bison Chat</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
<div class="flex-container flex">
|
||||
<div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
|
||||
|
|
|
@ -2557,8 +2557,8 @@ function getCharacterCardFields() {
|
|||
}
|
||||
|
||||
function isStreamingEnabled() {
|
||||
const noStreamSources = [chat_completion_sources.SCALE, chat_completion_sources.AI21, chat_completion_sources.PALM];
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai && !noStreamSources.includes(oai_settings.chat_completion_source))
|
||||
const noStreamSources = [chat_completion_sources.SCALE, chat_completion_sources.AI21];
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai && !noStreamSources.includes(oai_settings.chat_completion_source) && !(oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE && oai_settings.google_model.includes('bison')))
|
||||
|| (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming)
|
||||
|| (main_api == 'novel' && nai_settings.streaming_novel)
|
||||
|| (main_api == 'textgenerationwebui' && textgen_settings.streaming));
|
||||
|
@ -5395,7 +5395,7 @@ function changeMainAPI() {
|
|||
case chat_completion_sources.CLAUDE:
|
||||
case chat_completion_sources.OPENAI:
|
||||
case chat_completion_sources.AI21:
|
||||
case chat_completion_sources.PALM:
|
||||
case chat_completion_sources.MAKERSUITE:
|
||||
default:
|
||||
setupChatCompletionPromptManager(oai_settings);
|
||||
break;
|
||||
|
@ -5566,7 +5566,7 @@ async function doOnboarding(avatarId) {
|
|||
//***************SETTINGS****************//
|
||||
///////////////////////////////////////////
|
||||
async function getSettings() {
|
||||
const response = await fetch('/getsettings', {
|
||||
const response = await fetch('/api/settings/get', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({}),
|
||||
|
@ -5759,7 +5759,7 @@ async function saveSettings(type) {
|
|||
|
||||
return jQuery.ajax({
|
||||
type: 'POST',
|
||||
url: '/savesettings',
|
||||
url: '/api/settings/save',
|
||||
data: JSON.stringify({
|
||||
firstRun: firstRun,
|
||||
currentVersion: currentVersion,
|
||||
|
@ -7535,9 +7535,9 @@ async function connectAPISlash(_, text) {
|
|||
source: 'ai21',
|
||||
button: '#api_button_openai',
|
||||
},
|
||||
'palm': {
|
||||
'makersuite': {
|
||||
selected: 'openai',
|
||||
source: 'palm',
|
||||
source: 'makersuite',
|
||||
button: '#api_button_openai',
|
||||
},
|
||||
};
|
||||
|
@ -7826,7 +7826,7 @@ jQuery(async function () {
|
|||
}
|
||||
|
||||
registerSlashCommand('dupe', DupeChar, [], '– duplicates the currently selected character', true, true);
|
||||
registerSlashCommand('api', connectAPISlash, [], '<span class="monospace">(kobold, horde, novel, ooba, tabby, mancer, aphrodite, kcpp, oai, claude, windowai, openrouter, scale, ai21, palm)</span> – connect to an API', true, true);
|
||||
registerSlashCommand('api', connectAPISlash, [], '<span class="monospace">(kobold, horde, novel, ooba, tabby, mancer, aphrodite, kcpp, oai, claude, windowai, openrouter, scale, ai21, makersuite)</span> – connect to an API', true, true);
|
||||
registerSlashCommand('impersonate', doImpersonate, ['imp'], '– calls an impersonation response', true, true);
|
||||
registerSlashCommand('delchat', doDeleteChat, [], '– deletes the current chat', true, true);
|
||||
registerSlashCommand('closechat', doCloseChat, [], '– closes the current chat', true, true);
|
||||
|
|
|
@ -415,7 +415,7 @@ function RA_autoconnect(PrevApi) {
|
|||
|| (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI)
|
||||
|| (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
|
||||
|| (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)
|
||||
|| (secret_state[SECRET_KEYS.PALM] && oai_settings.chat_completion_source == chat_completion_sources.PALM)
|
||||
|| (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE)
|
||||
) {
|
||||
$('#api_button_openai').trigger('click');
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ async function doCaptionRequest(base64Img, fileData) {
|
|||
case 'horde':
|
||||
return await captionHorde(base64Img);
|
||||
case 'multimodal':
|
||||
return await captionMultimodal(fileData);
|
||||
return await captionMultimodal(extension_settings.caption.multimodal_api === 'google' ? base64Img : fileData);
|
||||
default:
|
||||
throw new Error('Unknown caption source.');
|
||||
}
|
||||
|
@ -273,6 +273,7 @@ jQuery(function () {
|
|||
(modules.includes('caption') && extension_settings.caption.source === 'extras') ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && secret_state[SECRET_KEYS.OPENAI]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && secret_state[SECRET_KEYS.MAKERSUITE]) ||
|
||||
extension_settings.caption.source === 'local' ||
|
||||
extension_settings.caption.source === 'horde';
|
||||
|
||||
|
@ -328,7 +329,7 @@ jQuery(function () {
|
|||
<label for="caption_source">Source</label>
|
||||
<select id="caption_source" class="text_pole">
|
||||
<option value="local">Local</option>
|
||||
<option value="multimodal">Multimodal (OpenAI / OpenRouter)</option>
|
||||
<option value="multimodal">Multimodal (OpenAI / OpenRouter / Google)</option>
|
||||
<option value="extras">Extras</option>
|
||||
<option value="horde">Horde</option>
|
||||
</select>
|
||||
|
@ -338,12 +339,14 @@ jQuery(function () {
|
|||
<select id="caption_multimodal_api" class="flex1 text_pole">
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="google">Google</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
||||
<label for="caption_multimodal_model">Model</label>
|
||||
<select id="caption_multimodal_model" class="flex1 text_pole">
|
||||
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
||||
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option>
|
||||
<option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
|
||||
<option data-type="openrouter" value="haotian-liu/llava-13b">haotian-liu/llava-13b</option>
|
||||
</select>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { saveSettingsDebounced, callPopup, getRequestHeaders, substituteParams, eventSource, event_types, animation_duration } from '../../../script.js';
|
||||
import { getContext, extension_settings } from '../../extensions.js';
|
||||
import { getSortableDelay, escapeHtml } from '../../utils.js';
|
||||
import { getSortableDelay, escapeHtml, delay } from '../../utils.js';
|
||||
import { executeSlashCommands, registerSlashCommand } from '../../slash-commands.js';
|
||||
import { ContextMenu } from './src/ContextMenu.js';
|
||||
import { MenuItem } from './src/MenuItem.js';
|
||||
|
@ -26,7 +26,7 @@ const defaultSettings = {
|
|||
|
||||
//method from worldinfo
|
||||
async function updateQuickReplyPresetList() {
|
||||
const result = await fetch('/getsettings', {
|
||||
const result = await fetch('/api/settings/get', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({}),
|
||||
|
@ -717,6 +717,218 @@ function saveQROrder() {
|
|||
});
|
||||
}
|
||||
|
||||
async function qrCreateCallback(args, mes) {
|
||||
const qr = {
|
||||
label: args.label ?? '',
|
||||
mes: (mes ?? '')
|
||||
.replace(/\\\|/g, '|')
|
||||
.replace(/\\\{/g, '{')
|
||||
.replace(/\\\}/g, '}')
|
||||
,
|
||||
title: args.title ?? '',
|
||||
autoExecute_chatLoad: JSON.parse(args.load ?? false),
|
||||
autoExecute_userMessage: JSON.parse(args.user ?? false),
|
||||
autoExecute_botMessage: JSON.parse(args.bot ?? false),
|
||||
autoExecute_appStartup: JSON.parse(args.startup ?? false),
|
||||
hidden: JSON.parse(args.hidden ?? false),
|
||||
};
|
||||
const setName = args.set ?? selected_preset;
|
||||
const preset = presets.find(x => x.name == setName);
|
||||
|
||||
if (!preset) {
|
||||
toastr.warning('Confirm you are using proper case sensitivity!', `QR preset '${setName}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
preset.quickReplySlots.push(qr);
|
||||
preset.numberOfSlots++;
|
||||
await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(preset),
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
await delay(400);
|
||||
applyQuickReplyPreset(selected_preset);
|
||||
return '';
|
||||
}
|
||||
async function qrUpdateCallback(args, mes) {
|
||||
const setName = args.set ?? selected_preset;
|
||||
const preset = presets.find(x => x.name == setName);
|
||||
|
||||
if (!preset) {
|
||||
toastr.warning('Confirm you are using proper case sensitivity!', `QR preset '${setName}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const idx = preset.quickReplySlots.findIndex(x => x.label == args.label);
|
||||
const oqr = preset.quickReplySlots[idx];
|
||||
const qr = {
|
||||
label: args.newlabel ?? oqr.label ?? '',
|
||||
mes: (mes ?? oqr.mes)
|
||||
.replace('\\|', '|')
|
||||
.replace('\\{', '{')
|
||||
.replace('\\}', '}')
|
||||
,
|
||||
title: args.title ?? oqr.title ?? '',
|
||||
autoExecute_chatLoad: JSON.parse(args.load ?? oqr.autoExecute_chatLoad ?? false),
|
||||
autoExecute_userMessage: JSON.parse(args.user ?? oqr.autoExecute_userMessage ?? false),
|
||||
autoExecute_botMessage: JSON.parse(args.bot ?? oqr.autoExecute_botMessage ?? false),
|
||||
autoExecute_appStartup: JSON.parse(args.startup ?? oqr.autoExecute_appStartup ?? false),
|
||||
hidden: JSON.parse(args.hidden ?? oqr.hidden ?? false),
|
||||
};
|
||||
preset.quickReplySlots[idx] = qr;
|
||||
await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(preset),
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
await delay(400);
|
||||
applyQuickReplyPreset(selected_preset);
|
||||
return '';
|
||||
}
|
||||
async function qrDeleteCallback(args, label) {
|
||||
const setName = args.set ?? selected_preset;
|
||||
const preset = presets.find(x => x.name == setName);
|
||||
|
||||
if (!preset) {
|
||||
toastr.warning('Confirm you are using proper case sensitivity!', `QR preset '${setName}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const idx = preset.quickReplySlots.findIndex(x => x.label == label);
|
||||
preset.quickReplySlots.splice(idx, 1);
|
||||
preset.numberOfSlots--;
|
||||
await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(preset),
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
await delay(400);
|
||||
applyQuickReplyPreset(selected_preset);
|
||||
return '';
|
||||
}
|
||||
|
||||
async function qrContextAddCallback(args, presetName) {
|
||||
const setName = args.set ?? selected_preset;
|
||||
const preset = presets.find(x => x.name == setName);
|
||||
|
||||
if (!preset) {
|
||||
toastr.warning('Confirm you are using proper case sensitivity!', `QR preset '${setName}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const idx = preset.quickReplySlots.findIndex(x => x.label == args.label);
|
||||
const oqr = preset.quickReplySlots[idx];
|
||||
if (!oqr.contextMenu) {
|
||||
oqr.contextMenu = [];
|
||||
}
|
||||
let item = oqr.contextMenu.find(it => it.preset == presetName);
|
||||
if (item) {
|
||||
item.chain = JSON.parse(args.chain ?? 'null') ?? item.chain ?? false;
|
||||
} else {
|
||||
oqr.contextMenu.push({ preset: presetName, chain: JSON.parse(args.chain ?? 'false') });
|
||||
}
|
||||
await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(preset),
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
await delay(400);
|
||||
applyQuickReplyPreset(selected_preset);
|
||||
return '';
|
||||
}
|
||||
async function qrContextDeleteCallback(args, presetName) {
|
||||
const setName = args.set ?? selected_preset;
|
||||
const preset = presets.find(x => x.name == setName);
|
||||
|
||||
if (!preset) {
|
||||
toastr.warning('Confirm you are using proper case sensitivity!', `QR preset '${setName}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const idx = preset.quickReplySlots.findIndex(x => x.label == args.label);
|
||||
const oqr = preset.quickReplySlots[idx];
|
||||
if (!oqr.contextMenu) return;
|
||||
const ctxIdx = oqr.contextMenu.findIndex(it => it.preset == presetName);
|
||||
if (ctxIdx > -1) {
|
||||
oqr.contextMenu.splice(ctxIdx, 1);
|
||||
}
|
||||
await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(preset),
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
await delay(400);
|
||||
applyQuickReplyPreset(selected_preset);
|
||||
return '';
|
||||
}
|
||||
async function qrContextClearCallback(args, label) {
|
||||
const setName = args.set ?? selected_preset;
|
||||
const preset = presets.find(x => x.name == setName);
|
||||
|
||||
if (!preset) {
|
||||
toastr.warning('Confirm you are using proper case sensitivity!', `QR preset '${setName}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const idx = preset.quickReplySlots.findIndex(x => x.label == label);
|
||||
const oqr = preset.quickReplySlots[idx];
|
||||
oqr.contextMenu = [];
|
||||
await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(preset),
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
await delay(400);
|
||||
applyQuickReplyPreset(selected_preset);
|
||||
return '';
|
||||
}
|
||||
|
||||
async function qrPresetAddCallback(args, name) {
|
||||
const quickReplyPreset = {
|
||||
name: name,
|
||||
quickReplyEnabled: JSON.parse(args.enabled ?? null) ?? true,
|
||||
quickActionEnabled: JSON.parse(args.nosend ?? null) ?? false,
|
||||
placeBeforeInputEnabled: JSON.parse(args.before ?? null) ?? false,
|
||||
quickReplySlots: [],
|
||||
numberOfSlots: Number(args.slots ?? '0'),
|
||||
AutoInputInject: JSON.parse(args.inject ?? 'false'),
|
||||
};
|
||||
|
||||
await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(quickReplyPreset),
|
||||
});
|
||||
await updateQuickReplyPresetList();
|
||||
}
|
||||
|
||||
async function qrPresetUpdateCallback(args, name) {
|
||||
const preset = presets.find(it => it.name == name);
|
||||
const quickReplyPreset = {
|
||||
name: preset.name,
|
||||
quickReplyEnabled: JSON.parse(args.enabled ?? null) ?? preset.quickReplyEnabled,
|
||||
quickActionEnabled: JSON.parse(args.nosend ?? null) ?? preset.quickActionEnabled,
|
||||
placeBeforeInputEnabled: JSON.parse(args.before ?? null) ?? preset.placeBeforeInputEnabled,
|
||||
quickReplySlots: preset.quickReplySlots,
|
||||
numberOfSlots: Number(args.slots ?? preset.numberOfSlots),
|
||||
AutoInputInject: JSON.parse(args.inject ?? 'null') ?? preset.AutoInputInject,
|
||||
};
|
||||
Object.assign(preset, quickReplyPreset);
|
||||
|
||||
await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(quickReplyPreset),
|
||||
});
|
||||
}
|
||||
|
||||
let onMessageSentExecuting = false;
|
||||
let onMessageReceivedExecuting = false;
|
||||
let onChatChangedExecuting = false;
|
||||
|
@ -901,4 +1113,33 @@ jQuery(async () => {
|
|||
jQuery(() => {
|
||||
registerSlashCommand('qr', doQR, [], '<span class="monospace">(number)</span> – activates the specified Quick Reply', true, true);
|
||||
registerSlashCommand('qrset', doQRPresetSwitch, [], '<span class="monospace">(name)</span> – swaps to the specified Quick Reply Preset', true, true);
|
||||
const qrArgs = `
|
||||
label - string - text on the button, e.g., label=MyButton
|
||||
set - string - name of the QR set, e.g., set=PresetName1
|
||||
hidden - bool - whether the button should be hidden, e.g., hidden=true
|
||||
startup - bool - auto execute on app startup, e.g., startup=true
|
||||
user - bool - auto execute on user message, e.g., user=true
|
||||
bot - bool - auto execute on AI message, e.g., bot=true
|
||||
load - bool - auto execute on chat load, e.g., load=true
|
||||
title - bool - title / tooltip to be shown on button, e.g., title="My Fancy Button"
|
||||
`.trim();
|
||||
const qrUpdateArgs = `
|
||||
newlabel - string - new text fort the button, e.g. newlabel=MyRenamedButton
|
||||
${qrArgs}
|
||||
`.trim();
|
||||
registerSlashCommand('qr-create', qrCreateCallback, [], `<span class="monospace" style="white-space:pre-line;">(arguments [message])\n arguments:\n ${qrArgs}</span> – creates a new Quick Reply, example: <tt>/qr-create set=MyPreset label=MyButton /echo 123</tt>`, true, true);
|
||||
registerSlashCommand('qr-update', qrUpdateCallback, [], `<span class="monospace" style="white-space:pre-line;">(arguments [message])\n arguments:\n ${qrUpdateArgs}</span> – updates Quick Reply, example: <tt>/qr-update set=MyPreset label=MyButton newlabel=MyRenamedButton /echo 123</tt>`, true, true);
|
||||
registerSlashCommand('qr-delete', qrDeleteCallback, [], '<span class="monospace">(set=string [label])</span> – deletes Quick Reply', true, true);
|
||||
registerSlashCommand('qr-contextadd', qrContextAddCallback, [], '<span class="monospace">(set=string label=string chain=bool [preset name])</span> – add context menu preset to a QR, example: <tt>/qr-contextadd set=MyPreset label=MyButton chain=true MyOtherPreset</tt>', true, true);
|
||||
registerSlashCommand('qr-contextdel', qrContextDeleteCallback, [], '<span class="monospace">(set=string label=string [preset name])</span> – remove context menu preset from a QR, example: <tt>/qr-contextdel set=MyPreset label=MyButton MyOtherPreset</tt>', true, true);
|
||||
registerSlashCommand('qr-contextclear', qrContextClearCallback, [], '<span class="monospace">(set=string [label])</span> – remove all context menu presets from a QR, example: <tt>/qr-contextclear set=MyPreset MyButton</tt>', true, true);
|
||||
const presetArgs = `
|
||||
enabled - bool - enable or disable the preset
|
||||
nosend - bool - disable send / insert in user input (invalid for slash commands)
|
||||
before - bool - place QR before user input
|
||||
slots - int - number of slots
|
||||
inject - bool - inject user input automatically (if disabled use {{input}})
|
||||
`.trim();
|
||||
registerSlashCommand('qr-presetadd', qrPresetAddCallback, [], `<span class="monospace" style="white-space:pre-line;">(arguments [label])\n arguments:\n ${presetArgs}</span> – create a new preset (overrides existing ones), example: <tt>/qr-presetadd slots=3 MyNewPreset</tt>`, true, true);
|
||||
registerSlashCommand('qr-presetupdate', qrPresetUpdateCallback, [], `<span class="monospace" style="white-space:pre-line;">(arguments [label])\n arguments:\n ${presetArgs}</span> – update an existing preset, example: <tt>/qr-presetupdate enabled=false MyPreset</tt>`, true, true);
|
||||
});
|
||||
|
|
|
@ -18,22 +18,35 @@ export async function getMultimodalCaption(base64Img, prompt) {
|
|||
throw new Error('OpenRouter API key is not set.');
|
||||
}
|
||||
|
||||
// OpenRouter has a payload limit of ~2MB
|
||||
const base64Bytes = base64Img.length * 0.75;
|
||||
const compressionLimit = 2 * 1024 * 1024;
|
||||
if (extension_settings.caption.multimodal_api === 'openrouter' && base64Bytes > compressionLimit) {
|
||||
const maxSide = 1024;
|
||||
base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg');
|
||||
if (extension_settings.caption.multimodal_api === 'google' && !secret_state[SECRET_KEYS.MAKERSUITE]) {
|
||||
throw new Error('MakerSuite API key is not set.');
|
||||
}
|
||||
|
||||
const apiResult = await fetch('/api/openai/caption-image', {
|
||||
// OpenRouter has a payload limit of ~2MB. Google is 4MB, but we love democracy.
|
||||
const isGoogle = extension_settings.caption.multimodal_api === 'google';
|
||||
const base64Bytes = base64Img.length * 0.75;
|
||||
const compressionLimit = 2 * 1024 * 1024;
|
||||
if (['google', 'openrouter'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) {
|
||||
const maxSide = 1024;
|
||||
base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg');
|
||||
|
||||
if (isGoogle) {
|
||||
base64Img = base64Img.split(',')[1];
|
||||
}
|
||||
}
|
||||
|
||||
const apiResult = await fetch(`/api/${isGoogle ? 'google' : 'openai'}/caption-image`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
image: base64Img,
|
||||
prompt: prompt,
|
||||
api: extension_settings.caption.multimodal_api || 'openai',
|
||||
model: extension_settings.caption.multimodal_model || 'gpt-4-vision-preview',
|
||||
...(isGoogle
|
||||
? {}
|
||||
: {
|
||||
api: extension_settings.caption.multimodal_api || 'openai',
|
||||
model: extension_settings.caption.multimodal_model || 'gpt-4-vision-preview',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -1756,22 +1756,28 @@ async function generateMultimodalPrompt(generationType, quietPrompt) {
|
|||
}
|
||||
}
|
||||
|
||||
const response = await fetch(avatarUrl);
|
||||
try {
|
||||
const response = await fetch(avatarUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Could not fetch avatar image.');
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error('Could not fetch avatar image.');
|
||||
}
|
||||
|
||||
const avatarBlob = await response.blob();
|
||||
const avatarBase64 = await getBase64Async(avatarBlob);
|
||||
const avatarBlob = await response.blob();
|
||||
const avatarBase64 = await getBase64Async(avatarBlob);
|
||||
|
||||
const caption = await getMultimodalCaption(avatarBase64, quietPrompt);
|
||||
const caption = await getMultimodalCaption(avatarBase64, quietPrompt);
|
||||
|
||||
if (!caption) {
|
||||
if (!caption) {
|
||||
throw new Error('No caption returned from the API.');
|
||||
}
|
||||
|
||||
return caption;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toastr.error('Multimodal captioning failed. Please try again.', 'Image Generation');
|
||||
throw new Error('Multimodal captioning failed.');
|
||||
}
|
||||
|
||||
return caption;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -394,7 +394,7 @@ async function getSavedHashes(collectionId) {
|
|||
*/
|
||||
async function insertVectorItems(collectionId, items) {
|
||||
if (settings.source === 'openai' && !secret_state[SECRET_KEYS.OPENAI] ||
|
||||
settings.source === 'palm' && !secret_state[SECRET_KEYS.PALM]) {
|
||||
settings.source === 'palm' && !secret_state[SECRET_KEYS.MAKERSUITE]) {
|
||||
throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });
|
||||
}
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ import {
|
|||
chatCompletionDefaultPrompts,
|
||||
INJECTION_POSITION,
|
||||
Prompt,
|
||||
promptManagerDefaultPromptOrders,
|
||||
PromptManager,
|
||||
promptManagerDefaultPromptOrders,
|
||||
} from './PromptManager.js';
|
||||
|
||||
import { getCustomStoppingStrings, persona_description_positions, power_user } from './power-user.js';
|
||||
|
@ -114,7 +114,6 @@ const max_128k = 128 * 1000;
|
|||
const max_200k = 200 * 1000;
|
||||
const scale_max = 8191;
|
||||
const claude_max = 9000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
||||
const palm2_max = 7400; // The real context window is 8192, spare some for padding due to using turbo tokenizer
|
||||
const claude_100k_max = 99000;
|
||||
let ai21_max = 9200; //can easily fit 9k gpt tokens because j2's tokenizer is efficient af
|
||||
const unlocked_max = 100 * 1024;
|
||||
|
@ -164,7 +163,7 @@ export const chat_completion_sources = {
|
|||
SCALE: 'scale',
|
||||
OPENROUTER: 'openrouter',
|
||||
AI21: 'ai21',
|
||||
PALM: 'palm',
|
||||
MAKERSUITE: 'makersuite',
|
||||
};
|
||||
|
||||
const prefixMap = selected_group ? {
|
||||
|
@ -207,6 +206,7 @@ const default_settings = {
|
|||
personality_format: default_personality_format,
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
claude_model: 'claude-instant-v1',
|
||||
google_model: 'gemini-pro',
|
||||
ai21_model: 'j2-ultra',
|
||||
windowai_model: '',
|
||||
openrouter_model: openrouter_website_model,
|
||||
|
@ -224,6 +224,7 @@ const default_settings = {
|
|||
assistant_prefill: '',
|
||||
human_sysprompt_message: '',
|
||||
use_ai21_tokenizer: false,
|
||||
use_google_tokenizer: false,
|
||||
exclude_assistant: false,
|
||||
claude_use_sysprompt: false,
|
||||
use_alt_scale: false,
|
||||
|
@ -262,6 +263,7 @@ const oai_settings = {
|
|||
personality_format: default_personality_format,
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
claude_model: 'claude-instant-v1',
|
||||
google_model: 'gemini-pro',
|
||||
ai21_model: 'j2-ultra',
|
||||
windowai_model: '',
|
||||
openrouter_model: openrouter_website_model,
|
||||
|
@ -279,6 +281,7 @@ const oai_settings = {
|
|||
assistant_prefill: '',
|
||||
human_sysprompt_message: '',
|
||||
use_ai21_tokenizer: false,
|
||||
use_google_tokenizer: false,
|
||||
exclude_assistant: false,
|
||||
claude_use_sysprompt: false,
|
||||
use_alt_scale: false,
|
||||
|
@ -1256,8 +1259,8 @@ function getChatCompletionModel() {
|
|||
return oai_settings.windowai_model;
|
||||
case chat_completion_sources.SCALE:
|
||||
return '';
|
||||
case chat_completion_sources.PALM:
|
||||
return '';
|
||||
case chat_completion_sources.MAKERSUITE:
|
||||
return oai_settings.google_model;
|
||||
case chat_completion_sources.OPENROUTER:
|
||||
return oai_settings.openrouter_model !== openrouter_website_model ? oai_settings.openrouter_model : null;
|
||||
case chat_completion_sources.AI21:
|
||||
|
@ -1447,20 +1450,20 @@ async function sendOpenAIRequest(type, messages, signal) {
|
|||
const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER;
|
||||
const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE;
|
||||
const isAI21 = oai_settings.chat_completion_source == chat_completion_sources.AI21;
|
||||
const isPalm = oai_settings.chat_completion_source == chat_completion_sources.PALM;
|
||||
const isGoogle = oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE;
|
||||
const isOAI = oai_settings.chat_completion_source == chat_completion_sources.OPENAI;
|
||||
const isTextCompletion = (isOAI && textCompletionModels.includes(oai_settings.openai_model)) || (isOpenRouter && oai_settings.openrouter_force_instruct && power_user.instruct.enabled);
|
||||
const isQuiet = type === 'quiet';
|
||||
const isImpersonate = type === 'impersonate';
|
||||
const isContinue = type === 'continue';
|
||||
const stream = oai_settings.stream_openai && !isQuiet && !isScale && !isAI21 && !isPalm;
|
||||
const stream = oai_settings.stream_openai && !isQuiet && !isScale && !isAI21 && !(isGoogle && oai_settings.google_model.includes('bison'));
|
||||
|
||||
if (isTextCompletion && isOpenRouter) {
|
||||
messages = convertChatCompletionToInstruct(messages, type);
|
||||
replaceItemizedPromptText(messageId, messages);
|
||||
}
|
||||
|
||||
if (isAI21 || isPalm) {
|
||||
if (isAI21) {
|
||||
const joinedMsgs = messages.reduce((acc, obj) => {
|
||||
const prefix = prefixMap[obj.role];
|
||||
return acc + (prefix ? (selected_group ? '\n' : prefix + ' ') : '') + obj.content + '\n';
|
||||
|
@ -1545,7 +1548,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
|||
generate_data['api_url_scale'] = oai_settings.api_url_scale;
|
||||
}
|
||||
|
||||
if (isPalm) {
|
||||
if (isGoogle) {
|
||||
const nameStopString = isImpersonate ? `\n${name2}:` : `\n${name1}:`;
|
||||
const stopStringsLimit = 3; // 5 - 2 (nameStopString and new_chat_prompt)
|
||||
generate_data['top_k'] = Number(oai_settings.top_k_openai);
|
||||
|
@ -1574,23 +1577,26 @@ async function sendOpenAIRequest(type, messages, signal) {
|
|||
tryParseStreamingError(response, await response.text());
|
||||
throw new Error(`Got response status ${response.status}`);
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
const eventStream = new EventSourceStream();
|
||||
response.body.pipeThrough(eventStream);
|
||||
const reader = eventStream.readable.getReader();
|
||||
let reader;
|
||||
let isSSEStream = oai_settings.chat_completion_source !== chat_completion_sources.MAKERSUITE;
|
||||
if (isSSEStream) {
|
||||
const eventStream = new EventSourceStream();
|
||||
response.body.pipeThrough(eventStream);
|
||||
reader = eventStream.readable.getReader();
|
||||
} else {
|
||||
reader = response.body.getReader();
|
||||
}
|
||||
return async function* streamData() {
|
||||
let text = '';
|
||||
let utf8Decoder = new TextDecoder();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) return;
|
||||
if (value.data === '[DONE]') return;
|
||||
|
||||
tryParseStreamingError(response, value.data);
|
||||
|
||||
// the first and last messages are undefined, protect against that
|
||||
text += getStreamingReply(JSON.parse(value.data));
|
||||
|
||||
const rawData = isSSEStream ? value.data : utf8Decoder.decode(value, { stream: true });
|
||||
if (isSSEStream && rawData === '[DONE]') return;
|
||||
tryParseStreamingError(response, rawData);
|
||||
text += getStreamingReply(JSON.parse(rawData));
|
||||
yield { text, swipes: [] };
|
||||
}
|
||||
};
|
||||
|
@ -1612,6 +1618,8 @@ async function sendOpenAIRequest(type, messages, signal) {
|
|||
function getStreamingReply(data) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
return data?.completion || '';
|
||||
} else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
return data?.candidates[0].content.parts[0].text || '';
|
||||
} else {
|
||||
return data.choices[0]?.delta?.content || data.choices[0]?.message?.content || data.choices[0]?.text || '';
|
||||
}
|
||||
|
@ -1793,13 +1801,15 @@ class Message {
|
|||
async addImage(image) {
|
||||
const textContent = this.content;
|
||||
const isDataUrl = isDataURL(image);
|
||||
|
||||
if (!isDataUrl) {
|
||||
try {
|
||||
const response = await fetch(image, { method: 'GET', cache: 'force-cache' });
|
||||
if (!response.ok) throw new Error('Failed to fetch image');
|
||||
const blob = await response.blob();
|
||||
image = await getBase64Async(blob);
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
|
||||
image = image.split(',')[1];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Image adding skipped', error);
|
||||
return;
|
||||
|
@ -2296,6 +2306,7 @@ function loadOpenAISettings(data, settings) {
|
|||
oai_settings.openrouter_use_fallback = settings.openrouter_use_fallback ?? default_settings.openrouter_use_fallback;
|
||||
oai_settings.openrouter_force_instruct = settings.openrouter_force_instruct ?? default_settings.openrouter_force_instruct;
|
||||
oai_settings.ai21_model = settings.ai21_model ?? default_settings.ai21_model;
|
||||
oai_settings.google_model = settings.google_model ?? default_settings.google_model;
|
||||
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
|
||||
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
|
||||
oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
|
||||
|
@ -2318,6 +2329,7 @@ function loadOpenAISettings(data, settings) {
|
|||
if (settings.names_in_completion !== undefined) oai_settings.names_in_completion = !!settings.names_in_completion;
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
if (settings.use_ai21_tokenizer !== undefined) { oai_settings.use_ai21_tokenizer = !!settings.use_ai21_tokenizer; oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200; }
|
||||
if (settings.use_google_tokenizer !== undefined) oai_settings.use_google_tokenizer = !!settings.use_google_tokenizer;
|
||||
if (settings.exclude_assistant !== undefined) oai_settings.exclude_assistant = !!settings.exclude_assistant;
|
||||
if (settings.claude_use_sysprompt !== undefined) oai_settings.claude_use_sysprompt = !!settings.claude_use_sysprompt;
|
||||
if (settings.use_alt_scale !== undefined) { oai_settings.use_alt_scale = !!settings.use_alt_scale; updateScaleForm(); }
|
||||
|
@ -2335,6 +2347,8 @@ function loadOpenAISettings(data, settings) {
|
|||
$(`#model_claude_select option[value="${oai_settings.claude_model}"`).attr('selected', true);
|
||||
$('#model_windowai_select').val(oai_settings.windowai_model);
|
||||
$(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).attr('selected', true);
|
||||
$('#model_google_select').val(oai_settings.google_model);
|
||||
$(`#model_google_select option[value="${oai_settings.google_model}"`).attr('selected', true);
|
||||
$('#model_ai21_select').val(oai_settings.ai21_model);
|
||||
$(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).attr('selected', true);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context);
|
||||
|
@ -2350,6 +2364,7 @@ function loadOpenAISettings(data, settings) {
|
|||
$('#openai_show_external_models').prop('checked', oai_settings.show_external_models);
|
||||
$('#openai_external_category').toggle(oai_settings.show_external_models);
|
||||
$('#use_ai21_tokenizer').prop('checked', oai_settings.use_ai21_tokenizer);
|
||||
$('#use_google_tokenizer').prop('checked', oai_settings.use_google_tokenizer);
|
||||
$('#exclude_assistant').prop('checked', oai_settings.exclude_assistant);
|
||||
$('#claude_use_sysprompt').prop('checked', oai_settings.claude_use_sysprompt);
|
||||
$('#scale-alt').prop('checked', oai_settings.use_alt_scale);
|
||||
|
@ -2406,6 +2421,11 @@ function loadOpenAISettings(data, settings) {
|
|||
}
|
||||
$('#openai_logit_bias_preset').trigger('change');
|
||||
|
||||
// Upgrade Palm to Makersuite
|
||||
if (oai_settings.chat_completion_source === 'palm') {
|
||||
oai_settings.chat_completion_source = chat_completion_sources.MAKERSUITE;
|
||||
}
|
||||
|
||||
$('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change');
|
||||
$('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked);
|
||||
}
|
||||
|
@ -2426,7 +2446,7 @@ async function getStatusOpen() {
|
|||
return resultCheckStatus();
|
||||
}
|
||||
|
||||
const noValidateSources = [chat_completion_sources.SCALE, chat_completion_sources.CLAUDE, chat_completion_sources.AI21, chat_completion_sources.PALM];
|
||||
const noValidateSources = [chat_completion_sources.SCALE, chat_completion_sources.CLAUDE, chat_completion_sources.AI21, chat_completion_sources.MAKERSUITE];
|
||||
if (noValidateSources.includes(oai_settings.chat_completion_source)) {
|
||||
let status = 'Unable to verify key; press "Test Message" to validate.';
|
||||
setOnlineStatus(status);
|
||||
|
@ -2509,6 +2529,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
|||
openrouter_group_models: settings.openrouter_group_models,
|
||||
openrouter_sort_models: settings.openrouter_sort_models,
|
||||
ai21_model: settings.ai21_model,
|
||||
google_model: settings.google_model,
|
||||
temperature: settings.temp_openai,
|
||||
frequency_penalty: settings.freq_pen_openai,
|
||||
presence_penalty: settings.pres_pen_openai,
|
||||
|
@ -2543,6 +2564,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
|||
assistant_prefill: settings.assistant_prefill,
|
||||
human_sysprompt_message: settings.human_sysprompt_message,
|
||||
use_ai21_tokenizer: settings.use_ai21_tokenizer,
|
||||
use_google_tokenizer: settings.use_google_tokenizer,
|
||||
exclude_assistant: settings.exclude_assistant,
|
||||
claude_use_sysprompt: settings.claude_use_sysprompt,
|
||||
use_alt_scale: settings.use_alt_scale,
|
||||
|
@ -2880,6 +2902,7 @@ function onSettingsPresetChange() {
|
|||
openrouter_group_models: ['#openrouter_group_models', 'openrouter_group_models', false],
|
||||
openrouter_sort_models: ['#openrouter_sort_models', 'openrouter_sort_models', false],
|
||||
ai21_model: ['#model_ai21_select', 'ai21_model', false],
|
||||
google_model: ['#model_google_select', 'google_model', false],
|
||||
openai_max_context: ['#openai_max_context', 'openai_max_context', false],
|
||||
openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false],
|
||||
wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true],
|
||||
|
@ -2905,6 +2928,7 @@ function onSettingsPresetChange() {
|
|||
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
|
||||
human_sysprompt_message: ['#claude_human_sysprompt_message', 'human_sysprompt_message', false],
|
||||
use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', true],
|
||||
use_google_tokenizer: ['#use_google_tokenizer', 'use_google_tokenizer', true],
|
||||
exclude_assistant: ['#exclude_assistant', 'exclude_assistant', true],
|
||||
claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true],
|
||||
use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true],
|
||||
|
@ -3014,7 +3038,7 @@ function getMaxContextWindowAI(value) {
|
|||
return max_8k;
|
||||
}
|
||||
else if (value.includes('palm-2')) {
|
||||
return palm2_max;
|
||||
return max_8k;
|
||||
}
|
||||
else if (value.includes('GPT-NeoXT')) {
|
||||
return max_2k;
|
||||
|
@ -3059,6 +3083,11 @@ async function onModelChange() {
|
|||
oai_settings.ai21_model = value;
|
||||
}
|
||||
|
||||
if ($(this).is('#model_google_select')) {
|
||||
console.log('Google model changed to', value);
|
||||
oai_settings.google_model = value;
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
|
@ -3069,13 +3098,18 @@ async function onModelChange() {
|
|||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.PALM) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
} else if (value === 'gemini-pro') {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (value === 'gemini-pro-vision') {
|
||||
$('#openai_max_context').attr('max', max_16k);
|
||||
} else {
|
||||
$('#openai_max_context').attr('max', palm2_max);
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
}
|
||||
|
||||
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
|
||||
$('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
}
|
||||
|
@ -3268,15 +3302,15 @@ async function onConnectButtonClick(e) {
|
|||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.PALM) {
|
||||
const api_key_palm = String($('#api_key_palm').val()).trim();
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
const api_key_makersuite = String($('#api_key_makersuite').val()).trim();
|
||||
|
||||
if (api_key_palm.length) {
|
||||
await writeSecret(SECRET_KEYS.PALM, api_key_palm);
|
||||
if (api_key_makersuite.length) {
|
||||
await writeSecret(SECRET_KEYS.MAKERSUITE, api_key_makersuite);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.PALM]) {
|
||||
console.log('No secret key saved for PALM');
|
||||
if (!secret_state[SECRET_KEYS.MAKERSUITE]) {
|
||||
console.log('No secret key saved for MakerSuite');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -3343,8 +3377,8 @@ function toggleChatCompletionForms() {
|
|||
else if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
$('#model_scale_select').trigger('change');
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.PALM) {
|
||||
$('#model_palm_select').trigger('change');
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
$('#model_google_select').trigger('change');
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {
|
||||
$('#model_openrouter_select').trigger('change');
|
||||
|
@ -3413,6 +3447,7 @@ export function isImageInliningSupported() {
|
|||
}
|
||||
|
||||
const gpt4v = 'gpt-4-vision';
|
||||
const geminiProV = 'gemini-pro-vision';
|
||||
const llava13b = 'llava-13b';
|
||||
|
||||
if (!oai_settings.image_inlining) {
|
||||
|
@ -3422,6 +3457,8 @@ export function isImageInliningSupported() {
|
|||
switch (oai_settings.chat_completion_source) {
|
||||
case chat_completion_sources.OPENAI:
|
||||
return oai_settings.openai_model.includes(gpt4v);
|
||||
case chat_completion_sources.MAKERSUITE:
|
||||
return oai_settings.google_model.includes(geminiProV);
|
||||
case chat_completion_sources.OPENROUTER:
|
||||
return oai_settings.openrouter_model.includes(gpt4v) || oai_settings.openrouter_model.includes(llava13b);
|
||||
default:
|
||||
|
@ -3506,6 +3543,11 @@ $(document).ready(async function () {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#use_google_tokenizer').on('change', function () {
|
||||
oai_settings.use_google_tokenizer = !!$('#use_google_tokenizer').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#exclude_assistant').on('change', function () {
|
||||
oai_settings.exclude_assistant = !!$('#exclude_assistant').prop('checked');
|
||||
$('#claude_assistant_prefill_block').toggle(!oai_settings.exclude_assistant);
|
||||
|
@ -3728,7 +3770,7 @@ $(document).ready(async function () {
|
|||
$('#model_claude_select').on('change', onModelChange);
|
||||
$('#model_windowai_select').on('change', onModelChange);
|
||||
$('#model_scale_select').on('change', onModelChange);
|
||||
$('#model_palm_select').on('change', onModelChange);
|
||||
$('#model_google_select').on('change', onModelChange);
|
||||
$('#model_openrouter_select').on('change', onModelChange);
|
||||
$('#openrouter_group_models').on('change', onOpenrouterModelSortChange);
|
||||
$('#openrouter_sort_models').on('change', onOpenrouterModelSortChange);
|
||||
|
|
|
@ -12,7 +12,7 @@ export const SECRET_KEYS = {
|
|||
SCALE: 'api_key_scale',
|
||||
AI21: 'api_key_ai21',
|
||||
SCALE_COOKIE: 'scale_cookie',
|
||||
PALM: 'api_key_palm',
|
||||
MAKERSUITE: 'api_key_makersuite',
|
||||
SERPAPI: 'api_key_serpapi',
|
||||
};
|
||||
|
||||
|
@ -26,7 +26,7 @@ const INPUT_MAP = {
|
|||
[SECRET_KEYS.SCALE]: '#api_key_scale',
|
||||
[SECRET_KEYS.AI21]: '#api_key_ai21',
|
||||
[SECRET_KEYS.SCALE_COOKIE]: '#scale_cookie',
|
||||
[SECRET_KEYS.PALM]: '#api_key_palm',
|
||||
[SECRET_KEYS.MAKERSUITE]: '#api_key_makersuite',
|
||||
[SECRET_KEYS.APHRODITE]: '#api_key_aphrodite',
|
||||
[SECRET_KEYS.TABBY]: '#api_key_tabby',
|
||||
};
|
||||
|
|
|
@ -376,6 +376,10 @@ export function getTokenizerModel() {
|
|||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
return oai_settings.google_model;
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
return claudeTokenizer;
|
||||
}
|
||||
|
@ -389,6 +393,15 @@ export function getTokenizerModel() {
|
|||
*/
|
||||
export function countTokensOpenAI(messages, full = false) {
|
||||
const shouldTokenizeAI21 = oai_settings.chat_completion_source === chat_completion_sources.AI21 && oai_settings.use_ai21_tokenizer;
|
||||
const shouldTokenizeGoogle = oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE && oai_settings.use_google_tokenizer;
|
||||
let tokenizerEndpoint = '';
|
||||
if (shouldTokenizeAI21) {
|
||||
tokenizerEndpoint = '/api/tokenizers/ai21/count';
|
||||
} else if (shouldTokenizeGoogle) {
|
||||
tokenizerEndpoint = `/api/tokenizers/google/count?model=${getTokenizerModel()}`;
|
||||
} else {
|
||||
tokenizerEndpoint = `/api/tokenizers/openai/count?model=${getTokenizerModel()}`;
|
||||
}
|
||||
const cacheObject = getTokenCacheObject();
|
||||
|
||||
if (!Array.isArray(messages)) {
|
||||
|
@ -400,7 +413,7 @@ export function countTokensOpenAI(messages, full = false) {
|
|||
for (const message of messages) {
|
||||
const model = getTokenizerModel();
|
||||
|
||||
if (model === 'claude' || shouldTokenizeAI21) {
|
||||
if (model === 'claude' || shouldTokenizeAI21 || shouldTokenizeGoogle) {
|
||||
full = true;
|
||||
}
|
||||
|
||||
|
@ -416,7 +429,7 @@ export function countTokensOpenAI(messages, full = false) {
|
|||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST', //
|
||||
url: shouldTokenizeAI21 ? '/api/tokenizers/ai21/count' : `/api/tokenizers/openai/count?model=${model}`,
|
||||
url: tokenizerEndpoint,
|
||||
data: JSON.stringify([message]),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
|
|
|
@ -1030,6 +1030,11 @@ export function loadFileToDocument(url, type) {
|
|||
* @returns {Promise<string>} A promise that resolves to the thumbnail data URL.
|
||||
*/
|
||||
export function createThumbnail(dataUrl, maxWidth, maxHeight, type = 'image/jpeg') {
|
||||
// Someone might pass in a base64 encoded string without the data URL prefix
|
||||
if (!dataUrl.includes('data:')) {
|
||||
dataUrl = `data:image/jpeg;base64,${dataUrl}`;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = dataUrl;
|
||||
|
|
|
@ -637,6 +637,11 @@ function lenValuesCallback(value) {
|
|||
return parsedValue.length;
|
||||
}
|
||||
|
||||
function randValuesCallback(from, to) {
|
||||
const range = to - from;
|
||||
return from + Math.random() * range;
|
||||
}
|
||||
|
||||
export function registerVariableCommands() {
|
||||
registerSlashCommand('listvar', listVariablesCallback, [], ' – list registered chat variables', true, true);
|
||||
registerSlashCommand('setvar', (args, value) => setLocalVariable(args.key || args.name, value, args), [], '<span class="monospace">key=varname index=listIndex (value)</span> – set a local variable value and pass it down the pipe, index is optional, e.g. <tt>/setvar key=color green</tt>', true, true);
|
||||
|
@ -668,4 +673,5 @@ export function registerVariableCommands() {
|
|||
registerSlashCommand('sqrt', (_, value) => sqrtValuesCallback(value), [], '<span class="monospace">(a)</span> – performs a square root operation of a value and passes the result down the pipe, can use variable names, e.g. <tt>/sqrt i</tt>', true, true);
|
||||
registerSlashCommand('round', (_, value) => roundValuesCallback(value), [], '<span class="monospace">(a)</span> – rounds a value and passes the result down the pipe, can use variable names, e.g. <tt>/round i</tt>', true, true);
|
||||
registerSlashCommand('len', (_, value) => lenValuesCallback(value), [], '<span class="monospace">(a)</span> – gets the length of a value and passes the result down the pipe, can use variable names, e.g. <tt>/len i</tt>', true, true);
|
||||
registerSlashCommand('rand', (args, value) => randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value.length ? value : 1))), [], '<span class="monospace">(from=number=0 to=number=1)</span> – returns a random number between from and to, e.g. <tt>/rand</tt> or <tt>/rand 10</tt> or <tt>/rand from=5 to=10</tt>', true, true);
|
||||
}
|
||||
|
|
|
@ -441,7 +441,7 @@ async function loadWorldInfoData(name) {
|
|||
}
|
||||
|
||||
async function updateWorldInfoList() {
|
||||
const result = await fetch('/getsettings', {
|
||||
const result = await fetch('/api/settings/get', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({}),
|
||||
|
|
164
server.js
164
server.js
|
@ -43,7 +43,6 @@ util.inspect.defaultOptions.maxStringLength = null;
|
|||
const basicAuthMiddleware = require('./src/middleware/basicAuthMiddleware');
|
||||
const { jsonParser, urlencodedParser } = require('./src/express-common.js');
|
||||
const contentManager = require('./src/endpoints/content-manager');
|
||||
const { migrateSecrets } = require('./src/endpoints/secrets');
|
||||
const {
|
||||
getVersion,
|
||||
getConfigValue,
|
||||
|
@ -51,8 +50,6 @@ const {
|
|||
tryParse,
|
||||
clientRelativePath,
|
||||
removeFileExtension,
|
||||
generateTimestamp,
|
||||
removeOldBackups,
|
||||
getImages,
|
||||
forwardFetchResponse,
|
||||
} = require('./src/util');
|
||||
|
@ -122,10 +119,8 @@ if (fs.existsSync(whitelistPath)) {
|
|||
|
||||
const whitelistMode = getConfigValue('whitelistMode', true);
|
||||
const autorun = (getConfigValue('autorun', false) || cliArguments.autorun) && !cliArguments.ssl;
|
||||
const enableExtensions = getConfigValue('enableExtensions', true);
|
||||
const listen = getConfigValue('listen', false);
|
||||
|
||||
const SETTINGS_FILE = './public/settings.json';
|
||||
const { DIRECTORIES, UPLOADS_PATH, AVATAR_WIDTH, AVATAR_HEIGHT } = require('./src/constants');
|
||||
|
||||
// CORS Settings //
|
||||
|
@ -293,9 +288,6 @@ app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).
|
|||
app.get('/', function (request, response) {
|
||||
response.sendFile(process.cwd() + '/public/index.html');
|
||||
});
|
||||
app.get('/notes/*', function (request, response) {
|
||||
response.sendFile(process.cwd() + '/public' + request.url + '.html');
|
||||
});
|
||||
app.get('/deviceinfo', function (request, response) {
|
||||
const userAgent = request.header('user-agent');
|
||||
const deviceDetector = new DeviceDetector();
|
||||
|
@ -331,135 +323,6 @@ app.post('/deleteuseravatar', jsonParser, function (request, response) {
|
|||
return response.sendStatus(404);
|
||||
});
|
||||
|
||||
|
||||
app.post('/savesettings', jsonParser, function (request, response) {
|
||||
try {
|
||||
writeFileAtomicSync('public/settings.json', JSON.stringify(request.body, null, 4), 'utf8');
|
||||
response.send({ result: 'ok' });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
response.send(err);
|
||||
}
|
||||
});
|
||||
|
||||
function readAndParseFromDirectory(directoryPath, fileExtension = '.json') {
|
||||
const files = fs
|
||||
.readdirSync(directoryPath)
|
||||
.filter(x => path.parse(x).ext == fileExtension)
|
||||
.sort();
|
||||
|
||||
const parsedFiles = [];
|
||||
|
||||
files.forEach(item => {
|
||||
try {
|
||||
const file = fs.readFileSync(path.join(directoryPath, item), 'utf-8');
|
||||
parsedFiles.push(fileExtension == '.json' ? JSON.parse(file) : file);
|
||||
}
|
||||
catch {
|
||||
// skip
|
||||
}
|
||||
});
|
||||
|
||||
return parsedFiles;
|
||||
}
|
||||
|
||||
function sortByName(_) {
|
||||
return (a, b) => a.localeCompare(b);
|
||||
}
|
||||
|
||||
function readPresetsFromDirectory(directoryPath, options = {}) {
|
||||
const {
|
||||
sortFunction,
|
||||
removeFileExtension = false,
|
||||
fileExtension = '.json',
|
||||
} = options;
|
||||
|
||||
const files = fs.readdirSync(directoryPath).sort(sortFunction).filter(x => path.parse(x).ext == fileExtension);
|
||||
const fileContents = [];
|
||||
const fileNames = [];
|
||||
|
||||
files.forEach(item => {
|
||||
try {
|
||||
const file = fs.readFileSync(path.join(directoryPath, item), 'utf8');
|
||||
JSON.parse(file);
|
||||
fileContents.push(file);
|
||||
fileNames.push(removeFileExtension ? item.replace(/\.[^/.]+$/, '') : item);
|
||||
} catch {
|
||||
// skip
|
||||
console.log(`${item} is not a valid JSON`);
|
||||
}
|
||||
});
|
||||
|
||||
return { fileContents, fileNames };
|
||||
}
|
||||
|
||||
// Wintermute's code
|
||||
app.post('/getsettings', jsonParser, (request, response) => {
|
||||
let settings;
|
||||
try {
|
||||
settings = fs.readFileSync('public/settings.json', 'utf8');
|
||||
} catch (e) {
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
// NovelAI Settings
|
||||
const { fileContents: novelai_settings, fileNames: novelai_setting_names }
|
||||
= readPresetsFromDirectory(DIRECTORIES.novelAI_Settings, {
|
||||
sortFunction: sortByName(DIRECTORIES.novelAI_Settings),
|
||||
removeFileExtension: true,
|
||||
});
|
||||
|
||||
// OpenAI Settings
|
||||
const { fileContents: openai_settings, fileNames: openai_setting_names }
|
||||
= readPresetsFromDirectory(DIRECTORIES.openAI_Settings, {
|
||||
sortFunction: sortByName(DIRECTORIES.openAI_Settings), removeFileExtension: true,
|
||||
});
|
||||
|
||||
// TextGenerationWebUI Settings
|
||||
const { fileContents: textgenerationwebui_presets, fileNames: textgenerationwebui_preset_names }
|
||||
= readPresetsFromDirectory(DIRECTORIES.textGen_Settings, {
|
||||
sortFunction: sortByName(DIRECTORIES.textGen_Settings), removeFileExtension: true,
|
||||
});
|
||||
|
||||
//Kobold
|
||||
const { fileContents: koboldai_settings, fileNames: koboldai_setting_names }
|
||||
= readPresetsFromDirectory(DIRECTORIES.koboldAI_Settings, {
|
||||
sortFunction: sortByName(DIRECTORIES.koboldAI_Settings), removeFileExtension: true,
|
||||
});
|
||||
|
||||
const worldFiles = fs
|
||||
.readdirSync(DIRECTORIES.worlds)
|
||||
.filter(file => path.extname(file).toLowerCase() === '.json')
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
const world_names = worldFiles.map(item => path.parse(item).name);
|
||||
|
||||
const themes = readAndParseFromDirectory(DIRECTORIES.themes);
|
||||
const movingUIPresets = readAndParseFromDirectory(DIRECTORIES.movingUI);
|
||||
const quickReplyPresets = readAndParseFromDirectory(DIRECTORIES.quickreplies);
|
||||
|
||||
const instruct = readAndParseFromDirectory(DIRECTORIES.instruct);
|
||||
const context = readAndParseFromDirectory(DIRECTORIES.context);
|
||||
|
||||
response.send({
|
||||
settings,
|
||||
koboldai_settings,
|
||||
koboldai_setting_names,
|
||||
world_names,
|
||||
novelai_settings,
|
||||
novelai_setting_names,
|
||||
openai_settings,
|
||||
openai_setting_names,
|
||||
textgenerationwebui_presets,
|
||||
textgenerationwebui_preset_names,
|
||||
themes,
|
||||
movingUIPresets,
|
||||
quickReplyPresets,
|
||||
instruct,
|
||||
context,
|
||||
enable_extensions: enableExtensions,
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/savetheme', jsonParser, (request, response) => {
|
||||
if (!request.body || !request.body.name) {
|
||||
return response.sendStatus(400);
|
||||
|
@ -689,6 +552,9 @@ redirect('/downloadbackground', '/api/backgrounds/upload'); // yes, the download
|
|||
// OpenAI API
|
||||
app.use('/api/openai', require('./src/endpoints/openai').router);
|
||||
|
||||
//Google API
|
||||
app.use('/api/google', require('./src/endpoints/google').router);
|
||||
|
||||
// Tokenizers
|
||||
app.use('/api/tokenizers', require('./src/endpoints/tokenizers').router);
|
||||
|
||||
|
@ -738,6 +604,10 @@ app.use('/api/sprites', require('./src/endpoints/sprites').router);
|
|||
// Custom content management
|
||||
app.use('/api/content', require('./src/endpoints/content-manager').router);
|
||||
|
||||
// Settings load/store
|
||||
const settingsEndpoint = require('./src/endpoints/settings');
|
||||
app.use('/api/settings', settingsEndpoint.router);
|
||||
|
||||
// Stable Diffusion generation
|
||||
app.use('/api/sd', require('./src/endpoints/stable-diffusion').router);
|
||||
|
||||
|
@ -790,8 +660,9 @@ const setupTasks = async function () {
|
|||
|
||||
console.log(`SillyTavern ${version.pkgVersion}` + (version.gitBranch ? ` '${version.gitBranch}' (${version.gitRevision})` : ''));
|
||||
|
||||
backupSettings();
|
||||
migrateSecrets(SETTINGS_FILE);
|
||||
// TODO: do endpoint init functions depend on certain directories existing or not existing? They should be callable
|
||||
// in any order for encapsulation reasons, but right now it's unknown if that would break anything.
|
||||
await settingsEndpoint.init();
|
||||
ensurePublicDirectoriesExist();
|
||||
await ensureThumbnailCache();
|
||||
contentManager.checkForNewContent();
|
||||
|
@ -853,21 +724,6 @@ if (cliArguments.ssl) {
|
|||
);
|
||||
}
|
||||
|
||||
function backupSettings() {
|
||||
try {
|
||||
if (!fs.existsSync(DIRECTORIES.backups)) {
|
||||
fs.mkdirSync(DIRECTORIES.backups);
|
||||
}
|
||||
|
||||
const backupFile = path.join(DIRECTORIES.backups, `settings_${generateTimestamp()}.json`);
|
||||
fs.copyFileSync(SETTINGS_FILE, backupFile);
|
||||
|
||||
removeOldBackups('settings_');
|
||||
} catch (err) {
|
||||
console.log('Could not backup settings file', err);
|
||||
}
|
||||
}
|
||||
|
||||
function ensurePublicDirectoriesExist() {
|
||||
for (const dir of Object.values(DIRECTORIES)) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
|
|
|
@ -105,7 +105,26 @@ const UNSAFE_EXTENSIONS = [
|
|||
'.ws',
|
||||
];
|
||||
|
||||
const PALM_SAFETY = [
|
||||
const GEMINI_SAFETY = [
|
||||
{
|
||||
category: 'HARM_CATEGORY_HARASSMENT',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
{
|
||||
category: 'HARM_CATEGORY_HATE_SPEECH',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
{
|
||||
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
{
|
||||
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
|
||||
threshold: 'BLOCK_NONE',
|
||||
},
|
||||
];
|
||||
|
||||
const BISON_SAFETY = [
|
||||
{
|
||||
category: 'HARM_CATEGORY_DEROGATORY',
|
||||
threshold: 'BLOCK_NONE',
|
||||
|
@ -139,7 +158,7 @@ const CHAT_COMPLETION_SOURCES = {
|
|||
SCALE: 'scale',
|
||||
OPENROUTER: 'openrouter',
|
||||
AI21: 'ai21',
|
||||
PALM: 'palm',
|
||||
MAKERSUITE: 'makersuite',
|
||||
};
|
||||
|
||||
const UPLOADS_PATH = './uploads';
|
||||
|
@ -160,7 +179,8 @@ module.exports = {
|
|||
DIRECTORIES,
|
||||
UNSAFE_EXTENSIONS,
|
||||
UPLOADS_PATH,
|
||||
PALM_SAFETY,
|
||||
GEMINI_SAFETY,
|
||||
BISON_SAFETY,
|
||||
TEXTGEN_TYPES,
|
||||
CHAT_COMPLETION_SOURCES,
|
||||
AVATAR_WIDTH,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
const express = require('express');
|
||||
const fetch = require('node-fetch').default;
|
||||
const { Readable } = require('stream');
|
||||
|
||||
const { jsonParser } = require('../../express-common');
|
||||
const { CHAT_COMPLETION_SOURCES, PALM_SAFETY } = require('../../constants');
|
||||
const { CHAT_COMPLETION_SOURCES, GEMINI_SAFETY, BISON_SAFETY } = require('../../constants');
|
||||
const { forwardFetchResponse, getConfigValue, tryParse, uuidv4 } = require('../../util');
|
||||
const { convertClaudePrompt, convertTextCompletionPrompt } = require('../prompt-converters');
|
||||
const { convertClaudePrompt, convertGooglePrompt, convertTextCompletionPrompt } = require('../prompt-converters');
|
||||
|
||||
const { readSecret, SECRET_KEYS } = require('../secrets');
|
||||
const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sentencepieceTokenizers, TEXT_COMPLETION_MODELS } = require('../tokenizers');
|
||||
|
@ -149,28 +150,70 @@ async function sendScaleRequest(request, response) {
|
|||
* @param {express.Request} request Express request
|
||||
* @param {express.Response} response Express response
|
||||
*/
|
||||
async function sendPalmRequest(request, response) {
|
||||
const api_key_palm = readSecret(SECRET_KEYS.PALM);
|
||||
async function sendMakerSuiteRequest(request, response) {
|
||||
const apiKey = readSecret(SECRET_KEYS.MAKERSUITE);
|
||||
|
||||
if (!api_key_palm) {
|
||||
console.log('Palm API key is missing.');
|
||||
if (!apiKey) {
|
||||
console.log('MakerSuite API key is missing.');
|
||||
return response.status(400).send({ error: true });
|
||||
}
|
||||
|
||||
const body = {
|
||||
prompt: {
|
||||
text: request.body.messages,
|
||||
},
|
||||
const model = String(request.body.model);
|
||||
const isGemini = model.includes('gemini');
|
||||
const isText = model.includes('text');
|
||||
const stream = Boolean(request.body.stream) && isGemini;
|
||||
|
||||
const generationConfig = {
|
||||
stopSequences: request.body.stop,
|
||||
safetySettings: PALM_SAFETY,
|
||||
candidateCount: 1,
|
||||
maxOutputTokens: request.body.max_tokens,
|
||||
temperature: request.body.temperature,
|
||||
topP: request.body.top_p,
|
||||
topK: request.body.top_k || undefined,
|
||||
maxOutputTokens: request.body.max_tokens,
|
||||
candidate_count: 1,
|
||||
};
|
||||
|
||||
console.log('Palm request:', body);
|
||||
function getGeminiBody() {
|
||||
return {
|
||||
contents: convertGooglePrompt(request.body.messages, model),
|
||||
safetySettings: GEMINI_SAFETY,
|
||||
generationConfig: generationConfig,
|
||||
};
|
||||
}
|
||||
|
||||
function getBisonBody() {
|
||||
const prompt = isText
|
||||
? ({ text: convertTextCompletionPrompt(request.body.messages) })
|
||||
: ({ messages: convertGooglePrompt(request.body.messages, model) });
|
||||
|
||||
/** @type {any} Shut the lint up */
|
||||
const bisonBody = {
|
||||
...generationConfig,
|
||||
safetySettings: BISON_SAFETY,
|
||||
candidate_count: 1, // lewgacy spelling
|
||||
prompt: prompt,
|
||||
};
|
||||
|
||||
if (!isText) {
|
||||
delete bisonBody.stopSequences;
|
||||
delete bisonBody.maxOutputTokens;
|
||||
delete bisonBody.safetySettings;
|
||||
|
||||
if (Array.isArray(prompt.messages)) {
|
||||
for (const msg of prompt.messages) {
|
||||
msg.author = msg.role;
|
||||
msg.content = msg.parts[0].text;
|
||||
delete msg.parts;
|
||||
delete msg.role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete bisonBody.candidateCount;
|
||||
return bisonBody;
|
||||
}
|
||||
|
||||
const body = isGemini ? getGeminiBody() : getBisonBody();
|
||||
console.log('MakerSuite request:', body);
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
|
@ -179,7 +222,12 @@ async function sendPalmRequest(request, response) {
|
|||
controller.abort();
|
||||
});
|
||||
|
||||
const generateResponse = await fetch(`https://generativelanguage.googleapis.com/v1beta2/models/text-bison-001:generateText?key=${api_key_palm}`, {
|
||||
const apiVersion = isGemini ? 'v1beta' : 'v1beta2';
|
||||
const responseType = isGemini
|
||||
? (stream ? 'streamGenerateContent' : 'generateContent')
|
||||
: (isText ? 'generateText' : 'generateMessage');
|
||||
|
||||
const generateResponse = await fetch(`https://generativelanguage.googleapis.com/${apiVersion}/models/${model}:${responseType}?key=${apiKey}`, {
|
||||
body: JSON.stringify(body),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -188,34 +236,79 @@ async function sendPalmRequest(request, response) {
|
|||
signal: controller.signal,
|
||||
timeout: 0,
|
||||
});
|
||||
// have to do this because of their busted ass streaming endpoint
|
||||
if (stream) {
|
||||
try {
|
||||
let partialData = '';
|
||||
generateResponse.body.on('data', (data) => {
|
||||
const chunk = data.toString();
|
||||
if (chunk.startsWith(',') || chunk.endsWith(',') || chunk.startsWith('[') || chunk.endsWith(']')) {
|
||||
partialData = chunk.slice(1);
|
||||
} else {
|
||||
partialData += chunk;
|
||||
}
|
||||
while (true) {
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(partialData);
|
||||
} catch (e) {
|
||||
break;
|
||||
}
|
||||
response.write(JSON.stringify(json));
|
||||
partialData = '';
|
||||
}
|
||||
});
|
||||
|
||||
if (!generateResponse.ok) {
|
||||
console.log(`Palm API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
|
||||
return response.status(generateResponse.status).send({ error: true });
|
||||
}
|
||||
request.socket.on('close', function () {
|
||||
if (generateResponse.body instanceof Readable) generateResponse.body.destroy();
|
||||
response.end();
|
||||
});
|
||||
|
||||
const generateResponseJson = await generateResponse.json();
|
||||
const responseText = generateResponseJson?.candidates?.[0]?.output;
|
||||
generateResponse.body.on('end', () => {
|
||||
console.log('Streaming request finished');
|
||||
response.end();
|
||||
});
|
||||
|
||||
if (!responseText) {
|
||||
console.log('Palm API returned no response', generateResponseJson);
|
||||
let message = `Palm API returned no response: ${JSON.stringify(generateResponseJson)}`;
|
||||
|
||||
// Check for filters
|
||||
if (generateResponseJson?.filters?.[0]?.reason) {
|
||||
message = `Palm filter triggered: ${generateResponseJson.filters[0].reason}`;
|
||||
} catch (error) {
|
||||
console.log('Error forwarding streaming response:', error);
|
||||
if (!response.headersSent) {
|
||||
return response.status(500).send({ error: true });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!generateResponse.ok) {
|
||||
console.log(`MakerSuite API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
|
||||
return response.status(generateResponse.status).send({ error: true });
|
||||
}
|
||||
|
||||
return response.send({ error: { message } });
|
||||
const generateResponseJson = await generateResponse.json();
|
||||
|
||||
const candidates = generateResponseJson?.candidates;
|
||||
if (!candidates || candidates.length === 0) {
|
||||
let message = 'MakerSuite API returned no candidate';
|
||||
console.log(message, generateResponseJson);
|
||||
if (generateResponseJson?.promptFeedback?.blockReason) {
|
||||
message += `\nPrompt was blocked due to : ${generateResponseJson.promptFeedback.blockReason}`;
|
||||
}
|
||||
return response.send({ error: { message } });
|
||||
}
|
||||
|
||||
const responseContent = candidates[0].content ?? candidates[0].output;
|
||||
const responseText = typeof responseContent === 'string' ? responseContent : responseContent.parts?.[0]?.text;
|
||||
if (!responseText) {
|
||||
let message = 'MakerSuite Candidate text empty';
|
||||
console.log(message, generateResponseJson);
|
||||
return response.send({ error: { message } });
|
||||
}
|
||||
|
||||
console.log('MakerSuite response:', responseText);
|
||||
|
||||
// Wrap it back to OAI format
|
||||
const reply = { choices: [{ 'message': { 'content': responseText } }] };
|
||||
return response.send(reply);
|
||||
}
|
||||
|
||||
console.log('Palm response:', responseText);
|
||||
|
||||
// Wrap it back to OAI format
|
||||
const reply = { choices: [{ 'message': { 'content': responseText } }] };
|
||||
return response.send(reply);
|
||||
} catch (error) {
|
||||
console.log('Error communicating with Palm API: ', error);
|
||||
console.log('Error communicating with MakerSuite API: ', error);
|
||||
if (!response.headersSent) {
|
||||
return response.status(500).send({ error: true });
|
||||
}
|
||||
|
@ -223,7 +316,7 @@ async function sendPalmRequest(request, response) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sends a request to Google AI API.
|
||||
* Sends a request to AI21 API.
|
||||
* @param {express.Request} request Express request
|
||||
* @param {express.Response} response Express response
|
||||
*/
|
||||
|
@ -455,7 +548,7 @@ router.post('/generate', jsonParser, function (request, response) {
|
|||
case CHAT_COMPLETION_SOURCES.CLAUDE: return sendClaudeRequest(request, response);
|
||||
case CHAT_COMPLETION_SOURCES.SCALE: return sendScaleRequest(request, response);
|
||||
case CHAT_COMPLETION_SOURCES.AI21: return sendAI21Request(request, response);
|
||||
case CHAT_COMPLETION_SOURCES.PALM: return sendPalmRequest(request, response);
|
||||
case CHAT_COMPLETION_SOURCES.MAKERSUITE: return sendMakerSuiteRequest(request, response);
|
||||
}
|
||||
|
||||
let apiUrl;
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
const { readSecret, SECRET_KEYS } = require('./secrets');
|
||||
const fetch = require('node-fetch').default;
|
||||
const express = require('express');
|
||||
const { jsonParser } = require('../express-common');
|
||||
const { GEMINI_SAFETY } = require('../constants');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/caption-image', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const mimeType = request.body.image.split(';')[0].split(':')[1];
|
||||
const base64Data = request.body.image.split(',')[1];
|
||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${readSecret(SECRET_KEYS.MAKERSUITE)}`;
|
||||
const body = {
|
||||
contents: [{
|
||||
parts: [
|
||||
{ text: request.body.prompt },
|
||||
{
|
||||
inlineData: {
|
||||
mimeType: 'image/png', // It needs to specify a MIME type in data if it's not a PNG
|
||||
data: mimeType === 'image/png' ? base64Data : request.body.image,
|
||||
},
|
||||
}],
|
||||
}],
|
||||
safetySettings: GEMINI_SAFETY,
|
||||
generationConfig: { maxOutputTokens: 1000 },
|
||||
};
|
||||
|
||||
console.log('Multimodal captioning request', body);
|
||||
|
||||
const result = await fetch(url, {
|
||||
body: JSON.stringify(body),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
const error = await result.json();
|
||||
console.log(`MakerSuite API returned error: ${result.status} ${result.statusText}`, error);
|
||||
return response.status(result.status).send({ error: true });
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
console.log('Multimodal captioning response', data);
|
||||
|
||||
const candidates = data?.candidates;
|
||||
if (!candidates) {
|
||||
return response.status(500).send('No candidates found, image was most likely filtered.');
|
||||
}
|
||||
|
||||
const caption = candidates[0].content.parts[0].text;
|
||||
if (!caption) {
|
||||
return response.status(500).send('No caption found');
|
||||
}
|
||||
|
||||
return response.json({ caption });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
response.status(500).send('Internal server error');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = { router };
|
|
@ -91,6 +91,68 @@ function convertClaudePrompt(messages, addAssistantPostfix, addAssistantPrefill,
|
|||
return requestPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a prompt from the ChatML objects to the format used by Google MakerSuite models.
|
||||
* @param {object[]} messages Array of messages
|
||||
* @param {string} model Model name
|
||||
* @returns {object[]} Prompt for Google MakerSuite models
|
||||
*/
|
||||
function convertGooglePrompt(messages, model) {
|
||||
// This is a 1x1 transparent PNG
|
||||
const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
||||
const contents = [];
|
||||
let lastRole = '';
|
||||
let currentText = '';
|
||||
|
||||
const isMultimodal = model === 'gemini-pro-vision';
|
||||
|
||||
if (isMultimodal) {
|
||||
const combinedText = messages.map((message) => {
|
||||
const role = message.role === 'assistant' ? 'MODEL: ' : 'USER: ';
|
||||
return role + message.content;
|
||||
}).join('\n\n').trim();
|
||||
|
||||
const imageEntry = messages.find((message) => message.content?.[1]?.image_url);
|
||||
const imageData = imageEntry?.content?.[1]?.image_url?.data ?? PNG_PIXEL;
|
||||
contents.push({
|
||||
parts: [
|
||||
{ text: combinedText },
|
||||
{
|
||||
inlineData: {
|
||||
mimeType: 'image/png',
|
||||
data: imageData,
|
||||
},
|
||||
},
|
||||
],
|
||||
role: 'user',
|
||||
});
|
||||
} else {
|
||||
messages.forEach((message, index) => {
|
||||
const role = message.role === 'assistant' ? 'model' : 'user';
|
||||
if (lastRole === role) {
|
||||
currentText += '\n\n' + message.content;
|
||||
} else {
|
||||
if (currentText !== '') {
|
||||
contents.push({
|
||||
parts: [{ text: currentText.trim() }],
|
||||
role: lastRole,
|
||||
});
|
||||
}
|
||||
currentText = message.content;
|
||||
lastRole = role;
|
||||
}
|
||||
if (index === messages.length - 1) {
|
||||
contents.push({
|
||||
parts: [{ text: currentText.trim() }],
|
||||
role: lastRole,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a prompt from the ChatML objects to the format used by Text Completion API.
|
||||
* @param {object[]} messages Array of messages
|
||||
|
@ -118,5 +180,6 @@ function convertTextCompletionPrompt(messages) {
|
|||
|
||||
module.exports = {
|
||||
convertClaudePrompt,
|
||||
convertGooglePrompt,
|
||||
convertTextCompletionPrompt,
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ const SECRET_KEYS = {
|
|||
SCALE_COOKIE: 'scale_cookie',
|
||||
ONERING_URL: 'oneringtranslator_url',
|
||||
DEEPLX_URL: 'deeplx_url',
|
||||
PALM: 'api_key_palm',
|
||||
MAKERSUITE: 'api_key_makersuite',
|
||||
SERPAPI: 'api_key_serpapi',
|
||||
};
|
||||
|
||||
|
@ -44,6 +44,17 @@ function writeSecret(key, value) {
|
|||
writeFileAtomicSync(SECRETS_FILE, JSON.stringify(secrets, null, 4), 'utf-8');
|
||||
}
|
||||
|
||||
function deleteSecret(key) {
|
||||
if (!fs.existsSync(SECRETS_FILE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContents = fs.readFileSync(SECRETS_FILE, 'utf-8');
|
||||
const secrets = JSON.parse(fileContents);
|
||||
delete secrets[key];
|
||||
writeFileAtomicSync(SECRETS_FILE, JSON.stringify(secrets, null, 4), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a secret from the secrets file
|
||||
* @param {string} key Secret key
|
||||
|
@ -85,6 +96,13 @@ function readSecretState() {
|
|||
* @returns {void}
|
||||
*/
|
||||
function migrateSecrets(settingsFile) {
|
||||
const palmKey = readSecret('api_key_palm');
|
||||
if (palmKey) {
|
||||
console.log('Migrating Palm key...');
|
||||
writeSecret(SECRET_KEYS.MAKERSUITE, palmKey);
|
||||
deleteSecret('api_key_palm');
|
||||
}
|
||||
|
||||
if (!fs.existsSync(settingsFile)) {
|
||||
console.log('Settings file does not exist');
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||
const { DIRECTORIES } = require('../constants');
|
||||
const { getConfigValue, generateTimestamp, removeOldBackups } = require('../util');
|
||||
const { jsonParser } = require('../express-common');
|
||||
const { migrateSecrets } = require('./secrets');
|
||||
|
||||
const enableExtensions = getConfigValue('enableExtensions', true);
|
||||
const SETTINGS_FILE = './public/settings.json';
|
||||
|
||||
function readAndParseFromDirectory(directoryPath, fileExtension = '.json') {
|
||||
const files = fs
|
||||
.readdirSync(directoryPath)
|
||||
.filter(x => path.parse(x).ext == fileExtension)
|
||||
.sort();
|
||||
|
||||
const parsedFiles = [];
|
||||
|
||||
files.forEach(item => {
|
||||
try {
|
||||
const file = fs.readFileSync(path.join(directoryPath, item), 'utf-8');
|
||||
parsedFiles.push(fileExtension == '.json' ? JSON.parse(file) : file);
|
||||
}
|
||||
catch {
|
||||
// skip
|
||||
}
|
||||
});
|
||||
|
||||
return parsedFiles;
|
||||
}
|
||||
|
||||
function sortByName(_) {
|
||||
return (a, b) => a.localeCompare(b);
|
||||
}
|
||||
|
||||
function readPresetsFromDirectory(directoryPath, options = {}) {
|
||||
const {
|
||||
sortFunction,
|
||||
removeFileExtension = false,
|
||||
fileExtension = '.json',
|
||||
} = options;
|
||||
|
||||
const files = fs.readdirSync(directoryPath).sort(sortFunction).filter(x => path.parse(x).ext == fileExtension);
|
||||
const fileContents = [];
|
||||
const fileNames = [];
|
||||
|
||||
files.forEach(item => {
|
||||
try {
|
||||
const file = fs.readFileSync(path.join(directoryPath, item), 'utf8');
|
||||
JSON.parse(file);
|
||||
fileContents.push(file);
|
||||
fileNames.push(removeFileExtension ? item.replace(/\.[^/.]+$/, '') : item);
|
||||
} catch {
|
||||
// skip
|
||||
console.log(`${item} is not a valid JSON`);
|
||||
}
|
||||
});
|
||||
|
||||
return { fileContents, fileNames };
|
||||
}
|
||||
|
||||
function backupSettings() {
|
||||
try {
|
||||
if (!fs.existsSync(DIRECTORIES.backups)) {
|
||||
fs.mkdirSync(DIRECTORIES.backups);
|
||||
}
|
||||
|
||||
const backupFile = path.join(DIRECTORIES.backups, `settings_${generateTimestamp()}.json`);
|
||||
fs.copyFileSync(SETTINGS_FILE, backupFile);
|
||||
|
||||
removeOldBackups('settings_');
|
||||
} catch (err) {
|
||||
console.log('Could not backup settings file', err);
|
||||
}
|
||||
}
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/save', jsonParser, function (request, response) {
|
||||
try {
|
||||
writeFileAtomicSync('public/settings.json', JSON.stringify(request.body, null, 4), 'utf8');
|
||||
response.send({ result: 'ok' });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
response.send(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Wintermute's code
|
||||
router.post('/get', jsonParser, (request, response) => {
|
||||
let settings;
|
||||
try {
|
||||
settings = fs.readFileSync('public/settings.json', 'utf8');
|
||||
} catch (e) {
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
// NovelAI Settings
|
||||
const { fileContents: novelai_settings, fileNames: novelai_setting_names }
|
||||
= readPresetsFromDirectory(DIRECTORIES.novelAI_Settings, {
|
||||
sortFunction: sortByName(DIRECTORIES.novelAI_Settings),
|
||||
removeFileExtension: true,
|
||||
});
|
||||
|
||||
// OpenAI Settings
|
||||
const { fileContents: openai_settings, fileNames: openai_setting_names }
|
||||
= readPresetsFromDirectory(DIRECTORIES.openAI_Settings, {
|
||||
sortFunction: sortByName(DIRECTORIES.openAI_Settings), removeFileExtension: true,
|
||||
});
|
||||
|
||||
// TextGenerationWebUI Settings
|
||||
const { fileContents: textgenerationwebui_presets, fileNames: textgenerationwebui_preset_names }
|
||||
= readPresetsFromDirectory(DIRECTORIES.textGen_Settings, {
|
||||
sortFunction: sortByName(DIRECTORIES.textGen_Settings), removeFileExtension: true,
|
||||
});
|
||||
|
||||
//Kobold
|
||||
const { fileContents: koboldai_settings, fileNames: koboldai_setting_names }
|
||||
= readPresetsFromDirectory(DIRECTORIES.koboldAI_Settings, {
|
||||
sortFunction: sortByName(DIRECTORIES.koboldAI_Settings), removeFileExtension: true,
|
||||
});
|
||||
|
||||
const worldFiles = fs
|
||||
.readdirSync(DIRECTORIES.worlds)
|
||||
.filter(file => path.extname(file).toLowerCase() === '.json')
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
const world_names = worldFiles.map(item => path.parse(item).name);
|
||||
|
||||
const themes = readAndParseFromDirectory(DIRECTORIES.themes);
|
||||
const movingUIPresets = readAndParseFromDirectory(DIRECTORIES.movingUI);
|
||||
const quickReplyPresets = readAndParseFromDirectory(DIRECTORIES.quickreplies);
|
||||
|
||||
const instruct = readAndParseFromDirectory(DIRECTORIES.instruct);
|
||||
const context = readAndParseFromDirectory(DIRECTORIES.context);
|
||||
|
||||
response.send({
|
||||
settings,
|
||||
koboldai_settings,
|
||||
koboldai_setting_names,
|
||||
world_names,
|
||||
novelai_settings,
|
||||
novelai_setting_names,
|
||||
openai_settings,
|
||||
openai_setting_names,
|
||||
textgenerationwebui_presets,
|
||||
textgenerationwebui_preset_names,
|
||||
themes,
|
||||
movingUIPresets,
|
||||
quickReplyPresets,
|
||||
instruct,
|
||||
context,
|
||||
enable_extensions: enableExtensions,
|
||||
});
|
||||
});
|
||||
|
||||
// Sync for now, but should probably be migrated to async file APIs
|
||||
async function init() {
|
||||
backupSettings();
|
||||
migrateSecrets(SETTINGS_FILE);
|
||||
}
|
||||
|
||||
module.exports = { router, init };
|
|
@ -4,7 +4,7 @@ const express = require('express');
|
|||
const { SentencePieceProcessor } = require('@agnai/sentencepiece-js');
|
||||
const tiktoken = require('@dqbd/tiktoken');
|
||||
const { Tokenizer } = require('@agnai/web-tokenizers');
|
||||
const { convertClaudePrompt } = require('./prompt-converters');
|
||||
const { convertClaudePrompt, convertGooglePrompt } = require('./prompt-converters');
|
||||
const { readSecret, SECRET_KEYS } = require('./secrets');
|
||||
const { TEXTGEN_TYPES } = require('../constants');
|
||||
const { jsonParser } = require('../express-common');
|
||||
|
@ -387,6 +387,26 @@ router.post('/ai21/count', jsonParser, async function (req, res) {
|
|||
}
|
||||
});
|
||||
|
||||
router.post('/google/count', jsonParser, async function (req, res) {
|
||||
if (!req.body) return res.sendStatus(400);
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ contents: convertGooglePrompt(req.body) }),
|
||||
};
|
||||
try {
|
||||
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${req.query.model}:countTokens?key=${readSecret(SECRET_KEYS.MAKERSUITE)}`, options);
|
||||
const data = await response.json();
|
||||
return res.send({ 'token_count': data?.totalTokens || 0 });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.send({ 'token_count': 0 });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/llama/encode', jsonParser, createSentencepieceEncodingHandler(spp_llama));
|
||||
router.post('/nerdstash/encode', jsonParser, createSentencepieceEncodingHandler(spp_nerd));
|
||||
router.post('/nerdstash_v2/encode', jsonParser, createSentencepieceEncodingHandler(spp_nerd_v2));
|
||||
|
|
|
@ -17,7 +17,7 @@ async function getVector(source, text) {
|
|||
case 'transformers':
|
||||
return require('../embedding').getTransformersVector(text);
|
||||
case 'palm':
|
||||
return require('../palm-vectors').getPaLMVector(text);
|
||||
return require('../makersuite-vectors').getMakerSuiteVector(text);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown vector source ${source}`);
|
||||
|
@ -196,7 +196,7 @@ router.post('/purge', jsonParser, async (req, res) => {
|
|||
|
||||
const collectionId = String(req.body.collectionId);
|
||||
|
||||
const sources = ['transformers', 'openai'];
|
||||
const sources = ['transformers', 'openai', 'palm'];
|
||||
for (const source of sources) {
|
||||
const index = await getIndex(collectionId, source, false);
|
||||
|
||||
|
|
|
@ -6,15 +6,15 @@ const { SECRET_KEYS, readSecret } = require('./endpoints/secrets');
|
|||
* @param {string} text - The text to get the vector for
|
||||
* @returns {Promise<number[]>} - The vector for the text
|
||||
*/
|
||||
async function getPaLMVector(text) {
|
||||
const key = readSecret(SECRET_KEYS.PALM);
|
||||
async function getMakerSuiteVector(text) {
|
||||
const key = readSecret(SECRET_KEYS.MAKERSUITE);
|
||||
|
||||
if (!key) {
|
||||
console.log('No PaLM key found');
|
||||
throw new Error('No PaLM key found');
|
||||
console.log('No MakerSuite key found');
|
||||
throw new Error('No MakerSuite key found');
|
||||
}
|
||||
|
||||
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta2/models/embedding-gecko-001:embedText?key=${key}`, {
|
||||
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/embedding-gecko-001:embedText?key=${key}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -26,8 +26,8 @@ async function getPaLMVector(text) {
|
|||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
console.log('PaLM request failed', response.statusText, text);
|
||||
throw new Error('PaLM request failed');
|
||||
console.log('MakerSuite request failed', response.statusText, text);
|
||||
throw new Error('MakerSuite request failed');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
@ -39,5 +39,5 @@ async function getPaLMVector(text) {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
getPaLMVector,
|
||||
getMakerSuiteVector,
|
||||
};
|
Loading…
Reference in New Issue