Add generic text completion API type (100% OAI compatible)
| @@ -2187,10 +2187,10 @@ | |||
| 2187 | <div> | 2187 | <div> |
| 2188 | <h4 data-i18n="API Type">API Type</h4> | 2188 | <h4 data-i18n="API Type">API Type</h4> |
| 2189 | <select id="textgen_type"> | 2189 | <select id="textgen_type"> |
| 2190 | <option value="ooba" data-i18n="Default (completions compatible)">Default [OpenAI /completions compatible: oobabooga, LM Studio, etc.]</option> | ||
| 2191 | <option value="aphrodite">Aphrodite</option> | 2190 | <option value="aphrodite">Aphrodite</option> |
| 2192 | <option value="dreamgen">DreamGen</option> | 2191 | <option value="dreamgen">DreamGen</option> |
| 2193 | <option value="featherless">Featherless</option> | 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 | <option value="huggingface">HuggingFace (Inference Endpoint)</option> | 2194 | <option value="huggingface">HuggingFace (Inference Endpoint)</option> |
| 2195 | <option value="infermaticai">InfermaticAI</option> | 2195 | <option value="infermaticai">InfermaticAI</option> |
| 2196 | <option value="koboldcpp">KoboldCpp</option> | 2196 | <option value="koboldcpp">KoboldCpp</option> |
| @@ -2199,6 +2199,7 @@ | |||
| 2199 | <option value="ollama">Ollama</option> | 2199 | <option value="ollama">Ollama</option> |
| 2200 | <option value="openrouter">OpenRouter</option> | 2200 | <option value="openrouter">OpenRouter</option> |
| 2201 | <option value="tabby">TabbyAPI</option> | 2201 | <option value="tabby">TabbyAPI</option> |
| 2202 | <option value="ooba">Text Generation WebUI (oobabooga)</option> | ||
| 2202 | <option value="togetherai">TogetherAI</option> | 2203 | <option value="togetherai">TogetherAI</option> |
| 2203 | <option value="vllm">vLLM</option> | 2204 | <option value="vllm">vLLM</option> |
| 2204 | </select> | 2205 | </select> |
| @@ -2321,6 +2322,24 @@ | |||
| 2321 | </select> | 2322 | </select> |
| 2322 | </div> | 2323 | </div> |
| 2323 | </div> | 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 | <div data-tg-type="ooba" class="flex-container flexFlowColumn"> | 2343 | <div data-tg-type="ooba" class="flex-container flexFlowColumn"> |
| 2325 | <div class="flex-container flexFlowColumn"> | 2344 | <div class="flex-container flexFlowColumn"> |
| 2326 | <a href="https://github.com/oobabooga/text-generation-webui" target="_blank"> | 2345 | <a href="https://github.com/oobabooga/text-generation-webui" target="_blank"> |
| @@ -234,7 +234,7 @@ import { | |||
| 234 | import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; | 234 | import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; |
| 235 | import { hideLoader, showLoader } from './scripts/loader.js'; | 235 | import { hideLoader, showLoader } from './scripts/loader.js'; |
| 236 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; | 236 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; |
| 237 | import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js'; | 237 | import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js'; |
| 238 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js'; | 238 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js'; |
| 239 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; | 239 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; |
| 240 | import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; | 240 | import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; |
| @@ -1221,6 +1221,9 @@ async function getStatusTextgen() { | |||
| 1221 | } else if (textgen_settings.type === textgen_types.TABBY) { | 1221 | } else if (textgen_settings.type === textgen_types.TABBY) { |
| 1222 | loadTabbyModels(data?.data); | 1222 | loadTabbyModels(data?.data); |
| 1223 | setOnlineStatus(textgen_settings.tabby_model || data?.result); | 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 | } else { | 1227 | } else { |
| 1225 | setOnlineStatus(data?.result); | 1228 | setOnlineStatus(data?.result); |
| 1226 | } | 1229 | } |
| @@ -10000,6 +10003,7 @@ jQuery(async function () { | |||
| 10000 | { id: 'api_key_llamacpp', secret: SECRET_KEYS.LLAMACPP }, | 10003 | { id: 'api_key_llamacpp', secret: SECRET_KEYS.LLAMACPP }, |
| 10001 | { id: 'api_key_featherless', secret: SECRET_KEYS.FEATHERLESS }, | 10004 | { id: 'api_key_featherless', secret: SECRET_KEYS.FEATHERLESS }, |
| 10002 | { id: 'api_key_huggingface', secret: SECRET_KEYS.HUGGINGFACE }, | 10005 | { id: 'api_key_huggingface', secret: SECRET_KEYS.HUGGINGFACE }, |
| 10006 | { id: 'api_key_generic', secret: SECRET_KEYS.GENERIC }, | ||
| 10003 | ]; | 10007 | ]; |
| 10004 | 10008 | ||
| 10005 | for (const key of keys) { | 10009 | for (const key of keys) { |
| @@ -585,6 +585,7 @@ class PresetManager { | |||
| 585 | 'openrouter_allow_fallbacks', | 585 | 'openrouter_allow_fallbacks', |
| 586 | 'tabby_model', | 586 | 'tabby_model', |
| 587 | 'derived', | 587 | 'derived', |
| 588 | 'generic_model', | ||
| 588 | ]; | 589 | ]; |
| 589 | const settings = Object.assign({}, getSettingsByApiId(this.apiId)); | 590 | const settings = Object.assign({}, getSettingsByApiId(this.apiId)); |
| 590 | 591 | ||
| @@ -38,6 +38,7 @@ export const SECRET_KEYS = { | |||
| 38 | NANOGPT: 'api_key_nanogpt', | 38 | NANOGPT: 'api_key_nanogpt', |
| 39 | TAVILY: 'api_key_tavily', | 39 | TAVILY: 'api_key_tavily', |
| 40 | BFL: 'api_key_bfl', | 40 | BFL: 'api_key_bfl', |
| 41 | GENERIC: 'api_key_generic', | ||
| 41 | }; | 42 | }; |
| 42 | 43 | ||
| 43 | const INPUT_MAP = { | 44 | const INPUT_MAP = { |
| @@ -71,6 +72,7 @@ const INPUT_MAP = { | |||
| 71 | [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface', | 72 | [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface', |
| 72 | [SECRET_KEYS.BLOCKENTROPY]: '#api_key_blockentropy', | 73 | [SECRET_KEYS.BLOCKENTROPY]: '#api_key_blockentropy', |
| 73 | [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt', | 74 | [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt', |
| 75 | [SECRET_KEYS.GENERIC]: '#api_key_generic', | ||
| 74 | }; | 76 | }; |
| 75 | 77 | ||
| 76 | async function clearSecret() { | 78 | async function clearSecret() { |
| @@ -3687,6 +3687,7 @@ function setBackgroundCallback(_, bg) { | |||
| 3687 | function getModelOptions(quiet) { | 3687 | function getModelOptions(quiet) { |
| 3688 | const nullResult = { control: null, options: null }; | 3688 | const nullResult = { control: null, options: null }; |
| 3689 | const modelSelectMap = [ | 3689 | const modelSelectMap = [ |
| 3690 | { id: 'generic_model_textgenerationwebui', api: 'textgenerationwebui', type: textgen_types.GENERIC }, | ||
| 3690 | { id: 'custom_model_textgenerationwebui', api: 'textgenerationwebui', type: textgen_types.OOBA }, | 3691 | { id: 'custom_model_textgenerationwebui', api: 'textgenerationwebui', type: textgen_types.OOBA }, |
| 3691 | { id: 'model_togetherai_select', api: 'textgenerationwebui', type: textgen_types.TOGETHERAI }, | 3692 | { id: 'model_togetherai_select', api: 'textgenerationwebui', type: textgen_types.TOGETHERAI }, |
| 3692 | { id: 'openrouter_model', api: 'textgenerationwebui', type: textgen_types.OPENROUTER }, | 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 | export async function loadDreamGenModels(data) { | 181 | export async function loadDreamGenModels(data) { |
| 164 | if (!Array.isArray(data)) { | 182 | if (!Array.isArray(data)) { |
| 165 | console.error('Invalid DreamGen models data', data); | 183 | console.error('Invalid DreamGen models data', data); |
| @@ -33,9 +33,11 @@ export const textgen_types = { | |||
| 33 | OPENROUTER: 'openrouter', | 33 | OPENROUTER: 'openrouter', |
| 34 | FEATHERLESS: 'featherless', | 34 | FEATHERLESS: 'featherless', |
| 35 | HUGGINGFACE: 'huggingface', | 35 | HUGGINGFACE: 'huggingface', |
| 36 | GENERIC: 'generic', | ||
| 36 | }; | 37 | }; |
| 37 | 38 | ||
| 38 | const { | 39 | const { |
| 40 | GENERIC, | ||
| 39 | MANCER, | 41 | MANCER, |
| 40 | VLLM, | 42 | VLLM, |
| 41 | APHRODITE, | 43 | APHRODITE, |
| @@ -120,6 +122,7 @@ export const SERVER_INPUTS = { | |||
| 120 | [textgen_types.LLAMACPP]: '#llamacpp_api_url_text', | 122 | [textgen_types.LLAMACPP]: '#llamacpp_api_url_text', |
| 121 | [textgen_types.OLLAMA]: '#ollama_api_url_text', | 123 | [textgen_types.OLLAMA]: '#ollama_api_url_text', |
| 122 | [textgen_types.HUGGINGFACE]: '#huggingface_api_url_text', | 124 | [textgen_types.HUGGINGFACE]: '#huggingface_api_url_text', |
| 125 | [textgen_types.GENERIC]: '#generic_api_url_text', | ||
| 123 | }; | 126 | }; |
| 124 | 127 | ||
| 125 | const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5]; | 128 | const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5]; |
| @@ -205,6 +208,7 @@ const settings = { | |||
| 205 | xtc_probability: 0, | 208 | xtc_probability: 0, |
| 206 | nsigma: 0.0, | 209 | nsigma: 0.0, |
| 207 | featherless_model: '', | 210 | featherless_model: '', |
| 211 | generic_model: '', | ||
| 208 | }; | 212 | }; |
| 209 | 213 | ||
| 210 | export { | 214 | export { |
| @@ -282,6 +286,7 @@ export const setting_names = [ | |||
| 282 | 'xtc_threshold', | 286 | 'xtc_threshold', |
| 283 | 'xtc_probability', | 287 | 'xtc_probability', |
| 284 | 'nsigma', | 288 | 'nsigma', |
| 289 | 'generic_model', | ||
| 285 | ]; | 290 | ]; |
| 286 | 291 | ||
| 287 | const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba'); | 292 | const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba'); |
| @@ -1100,6 +1105,11 @@ export function getTextGenModel() { | |||
| 1100 | return settings.custom_model; | 1105 | return settings.custom_model; |
| 1101 | } | 1106 | } |
| 1102 | break; | 1107 | break; |
| 1108 | case GENERIC: | ||
| 1109 | if (settings.generic_model) { | ||
| 1110 | return settings.generic_model; | ||
| 1111 | } | ||
| 1112 | break; | ||
| 1103 | case MANCER: | 1113 | case MANCER: |
| 1104 | return settings.mancer_model; | 1114 | return settings.mancer_model; |
| 1105 | case TOGETHERAI: | 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 | export function getOverrideHeaders(urlHost) { | 188 | export function getOverrideHeaders(urlHost) { |
| 176 | const requestOverrides = getConfigValue('requestOverrides', []); | 189 | const requestOverrides = getConfigValue('requestOverrides', []); |
| 177 | const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers; | 190 | const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers; |
| @@ -214,6 +227,7 @@ export function setAdditionalHeadersByType(requestHeaders, type, server, directo | |||
| 214 | [TEXTGEN_TYPES.LLAMACPP]: getLlamaCppHeaders, | 227 | [TEXTGEN_TYPES.LLAMACPP]: getLlamaCppHeaders, |
| 215 | [TEXTGEN_TYPES.FEATHERLESS]: getFeatherlessHeaders, | 228 | [TEXTGEN_TYPES.FEATHERLESS]: getFeatherlessHeaders, |
| 216 | [TEXTGEN_TYPES.HUGGINGFACE]: getHuggingFaceHeaders, | 229 | [TEXTGEN_TYPES.HUGGINGFACE]: getHuggingFaceHeaders, |
| 230 | [TEXTGEN_TYPES.GENERIC]: getGenericHeaders, | ||
| 217 | }; | 231 | }; |
| 218 | 232 | ||
| 219 | const getHeaders = headerGetters[type]; | 233 | const getHeaders = headerGetters[type]; |
| @@ -225,6 +225,7 @@ export const TEXTGEN_TYPES = { | |||
| 225 | OPENROUTER: 'openrouter', | 225 | OPENROUTER: 'openrouter', |
| 226 | FEATHERLESS: 'featherless', | 226 | FEATHERLESS: 'featherless', |
| 227 | HUGGINGFACE: 'huggingface', | 227 | HUGGINGFACE: 'huggingface', |
| 228 | GENERIC: 'generic', | ||
| 228 | }; | 229 | }; |
| 229 | 230 | ||
| 230 | export const INFERMATICAI_KEYS = [ | 231 | export const INFERMATICAI_KEYS = [ |
| @@ -346,6 +347,24 @@ export const OLLAMA_KEYS = [ | |||
| 346 | 'min_p', | 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 | export const AVATAR_WIDTH = 512; | 368 | export const AVATAR_WIDTH = 512; |
| 350 | export const AVATAR_HEIGHT = 768; | 369 | export const AVATAR_HEIGHT = 768; |
| 351 | 370 | ||
| @@ -13,6 +13,7 @@ import { | |||
| 13 | VLLM_KEYS, | 13 | VLLM_KEYS, |
| 14 | DREAMGEN_KEYS, | 14 | DREAMGEN_KEYS, |
| 15 | FEATHERLESS_KEYS, | 15 | FEATHERLESS_KEYS, |
| 16 | OPENAI_KEYS, | ||
| 16 | } from '../../constants.js'; | 17 | } from '../../constants.js'; |
| 17 | import { forwardFetchResponse, trimV1, getConfigValue } from '../../util.js'; | 18 | import { forwardFetchResponse, trimV1, getConfigValue } from '../../util.js'; |
| 18 | import { setAdditionalHeaders } from '../../additional-headers.js'; | 19 | import { setAdditionalHeaders } from '../../additional-headers.js'; |
| @@ -113,8 +114,8 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 113 | let url = baseUrl; | 114 | let url = baseUrl; |
| 114 | let result = ''; | 115 | let result = ''; |
| 115 | 116 | ||
| 116 | |||
| 117 | switch (apiType) { | 117 | switch (apiType) { |
| 118 | case TEXTGEN_TYPES.GENERIC: | ||
| 118 | case TEXTGEN_TYPES.OOBA: | 119 | case TEXTGEN_TYPES.OOBA: |
| 119 | case TEXTGEN_TYPES.VLLM: | 120 | case TEXTGEN_TYPES.VLLM: |
| 120 | case TEXTGEN_TYPES.APHRODITE: | 121 | case TEXTGEN_TYPES.APHRODITE: |
| @@ -287,6 +288,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 287 | let url = trimV1(baseUrl); | 288 | let url = trimV1(baseUrl); |
| 288 | 289 | ||
| 289 | switch (request.body.api_type) { | 290 | switch (request.body.api_type) { |
| 291 | case TEXTGEN_TYPES.GENERIC: | ||
| 290 | case TEXTGEN_TYPES.VLLM: | 292 | case TEXTGEN_TYPES.VLLM: |
| 291 | case TEXTGEN_TYPES.FEATHERLESS: | 293 | case TEXTGEN_TYPES.FEATHERLESS: |
| 292 | case TEXTGEN_TYPES.APHRODITE: | 294 | case TEXTGEN_TYPES.APHRODITE: |
| @@ -347,6 +349,12 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 347 | args.body = JSON.stringify(request.body); | 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 | if (request.body.api_type === TEXTGEN_TYPES.OPENROUTER) { | 358 | if (request.body.api_type === TEXTGEN_TYPES.OPENROUTER) { |
| 351 | if (Array.isArray(request.body.provider) && request.body.provider.length > 0) { | 359 | if (Array.isArray(request.body.provider) && request.body.provider.length > 0) { |
| 352 | request.body.provider = { | 360 | request.body.provider = { |
| @@ -50,6 +50,7 @@ export const SECRET_KEYS = { | |||
| 50 | TAVILY: 'api_key_tavily', | 50 | TAVILY: 'api_key_tavily', |
| 51 | NANOGPT: 'api_key_nanogpt', | 51 | NANOGPT: 'api_key_nanogpt', |
| 52 | BFL: 'api_key_bfl', | 52 | BFL: 'api_key_bfl', |
| 53 | GENERIC: 'api_key_generic', | ||
| 53 | }; | 54 | }; |
| 54 | 55 | ||
| 55 | // These are the keys that are safe to expose, even if allowKeysExposure is false | 56 | // These are the keys that are safe to expose, even if allowKeysExposure is false |