Add generic text completion API type (100% OAI compatible)
| @@ -2187,10 +2187,10 @@ | ||
| 2187 | 2187 | <div> |
| 2188 | 2188 | <h4 data-i18n="API Type">API Type</h4> |
| 2189 | 2189 | <select id="textgen_type"> |
| 2190 | - <option value="ooba" data-i18n="Default (completions compatible)">Default [OpenAI /completions compatible: oobabooga, LM Studio, etc.]</option> | |
| 2191 | 2190 | <option value="aphrodite">Aphrodite</option> |
| 2192 | 2191 | <option value="dreamgen">DreamGen</option> |
| 2193 | 2192 | <option value="featherless">Featherless</option> |
| 2193 | + <option value="generic" data-i18n="Generic (OpenAI-compatible) [LM Studio, etc.]">Generic (OpenAI-compatible) [LM Studio, etc.]</option> | |
| 2194 | 2194 | <option value="huggingface">HuggingFace (Inference Endpoint)</option> |
| 2195 | 2195 | <option value="infermaticai">InfermaticAI</option> |
| 2196 | 2196 | <option value="koboldcpp">KoboldCpp</option> |
| @@ -2199,6 +2199,7 @@ | ||
| 2199 | 2199 | <option value="ollama">Ollama</option> |
| 2200 | 2200 | <option value="openrouter">OpenRouter</option> |
| 2201 | 2201 | <option value="tabby">TabbyAPI</option> |
| 2202 | + <option value="ooba">Text Generation WebUI (oobabooga)</option> | |
| 2202 | 2203 | <option value="togetherai">TogetherAI</option> |
| 2203 | 2204 | <option value="vllm">vLLM</option> |
| 2204 | 2205 | </select> |
| @@ -2321,6 +2322,24 @@ | ||
| 2321 | 2322 | </select> |
| 2322 | 2323 | </div> |
| 2323 | 2324 | </div> |
| 2325 | + <div data-tg-type="generic" class="flex-container flexFlowColumn"> | |
| 2326 | + <h4 data-i18n="API key (optional)">API key (optional)</h4> | |
| 2327 | + <div class="flex-container"> | |
| 2328 | + <input id="api_key_generic" name="api_key_generic" class="text_pole flex1 wide100p" type="text" autocomplete="off"> | |
| 2329 | + <div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_generic"> | |
| 2330 | + </div> | |
| 2331 | + </div> | |
| 2332 | + <div data-for="api_key_generic" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page."> | |
| 2333 | + For privacy reasons, your API key will be hidden after you reload the page. | |
| 2334 | + </div> | |
| 2335 | + <div class="flex1"> | |
| 2336 | + <h4 data-i18n="Server url">Server URL</h4> | |
| 2337 | + <small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small> | |
| 2338 | + <input id="generic_api_url_text" name="generic_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="generic"> | |
| 2339 | + </div> | |
| 2340 | + <datalist id="generic_model_fill"></datalist> | |
| 2341 | + <input id="generic_model_textgenerationwebui" list="generic_model_fill" class="text_pole wide100p" placeholder="Model ID (optional)" data-i18n="[placeholder]Model ID (optional)" type="text"> | |
| 2342 | + </div> | |
| 2324 | 2343 | <div data-tg-type="ooba" class="flex-container flexFlowColumn"> |
| 2325 | 2344 | <div class="flex-container flexFlowColumn"> |
| 2326 | 2345 | <a href="https://github.com/oobabooga/text-generation-webui" target="_blank"> |
| @@ -234,7 +234,7 @@ import { | ||
| 234 | 234 | import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; |
| 235 | 235 | import { hideLoader, showLoader } from './scripts/loader.js'; |
| 236 | 236 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; |
| 237 | 237 | import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js'; |
| 238 | 238 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js'; |
| 239 | 239 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; |
| 240 | 240 | import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; |
| @@ -1221,6 +1221,9 @@ async function getStatusTextgen() { | ||
| 1221 | 1221 | } else if (textgen_settings.type === textgen_types.TABBY) { |
| 1222 | 1222 | loadTabbyModels(data?.data); |
| 1223 | 1223 | setOnlineStatus(textgen_settings.tabby_model || data?.result); |
| 1224 | + } else if (textgen_settings.type === textgen_types.GENERIC) { | |
| 1225 | + loadGenericModels(data?.data); | |
| 1226 | + setOnlineStatus(textgen_settings.generic_model || 'Connected'); | |
| 1224 | 1227 | } else { |
| 1225 | 1228 | setOnlineStatus(data?.result); |
| 1226 | 1229 | } |
| @@ -10000,6 +10003,7 @@ jQuery(async function () { | ||
| 10000 | 10003 | { id: 'api_key_llamacpp', secret: SECRET_KEYS.LLAMACPP }, |
| 10001 | 10004 | { id: 'api_key_featherless', secret: SECRET_KEYS.FEATHERLESS }, |
| 10002 | 10005 | { id: 'api_key_huggingface', secret: SECRET_KEYS.HUGGINGFACE }, |
| 10006 | + { id: 'api_key_generic', secret: SECRET_KEYS.GENERIC }, | |
| 10003 | 10007 | ]; |
| 10004 | 10008 | |
| 10005 | 10009 | for (const key of keys) { |
| @@ -585,6 +585,7 @@ class PresetManager { | ||
| 585 | 585 | 'openrouter_allow_fallbacks', |
| 586 | 586 | 'tabby_model', |
| 587 | 587 | 'derived', |
| 588 | + 'generic_model', | |
| 588 | 589 | ]; |
| 589 | 590 | const settings = Object.assign({}, getSettingsByApiId(this.apiId)); |
| 590 | 591 | |
| @@ -38,6 +38,7 @@ export const SECRET_KEYS = { | ||
| 38 | 38 | NANOGPT: 'api_key_nanogpt', |
| 39 | 39 | TAVILY: 'api_key_tavily', |
| 40 | 40 | BFL: 'api_key_bfl', |
| 41 | + GENERIC: 'api_key_generic', | |
| 41 | 42 | }; |
| 42 | 43 | |
| 43 | 44 | const INPUT_MAP = { |
| @@ -71,6 +72,7 @@ const INPUT_MAP = { | ||
| 71 | 72 | [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface', |
| 72 | 73 | [SECRET_KEYS.BLOCKENTROPY]: '#api_key_blockentropy', |
| 73 | 74 | [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt', |
| 75 | + [SECRET_KEYS.GENERIC]: '#api_key_generic', | |
| 74 | 76 | }; |
| 75 | 77 | |
| 76 | 78 | async function clearSecret() { |
| @@ -3687,6 +3687,7 @@ function setBackgroundCallback(_, bg) { | ||
| 3687 | 3687 | function getModelOptions(quiet) { |
| 3688 | 3688 | const nullResult = { control: null, options: null }; |
| 3689 | 3689 | const modelSelectMap = [ |
| 3690 | + { id: 'generic_model_textgenerationwebui', api: 'textgenerationwebui', type: textgen_types.GENERIC }, | |
| 3690 | 3691 | { id: 'custom_model_textgenerationwebui', api: 'textgenerationwebui', type: textgen_types.OOBA }, |
| 3691 | 3692 | { id: 'model_togetherai_select', api: 'textgenerationwebui', type: textgen_types.TOGETHERAI }, |
| 3692 | 3693 | { id: 'openrouter_model', api: 'textgenerationwebui', type: textgen_types.OPENROUTER }, |
| @@ -160,6 +160,24 @@ export async function loadInfermaticAIModels(data) { | ||
| 160 | 160 | } |
| 161 | 161 | } |
| 162 | 162 | |
| 163 | +export function loadGenericModels(data) { | |
| 164 | + if (!Array.isArray(data)) { | |
| 165 | + console.error('Invalid Generic models data', data); | |
| 166 | + return; | |
| 167 | + } | |
| 168 | + | |
| 169 | + data.sort((a, b) => a.id.localeCompare(b.id)); | |
| 170 | + const dataList = $('#generic_model_fill'); | |
| 171 | + dataList.empty(); | |
| 172 | + | |
| 173 | + for (const model of data) { | |
| 174 | + const option = document.createElement('option'); | |
| 175 | + option.value = model.id; | |
| 176 | + option.text = model.id; | |
| 177 | + dataList.append(option); | |
| 178 | + } | |
| 179 | +} | |
| 180 | + | |
| 163 | 181 | export async function loadDreamGenModels(data) { |
| 164 | 182 | if (!Array.isArray(data)) { |
| 165 | 183 | console.error('Invalid DreamGen models data', data); |
| @@ -33,9 +33,11 @@ export const textgen_types = { | ||
| 33 | 33 | OPENROUTER: 'openrouter', |
| 34 | 34 | FEATHERLESS: 'featherless', |
| 35 | 35 | HUGGINGFACE: 'huggingface', |
| 36 | + GENERIC: 'generic', | |
| 36 | 37 | }; |
| 37 | 38 | |
| 38 | 39 | const { |
| 40 | + GENERIC, | |
| 39 | 41 | MANCER, |
| 40 | 42 | VLLM, |
| 41 | 43 | APHRODITE, |
| @@ -120,6 +122,7 @@ export const SERVER_INPUTS = { | ||
| 120 | 122 | [textgen_types.LLAMACPP]: '#llamacpp_api_url_text', |
| 121 | 123 | [textgen_types.OLLAMA]: '#ollama_api_url_text', |
| 122 | 124 | [textgen_types.HUGGINGFACE]: '#huggingface_api_url_text', |
| 125 | + [textgen_types.GENERIC]: '#generic_api_url_text', | |
| 123 | 126 | }; |
| 124 | 127 | |
| 125 | 128 | const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5]; |
| @@ -205,6 +208,7 @@ const settings = { | ||
| 205 | 208 | xtc_probability: 0, |
| 206 | 209 | nsigma: 0.0, |
| 207 | 210 | featherless_model: '', |
| 211 | + generic_model: '', | |
| 208 | 212 | }; |
| 209 | 213 | |
| 210 | 214 | export { |
| @@ -282,6 +286,7 @@ export const setting_names = [ | ||
| 282 | 286 | 'xtc_threshold', |
| 283 | 287 | 'xtc_probability', |
| 284 | 288 | 'nsigma', |
| 289 | + 'generic_model', | |
| 285 | 290 | ]; |
| 286 | 291 | |
| 287 | 292 | const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba'); |
| @@ -1100,6 +1105,11 @@ export function getTextGenModel() { | ||
| 1100 | 1105 | return settings.custom_model; |
| 1101 | 1106 | } |
| 1102 | 1107 | break; |
| 1108 | + case GENERIC: | |
| 1109 | + if (settings.generic_model) { | |
| 1110 | + return settings.generic_model; | |
| 1111 | + } | |
| 1112 | + break; | |
| 1103 | 1113 | case MANCER: |
| 1104 | 1114 | return settings.mancer_model; |
| 1105 | 1115 | case TOGETHERAI: |
| @@ -172,6 +172,19 @@ function getHuggingFaceHeaders(directories) { | ||
| 172 | 172 | }) : {}; |
| 173 | 173 | } |
| 174 | 174 | |
| 175 | +/** | |
| 176 | + * Gets the headers for the Generic text completion API. | |
| 177 | + * @param {import('./users.js').UserDirectoryList} directories | |
| 178 | + * @returns {object} Headers for the request | |
| 179 | + */ | |
| 180 | +function getGenericHeaders(directories) { | |
| 181 | + const apiKey = readSecret(directories, SECRET_KEYS.GENERIC); | |
| 182 | + | |
| 183 | + return apiKey ? ({ | |
| 184 | + 'Authorization': `Bearer ${apiKey}`, | |
| 185 | + }) : {}; | |
| 186 | +} | |
| 187 | + | |
| 175 | 188 | export function getOverrideHeaders(urlHost) { |
| 176 | 189 | const requestOverrides = getConfigValue('requestOverrides', []); |
| 177 | 190 | const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers; |
| @@ -214,6 +227,7 @@ export function setAdditionalHeadersByType(requestHeaders, type, server, directo | ||
| 214 | 227 | [TEXTGEN_TYPES.LLAMACPP]: getLlamaCppHeaders, |
| 215 | 228 | [TEXTGEN_TYPES.FEATHERLESS]: getFeatherlessHeaders, |
| 216 | 229 | [TEXTGEN_TYPES.HUGGINGFACE]: getHuggingFaceHeaders, |
| 230 | + [TEXTGEN_TYPES.GENERIC]: getGenericHeaders, | |
| 217 | 231 | }; |
| 218 | 232 | |
| 219 | 233 | const getHeaders = headerGetters[type]; |
| @@ -225,6 +225,7 @@ export const TEXTGEN_TYPES = { | ||
| 225 | 225 | OPENROUTER: 'openrouter', |
| 226 | 226 | FEATHERLESS: 'featherless', |
| 227 | 227 | HUGGINGFACE: 'huggingface', |
| 228 | + GENERIC: 'generic', | |
| 228 | 229 | }; |
| 229 | 230 | |
| 230 | 231 | export const INFERMATICAI_KEYS = [ |
| @@ -346,6 +347,24 @@ export const OLLAMA_KEYS = [ | ||
| 346 | 347 | 'min_p', |
| 347 | 348 | ]; |
| 348 | 349 | |
| 350 | +// https://platform.openai.com/docs/api-reference/completions | |
| 351 | +export const OPENAI_KEYS = [ | |
| 352 | + 'model', | |
| 353 | + 'prompt', | |
| 354 | + 'stream', | |
| 355 | + 'temperature', | |
| 356 | + 'top_p', | |
| 357 | + 'frequency_penalty', | |
| 358 | + 'presence_penalty', | |
| 359 | + 'stop', | |
| 360 | + 'seed', | |
| 361 | + 'logit_bias', | |
| 362 | + 'logprobs', | |
| 363 | + 'max_tokens', | |
| 364 | + 'n', | |
| 365 | + 'best_of', | |
| 366 | +]; | |
| 367 | + | |
| 349 | 368 | export const AVATAR_WIDTH = 512; |
| 350 | 369 | export const AVATAR_HEIGHT = 768; |
| 351 | 370 | |
| @@ -13,6 +13,7 @@ import { | ||
| 13 | 13 | VLLM_KEYS, |
| 14 | 14 | DREAMGEN_KEYS, |
| 15 | 15 | FEATHERLESS_KEYS, |
| 16 | + OPENAI_KEYS, | |
| 16 | 17 | } from '../../constants.js'; |
| 17 | 18 | import { forwardFetchResponse, trimV1, getConfigValue } from '../../util.js'; |
| 18 | 19 | import { setAdditionalHeaders } from '../../additional-headers.js'; |
| @@ -113,8 +114,8 @@ router.post('/status', jsonParser, async function (request, response) { | ||
| 113 | 114 | let url = baseUrl; |
| 114 | 115 | let result = ''; |
| 115 | 116 | |
| 116 | - | |
| 117 | 117 | switch (apiType) { |
| 118 | + case TEXTGEN_TYPES.GENERIC: | |
| 118 | 119 | case TEXTGEN_TYPES.OOBA: |
| 119 | 120 | case TEXTGEN_TYPES.VLLM: |
| 120 | 121 | case TEXTGEN_TYPES.APHRODITE: |
| @@ -287,6 +288,7 @@ router.post('/generate', jsonParser, async function (request, response) { | ||
| 287 | 288 | let url = trimV1(baseUrl); |
| 288 | 289 | |
| 289 | 290 | switch (request.body.api_type) { |
| 291 | + case TEXTGEN_TYPES.GENERIC: | |
| 290 | 292 | case TEXTGEN_TYPES.VLLM: |
| 291 | 293 | case TEXTGEN_TYPES.FEATHERLESS: |
| 292 | 294 | case TEXTGEN_TYPES.APHRODITE: |
| @@ -347,6 +349,12 @@ router.post('/generate', jsonParser, async function (request, response) { | ||
| 347 | 349 | args.body = JSON.stringify(request.body); |
| 348 | 350 | } |
| 349 | 351 | |
| 352 | + if (request.body.api_type === TEXTGEN_TYPES.GENERIC) { | |
| 353 | + request.body = _.pickBy(request.body, (_, key) => OPENAI_KEYS.includes(key)); | |
| 354 | + if (Array.isArray(request.body.stop)) { request.body.stop = request.body.stop.slice(0, 4); } | |
| 355 | + args.body = JSON.stringify(request.body); | |
| 356 | + } | |
| 357 | + | |
| 350 | 358 | if (request.body.api_type === TEXTGEN_TYPES.OPENROUTER) { |
| 351 | 359 | if (Array.isArray(request.body.provider) && request.body.provider.length > 0) { |
| 352 | 360 | request.body.provider = { |
| @@ -50,6 +50,7 @@ export const SECRET_KEYS = { | ||
| 50 | 50 | TAVILY: 'api_key_tavily', |
| 51 | 51 | NANOGPT: 'api_key_nanogpt', |
| 52 | 52 | BFL: 'api_key_bfl', |
| 53 | + GENERIC: 'api_key_generic', | |
| 53 | 54 | }; |
| 54 | 55 | |
| 55 | 56 | // These are the keys that are safe to expose, even if allowKeysExposure is false |