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
Signed| @@ -3722,6 +3722,11 @@ | ||
| 3722 | 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 | 3723 | For privacy reasons, your API key will be hidden after you click 'Connect'. |
| 3724 | 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 | 3730 | <h4 data-i18n="Z.AI Model">Z.AI Model</h4> |
| 3726 | 3731 | <select id="model_zai_select"> |
| 3727 | 3732 | <option value="glm-4.6">glm-4.6</option> |
| @@ -167,6 +167,9 @@ | ||
| 167 | 167 | <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option> |
| 168 | 168 | </select> |
| 169 | 169 | </div> |
| 170 | + <div data-type="zai"> | |
| 171 | + <b>Will use Common API. Coding API is not supported!</b> | |
| 172 | + </div> | |
| 170 | 173 | <div data-type="ollama"> |
| 171 | 174 | <div> |
| 172 | 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>. |
| @@ -239,6 +239,11 @@ export const reasoning_effort_types = { | ||
| 239 | 239 | max: 'max', |
| 240 | 240 | }; |
| 241 | 241 | |
| 242 | +export const ZAI_ENDPOINT = { | |
| 243 | + COMMON: 'common', | |
| 244 | + CODING: 'coding', | |
| 245 | +}; | |
| 246 | + | |
| 242 | 247 | const sensitiveFields = [ |
| 243 | 248 | 'reverse_proxy', |
| 244 | 249 | 'proxy_password', |
| @@ -302,6 +307,7 @@ export const settingsToUpdate = { | ||
| 302 | 307 | google_model: ['#model_google_select', 'google_model', false, true], |
| 303 | 308 | vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true], |
| 304 | 309 | zai_model: ['#model_zai_select', 'zai_model', false, true], |
| 310 | + zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true], | |
| 305 | 311 | openai_max_context: ['#openai_max_context', 'openai_max_context', false, false], |
| 306 | 312 | openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false], |
| 307 | 313 | wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false], |
| @@ -402,6 +408,7 @@ const default_settings = { | ||
| 402 | 408 | moonshot_model: 'kimi-latest', |
| 403 | 409 | fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct', |
| 404 | 410 | zai_model: 'glm-4.6', |
| 411 | + zai_endpoint: ZAI_ENDPOINT.COMMON, | |
| 405 | 412 | azure_base_url: '', |
| 406 | 413 | azure_deployment_name: '', |
| 407 | 414 | azure_api_version: '2024-02-15-preview', |
| @@ -2487,6 +2494,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } = | ||
| 2487 | 2494 | if (isZai) { |
| 2488 | 2495 | generate_data['top_p'] = generate_data.top_p || 0.01; |
| 2489 | 2496 | generate_data['stop'] = getCustomStoppingStrings(1); |
| 2497 | + generate_data['zai_endpoint'] = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON; | |
| 2490 | 2498 | delete generate_data.presence_penalty; |
| 2491 | 2499 | delete generate_data.frequency_penalty; |
| 2492 | 2500 | } |
| @@ -3701,6 +3709,7 @@ function loadOpenAISettings(data, settings) { | ||
| 3701 | 3709 | oai_settings.moonshot_model = settings.moonshot_model ?? default_settings.moonshot_model; |
| 3702 | 3710 | oai_settings.fireworks_model = settings.fireworks_model ?? default_settings.fireworks_model; |
| 3703 | 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 | 3713 | oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model; |
| 3705 | 3714 | oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url; |
| 3706 | 3715 | oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body; |
| @@ -3812,6 +3821,8 @@ function loadOpenAISettings(data, settings) { | ||
| 3812 | 3821 | $(`#model_moonshot_select option[value="${oai_settings.moonshot_model}"`).prop('selected', true); |
| 3813 | 3822 | $('#model_zai_select').val(oai_settings.zai_model); |
| 3814 | 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 | 3826 | $('#custom_model_id').val(oai_settings.custom_model); |
| 3816 | 3827 | $('#custom_api_url_text').val(oai_settings.custom_url); |
| 3817 | 3828 | $('#azure_base_url').val(oai_settings.azure_base_url); |
| @@ -4114,6 +4125,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { | ||
| 4114 | 4125 | fireworks_model: settings.fireworks_model, |
| 4115 | 4126 | cometapi_model: settings.cometapi_model, |
| 4116 | 4127 | zai_model: settings.zai_model, |
| 4128 | + zai_endpoint: settings.zai_endpoint, | |
| 4117 | 4129 | custom_model: settings.custom_model, |
| 4118 | 4130 | custom_url: settings.custom_url, |
| 4119 | 4131 | custom_include_body: settings.custom_include_body, |
| @@ -6678,6 +6690,10 @@ export function initOpenAI() { | ||
| 6678 | 6690 | oai_settings.vertexai_express_project_id = String($(this).val()); |
| 6679 | 6691 | saveSettingsDebounced(); |
| 6680 | 6692 | }); |
| 6693 | + $('#zai_endpoint').on('input', function () { | |
| 6694 | + oai_settings.zai_endpoint = String($(this).val()); | |
| 6695 | + saveSettingsDebounced(); | |
| 6696 | + }); | |
| 6681 | 6697 | $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange); |
| 6682 | 6698 | $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount); |
| 6683 | 6699 | $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount); |
| @@ -61,7 +61,7 @@ import { hideChatMessageRange } from './chats.js'; | ||
| 61 | 61 | import { getContext, saveMetadataDebounced } from './extensions.js'; |
| 62 | 62 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; |
| 63 | 63 | import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js'; |
| 64 | 64 | import { chat_completion_sources, oai_settings, promptManager, ZAI_ENDPOINT } from './openai.js'; |
| 65 | 65 | import { user_avatar } from './personas.js'; |
| 66 | 66 | import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, power_user } from './power-user.js'; |
| 67 | 67 | import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; |
| @@ -2551,6 +2551,7 @@ export function initDefaultSlashCommands() { | ||
| 2551 | 2551 | typeList: [ARGUMENT_TYPE.STRING], |
| 2552 | 2552 | enumList: [ |
| 2553 | 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 | 2555 | new SlashCommandEnumValue('kobold', 'KoboldAI Classic', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'kobold')), 'K'), |
| 2555 | 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 | 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 | 2586 | </div> |
| 2586 | 2587 | <div> |
| 2587 | 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 | 2589 | </div> |
| 2589 | 2590 | `, |
| 2590 | 2591 | })); |
| @@ -5159,6 +5160,32 @@ async function setApiUrlCallback({ api = null, connect = 'true', quiet = 'false' | ||
| 5159 | 5160 | return url; |
| 5160 | 5161 | } |
| 5161 | 5162 | |
| 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 | 5189 | // Special handling for Kobold Classic API |
| 5163 | 5190 | const isCurrentlyKoboldClassic = main_api === 'kobold'; |
| 5164 | 5191 | if (api === 'kobold' || (!api && isCurrentlyKoboldClassic)) { |
| @@ -512,3 +512,8 @@ export const MEDIA_EXTENSIONS = [ | ||
| 512 | 512 | 'm4a', |
| 513 | 513 | 'aiff', |
| 514 | 514 | ]; |
| 515 | + | |
| 516 | +export const ZAI_ENDPOINT = { | |
| 517 | + COMMON: 'common', | |
| 518 | + CODING: 'coding', | |
| 519 | +}; | |
| @@ -13,6 +13,7 @@ import { | ||
| 13 | 13 | OPENAI_REASONING_EFFORT_MODELS, |
| 14 | 14 | OPENROUTER_HEADERS, |
| 15 | 15 | VERTEX_SAFETY, |
| 16 | + ZAI_ENDPOINT, | |
| 16 | 17 | } from '../../constants.js'; |
| 17 | 18 | import { |
| 18 | 19 | forwardFetchResponse, |
| @@ -75,7 +76,8 @@ const API_POLLINATIONS = 'https://text.pollinations.ai/openai'; | ||
| 75 | 76 | const API_MOONSHOT = 'https://api.moonshot.ai/v1'; |
| 76 | 77 | const API_FIREWORKS = 'https://api.fireworks.ai/inference/v1'; |
| 77 | 78 | const API_COMETAPI = 'https://api.cometapi.com/v1'; |
| 78 | 79 | const API_ZAIAPI_ZAI_COMMON = 'https://api.z.ai/api/paas/v4'; |
| 80 | +const API_ZAI_CODING = 'https://api.z.ai/api/coding/paas/v4'; | |
| 79 | 81 | const API_SILICONFLOW = 'https://api.siliconflow.com/v1'; |
| 80 | 82 | |
| 81 | 83 | /** |
| @@ -1973,7 +1975,7 @@ router.post('/generate', function (request, response) { | ||
| 1973 | 1975 | }; |
| 1974 | 1976 | throw new Error('This provider is temporarily disabled.'); |
| 1975 | 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 | 1979 | apiKey = readSecret(request.user.directories, SECRET_KEYS.ZAI); |
| 1978 | 1980 | headers = { |
| 1979 | 1981 | 'Accept-Language': 'en-US,en', |