AI Studio Model Modifier

Modify the model for aistudio.google.com requests, allowing switching between official, preview, and internal test models with a categorized dropdown menu.

  1. // ==UserScript==
  2. // @name:zh-CN AI Studio 模型修改器 - 解锁隐藏模型
  3. // @name:en AI Studio Model Modifier - Unlock hidden models
  4. // @name AI Studio Model Modifier
  5. // @namespace http://tampermonkey.net/
  6. // @version 1.1.6
  7. // @description:zh-CN 拦截 aistudio.google.com 的 GenerateContent 请求修改模型,支持在官方、预览及内部测试模型间切换,并提供带分类的下拉菜单。
  8. // @description:en Modify the model for aistudio.google.com requests, allowing switching between official, preview, and internal test models with a categorized dropdown menu.
  9. // @author Z_06
  10. // @match *://aistudio.google.com/*
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
  12. // @license MIT
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @grant GM_registerMenuCommand
  16. // @grant GM_addStyle
  17. // @homepageURL https://greasyfork.org/zh-CN/scripts/539130-ai-studio-model-modifier
  18. // @supportURL https://greasyfork.org/zh-CN/scripts/539130-ai-studio-model-modifier/feedback
  19. // @description Modify the model for aistudio.google.com requests, allowing switching between official, preview, and internal test models with a categorized dropdown menu.
  20. // ==/UserScript==
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. // --- Localization Object ---
  26. const L10N = {
  27. _lang: navigator.language && navigator.language.toLowerCase().startsWith('zh') ? 'zh' : 'en',
  28. get: function(translations) {
  29. return translations[this._lang] || translations['en'];
  30. },
  31. format: function(translationKey, ...args) {
  32. let str = this.get(translationKey);
  33. args.forEach((arg, index) => {
  34. str = str.replace(new RegExp(`\\{${index}\\}`, 'g'), arg);
  35. });
  36. return str;
  37. }
  38. };
  39.  
  40. const STRINGS = {
  41. scriptName: { zh: "[AI Studio] 模型修改器", en: "[AI Studio] Model Modifier" },
  42. modelSelectorTitle: { zh: "所有请求都将被强制使用此下拉框选中的模型", en: "All requests will be forced to use the model selected in this dropdown." },
  43. customModelGroupLabel: { zh: "自定义模型", en: "Custom Models" },
  44. customModelOptionPrefix: { zh: "* ", en: "* " }, // Prefix for custom added models
  45. menu_addSetCustomModel: { zh: "添加/设置自定义模型", en: "Add/Set Custom Model" },
  46. prompt_enterModelName: { zh: "请输入要强制使用的完整模型名称:", en: "Please enter the full model name to enforce:" },
  47. alert_modelUpdatedTo: { zh: "模型已更新为:\n{0}", en: "Model updated to:\n{0}" },
  48. log_loadedWithModel: { zh: "已加载。当前强制模型为 \"{0}\"", en: "loaded. Current forced model is \"{0}\"" },
  49. log_containerFound: { zh: "发现容器,注入UI...", en: "Container found, injecting UI..." },
  50. log_uiInjected: { zh: "自定义UI注入成功。", en: "Custom UI injected successfully." },
  51. log_modelSwitchedAndSaved: { zh: "模型已切换并保存: {0}", en: "Model switched and saved: {0}" },
  52. log_interceptRequest: { zh: "拦截请求。原始: {0} -> 修改为: {1}", en: "Intercepting request. Original: {0} -> Modified to: {1}" },
  53. log_errorModifyingPayload: { zh: "修改请求负载时出错:", en: "Error modifying request payload:" },
  54.  
  55. // Model Group Labels
  56. group_internalTest: { zh: "内部测试模型", en: "Internal Test Models" },
  57. group_gemini25: { zh: "Gemini 2.5", en: "Gemini 2.5" },
  58. group_gemini20: { zh: "Gemini 2.0", en: "Gemini 2.0" },
  59. group_gemini15: { zh: "Gemini 1.5", en: "Gemini 1.5" },
  60. group_down: { zh: "已下线模型", en: "Offline Models" },
  61.  
  62. // Model Name Suffixes/Parts
  63. suffix_internal: { zh: " (内部测试)", en: " (Internal)" },
  64. suffix_down: { zh: " (已下架)", en: " (Down)" },
  65. suffix_preview: { zh: " 预览版", en: " Preview" },
  66. suffix_exp: { zh: " EXP", en: " EXP" },
  67. suffix_abTest: { zh: " AB-Test", en: " AB-Test" },
  68. suffix_thinking: { zh: " Thinking", en: " Thinking" },
  69. suffix_imageGen: { zh: " (图片生成)", en: " (Image Gen.)" }
  70. };
  71.  
  72. // --- Configuration ---
  73. const SCRIPT_NAME_LOCALIZED = L10N.get(STRINGS.scriptName);
  74. const STORAGE_KEY = "aistudio_custom_model_name_v2";
  75. const TARGET_URL = "https://alkalimakersuite-pa.clients6.google.com/$rpc/google.internal.alkali.applications.makersuite.v1.MakerSuiteService/GenerateContent";
  76. const MODEL_SELECTOR_CONTAINER = 'div.settings-model-selector';
  77.  
  78. const MODEL_OPTIONS = [
  79. {
  80. label: STRINGS.group_internalTest,
  81. options: [
  82. { baseName: "68zkqbz8vs", suffixKey: "suffix_internal", value: "models/68zkqbz8vs" },
  83. { baseName: "a24bo28u1a", suffixKey: "suffix_internal", value: "models/a24bo28u1a" },
  84. { baseName: "2vmc1bo4ri", suffixKey: "suffix_internal", value: "models/2vmc1bo4ri" },
  85. { baseName: "42fc3y4xfsz", suffixKey: "suffix_internal", value: "models/42fc3y4xfsz" },
  86. { baseName: "ixqzem8yj4j", suffixKey: "suffix_internal", value: "models/ixqzem8yj4j" },
  87. { baseName: "Calmriver", suffixKey: "suffix_internal", value: "models/calmriver-ab-test" },
  88. { baseName: "Claybrook", suffixKey: "suffix_internal", value: "models/claybrook-ab-test" },
  89. { baseName: "Frostwind", suffixKey: "suffix_internal", value: "models/frostwind-ab-test" },
  90. { baseName: "Goldmane", suffixKey: "suffix_internal", value: "models/goldmane-ab-test" },
  91. ]
  92. },
  93. {
  94. label: STRINGS.group_gemini25,
  95. options: [
  96. { baseName: "2.5 Pro", value: "models/gemini-2.5-pro" },
  97. { baseName: "2.5 Pro", date: "(06-05)", suffixKey: "suffix_preview", value: "models/gemini-2.5-pro-preview-06-05" },
  98. { baseName: "2.5 Pro", date: "(05-06)", suffixKey: "suffix_preview", value: "models/gemini-2.5-pro-preview-05-06" },
  99. { baseName: "2.5 Pro", date: "(03-25)", suffixKey: "suffix_preview", value: "models/gemini-2.5-pro-preview-03-25" },
  100. { baseName: "2.5 Pro", date: "(03-25)", suffixKey: "suffix_exp", value: "models/gemini-2.5-pro-exp-03-25" },
  101. { baseName: "2.5 Flash", value: "models/gemini-2.5-flash" },
  102. { baseName: "2.5 Flash", date: "(05-20)", suffixKey: "suffix_preview", value: "models/gemini-2.5-flash-preview-05-20" },
  103. { baseName: "2.5 Flash", date: "(04-17)", suffixKey: "suffix_preview", value: "models/gemini-2.5-flash-preview-04-17" },
  104. { baseName: "2.5 Flash", date: "(04-17)", suffixKey: "suffix_thinking", value: "models/gemini-2.5-flash-preview-04-17-thinking" },
  105. { baseName: "2.5 Flash Lite", date: "(06-17)", suffixKey: "suffix_preview", value: "models/gemini-2.5-flash-lite-preview-06-17" },
  106. ]
  107. },
  108. {
  109. label: STRINGS.group_gemini20,
  110. options: [
  111. { baseName: "2.0 Flash", value: "models/gemini-2.0-flash" },
  112. { baseName: "2.0 Flash", suffixKey: "suffix_imageGen", value: "models/gemini-2.0-flash-preview-image-generation" },
  113. { baseName: "2.0 Flash-Lite", value: "models/gemini-2.0-flash-lite" },
  114. ]
  115. },
  116. {
  117. label: STRINGS.group_gemini15,
  118. options: [
  119. { baseName: "1.5 Pro", value: "models/gemini-1.5-pro" },
  120. { baseName: "1.5 Flash", value: "models/gemini-1.5-flash" },
  121. { baseName: "1.5 Flash-8B", value: "models/gemini-1.5-flash-8b" },
  122. ]
  123. },
  124. {
  125. label: STRINGS.group_down,
  126. options: [
  127. { baseName: "Blacktooth", suffixKey: "suffix_down", value: "models/blacktooth-ab-test" },
  128. { baseName: "jfdksal98a", suffixKey: "suffix_down", value: "models/jfdksal98a" },
  129. { baseName: "Kingfall", suffixKey: "suffix_down", value: "models/kingfall-ab-test" },
  130. { baseName: "2.5 Pro AB-Test", date: "(03-25)", suffixKey: "suffix_down", value: "models/gemini-2.5-pro-preview-03-25-ab-test" },
  131. ]
  132. }
  133. ];
  134. const DEFAULT_MODEL = MODEL_OPTIONS[1].options[3].value; // 0325
  135.  
  136. let customModelName = GM_getValue(STORAGE_KEY, DEFAULT_MODEL);
  137.  
  138. GM_addStyle(`
  139. ${MODEL_SELECTOR_CONTAINER} ms-model-selector-two-column { display: none !important; }
  140. #custom-model-selector {
  141. width: 100%; padding: 8px 12px; margin-top: 4px; border: 1px solid #5f6368;
  142. border-radius: 8px; color: #e2e2e5; background-color: #35373a;
  143. font-family: 'Google Sans', 'Roboto', sans-serif; font-size: 14px; font-weight: 500;
  144. box-sizing: border-box; cursor: pointer; -webkit-appearance: none; -moz-appearance: none; appearance: none;
  145. background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23e2e2e5%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.4-5.4-13z%22%2F%3E%3C%2Fsvg%3E');
  146. background-repeat: no-repeat; background-position: right 12px center; background-size: 10px;
  147. }
  148. #custom-model-selector optgroup { font-weight: bold; color: #8ab4f8; }
  149. `);
  150.  
  151. function getModelDisplayName(modelOption) {
  152. let name = modelOption.baseName;
  153. if (modelOption.date) name += ` ${modelOption.date}`;
  154. if (modelOption.suffixKey && STRINGS[modelOption.suffixKey]) {
  155. name += L10N.get(STRINGS[modelOption.suffixKey]);
  156. }
  157. return name;
  158. }
  159.  
  160. function updateAndSelectModel(modelValue) {
  161. const selector = document.getElementById('custom-model-selector');
  162. if (!selector) return;
  163.  
  164. if (!selector.querySelector(`option[value="${modelValue}"]`)) {
  165. let customGroup = document.getElementById('custom-model-optgroup');
  166. if (!customGroup) {
  167. customGroup = document.createElement('optgroup');
  168. customGroup.id = 'custom-model-optgroup';
  169. customGroup.label = L10N.get(STRINGS.customModelGroupLabel);
  170. selector.appendChild(customGroup);
  171. }
  172. const newOption = document.createElement('option');
  173. newOption.value = modelValue;
  174. newOption.textContent = L10N.get(STRINGS.customModelOptionPrefix) + modelValue.replace('models/', '');
  175. customGroup.appendChild(newOption);
  176. }
  177. selector.value = modelValue;
  178. }
  179.  
  180. function createModelSelectorUI(container) {
  181. console.log(`[${SCRIPT_NAME_LOCALIZED}] ${L10N.get(STRINGS.log_containerFound)}`);
  182. const selector = document.createElement('select');
  183. selector.id = 'custom-model-selector';
  184. selector.title = L10N.get(STRINGS.modelSelectorTitle);
  185.  
  186. MODEL_OPTIONS.forEach(group => {
  187. const optgroup = document.createElement('optgroup');
  188. optgroup.label = L10N.get(group.label);
  189. group.options.forEach(opt => {
  190. const option = document.createElement('option');
  191. option.value = opt.value;
  192. option.textContent = getModelDisplayName(opt);
  193. optgroup.appendChild(option);
  194. });
  195. selector.appendChild(optgroup);
  196. });
  197.  
  198. selector.addEventListener('change', (event) => {
  199. const newModel = event.target.value;
  200. customModelName = newModel;
  201. GM_setValue(STORAGE_KEY, newModel);
  202. console.log(`[${SCRIPT_NAME_LOCALIZED}] ${L10N.format(STRINGS.log_modelSwitchedAndSaved, newModel)}`);
  203. });
  204.  
  205. const injectionPoint = container.querySelector('.item-input-form-field');
  206. if (injectionPoint) {
  207. injectionPoint.appendChild(selector);
  208. updateAndSelectModel(customModelName);
  209. console.log(`[${SCRIPT_NAME_LOCALIZED}] ${L10N.get(STRINGS.log_uiInjected)}`);
  210. }
  211. }
  212.  
  213. GM_registerMenuCommand(L10N.get(STRINGS.menu_addSetCustomModel), () => {
  214. const newModel = prompt(L10N.get(STRINGS.prompt_enterModelName), customModelName);
  215. if (newModel && newModel.trim() !== "") {
  216. const trimmedModel = newModel.trim();
  217. customModelName = trimmedModel;
  218. GM_setValue(STORAGE_KEY, trimmedModel);
  219. alert(L10N.format(STRINGS.alert_modelUpdatedTo, trimmedModel));
  220. updateAndSelectModel(trimmedModel);
  221. }
  222. });
  223.  
  224. const observer = new MutationObserver((mutations, obs) => {
  225. const container = document.querySelector(MODEL_SELECTOR_CONTAINER);
  226. if (container && !document.getElementById('custom-model-selector')) {
  227. createModelSelectorUI(container);
  228. }
  229. });
  230. observer.observe(document.body, { childList: true, subtree: true });
  231.  
  232. const originalOpen = XMLHttpRequest.prototype.open;
  233. XMLHttpRequest.prototype.open = function(method, url) {
  234. this._url = url;
  235. this._method = method;
  236. return originalOpen.apply(this, arguments);
  237. };
  238.  
  239. const originalSend = XMLHttpRequest.prototype.send;
  240. XMLHttpRequest.prototype.send = function(data) {
  241. if (this._url === TARGET_URL && this._method.toUpperCase() === 'POST' && data) {
  242. try {
  243. let payload = JSON.parse(data);
  244. const originalModel = payload[0];
  245. if (typeof originalModel === 'string' && originalModel.startsWith('models/')) {
  246. console.log(`[${SCRIPT_NAME_LOCALIZED}] ${L10N.format(STRINGS.log_interceptRequest, originalModel, customModelName)}`);
  247. payload[0] = customModelName;
  248. const modifiedData = JSON.stringify(payload);
  249. return originalSend.call(this, modifiedData);
  250. }
  251. } catch (e) {
  252. console.error(`[${SCRIPT_NAME_LOCALIZED}] ${L10N.get(STRINGS.log_errorModifyingPayload)}`, e);
  253. }
  254. }
  255. return originalSend.apply(this, arguments);
  256. };
  257.  
  258. console.log(`[${SCRIPT_NAME_LOCALIZED}] ${L10N.format(STRINGS.log_loadedWithModel, customModelName)}`);
  259. })();