Merge branch 'staging' into whitelist-hosts
| @@ -2867,6 +2867,10 @@ | |||
| 2867 | <option value="o3-mini">o3-mini</option> | 2867 | <option value="o3-mini">o3-mini</option> |
| 2868 | <option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option> | 2868 | <option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option> |
| 2869 | </optgroup> | 2869 | </optgroup> |
| 2870 | <optgroup label="GPT-4.5"> | ||
| 2871 | <option value="gpt-4.5-preview">gpt-4.5-preview</option> | ||
| 2872 | <option value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option> | ||
| 2873 | </optgroup> | ||
| 2870 | <optgroup label="GPT-4 Turbo and GPT-4"> | 2874 | <optgroup label="GPT-4 Turbo and GPT-4"> |
| 2871 | <option value="gpt-4-turbo">gpt-4-turbo</option> | 2875 | <option value="gpt-4-turbo">gpt-4-turbo</option> |
| 2872 | <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option> | 2876 | <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option> |
| @@ -47,6 +47,8 @@ | |||
| 47 | <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option> | 47 | <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option> |
| 48 | <option data-type="openai" value="o1">o1</option> | 48 | <option data-type="openai" value="o1">o1</option> |
| 49 | <option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option> | 49 | <option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option> |
| 50 | <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option> | ||
| 51 | <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option> | ||
| 50 | <option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option> | 52 | <option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option> |
| 51 | <option data-type="anthropic" value="claude-3-7-sonnet-20250219">claude-3-7-sonnet-20250219</option> | 53 | <option data-type="anthropic" value="claude-3-7-sonnet-20250219">claude-3-7-sonnet-20250219</option> |
| 52 | <option data-type="anthropic" value="claude-3-5-sonnet-latest">claude-3-5-sonnet-latest</option> | 54 | <option data-type="anthropic" value="claude-3-5-sonnet-latest">claude-3-5-sonnet-latest</option> |
| @@ -9,6 +9,9 @@ var langs; | |||
| 9 | // eslint-disable-next-line prefer-const | 9 | // eslint-disable-next-line prefer-const |
| 10 | var localeData; | 10 | var localeData; |
| 11 | 11 | ||
| 12 | /** @type {Set<string>|null} Array of translations keys if they should be tracked - if not tracked then null */ | ||
| 13 | let trackMissingDynamicTranslate = null; | ||
| 14 | |||
| 12 | export const getCurrentLocale = () => localeFile; | 15 | export const getCurrentLocale = () => localeFile; |
| 13 | 16 | ||
| 14 | /** | 17 | /** |
| @@ -97,6 +100,13 @@ export function t(strings, ...values) { | |||
| 97 | */ | 100 | */ |
| 98 | export function translate(text, key = null) { | 101 | export function translate(text, key = null) { |
| 99 | const translationKey = key || text; | 102 | const translationKey = key || text; |
| 103 | if (translationKey === null || translationKey === undefined) { | ||
| 104 | console.trace('WARN: No translation key provided'); | ||
| 105 | return ''; | ||
| 106 | } | ||
| 107 | if (trackMissingDynamicTranslate && localeData && !Object.hasOwn(localeData, translationKey)) { | ||
| 108 | trackMissingDynamicTranslate.add(translationKey); | ||
| 109 | } | ||
| 100 | return localeData?.[translationKey] || text; | 110 | return localeData?.[translationKey] || text; |
| 101 | } | 111 | } |
| 102 | 112 | ||
| @@ -153,13 +163,26 @@ function translateElement(element) { | |||
| 153 | } | 163 | } |
| 154 | } | 164 | } |
| 155 | 165 | ||
| 166 | /** | ||
| 167 | * Checks if the given locale is supported and not English. | ||
| 168 | * @param {string} [locale=null] The locale to check (defaults to the current locale) | ||
| 169 | * @returns {boolean} True if the locale is not English and supported | ||
| 170 | */ | ||
| 171 | function isSupportedNonEnglish(locale = null) { | ||
| 172 | const lang = locale || localeFile; | ||
| 173 | return lang && lang != 'en' && findLang(lang); | ||
| 174 | } | ||
| 156 | 175 | ||
| 157 | async function getMissingTranslations() { | 176 | async function getMissingTranslations() { |
| 177 | /** @type {Array<{key: string, language: string, value: string}>} */ | ||
| 158 | const missingData = []; | 178 | const missingData = []; |
| 159 | 179 | ||
| 180 | if (trackMissingDynamicTranslate) { | ||
| 181 | missingData.push(...Array.from(trackMissingDynamicTranslate).map(key => ({ key, language: localeFile, value: key }))); | ||
| 182 | } | ||
| 183 | |||
| 160 | // Determine locales to search for untranslated strings | 184 | // Determine locales to search for untranslated strings |
| 161 | const isNotSupported = !findLang(localeFile); | 185 | const langsToProcess = isSupportedNonEnglish() ? [findLang(localeFile)] : langs; |
| 162 | const langsToProcess = (isNotSupported || localeFile == 'en') ? langs : [findLang(localeFile)]; | ||
| 163 | 186 | ||
| 164 | for (const language of langsToProcess) { | 187 | for (const language of langsToProcess) { |
| 165 | const localeData = await getLocaleData(language.lang); | 188 | const localeData = await getLocaleData(language.lang); |
| @@ -170,7 +193,7 @@ async function getMissingTranslations() { | |||
| 170 | if (attributeMatch) { // attribute-tagged key | 193 | if (attributeMatch) { // attribute-tagged key |
| 171 | const localizedValue = localeData?.[attributeMatch[2]]; | 194 | const localizedValue = localeData?.[attributeMatch[2]]; |
| 172 | if (!localizedValue) { | 195 | if (!localizedValue) { |
| 173 | missingData.push({ key, language: language.lang, value: $(this).attr(attributeMatch[1]) }); | 196 | missingData.push({ key, language: language.lang, value: String($(this).attr(attributeMatch[1])) }); |
| 174 | } | 197 | } |
| 175 | } else { // No attribute tag, treat as 'text' | 198 | } else { // No attribute tag, treat as 'text' |
| 176 | const localizedValue = localeData?.[key]; | 199 | const localizedValue = localeData?.[key]; |
| @@ -194,17 +217,19 @@ async function getMissingTranslations() { | |||
| 194 | uniqueMissingData.sort((a, b) => a.language.localeCompare(b.language) || a.key.localeCompare(b.key)); | 217 | uniqueMissingData.sort((a, b) => a.language.localeCompare(b.language) || a.key.localeCompare(b.key)); |
| 195 | 218 | ||
| 196 | // Map to { language: { key: value } } | 219 | // Map to { language: { key: value } } |
| 197 | let missingDataMap = {}; | 220 | const missingDataMap = Object.fromEntries(uniqueMissingData.map(({ key, value }) => [key, value])); |
| 198 | for (const { key, value } of uniqueMissingData) { | ||
| 199 | if (!missingDataMap) { | ||
| 200 | missingDataMap = {}; | ||
| 201 | } | ||
| 202 | missingDataMap[key] = value; | ||
| 203 | } | ||
| 204 | 221 | ||
| 222 | console.log(`Missing Translations (${uniqueMissingData.length}):`); | ||
| 205 | console.table(uniqueMissingData); | 223 | console.table(uniqueMissingData); |
| 224 | console.log(`Full map of missing data (${Object.keys(missingDataMap).length}):`); | ||
| 206 | console.log(missingDataMap); | 225 | console.log(missingDataMap); |
| 207 | 226 | ||
| 227 | if (trackMissingDynamicTranslate) { | ||
| 228 | const trackMissingDynamicTranslateMap = Object.fromEntries(Array.from(trackMissingDynamicTranslate).map(key => [key, key])); | ||
| 229 | console.log(`Dynamic translations missing (${Object.keys(trackMissingDynamicTranslateMap).length}):`); | ||
| 230 | console.log(trackMissingDynamicTranslateMap); | ||
| 231 | } | ||
| 232 | |||
| 208 | toastr.success(`Found ${uniqueMissingData.length} missing translations. See browser console for details.`); | 233 | toastr.success(`Found ${uniqueMissingData.length} missing translations. See browser console for details.`); |
| 209 | } | 234 | } |
| 210 | 235 | ||
| @@ -266,6 +291,31 @@ export async function initLocales() { | |||
| 266 | attributeFilter: ['data-i18n'], | 291 | attributeFilter: ['data-i18n'], |
| 267 | }); | 292 | }); |
| 268 | 293 | ||
| 269 | registerDebugFunction('getMissingTranslations', 'Get missing translations', 'Detects missing localization data in the current locale and dumps the data into the browser console. If the current locale is English, searches all other locales.', getMissingTranslations); | 294 | if (localStorage.getItem('trackDynamicTranslate') === 'true' && isSupportedNonEnglish()) { |
| 295 | trackMissingDynamicTranslate = new Set(); | ||
| 296 | } | ||
| 297 | |||
| 298 | registerDebugFunction('getMissingTranslations', 'Get missing translations', | ||
| 299 | 'Detects missing localization data in the current locale and dumps the data into the browser console. ' + | ||
| 300 | 'If the current locale is English, searches all other locales.', | ||
| 301 | getMissingTranslations); | ||
| 302 | registerDebugFunction('trackDynamicTranslate', 'Track dynamic translation', | ||
| 303 | 'Toggles tracking of dynamic translations, which will be dumped into the missing translations translations too. ' + | ||
| 304 | 'This includes things translated via the t`...` function and translate(). It will only track strings translated <b>after</b> this is toggled on, ' | ||
| 305 | + 'and when they actually pop up, so refreshing the page and opening popups, etc, is needed. Will only track if the current locale is not English.', | ||
| 306 | () => { | ||
| 307 | const isTracking = localStorage.getItem('trackDynamicTranslate') !== 'true'; | ||
| 308 | localStorage.setItem('trackDynamicTranslate', isTracking ? 'true' : 'false'); | ||
| 309 | if (isTracking && isSupportedNonEnglish()) { | ||
| 310 | trackMissingDynamicTranslate = new Set(); | ||
| 311 | toastr.success('Dynamic translation tracking enabled.'); | ||
| 312 | } else if (isTracking) { | ||
| 313 | trackMissingDynamicTranslate = null; | ||
| 314 | toastr.warning('Dynamic translation tracking enabled, but will not be tracked with locale English.'); | ||
| 315 | } else { | ||
| 316 | trackMissingDynamicTranslate = null; | ||
| 317 | toastr.info('Dynamic translation tracking disabled.'); | ||
| 318 | } | ||
| 319 | }); | ||
| 270 | registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale); | 320 | registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale); |
| 271 | } | 321 | } |
| @@ -1945,7 +1945,7 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 1945 | } | 1945 | } |
| 1946 | 1946 | ||
| 1947 | // Remove logit bias, logprobs and stop strings if it's not supported by the model | 1947 | // Remove logit bias, logprobs and stop strings if it's not supported by the model |
| 1948 | if (isOAI && oai_settings.openai_model.includes('vision') || isOpenRouter && oai_settings.openrouter_model.includes('vision')) { | 1948 | if (isOAI && oai_settings.openai_model.includes('vision') || isOpenRouter && oai_settings.openrouter_model.includes('vision') || isOAI && oai_settings.openai_model.includes('gpt-4.5-preview')) { |
| 1949 | delete generate_data.logit_bias; | 1949 | delete generate_data.logit_bias; |
| 1950 | delete generate_data.stop; | 1950 | delete generate_data.stop; |
| 1951 | delete generate_data.logprobs; | 1951 | delete generate_data.logprobs; |
| @@ -4997,6 +4997,8 @@ export function isImageInliningSupported() { | |||
| 4997 | 'gpt-4-turbo', | 4997 | 'gpt-4-turbo', |
| 4998 | 'gpt-4o', | 4998 | 'gpt-4o', |
| 4999 | 'gpt-4o-mini', | 4999 | 'gpt-4o-mini', |
| 5000 | 'gpt-4.5-preview', | ||
| 5001 | 'gpt-4.5-preview-2025-02-27', | ||
| 5000 | 'o1', | 5002 | 'o1', |
| 5001 | 'o1-2024-12-17', | 5003 | 'o1-2024-12-17', |
| 5002 | 'chatgpt-4o-latest', | 5004 | 'chatgpt-4o-latest', |
| @@ -108,6 +108,7 @@ export function isHiddenReasoningModel() { | |||
| 108 | 108 | ||
| 109 | /** @type {{ name: string; func: MatchingFunc; }[]} */ | 109 | /** @type {{ name: string; func: MatchingFunc; }[]} */ |
| 110 | const hiddenReasoningModels = [ | 110 | const hiddenReasoningModels = [ |
| 111 | { name: 'gpt-4.5', func: FUNCS.startsWith }, | ||
| 111 | { name: 'o1', func: FUNCS.startsWith }, | 112 | { name: 'o1', func: FUNCS.startsWith }, |
| 112 | { name: 'o3', func: FUNCS.startsWith }, | 113 | { name: 'o3', func: FUNCS.startsWith }, |
| 113 | { name: 'gemini-2.0-flash-thinking-exp', func: FUNCS.startsWith }, | 114 | { name: 'gemini-2.0-flash-thinking-exp', func: FUNCS.startsWith }, |
| @@ -493,7 +494,7 @@ export class ReasoningHandler { | |||
| 493 | data = null; | 494 | data = null; |
| 494 | } | 495 | } |
| 495 | 496 | ||
| 496 | if (this.type !== ReasoningType.Model) { | 497 | if (this.type && this.type !== ReasoningType.Model) { |
| 497 | title += ` [${translate(this.type)}]`; | 498 | title += ` [${translate(this.type)}]`; |
| 498 | title = title.trim(); | 499 | title = title.trim(); |
| 499 | } | 500 | } |
| @@ -410,6 +410,10 @@ export function getTokenizerModel(requestModel) { | |||
| 410 | return 'gpt-4o'; | 410 | return 'gpt-4o'; |
| 411 | } | 411 | } |
| 412 | 412 | ||
| 413 | if (requestModel.includes('gpt-4.5-preview')) { | ||
| 414 | return 'gpt-4o'; | ||
| 415 | } | ||
| 416 | |||
| 413 | if (requestModel.includes('gpt-4-32k')) { | 417 | if (requestModel.includes('gpt-4-32k')) { |
| 414 | return 'gpt-4-32k'; | 418 | return 'gpt-4-32k'; |
| 415 | } | 419 | } |