mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge pull request #4046 from InterestingDarknessII/vertexfull
Add Vertex AI Full Version support
This commit is contained in:
@@ -402,7 +402,8 @@ function RA_autoconnect(PrevApi) {
|
||||
|| (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.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE)
|
||||
|| (secret_state[SECRET_KEYS.VERTEXAI] && oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI)
|
||||
|| (secret_state[SECRET_KEYS.VERTEXAI] && oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI && oai_settings.vertexai_auth_mode === 'express')
|
||||
|| (secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT] && oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI && oai_settings.vertexai_auth_mode === 'full')
|
||||
|| (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI)
|
||||
|| (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
|
||||
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|
||||
|
@@ -56,6 +56,12 @@ export async function getMultimodalCaption(base64Img, prompt) {
|
||||
model: extension_settings.caption.multimodal_model || 'gpt-4-turbo',
|
||||
};
|
||||
|
||||
// Add Vertex AI specific parameters if using Vertex AI
|
||||
if (extension_settings.caption.multimodal_api === 'vertexai') {
|
||||
requestBody.vertexai_auth_mode = oai_settings.vertexai_auth_mode;
|
||||
requestBody.vertexai_region = oai_settings.vertexai_region;
|
||||
}
|
||||
|
||||
if (isOllama) {
|
||||
if (extension_settings.caption.multimodal_model === 'ollama_current') {
|
||||
requestBody.model = textgenerationwebui_settings.ollama_model;
|
||||
@@ -164,8 +170,24 @@ function throwIfInvalidModel(useReverseProxy) {
|
||||
throw new Error('Google AI Studio API key is not set.');
|
||||
}
|
||||
|
||||
if (multimodalApi === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !useReverseProxy) {
|
||||
throw new Error('Google Vertex AI API key is not set.');
|
||||
if (multimodalApi === 'vertexai' && !useReverseProxy) {
|
||||
// Check based on authentication mode
|
||||
const authMode = oai_settings.vertexai_auth_mode || 'express';
|
||||
|
||||
if (authMode === 'express') {
|
||||
// Express mode requires API key
|
||||
if (!secret_state[SECRET_KEYS.VERTEXAI]) {
|
||||
throw new Error('Google Vertex AI API key is not set for Express mode.');
|
||||
}
|
||||
} else if (authMode === 'full') {
|
||||
// Full mode requires Service Account JSON and region settings
|
||||
if (!secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]) {
|
||||
throw new Error('Service Account JSON is required for Vertex AI Full mode. Please validate and save your Service Account JSON.');
|
||||
}
|
||||
if (!oai_settings.vertexai_region) {
|
||||
throw new Error('Region is required for Vertex AI Full mode.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (multimodalApi === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) {
|
||||
|
@@ -235,6 +235,7 @@ const sensitiveFields = [
|
||||
'custom_include_body',
|
||||
'custom_exclude_body',
|
||||
'custom_include_headers',
|
||||
'vertexai_region',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -306,6 +307,8 @@ export const settingsToUpdate = {
|
||||
assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false, false],
|
||||
claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true, false],
|
||||
use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true, false],
|
||||
vertexai_auth_mode: ['#vertexai_auth_mode', 'vertexai_auth_mode', false, true],
|
||||
vertexai_region: ['#vertexai_region', 'vertexai_region', false, true],
|
||||
use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true, true],
|
||||
squash_system_messages: ['#squash_system_messages', 'squash_system_messages', true, false],
|
||||
image_inlining: ['#openai_image_inlining', 'image_inlining', true, false],
|
||||
@@ -387,6 +390,8 @@ const default_settings = {
|
||||
assistant_impersonation: '',
|
||||
claude_use_sysprompt: false,
|
||||
use_makersuite_sysprompt: true,
|
||||
vertexai_auth_mode: 'express',
|
||||
vertexai_region: 'us-central1',
|
||||
use_alt_scale: false,
|
||||
squash_system_messages: false,
|
||||
image_inlining: false,
|
||||
@@ -471,6 +476,8 @@ const oai_settings = {
|
||||
assistant_impersonation: '',
|
||||
claude_use_sysprompt: false,
|
||||
use_makersuite_sysprompt: true,
|
||||
vertexai_auth_mode: 'express',
|
||||
vertexai_region: 'us-central1',
|
||||
use_alt_scale: false,
|
||||
squash_system_messages: false,
|
||||
image_inlining: false,
|
||||
@@ -2188,6 +2195,10 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
generate_data['top_k'] = Number(oai_settings.top_k_openai);
|
||||
generate_data['stop'] = getCustomStoppingStrings(stopStringsLimit).slice(0, stopStringsLimit).filter(x => x.length >= 1 && x.length <= 16);
|
||||
generate_data['use_makersuite_sysprompt'] = oai_settings.use_makersuite_sysprompt;
|
||||
if (isVertexAI) {
|
||||
generate_data['vertexai_auth_mode'] = oai_settings.vertexai_auth_mode;
|
||||
generate_data['vertexai_region'] = oai_settings.vertexai_region;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMistral) {
|
||||
@@ -3423,6 +3434,8 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
if (settings.claude_use_sysprompt !== undefined) oai_settings.claude_use_sysprompt = !!settings.claude_use_sysprompt;
|
||||
if (settings.use_makersuite_sysprompt !== undefined) oai_settings.use_makersuite_sysprompt = !!settings.use_makersuite_sysprompt;
|
||||
if (settings.vertexai_auth_mode !== undefined) oai_settings.vertexai_auth_mode = settings.vertexai_auth_mode;
|
||||
if (settings.vertexai_region !== undefined) oai_settings.vertexai_region = settings.vertexai_region;
|
||||
if (settings.use_alt_scale !== undefined) { oai_settings.use_alt_scale = !!settings.use_alt_scale; updateScaleForm(); }
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
$('#api_url_scale').val(oai_settings.api_url_scale);
|
||||
@@ -3478,6 +3491,11 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#openai_external_category').toggle(oai_settings.show_external_models);
|
||||
$('#claude_use_sysprompt').prop('checked', oai_settings.claude_use_sysprompt);
|
||||
$('#use_makersuite_sysprompt').prop('checked', oai_settings.use_makersuite_sysprompt);
|
||||
$('#vertexai_auth_mode').val(oai_settings.vertexai_auth_mode);
|
||||
$('#vertexai_region').val(oai_settings.vertexai_region);
|
||||
// Don't display Service Account JSON in textarea - it's stored in backend secrets
|
||||
$('#vertexai_service_account_json').val('');
|
||||
updateVertexAIServiceAccountStatus();
|
||||
$('#scale-alt').prop('checked', oai_settings.use_alt_scale);
|
||||
$('#openrouter_use_fallback').prop('checked', oai_settings.openrouter_use_fallback);
|
||||
$('#openrouter_group_models').prop('checked', oai_settings.openrouter_group_models);
|
||||
@@ -3800,6 +3818,8 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
assistant_impersonation: settings.assistant_impersonation,
|
||||
claude_use_sysprompt: settings.claude_use_sysprompt,
|
||||
use_makersuite_sysprompt: settings.use_makersuite_sysprompt,
|
||||
vertexai_auth_mode: settings.vertexai_auth_mode,
|
||||
vertexai_region: settings.vertexai_region,
|
||||
use_alt_scale: settings.use_alt_scale,
|
||||
squash_system_messages: settings.squash_system_messages,
|
||||
image_inlining: settings.image_inlining,
|
||||
@@ -4975,15 +4995,27 @@ async function onConnectButtonClick(e) {
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) {
|
||||
const api_key_vertexai = String($('#api_key_vertexai').val()).trim();
|
||||
if (oai_settings.vertexai_auth_mode === 'express') {
|
||||
// Express mode - use API key
|
||||
const api_key_vertexai = String($('#api_key_vertexai').val()).trim();
|
||||
|
||||
if (api_key_vertexai.length) {
|
||||
await writeSecret(SECRET_KEYS.VERTEXAI, api_key_vertexai);
|
||||
}
|
||||
if (api_key_vertexai.length) {
|
||||
await writeSecret(SECRET_KEYS.VERTEXAI, api_key_vertexai);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.VERTEXAI] && !oai_settings.reverse_proxy) {
|
||||
console.log('No secret key saved for Vertex AI');
|
||||
return;
|
||||
if (!secret_state[SECRET_KEYS.VERTEXAI] && !oai_settings.reverse_proxy) {
|
||||
console.log('No secret key saved for Vertex AI Express mode');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Full version - use service account
|
||||
// Project ID will be extracted from the Service Account JSON
|
||||
|
||||
// Check if service account JSON is saved in backend
|
||||
if (!secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]) {
|
||||
toastr.error('Service Account JSON is required for Vertex AI full version. Please validate and save your Service Account JSON.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5166,6 +5198,8 @@ function toggleChatCompletionForms() {
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) {
|
||||
$('#model_vertexai_select').trigger('change');
|
||||
// Update UI based on authentication mode
|
||||
onVertexAIAuthModeChange.call($('#vertexai_auth_mode')[0]);
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {
|
||||
$('#model_openrouter_select').trigger('change');
|
||||
@@ -5476,6 +5510,136 @@ function runProxyCallback(_, value) {
|
||||
return foundName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Vertex AI authentication mode change
|
||||
*/
|
||||
function onVertexAIAuthModeChange() {
|
||||
const authMode = String($(this).val());
|
||||
oai_settings.vertexai_auth_mode = authMode;
|
||||
|
||||
$('#vertexai_form [data-mode]').each(function () {
|
||||
const mode = $(this).data('mode');
|
||||
$(this).toggle(mode === authMode);
|
||||
$(this).find('option').toggle(mode === authMode);
|
||||
});
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Vertex AI service account JSON
|
||||
*/
|
||||
async function onVertexAIValidateServiceAccount() {
|
||||
const jsonContent = String($('#vertexai_service_account_json').val()).trim();
|
||||
|
||||
if (!jsonContent) {
|
||||
toastr.error(t`Please enter Service Account JSON content`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const serviceAccount = JSON.parse(jsonContent);
|
||||
const requiredFields = ['type', 'project_id', 'private_key', 'client_email', 'client_id'];
|
||||
const missingFields = requiredFields.filter(field => !serviceAccount[field]);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
toastr.error(t`Missing required fields: ${missingFields.join(', ')}`);
|
||||
updateVertexAIServiceAccountStatus(false, t`Missing fields: ${missingFields.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceAccount.type !== 'service_account') {
|
||||
toastr.error(t`Invalid service account type. Expected "service_account"`);
|
||||
updateVertexAIServiceAccountStatus(false, t`Invalid service account type`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save to backend secret storage
|
||||
await writeSecret(SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT, jsonContent);
|
||||
|
||||
// Show success status
|
||||
updateVertexAIServiceAccountStatus(true, `Project: ${serviceAccount.project_id}, Email: ${serviceAccount.client_email}`);
|
||||
|
||||
toastr.success(t`Service Account JSON is valid and saved securely`);
|
||||
saveSettingsDebounced();
|
||||
} catch (error) {
|
||||
console.error('JSON validation error:', error);
|
||||
toastr.error(t`Invalid JSON format`);
|
||||
updateVertexAIServiceAccountStatus(false, t`Invalid JSON format`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Vertex AI service account JSON
|
||||
*/
|
||||
async function onVertexAIClearServiceAccount() {
|
||||
$('#vertexai_service_account_json').val('');
|
||||
|
||||
// Clear from backend secret storage
|
||||
await writeSecret(SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT, '');
|
||||
|
||||
updateVertexAIServiceAccountStatus(false);
|
||||
toastr.info(t`Service Account JSON cleared`);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Vertex AI service account JSON input change
|
||||
*/
|
||||
function onVertexAIServiceAccountJsonChange() {
|
||||
const jsonContent = String($(this).val()).trim();
|
||||
|
||||
if (jsonContent) {
|
||||
// Auto-validate when content is pasted
|
||||
try {
|
||||
const serviceAccount = JSON.parse(jsonContent);
|
||||
const requiredFields = ['type', 'project_id', 'private_key', 'client_email'];
|
||||
const hasAllFields = requiredFields.every(field => serviceAccount[field]);
|
||||
|
||||
if (hasAllFields && serviceAccount.type === 'service_account') {
|
||||
updateVertexAIServiceAccountStatus(false, t`JSON appears valid - click "Validate JSON" to save`);
|
||||
} else {
|
||||
updateVertexAIServiceAccountStatus(false, t`Incomplete or invalid JSON`);
|
||||
}
|
||||
} catch (error) {
|
||||
updateVertexAIServiceAccountStatus(false, t`Invalid JSON format`);
|
||||
}
|
||||
} else {
|
||||
updateVertexAIServiceAccountStatus(false);
|
||||
}
|
||||
|
||||
// Don't save settings automatically
|
||||
// saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Vertex AI service account status display
|
||||
* @param {boolean} isValid - Whether the service account is valid
|
||||
* @param {string} message - Status message to display
|
||||
*/
|
||||
function updateVertexAIServiceAccountStatus(isValid = false, message = '') {
|
||||
const statusDiv = $('#vertexai_service_account_status');
|
||||
const infoSpan = $('#vertexai_service_account_info');
|
||||
|
||||
// If no explicit message provided, check if we have a saved service account
|
||||
if (!message && secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]) {
|
||||
isValid = true;
|
||||
message = t`Service Account JSON is saved and ready to use`;
|
||||
}
|
||||
|
||||
if (isValid && message) {
|
||||
infoSpan.html(`<i class="fa-solid fa-check-circle" style="color: green;"></i> ${message}`);
|
||||
statusDiv.show();
|
||||
} else if (!isValid && message) {
|
||||
infoSpan.html(`<i class="fa-solid fa-exclamation-triangle" style="color: orange;"></i> ${message}`);
|
||||
statusDiv.show();
|
||||
} else {
|
||||
statusDiv.hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function initOpenAI() {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'proxy',
|
||||
@@ -5939,6 +6103,14 @@ export function initOpenAI() {
|
||||
$('#model_scale_select').on('change', onModelChange);
|
||||
$('#model_google_select').on('change', onModelChange);
|
||||
$('#model_vertexai_select').on('change', onModelChange);
|
||||
$('#vertexai_auth_mode').on('change', onVertexAIAuthModeChange);
|
||||
$('#vertexai_region').on('input', function () {
|
||||
oai_settings.vertexai_region = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange);
|
||||
$('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);
|
||||
$('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);
|
||||
$('#model_openrouter_select').on('change', onModelChange);
|
||||
$('#openrouter_group_models').on('change', onOpenrouterModelSortChange);
|
||||
$('#openrouter_sort_models').on('change', onOpenrouterModelSortChange);
|
||||
|
@@ -44,6 +44,7 @@ export const SECRET_KEYS = {
|
||||
SERPER: 'api_key_serper',
|
||||
FALAI: 'api_key_falai',
|
||||
XAI: 'api_key_xai',
|
||||
VERTEXAI_SERVICE_ACCOUNT: 'vertexai_service_account_json',
|
||||
};
|
||||
|
||||
const INPUT_MAP = {
|
||||
@@ -80,8 +81,13 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.GENERIC]: '#api_key_generic',
|
||||
[SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
|
||||
[SECRET_KEYS.XAI]: '#api_key_xai',
|
||||
[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]: '#vertexai_service_account_json',
|
||||
};
|
||||
|
||||
const STATIC_PLACEHOLDER_KEYS = [
|
||||
SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT,
|
||||
];
|
||||
|
||||
async function clearSecret() {
|
||||
const key = $(this).data('key');
|
||||
await writeSecret(key, '');
|
||||
@@ -93,6 +99,9 @@ async function clearSecret() {
|
||||
|
||||
export function updateSecretDisplay() {
|
||||
for (const [secret_key, input_selector] of Object.entries(INPUT_MAP)) {
|
||||
if (STATIC_PLACEHOLDER_KEYS.includes(secret_key)) {
|
||||
continue;
|
||||
}
|
||||
const validSecret = !!secret_state[secret_key];
|
||||
|
||||
const placeholder = $('#viewSecrets').attr(validSecret ? 'key_saved_text' : 'missing_key_text');
|
||||
|
Reference in New Issue
Block a user