Merge pull request #1751 from kingbased/proxypreset

Reverse proxy presets
This commit is contained in:
Cohee 2024-01-29 22:09:33 +02:00 committed by GitHub
commit da7b435b7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 200 additions and 50 deletions

View File

@ -746,48 +746,6 @@
</div>
</div>
</div>
<hr data-source="openai,claude">
<div class="range-block" data-source="openai,claude">
<div class="range-block-title justifyLeft" data-i18n="OpenAI Reverse Proxy">
OpenAI / Claude Reverse Proxy
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="Alternative server URL (leave empty to use the default value).">
Alternative server URL (leave empty to use the default value).<br>
</span>
<div id="ReverseProxyWarningMessage" class="reverse_proxy_warning">
<b data-i18n="Remove your real OAI API Key from the API panel BEFORE typing anything into this box">
Remove your real OAI API Key from the API panel BEFORE typing anything
into this box.
</b>
<hr>
<b data-i18n="We cannot provide support for problems encountered while using an unofficial OpenAI proxy">
We cannot provide support for problems encountered while using an
unofficial OpenAI proxy.
</b>
</div>
</div>
<div class="wide100p">
<input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" maxlength="500" />
<small class="reverse_proxy_warning">
Doesn't work? Try adding <code>/v1</code> at the end!
</small>
</div>
</div>
<div class="range-block" data-source="openai,claude">
<div class="range-block-title justifyLeft" data-i18n="Proxy Password">
Proxy Password
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="Will be used as a password for the proxy instead of API key.">
Will be used as a password for the proxy instead of API key.<br>
</span>
</div>
<div class="flex-container width100p">
<input id="openai_proxy_password" type="password" class="text_pole flex1" placeholder="" maxlength="500" form="openai_form" />
<div id="openai_proxy_password_show" title="Peek a password" class="menu_button fa-solid fa-eye-slash fa-fw"></div>
</div>
</div>
<div data-newbie-hidden class="range-block" data-source="openai,openrouter,mistralai,custom">
<div class="range-block-title justifyLeft" data-i18n="Seed">
Seed
@ -2073,6 +2031,76 @@
<option value="mistralai">MistralAI</option>
<option value="custom">Custom (OpenAI-compatible)</option>
</select>
<div data-newbie-hidden class="inline-drawer wide100p" data-source="openai,claude,mistralai">
<div class="inline-drawer-toggle inline-drawer-header">
<b data-i18n="Reverse Proxy">Reverse Proxy</b>
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
</div>
<div class="inline-drawer-content">
<div class="range-block-title justifyLeft" data-i18n="Proxy Presets">
Proxy Presets
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="Saved addresses and passwords.">
Saved addresses and passwords.<br>
</span>
</div>
<div class="openai_logit_bias_preset_form">
<select id="openai_proxy_preset">
</select>
<i id="save_proxy" class="menu_button fa-solid fa-save" title="Save Proxy" data-i18n="[title]Save Proxy"></i>
<i id="delete_proxy" class="menu_button fa-solid fa-trash" title="Delete Proxy" data-i18n="[title]Delete Proxy"></i>
</div>
<div class="range-block-title justifyLeft" data-i18n="Proxy Name">
Proxy Name
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="This will show up as your saved preset.">
This will show up as your saved preset.<br>
</span>
</div>
<div class="wide100p">
<input id="openai_reverse_proxy_name" type="text" class="text_pole" placeholder="..." maxlength="100" />
</div>
<div class="range-block-title justifyLeft" data-i18n="Proxy Server URL">
Proxy Server URL
</div>
<div class="toggle-description justifyLeft wide100p">
<span data-i18n="Alternative server URL (leave empty to use the default value).">
Alternative server URL (leave empty to use the default value).<br>
</span>
<div id="ReverseProxyWarningMessage" class="reverse_proxy_warning">
<b data-i18n="Remove your real OAI API Key from the API panel BEFORE typing anything into this box">
Remove your real OAI API Key from the API panel BEFORE typing anything
into this box.
</b>
<hr>
<b data-i18n="We cannot provide support for problems encountered while using an unofficial OpenAI proxy">
We cannot provide support for problems encountered while using an
unofficial OpenAI proxy.
</b>
</div>
</div>
<div class="wide100p">
<input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" maxlength="500" />
<small class="reverse_proxy_warning">
Doesn't work? Try adding <code>/v1</code> at the end!
</small>
</div>
<div class="range-block-title justifyLeft" data-i18n="Proxy Password">
Proxy Password
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="Will be used as a password for the proxy instead of API key.">
Will be used as a password for the proxy instead of API key.<br>
</span>
</div>
<div class="flex-container width100p">
<input id="openai_proxy_password" type="password" class="text_pole flex1" placeholder="" maxlength="500" form="openai_form" />
<div id="openai_proxy_password_show" title="Peek a password" class="menu_button fa-solid fa-eye-slash fa-fw"></div>
</div>
</div>
</div>
<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>
<div>

View File

@ -93,6 +93,9 @@ import {
chat_completion_sources,
getChatCompletionModel,
isOpenRouterWithInstruct,
proxies,
loadProxyPresets,
selected_proxy,
} from './scripts/openai.js';
import {
@ -5664,6 +5667,9 @@ async function getSettings() {
// Load background
loadBackgroundSettings(settings);
// Load proxy presets
loadProxyPresets(settings);
// Allow subscribers to mutate settings
eventSource.emit(event_types.SETTINGS_LOADED_AFTER, settings);
@ -5742,7 +5748,6 @@ async function saveSettings(type) {
}
//console.log('Entering settings with name1 = '+name1);
return jQuery.ajax({
type: 'POST',
url: '/api/settings/save',
@ -5770,6 +5775,8 @@ async function saveSettings(type) {
kai_settings: kai_settings,
oai_settings: oai_settings,
background: background_settings,
proxies: proxies,
selected_proxy: selected_proxy,
}, null, 4),
beforeSend: function () { },
cache: false,

View File

@ -317,6 +317,15 @@ const oai_settings = {
seed: -1,
};
export let proxies = [
{
name: 'None',
url: '',
password: '',
},
];
export let selected_proxy = proxies[0];
let openai_setting_names;
let openai_settings;
@ -1596,8 +1605,8 @@ async function sendOpenAIRequest(type, messages, signal) {
delete generate_data.stop;
}
// Proxy is only supported for Claude and OpenAI
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI].includes(oai_settings.chat_completion_source)) {
// Proxy is only supported for Claude, OpenAI and Mistral
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI].includes(oai_settings.chat_completion_source)) {
validateReverseProxy();
generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
generate_data['proxy_password'] = oai_settings.proxy_password;
@ -3803,6 +3812,110 @@ export function isImageInliningSupported() {
}
}
/**
* Proxy stuff
*/
export function loadProxyPresets(settings) {
let proxyPresets = settings.proxies;
selected_proxy = settings.selected_proxy || selected_proxy;
if (!Array.isArray(proxyPresets) || proxyPresets.length === 0) {
proxyPresets = proxies;
} else {
proxies = proxyPresets;
}
$('#openai_proxy_preset').empty();
for (const preset of proxyPresets) {
const option = document.createElement('option');
option.innerText = preset.name;
option.value = preset.name;
option.selected = preset.name === 'None';
$('#openai_proxy_preset').append(option);
}
$('#openai_proxy_preset').val(selected_proxy.name);
setProxyPreset(selected_proxy.name, selected_proxy.url, selected_proxy.password);
}
function setProxyPreset(name, url, password) {
const preset = proxies.find(p => p.name === name);
if (preset) {
preset.url = url;
preset.password = password;
selected_proxy = preset;
} else {
let new_proxy = { name, url, password };
proxies.push(new_proxy);
selected_proxy = new_proxy;
}
$('#openai_reverse_proxy_name').val(name);
oai_settings.reverse_proxy = url;
$('#openai_reverse_proxy').val(oai_settings.reverse_proxy);
oai_settings.proxy_password = password;
$('#openai_proxy_password').val(oai_settings.proxy_password);
reconnectOpenAi();
}
function onProxyPresetChange() {
const value = String($('#openai_proxy_preset').find(':selected').val());
const selectedPreset = proxies.find(preset => preset.name === value);
if (selectedPreset) {
setProxyPreset(selectedPreset.name, selectedPreset.url, selectedPreset.password);
} else {
console.error(`Proxy preset "${value}" not found in proxies array.`);
}
saveSettingsDebounced();
}
$('#save_proxy').on('click', async function () {
const presetName = $('#openai_reverse_proxy_name').val();
const reverseProxy = $('#openai_reverse_proxy').val();
const proxyPassword = $('#openai_proxy_password').val();
setProxyPreset(presetName, reverseProxy, proxyPassword);
saveSettingsDebounced();
toastr.success('Proxy Saved');
if($('#openai_proxy_preset').val() !== presetName) {
const option = document.createElement('option');
option.text = presetName;
option.value = presetName;
$('#openai_proxy_preset').append(option);
}
$('#openai_proxy_preset').val(presetName);
});
$('#delete_proxy').on('click', async function () {
const presetName = $('#openai_reverse_proxy_name').val();
const index = proxies.findIndex(preset => preset.name === presetName);
if (index !== -1) {
proxies.splice(index, 1);
$('#openai_proxy_preset option[value="' + presetName + '"]').remove();
if (proxies.length > 0) {
const newIndex = Math.max(0, index - 1);
selected_proxy = proxies[newIndex];
} else {
selected_proxy = { name: 'None', url: '', password: '' };
}
$('#openai_reverse_proxy_name').val(selected_proxy.name);
oai_settings.reverse_proxy = selected_proxy.url;
$('#openai_reverse_proxy').val(selected_proxy.url);
oai_settings.proxy_password = selected_proxy.password;
$('#openai_proxy_password').val(selected_proxy.password);
saveSettingsDebounced();
$('#openai_proxy_preset').val(selected_proxy.name);
toastr.success('Proxy Deleted');
} else {
toastr.error(`Could not find proxy with name "${presetName}"`);
}
});
$(document).ready(async function () {
$('#test_api_button').on('click', testApiConnection);
@ -4183,4 +4296,5 @@ $(document).ready(async function () {
$('#import_oai_preset').on('click', onImportPresetClick);
$('#openai_proxy_password_show').on('click', onProxyPasswordShowClick);
$('#customize_additional_parameters').on('click', onCustomizeParametersClick);
$('#openai_proxy_preset').on('change', onProxyPresetChange);
});

View File

@ -12,7 +12,7 @@ const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sente
const API_OPENAI = 'https://api.openai.com/v1';
const API_CLAUDE = 'https://api.anthropic.com/v1';
const API_MISTRAL = 'https://api.mistral.ai/v1';
/**
* Sends a request to Claude API.
* @param {express.Request} request Express request
@ -436,7 +436,8 @@ async function sendAI21Request(request, response) {
* @param {express.Response} response Express response
*/
async function sendMistralAIRequest(request, response) {
const apiKey = readSecret(SECRET_KEYS.MISTRALAI);
const apiUrl = new URL(request.body.reverse_proxy || API_MISTRAL).toString();
const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.MISTRALAI);
if (!apiKey) {
console.log('MistralAI API key is missing.');
@ -500,7 +501,7 @@ async function sendMistralAIRequest(request, response) {
console.log('MisralAI request:', requestBody);
const generateResponse = await fetch('https://api.mistral.ai/v1/chat/completions', config);
const generateResponse = await fetch(apiUrl + '/chat/completions', config);
if (request.body.stream) {
forwardFetchResponse(generateResponse, response);
} else {
@ -543,8 +544,8 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
headers = { 'HTTP-Referer': request.headers.referer };
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) {
api_url = 'https://api.mistral.ai/v1';
api_key_openai = readSecret(SECRET_KEYS.MISTRALAI);
api_url = new URL(request.body.reverse_proxy || API_MISTRAL).toString();
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.MISTRALAI);
headers = {};
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.CUSTOM) {
api_url = request.body.custom_url;