Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ English   |   [Indonesia](README_IN.md)   | &
- 📱 Support for mobile devices.
- 📓 Summarize any page with right-click menu. (<kbd>Alt</kbd>+<kbd>B</kbd>)
- 📖 Independent conversation page. (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>H</kbd>)
- 🔗 Multiple API support (Web API for Free and Plus users, GPT-3.5, GPT-4, Claude, New Bing, Moonshot, Self-Hosted, Azure etc.).
- 🔗 Multiple API support (Web API for Free and Plus users, GPT-3.5, GPT-4, Claude, New Bing, Moonshot, MiniMax, DeepSeek, Self-Hosted, Azure etc.).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The list of supported APIs is getting long. To improve readability and make it easier for users to find a specific provider, consider sorting the list of API providers alphabetically.

Suggested change
- 🔗 Multiple API support (Web API for Free and Plus users, GPT-3.5, GPT-4, Claude, New Bing, Moonshot, MiniMax, DeepSeek, Self-Hosted, Azure etc.).
- 🔗 Multiple API support (Web API for Free and Plus users, Azure, Claude, DeepSeek, GPT-3.5, GPT-4, MiniMax, Moonshot, New Bing, Self-Hosted, etc.).

- 📦 Integration for various commonly used websites (Reddit, Quora, YouTube, GitHub, GitLab, StackOverflow, Zhihu, Bilibili). (Inspired by [wimdenherder](https://github.com/wimdenherder))
- 🔍 Integration to all mainstream search engines, and custom queries to support additional sites.
- 🧰 Selection tool and right-click menu to perform various tasks, such as translation, summarization, polishing,
Expand Down
5 changes: 5 additions & 0 deletions src/background/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
isUsingOpenRouterApiModel,
isUsingAimlApiModel,
isUsingDeepSeekApiModel,
isUsingMiniMaxApiModel,
} from '../config/index.mjs'
import '../_locales/i18n'
import { openUrl } from '../utils/open-url'
Expand All @@ -56,6 +57,7 @@ import { generateAnswersWithMoonshotCompletionApi } from '../services/apis/moons
import { generateAnswersWithMoonshotWebApi } from '../services/apis/moonshot-web.mjs'
import { isUsingModelName } from '../utils/model-name-convert.mjs'
import { generateAnswersWithDeepSeekApi } from '../services/apis/deepseek-api.mjs'
import { generateAnswersWithMiniMaxApi } from '../services/apis/minimax-api.mjs'
import { redactSensitiveFields } from './redact.mjs'

const RECONNECT_CONFIG = {
Expand Down Expand Up @@ -527,6 +529,9 @@ async function executeApi(session, port, config) {
} else if (isUsingDeepSeekApiModel(session)) {
console.debug('[background] Using DeepSeek API Model')
await generateAnswersWithDeepSeekApi(port, session.question, session, config.deepSeekApiKey)
} else if (isUsingMiniMaxApiModel(session)) {
console.debug('[background] Using MiniMax API Model')
await generateAnswersWithMiniMaxApi(port, session.question, session, config.minimaxApiKey)
} else if (isUsingOllamaApiModel(session)) {
console.debug('[background] Using Ollama API Model')
await generateAnswersWithOllamaApi(port, session.question, session)
Expand Down
23 changes: 23 additions & 0 deletions src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const moonshotApiModelKeys = [
'moonshot_v1_128k',
]
export const deepSeekApiModelKeys = ['deepseek_chat', 'deepseek_reasoner']
export const miniMaxApiModelKeys = ['minimax_m27', 'minimax_m25', 'minimax_m25_highspeed']
export const openRouterApiModelKeys = [
'openRouter_auto',
'openRouter_free',
Expand Down Expand Up @@ -202,6 +203,10 @@ export const ModelGroups = {
value: deepSeekApiModelKeys,
desc: 'DeepSeek (API)',
},
miniMaxApiModelKeys: {
value: miniMaxApiModelKeys,
desc: 'MiniMax (API)',
},
openRouterApiModelKeys: {
value: openRouterApiModelKeys,
desc: 'OpenRouter (API)',
Expand Down Expand Up @@ -390,6 +395,19 @@ export const Models = {
desc: 'DeepSeek (Reasoner)',
},

minimax_m27: {
value: 'MiniMax-M2.7',
desc: 'MiniMax (M2.7)',
},
minimax_m25: {
value: 'MiniMax-M2.5',
desc: 'MiniMax (M2.5)',
},
minimax_m25_highspeed: {
value: 'MiniMax-M2.5-highspeed',
desc: 'MiniMax (M2.5 Highspeed, 204K)',
},

openRouter_anthropic_claude_sonnet4: {
value: 'anthropic/claude-sonnet-4',
desc: 'OpenRouter (Claude Sonnet 4)',
Expand Down Expand Up @@ -521,6 +539,7 @@ export const defaultConfig = {
chatglmApiKey: '',
moonshotApiKey: '',
deepSeekApiKey: '',
minimaxApiKey: '',

customApiKey: '',

Expand Down Expand Up @@ -717,6 +736,10 @@ export function isUsingDeepSeekApiModel(configOrSession) {
return isInApiModeGroup(deepSeekApiModelKeys, configOrSession)
}

export function isUsingMiniMaxApiModel(configOrSession) {
return isInApiModeGroup(miniMaxApiModelKeys, configOrSession)
}

export function isUsingOpenRouterApiModel(configOrSession) {
return isInApiModeGroup(openRouterApiModelKeys, configOrSession)
}
Expand Down
12 changes: 12 additions & 0 deletions src/popup/sections/GeneralPart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
isUsingOpenRouterApiModel,
isUsingAimlApiModel,
isUsingDeepSeekApiModel,
isUsingMiniMaxApiModel,
} from '../../config/index.mjs'
import Browser from 'webextension-polyfill'
import { languageList } from '../../config/language.mjs'
Expand Down Expand Up @@ -342,6 +343,17 @@ export function GeneralPart({ config, updateConfig, setTabIndex }) {
}}
/>
)}
{isUsingMiniMaxApiModel(config) && (
<input
type="password"
value={config.minimaxApiKey}
placeholder={t('MiniMax API Key')}
onChange={(e) => {
const apiKey = e.target.value
updateConfig({ minimaxApiKey: apiKey })
}}
/>
Comment on lines +346 to +355
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Missing minimax api key locale 📘 Rule violation ⚙ Maintainability

The settings UI introduces a new localized string key MiniMax API Key but no corresponding entry
exists in the English locale (and therefore cannot be propagated). This can cause missing/incorrect
UI text and violates the localization key propagation requirement.
Agent Prompt
## Issue description
The UI adds a new i18n key `MiniMax API Key` but it is not present in the English locale file (and thus not propagated to other locales), violating localization requirements.

## Issue Context
`GeneralPart.jsx` uses `t('MiniMax API Key')` for the MiniMax API key input placeholder. Existing providers commonly reuse `t('API Key')`, which is already translated.

## Fix Focus Areas
- src/popup/sections/GeneralPart.jsx[346-355]
- src/_locales/en/main.json[84-90]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

)}
Comment on lines +346 to +356
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For a better user experience, consider adding a 'Get' button next to the API key input that links to the MiniMax API key page. This is consistent with how API keys for other providers like OpenAI and Moonshot are handled in the UI.

The URL for MiniMax API keys is: https://api.minimax.io/user-center/api-keys

        {isUsingMiniMaxApiModel(config) && (
          <span style="display: flex; gap: 5px;">
            <input
              style="width: 100%;"
              type="password"
              value={config.minimaxApiKey}
              placeholder={t('MiniMax API Key')}
              onChange={(e) => {
                const apiKey = e.target.value
                updateConfig({ minimaxApiKey: apiKey })
              }}
            />
            {config.minimaxApiKey.length === 0 ? (
              <a
                href="https://api.minimax.io/user-center/api-keys"
                target="_blank"
                rel="nofollow noopener noreferrer"
              >
                <button style="white-space: nowrap;" type="button">
                  {t('Get')}
                </button>
              </a>
            ) : null}
          </span>
        )}

{isUsingOllamaApiModel(config) && (
<input
type="password"
Expand Down
12 changes: 12 additions & 0 deletions src/services/apis/minimax-api.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { generateAnswersWithOpenAiApiCompat } from './openai-api.mjs'

/**
* @param {Browser.Runtime.Port} port
* @param {string} question
* @param {Session} session
* @param {string} apiKey
*/
export async function generateAnswersWithMiniMaxApi(port, question, session, apiKey) {
const baseUrl = 'https://api.minimax.io/v1'
return generateAnswersWithOpenAiApiCompat(baseUrl, port, question, session, apiKey)
}
59 changes: 59 additions & 0 deletions tests/integration/minimax-api.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import assert from 'node:assert/strict'
import { describe, test } from 'node:test'
import {
miniMaxApiModelKeys,
isUsingMiniMaxApiModel,
Models,
ModelGroups,
} from '../../src/config/index.mjs'

describe('MiniMax integration', () => {
test('all MiniMax model keys have corresponding Models entries', () => {
for (const key of miniMaxApiModelKeys) {
assert.ok(Models[key], `Models entry missing for ${key}`)
assert.ok(Models[key].value, `Models[${key}].value is empty`)
assert.ok(Models[key].desc, `Models[${key}].desc is empty`)
}
})

test('MiniMax model group is registered in ModelGroups', () => {
assert.ok(ModelGroups.miniMaxApiModelKeys, 'MiniMax group missing from ModelGroups')
assert.equal(ModelGroups.miniMaxApiModelKeys.desc, 'MiniMax (API)')
assert.deepEqual(ModelGroups.miniMaxApiModelKeys.value, miniMaxApiModelKeys)
})

test('MiniMax model values match expected API model names', () => {
assert.equal(Models.minimax_m27.value, 'MiniMax-M2.7')
assert.equal(Models.minimax_m25.value, 'MiniMax-M2.5')
assert.equal(Models.minimax_m25_highspeed.value, 'MiniMax-M2.5-highspeed')
})

test('isUsingMiniMaxApiModel does not match other provider models', () => {
const otherModels = [
'chatgptApi4oMini',
'deepseek_chat',
'moonshot_v1_8k',
'claude37SonnetApi',
'customModel',
'ollamaModel',
]
for (const modelName of otherModels) {
assert.equal(isUsingMiniMaxApiModel({ modelName }), false, `Should not match ${modelName}`)
}
})

test('MiniMax model keys are unique and do not overlap with other groups', () => {
const allOtherKeys = []
for (const [groupName, group] of Object.entries(ModelGroups)) {
if (groupName === 'miniMaxApiModelKeys') continue
allOtherKeys.push(...group.value)
}
for (const key of miniMaxApiModelKeys) {
assert.equal(
allOtherKeys.includes(key),
false,
`MiniMax key ${key} overlaps with another group`,
)
}
})
})
20 changes: 16 additions & 4 deletions tests/unit/config/config-predicates.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
claudeApiModelKeys,
openRouterApiModelKeys,
aimlApiModelKeys,
miniMaxApiModelKeys,
isUsingAimlApiModel,
isUsingAzureOpenAiApiModel,
isUsingBingWebModel,
Expand All @@ -19,6 +20,7 @@ import {
isUsingDeepSeekApiModel,
isUsingGeminiWebModel,
isUsingGithubThirdPartyApiModel,
isUsingMiniMaxApiModel,
isUsingMoonshotApiModel,
isUsingMoonshotWebModel,
isUsingMultiModeModel,
Expand All @@ -43,10 +45,7 @@ const representativeOpenRouterApiModelNames = [
'openRouter_anthropic_claude_sonnet4',
'openRouter_openai_o3',
]
const representativeAimlApiModelNames = [
'aiml_claude_sonnet_4_6_20260218',
'aiml_openai_gpt_5_2',
]
const representativeAimlApiModelNames = ['aiml_claude_sonnet_4_6_20260218', 'aiml_openai_gpt_5_2']

const originalNavigatorDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'navigator')

Expand Down Expand Up @@ -194,6 +193,19 @@ test('isUsingDeepSeekApiModel detects DeepSeek models', () => {
assert.equal(isUsingDeepSeekApiModel({ modelName: 'chatgptApi4oMini' }), false)
})

test('isUsingMiniMaxApiModel detects MiniMax models', () => {
assert.equal(isUsingMiniMaxApiModel({ modelName: 'minimax_m27' }), true)
assert.equal(isUsingMiniMaxApiModel({ modelName: 'minimax_m25' }), true)
assert.equal(isUsingMiniMaxApiModel({ modelName: 'minimax_m25_highspeed' }), true)
assert.equal(isUsingMiniMaxApiModel({ modelName: 'chatgptApi4oMini' }), false)
})

test('isUsingMiniMaxApiModel accepts exported MiniMax API model keys', () => {
for (const modelName of miniMaxApiModelKeys) {
assert.equal(isUsingMiniMaxApiModel({ modelName }), true)
}
})

test('isUsingOpenRouterApiModel matches representative OpenRouter API keys', () => {
for (const modelName of representativeOpenRouterApiModelNames) {
assert.equal(isUsingOpenRouterApiModel({ modelName }), true)
Expand Down
Loading