Gemini: add media resolution select (#4775)

5accfbc5d8566eae5aaeaa8b9f833d518e9893d0

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +27 -8Showing whitespace changes
public/index.html+1 -1
@@ -2013,7 +2013,7 @@
20132013 <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong>
20142014 <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong>
20152015 </div>
20162016 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow,chutes,makersuite,vertexai">
20172017 <div class="flex-container oneline-dropdown">
20182018 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
20192019 Inline Image Quality
public/scripts/openai.js+2 -1
@@ -3244,7 +3244,8 @@ class Message {
32443244 }
32453245
32463246 // Note: No compression for videos (unlike images)
3247- this.content.push({ type: 'video_url', video_url: { 'url': video } });
3247+ const quality = oai_settings.inline_image_quality || default_settings.inline_image_quality;
3248+ this.content.push({ type: 'video_url', video_url: { 'url': video, 'detail': quality } });
32483249
32493250 try {
32503251 // Using Gemini calculation (263 tokens per second)
src/prompt-converters.js+24 -6
@@ -25,6 +25,12 @@ export const PROMPT_PROCESSING_TYPE = {
2525 SINGLE: 'single',
2626};
2727
28+// 'auto' is intentionally unmapped
29+const GEMINI_MEDIA_RESOLUTION = {
30+ low: 'media_resolution_low',
31+ high: 'media_resolution_high',
32+};
33+
2834/**
2935 * @typedef {object} PromptNames
3036 * @property {string} charName Character name
@@ -501,17 +507,27 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) {
501507 //create the prompt parts
502508 const parts = [];
503509 message.content.forEach((part) => {
504510 const addDataUrlPart = (/** @type {string} */ url, /** @type {string} */ defaultMimeType, /** @type {string?} */ detail = null) => {
505511 if (url && url.startsWith('data:')) {
506512 const [header, base64Data] = url.split(',');
507513 const mimeType = header.match(/data:([^;]+)/)?.[1] || defaultMimeType;
514+ const mediaResolution = GEMINI_MEDIA_RESOLUTION[detail] || null;
508515
509- parts.push({
516+ const part = {
510517 inlineData: {
511518 mimeType: mimeType,
512519 data: base64Data,
513520 },
514521 });
522+
523+ // https://ai.google.dev/gemini-api/docs/gemini-3#media_resolution
524+ if (/gemini-3/.test(model) && mediaResolution) {
525+ part.mediaResolution = {
526+ level: mediaResolution,
527+ };
528+ }
529+
530+ parts.push(part);
515531 }
516532 };
517533
@@ -538,10 +554,12 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) {
538554 });
539555 } else if (part.type === 'image_url') {
540556 const imageUrl = part.image_url?.url;
541- addDataUrlPart(imageUrl, 'image/png');
557+ const detail = part.image_url?.detail;
558+ addDataUrlPart(imageUrl, 'image/png', detail);
542559 } else if (part.type === 'video_url') {
543560 const videoUrl = part.video_url?.url;
544- addDataUrlPart(videoUrl, 'video/mp4');
561+ const detail = part.video_url?.detail;
562+ addDataUrlPart(videoUrl, 'video/mp4', detail);
545563 } else if (part.type === 'audio_url') {
546564 const audioUrl = part.audio_url?.url;
547565 addDataUrlPart(audioUrl, 'audio/mpeg');
@@ -572,7 +590,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) {
572590 contents[contents.length - 1].parts.push(part);
573591 }
574592 }
575593 if (part.inlineData || part.functionCall || part.functionResponse || part.thoughtSignature || part.mediaResolution) {
576594 contents[contents.length - 1].parts.push(part);
577595 }
578596 });