Implement Vertex AI authentication modes and configuration in UI

- Updated index.html to include options for Vertex AI authentication modes (Express and Full).
- Enhanced openai.js to manage Vertex AI settings, including project ID, region, and service account JSON.
- Added validation and handling for service account JSON in the backend.
- Modified API request handling in google.js to support both authentication modes for Vertex AI.
- Updated secrets.js to include a key for storing Vertex AI service account JSON.
- Improved error handling and user feedback for authentication issues.
This commit is contained in:
InterestingDarkness
2025-05-26 22:09:59 +08:00
parent a6928289b3
commit 5656c7950d
7 changed files with 675 additions and 46 deletions

View File

@@ -56,6 +56,13 @@ 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_project_id = oai_settings.vertexai_project_id;
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 +171,27 @@ 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 project 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_project_id) {
throw new Error('Project ID is required for Vertex AI Full mode.');
}
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) {

View File

@@ -306,6 +306,10 @@ 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_project_id: ['#vertexai_project_id', 'vertexai_project_id', false, true],
vertexai_region: ['#vertexai_region', 'vertexai_region', false, true],
vertexai_service_account_json: ['#vertexai_service_account_json', 'vertexai_service_account_json', 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 +391,10 @@ const default_settings = {
assistant_impersonation: '',
claude_use_sysprompt: false,
use_makersuite_sysprompt: true,
vertexai_auth_mode: 'express',
vertexai_project_id: '',
vertexai_region: 'us-central1',
vertexai_service_account_json: '',
use_alt_scale: false,
squash_system_messages: false,
image_inlining: false,
@@ -471,6 +479,10 @@ const oai_settings = {
assistant_impersonation: '',
claude_use_sysprompt: false,
use_makersuite_sysprompt: true,
vertexai_auth_mode: 'express',
vertexai_project_id: '',
vertexai_region: 'us-central1',
vertexai_service_account_json: '',
use_alt_scale: false,
squash_system_messages: false,
image_inlining: false,
@@ -2188,6 +2200,11 @@ 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_project_id'] = oai_settings.vertexai_project_id;
generate_data['vertexai_region'] = oai_settings.vertexai_region;
}
}
if (isMistral) {
@@ -3427,6 +3444,9 @@ 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_project_id !== undefined) oai_settings.vertexai_project_id = settings.vertexai_project_id;
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);
@@ -3482,6 +3502,12 @@ 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_project_id').val(oai_settings.vertexai_project_id);
$('#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);
@@ -3804,6 +3830,10 @@ 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_project_id: settings.vertexai_project_id,
vertexai_region: settings.vertexai_region,
vertexai_service_account_json: settings.vertexai_service_account_json,
use_alt_scale: settings.use_alt_scale,
squash_system_messages: settings.squash_system_messages,
image_inlining: settings.image_inlining,
@@ -4979,15 +5009,30 @@ 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
if (!oai_settings.vertexai_project_id.trim()) {
toastr.error('Project ID is required for Vertex AI full version');
return;
}
// 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;
}
}
}
@@ -5170,6 +5215,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');
@@ -5480,6 +5527,160 @@ function runProxyCallback(_, value) {
return foundName;
}
/**
* Handle Vertex AI authentication mode change
*/
function onVertexAIAuthModeChange() {
const authMode = String($(this).val());
oai_settings.vertexai_auth_mode = authMode;
// Show/hide appropriate configuration sections
if (authMode === 'express') {
$('#vertexai_express_config').show();
$('#vertexai_full_config').hide();
$('#vertexai_express_models').show();
// Hide all full version model groups
$('#vertexai_full_gemini_25').hide();
$('#vertexai_full_gemini_20').hide();
$('#vertexai_full_gemini_15').hide();
$('#vertexai_full_gemma').hide();
$('#vertexai_full_learnlm').hide();
} else {
$('#vertexai_express_config').hide();
$('#vertexai_full_config').show();
$('#vertexai_express_models').hide();
// Show all full version model groups
$('#vertexai_full_gemini_25').show();
$('#vertexai_full_gemini_20').show();
$('#vertexai_full_gemini_15').show();
$('#vertexai_full_gemma').show();
$('#vertexai_full_learnlm').show();
}
saveSettingsDebounced();
}
/**
* Validate Vertex AI service account JSON
*/
async function onVertexAIValidateServiceAccount() {
const jsonContent = String($('#vertexai_service_account_json').val()).trim();
if (!jsonContent) {
toastr.error('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(`Missing required fields: ${missingFields.join(', ')}`);
updateVertexAIServiceAccountStatus(false, `Missing fields: ${missingFields.join(', ')}`);
return;
}
if (serviceAccount.type !== 'service_account') {
toastr.error('Invalid service account type. Expected "service_account"');
updateVertexAIServiceAccountStatus(false, 'Invalid service account type');
return;
}
// Save to backend secret storage
await writeSecret(SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT, jsonContent);
// Clear the textarea and update settings
$('#vertexai_service_account_json').val('');
oai_settings.vertexai_service_account_json = '';
// Show success status
updateVertexAIServiceAccountStatus(true, `Project: ${serviceAccount.project_id}, Email: ${serviceAccount.client_email}`);
toastr.success('Service Account JSON is valid and saved securely');
saveSettingsDebounced();
} catch (error) {
console.error('JSON validation error:', error);
toastr.error('Invalid JSON format');
updateVertexAIServiceAccountStatus(false, 'Invalid JSON format');
}
}
/**
* Clear Vertex AI service account JSON
*/
async function onVertexAIClearServiceAccount() {
$('#vertexai_service_account_json').val('');
oai_settings.vertexai_service_account_json = '';
// Clear from backend secret storage
await writeSecret(SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT, '');
updateVertexAIServiceAccountStatus(false);
toastr.info('Service Account JSON cleared');
saveSettingsDebounced();
}
/**
* Handle Vertex AI service account JSON input change
*/
function onVertexAIServiceAccountJsonChange() {
const jsonContent = String($(this).val()).trim();
// Don't save to settings automatically - only save when validated
// oai_settings.vertexai_service_account_json = jsonContent;
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, 'JSON appears valid - click "Validate JSON" to save');
} else {
updateVertexAIServiceAccountStatus(false, 'Incomplete or invalid JSON');
}
} catch (error) {
updateVertexAIServiceAccountStatus(false, '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 = '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',
@@ -5943,6 +6144,18 @@ 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_project_id').on('input', function () {
oai_settings.vertexai_project_id = String($(this).val());
saveSettingsDebounced();
});
$('#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);

View File

@@ -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 = {