Z.AI: Add endpoint selection (#4789) * Z.AI: Add endpoint selection Closes #4778 * Add i18n support for Z.AI endpoint options * Update message for captioning ext. * Remove unused imports * Add api-url autoconnect guard

a66801213831b507e774324c894f18aadec2acbc

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
6 files changed, +62 -4Ignore whitespace
public/index.html+5 -0
@@ -3722,6 +3722,11 @@
3722 <div data-for="api_key_zai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">3722 <div data-for="api_key_zai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3723 For privacy reasons, your API key will be hidden after you click 'Connect'.3723 For privacy reasons, your API key will be hidden after you click 'Connect'.
3724 </div>3724 </div>
3725 <h4 data-i18n="Z.AI Endpoint">Z.AI Endpoint</h4>
3726 <select id="zai_endpoint">
3727 <option value="common" data-i18n="Common API">Common API</option>
3728 <option value="coding" data-i18n="Coding API">Coding API</option>
3729 </select>
3725 <h4 data-i18n="Z.AI Model">Z.AI Model</h4>3730 <h4 data-i18n="Z.AI Model">Z.AI Model</h4>
3726 <select id="model_zai_select">3731 <select id="model_zai_select">
3727 <option value="glm-4.6">glm-4.6</option>3732 <option value="glm-4.6">glm-4.6</option>
public/scripts/extensions/caption/settings.html+3 -0
@@ -167,6 +167,9 @@
167 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>167 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
168 </select>168 </select>
169 </div>169 </div>
170 <div data-type="zai">
171 <b>Will use Common API. Coding API is not supported!</b>
172 </div>
170 <div data-type="ollama">173 <div data-type="ollama">
171 <div>174 <div>
172 The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>.175 The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>.
public/scripts/openai.js+16 -0
@@ -239,6 +239,11 @@ export const reasoning_effort_types = {
239 max: 'max',239 max: 'max',
240};240};
241241
242export const ZAI_ENDPOINT = {
243 COMMON: 'common',
244 CODING: 'coding',
245};
246
242const sensitiveFields = [247const sensitiveFields = [
243 'reverse_proxy',248 'reverse_proxy',
244 'proxy_password',249 'proxy_password',
@@ -302,6 +307,7 @@ export const settingsToUpdate = {
302 google_model: ['#model_google_select', 'google_model', false, true],307 google_model: ['#model_google_select', 'google_model', false, true],
303 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],308 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],
304 zai_model: ['#model_zai_select', 'zai_model', false, true],309 zai_model: ['#model_zai_select', 'zai_model', false, true],
310 zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true],
305 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],311 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],
306 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],312 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],
307 wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false],313 wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false],
@@ -402,6 +408,7 @@ const default_settings = {
402 moonshot_model: 'kimi-latest',408 moonshot_model: 'kimi-latest',
403 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',409 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
404 zai_model: 'glm-4.6',410 zai_model: 'glm-4.6',
411 zai_endpoint: ZAI_ENDPOINT.COMMON,
405 azure_base_url: '',412 azure_base_url: '',
406 azure_deployment_name: '',413 azure_deployment_name: '',
407 azure_api_version: '2024-02-15-preview',414 azure_api_version: '2024-02-15-preview',
@@ -2487,6 +2494,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2487 if (isZai) {2494 if (isZai) {
2488 generate_data['top_p'] = generate_data.top_p || 0.01;2495 generate_data['top_p'] = generate_data.top_p || 0.01;
2489 generate_data['stop'] = getCustomStoppingStrings(1);2496 generate_data['stop'] = getCustomStoppingStrings(1);
2497 generate_data['zai_endpoint'] = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
2490 delete generate_data.presence_penalty;2498 delete generate_data.presence_penalty;
2491 delete generate_data.frequency_penalty;2499 delete generate_data.frequency_penalty;
2492 }2500 }
@@ -3701,6 +3709,7 @@ function loadOpenAISettings(data, settings) {
3701 oai_settings.moonshot_model = settings.moonshot_model ?? default_settings.moonshot_model;3709 oai_settings.moonshot_model = settings.moonshot_model ?? default_settings.moonshot_model;
3702 oai_settings.fireworks_model = settings.fireworks_model ?? default_settings.fireworks_model;3710 oai_settings.fireworks_model = settings.fireworks_model ?? default_settings.fireworks_model;
3703 oai_settings.zai_model = settings.zai_model ?? default_settings.zai_model;3711 oai_settings.zai_model = settings.zai_model ?? default_settings.zai_model;
3712 oai_settings.zai_endpoint = settings.zai_endpoint ?? default_settings.zai_endpoint;
3704 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;3713 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
3705 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;3714 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
3706 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;3715 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
@@ -3812,6 +3821,8 @@ function loadOpenAISettings(data, settings) {
3812 $(`#model_moonshot_select option[value="${oai_settings.moonshot_model}"`).prop('selected', true);3821 $(`#model_moonshot_select option[value="${oai_settings.moonshot_model}"`).prop('selected', true);
3813 $('#model_zai_select').val(oai_settings.zai_model);3822 $('#model_zai_select').val(oai_settings.zai_model);
3814 $(`#model_zai_select option[value="${oai_settings.zai_model}"`).prop('selected', true);3823 $(`#model_zai_select option[value="${oai_settings.zai_model}"`).prop('selected', true);
3824 $('#zai_endpoint').val(oai_settings.zai_endpoint);
3825 $(`#zai_endpoint option[value="${oai_settings.zai_endpoint}"`).prop('selected', true);
3815 $('#custom_model_id').val(oai_settings.custom_model);3826 $('#custom_model_id').val(oai_settings.custom_model);
3816 $('#custom_api_url_text').val(oai_settings.custom_url);3827 $('#custom_api_url_text').val(oai_settings.custom_url);
3817 $('#azure_base_url').val(oai_settings.azure_base_url);3828 $('#azure_base_url').val(oai_settings.azure_base_url);
@@ -4114,6 +4125,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
4114 fireworks_model: settings.fireworks_model,4125 fireworks_model: settings.fireworks_model,
4115 cometapi_model: settings.cometapi_model,4126 cometapi_model: settings.cometapi_model,
4116 zai_model: settings.zai_model,4127 zai_model: settings.zai_model,
4128 zai_endpoint: settings.zai_endpoint,
4117 custom_model: settings.custom_model,4129 custom_model: settings.custom_model,
4118 custom_url: settings.custom_url,4130 custom_url: settings.custom_url,
4119 custom_include_body: settings.custom_include_body,4131 custom_include_body: settings.custom_include_body,
@@ -6678,6 +6690,10 @@ export function initOpenAI() {
6678 oai_settings.vertexai_express_project_id = String($(this).val());6690 oai_settings.vertexai_express_project_id = String($(this).val());
6679 saveSettingsDebounced();6691 saveSettingsDebounced();
6680 });6692 });
6693 $('#zai_endpoint').on('input', function () {
6694 oai_settings.zai_endpoint = String($(this).val());
6695 saveSettingsDebounced();
6696 });
6681 $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange);6697 $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange);
6682 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);6698 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);
6683 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);6699 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);
public/scripts/slash-commands.js+29 -2
@@ -61,7 +61,7 @@ import { hideChatMessageRange } from './chats.js';
61import { getContext, saveMetadataDebounced } from './extensions.js';61import { getContext, saveMetadataDebounced } from './extensions.js';
62import { getRegexedString, regex_placement } from './extensions/regex/engine.js';62import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
63import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js';63import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js';
64import { chat_completion_sources, oai_settings, promptManager } from './openai.js';64import { chat_completion_sources, oai_settings, promptManager, ZAI_ENDPOINT } from './openai.js';
65import { user_avatar } from './personas.js';65import { user_avatar } from './personas.js';
66import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, power_user } from './power-user.js';66import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, power_user } from './power-user.js';
67import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';67import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
@@ -2551,6 +2551,7 @@ export function initDefaultSlashCommands() {
2551 typeList: [ARGUMENT_TYPE.STRING],2551 typeList: [ARGUMENT_TYPE.STRING],
2552 enumList: [2552 enumList: [
2553 new SlashCommandEnumValue('custom', 'custom OpenAI-compatible', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'openai')), 'O'),2553 new SlashCommandEnumValue('custom', 'custom OpenAI-compatible', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'openai')), 'O'),
2554 new SlashCommandEnumValue('zai', 'Z.AI', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'zai')), 'Z'),
2554 new SlashCommandEnumValue('kobold', 'KoboldAI Classic', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'kobold')), 'K'),2555 new SlashCommandEnumValue('kobold', 'KoboldAI Classic', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'kobold')), 'K'),
2555 ...Object.values(textgen_types).map(api => new SlashCommandEnumValue(api, null, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'textgenerationwebui')), 'T')),2556 ...Object.values(textgen_types).map(api => new SlashCommandEnumValue(api, null, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'textgenerationwebui')), 'T')),
2556 ],2557 ],
@@ -2584,7 +2585,7 @@ export function initDefaultSlashCommands() {
2584 ${t`If a manual API is provided to <b>set</b> the URL, make sure to set <code>connect=false</code>, as auto-connect only works for the currently selected API, or consider switching to it with <code>/api</code> first.`}2585 ${t`If a manual API is provided to <b>set</b> the URL, make sure to set <code>connect=false</code>, as auto-connect only works for the currently selected API, or consider switching to it with <code>/api</code> first.`}
2585 </div>2586 </div>
2586 <div>2587 <div>
2587 ${t`This slash command works for most of the Text Completion sources, KoboldAI Classic, and also Custom OpenAI compatible for the Chat Completion sources. If unsure which APIs are supported, check the auto-completion of the optional <code>api</code> argument of this command.`}2588 ${t`This slash command works for most of the Text Completion sources, KoboldAI Classic, and also Custom OpenAI compatible and Z.AI for the Chat Completion sources. If unsure which APIs are supported, check the auto-completion of the optional <code>api</code> argument of this command.`}
2588 </div>2589 </div>
2589 `,2590 `,
2590 }));2591 }));
@@ -5159,6 +5160,32 @@ async function setApiUrlCallback({ api = null, connect = 'true', quiet = 'false'
5159 return url;5160 return url;
5160 }5161 }
51615162
5163 const isCurrentlyZAI = main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.ZAI;
5164 if (api === chat_completion_sources.ZAI || (!api && isCurrentlyZAI)) {
5165 if (!url) {
5166 return oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
5167 }
5168
5169 const permittedValues = Object.values(ZAI_ENDPOINT);
5170 if (!permittedValues.includes(url)) {
5171 !isQuiet && toastr.warning(t`Valid options are: ${permittedValues.join(', ')}`, t`ZAI endpoint '${url}' is not a valid option.`);
5172 return '';
5173 }
5174
5175 if (!isCurrentlyZAI && autoConnect) {
5176 toastr.warning(t`Z.AI is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.`);
5177 return '';
5178 }
5179
5180 $('#zai_endpoint').val(url).trigger('input');
5181
5182 if (autoConnect) {
5183 $('#api_button_openai').trigger('click');
5184 }
5185
5186 return oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
5187 }
5188
5162 // Special handling for Kobold Classic API5189 // Special handling for Kobold Classic API
5163 const isCurrentlyKoboldClassic = main_api === 'kobold';5190 const isCurrentlyKoboldClassic = main_api === 'kobold';
5164 if (api === 'kobold' || (!api && isCurrentlyKoboldClassic)) {5191 if (api === 'kobold' || (!api && isCurrentlyKoboldClassic)) {
src/constants.js+5 -0
@@ -512,3 +512,8 @@ export const MEDIA_EXTENSIONS = [
512 'm4a',512 'm4a',
513 'aiff',513 'aiff',
514];514];
515
516export const ZAI_ENDPOINT = {
517 COMMON: 'common',
518 CODING: 'coding',
519};
src/endpoints/backends/chat-completions.js+4 -2
@@ -13,6 +13,7 @@ import {
13 OPENAI_REASONING_EFFORT_MODELS,13 OPENAI_REASONING_EFFORT_MODELS,
14 OPENROUTER_HEADERS,14 OPENROUTER_HEADERS,
15 VERTEX_SAFETY,15 VERTEX_SAFETY,
16 ZAI_ENDPOINT,
16} from '../../constants.js';17} from '../../constants.js';
17import {18import {
18 forwardFetchResponse,19 forwardFetchResponse,
@@ -75,7 +76,8 @@ const API_POLLINATIONS = 'https://text.pollinations.ai/openai';
75const API_MOONSHOT = 'https://api.moonshot.ai/v1';76const API_MOONSHOT = 'https://api.moonshot.ai/v1';
76const API_FIREWORKS = 'https://api.fireworks.ai/inference/v1';77const API_FIREWORKS = 'https://api.fireworks.ai/inference/v1';
77const API_COMETAPI = 'https://api.cometapi.com/v1';78const API_COMETAPI = 'https://api.cometapi.com/v1';
78const API_ZAI = 'https://api.z.ai/api/paas/v4';79const API_ZAI_COMMON = 'https://api.z.ai/api/paas/v4';
80const API_ZAI_CODING = 'https://api.z.ai/api/coding/paas/v4';
79const API_SILICONFLOW = 'https://api.siliconflow.com/v1';81const API_SILICONFLOW = 'https://api.siliconflow.com/v1';
8082
81/**83/**
@@ -1973,7 +1975,7 @@ router.post('/generate', function (request, response) {
1973 };1975 };
1974 throw new Error('This provider is temporarily disabled.');1976 throw new Error('This provider is temporarily disabled.');
1975 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZAI) {1977 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZAI) {
1976 apiUrl = API_ZAI;1978 apiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING ? API_ZAI_CODING : API_ZAI_COMMON;
1977 apiKey = readSecret(request.user.directories, SECRET_KEYS.ZAI);1979 apiKey = readSecret(request.user.directories, SECRET_KEYS.ZAI);
1978 headers = {1980 headers = {
1979 'Accept-Language': 'en-US,en',1981 'Accept-Language': 'en-US,en',