Skip to content
Draft
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
12 changes: 12 additions & 0 deletions src/_locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,5 +160,17 @@
"Type": "Type",
"Mode": "Mode",
"Custom": "Custom",
"Custom Providers": "Custom Providers",
"Manage OpenAI-compatible API providers. Each provider can have multiple models sharing the same API key and base URL.": "Manage OpenAI-compatible API providers. Each provider can have multiple models sharing the same API key and base URL.",
"Add New Provider": "Add New Provider",
"Add Provider": "Add Provider",
"Save Provider": "Save Provider",
"Provider Name (e.g., \"OpenRouter\", \"LocalAI\")": "Provider Name (e.g., \"OpenRouter\", \"LocalAI\")",
"Base URL (e.g., \"https://openrouter.ai/api/v1/chat/completions\")": "Base URL (e.g., \"https://openrouter.ai/api/v1/chat/completions\")",
"Models": "Models",
"Add Model": "Add Model",
"Model Name (e.g., \"gpt-4\", \"claude-3\")": "Model Name (e.g., \"gpt-4\", \"claude-3\")",
"Display Name (e.g., \"GPT-4\", \"Claude 3\")": "Display Name (e.g., \"GPT-4\", \"Claude 3\")",
"Active": "Active",
"Crop Text to ensure the input tokens do not exceed the model's limit": "Crop Text to ensure the input tokens do not exceed the model's limit"
}
23 changes: 20 additions & 3 deletions src/background/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
generateAnswersWithChatgptApi,
generateAnswersWithGptCompletionApi,
} from '../services/apis/openai-api'
import { generateAnswersWithCustomApi } from '../services/apis/custom-api.mjs'
import {
generateAnswersWithCustomApi,
generateAnswersWithCustomProviderApi,
} from '../services/apis/custom-api.mjs'
import { generateAnswersWithOllamaApi } from '../services/apis/ollama-api.mjs'
import { generateAnswersWithAzureOpenaiApi } from '../services/apis/azure-openai-api.mjs'
import { generateAnswersWithClaudeApi } from '../services/apis/claude-api.mjs'
Expand Down Expand Up @@ -84,7 +87,19 @@ async function executeApi(session, port, config) {
console.debug('modelName', session.modelName)
console.debug('apiMode', session.apiMode)
if (isUsingCustomModel(session)) {
if (!session.apiMode)
if (session.providerId && session.providerModelName) {
// Use provider-based configuration from session
await generateAnswersWithCustomProviderApi(port, session.question, session)
} else if (session.apiMode && session.apiMode.providerId) {
// Use provider-based configuration from API mode
const providerSession = {
...session,
providerId: session.apiMode.providerId,
providerModelName: session.apiMode.providerModelName,
}
await generateAnswersWithCustomProviderApi(port, session.question, providerSession)
} else if (!session.apiMode) {
// Legacy single custom model
await generateAnswersWithCustomApi(
port,
session.question,
Expand All @@ -93,7 +108,8 @@ async function executeApi(session, port, config) {
config.customApiKey,
config.customModelName,
)
else
} else {
// Custom API mode
await generateAnswersWithCustomApi(
port,
session.question,
Expand All @@ -104,6 +120,7 @@ async function executeApi(session, port, config) {
session.apiMode.apiKey.trim() || config.customApiKey,
session.apiMode.customName,
)
}
} else if (isUsingChatgptWebModel(session)) {
let tabId
if (
Expand Down
115 changes: 114 additions & 1 deletion src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,23 @@ export const defaultConfig = {
active: false,
},
],
// Provider-level management for OpenAI-compatible APIs
customProviders: [
{
id: '',
name: '',
baseUrl: '',
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

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

The default configuration contains empty strings for required fields like id, name, and baseUrl. This could lead to issues when the default is accidentally used. Consider using more descriptive placeholder values or null values that would fail fast if used incorrectly.

Suggested change
baseUrl: '',
id: null,
name: null,
baseUrl: null,

Copilot uses AI. Check for mistakes.

apiKey: '',
active: true,
models: [
{
name: '',
displayName: '',
active: true,
},
],
},
],
activeSelectionTools: ['translate', 'translateToEn', 'summary', 'polish', 'code', 'ask'],
customSelectionTools: [
{
Expand Down Expand Up @@ -712,6 +729,53 @@ export function isUsingCustomModel(configOrSession) {
return isInApiModeGroup(customApiModelKeys, configOrSession)
}

/**
* Check if a session is using a custom provider model
* @param {object} configOrSession
* @returns {boolean}
*/
export function isUsingCustomProviderModel(configOrSession) {
return configOrSession.providerId && configOrSession.providerModelName
}

/**
* Get provider configuration by ID
* @param {UserConfig} config
* @param {string} providerId
* @returns {object|null}
*/
export function getCustomProvider(config, providerId) {
return config.customProviders?.find((provider) => provider.id === providerId) || null
}

/**
* Get all active custom providers
* @param {UserConfig} config
* @returns {array}
*/
export function getActiveCustomProviders(config) {
return config.customProviders?.filter((provider) => provider.active) || []
}

/**
* Get all active models from all providers
* @param {UserConfig} config
* @returns {array} Array of {provider, model} objects
*/
export function getAllActiveProviderModels(config) {
const result = []
const activeProviders = getActiveCustomProviders(config)

for (const provider of activeProviders) {
const activeModels = provider.models?.filter((model) => model.active) || []
for (const model of activeModels) {
result.push({ provider, model })
}
}

return result
}

/**
* @deprecated
*/
Expand All @@ -729,11 +793,60 @@ export async function getPreferredLanguageKey() {
* get user config from local storage
* @returns {Promise<UserConfig>}
*/
/**
* Migrate legacy custom model configuration to providers if needed
* @param {UserConfig} config
* @returns {UserConfig}
*/
function migrateCustomModelToProvider(config) {
// Skip migration if no legacy custom model or already has providers
if (!config.customApiKey || !config.customModelApiUrl || !config.customModelName) {
return config
}

// Skip if already has custom providers (user has already migrated or set up providers)
if (config.customProviders && config.customProviders.length > 0 && config.customProviders[0].id) {
return config
}

// Create a provider from the legacy configuration
const legacyProvider = {
id: 'legacy_custom_' + Date.now(),
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

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

The migration function uses Date.now() for ID generation, which could create duplicate IDs if migration runs multiple times in quick succession. Consider using a more robust ID generation or checking for existing IDs.

Copilot uses AI. Check for mistakes.

name: 'Legacy Custom Model',
baseUrl: config.customModelApiUrl,
apiKey: config.customApiKey,
active: true,
models: [
{
name: config.customModelName,
displayName: config.customModelName,
active: true,
},
],
}

return {
...config,
customProviders: [legacyProvider],
}
}

export async function getUserConfig() {
const options = await Browser.storage.local.get(Object.keys(defaultConfig))
if (options.customChatGptWebApiUrl === 'https://chat.openai.com')
options.customChatGptWebApiUrl = 'https://chatgpt.com'
return defaults(options, defaultConfig)

const config = defaults(options, defaultConfig)

// Run migration for legacy custom models
const migratedConfig = migrateCustomModelToProvider(config)

// Save migrated config if changes were made
if (JSON.stringify(migratedConfig.customProviders) !== JSON.stringify(config.customProviders)) {
await setUserConfig({ customProviders: migratedConfig.customProviders })
}

return migratedConfig
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/popup/Popup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { GeneralPart } from './sections/GeneralPart'
import { FeaturePages } from './sections/FeaturePages'
import { AdvancedPart } from './sections/AdvancedPart'
import { ModulesPart } from './sections/ModulesPart'
import { CustomProviders } from './sections/CustomProviders'

// eslint-disable-next-line react/prop-types
function Footer({ currentVersion, latestVersion }) {
Expand Down Expand Up @@ -106,6 +107,7 @@ function Popup() {
<Tab className="popup-tab">{t('General')}</Tab>
<Tab className="popup-tab">{t('Feature Pages')}</Tab>
<Tab className="popup-tab">{t('Modules')}</Tab>
<Tab className="popup-tab">{t('Custom Providers')}</Tab>
<Tab className="popup-tab">{t('Advanced')}</Tab>
</TabList>

Expand All @@ -118,6 +120,9 @@ function Popup() {
<TabPanel>
<ModulesPart config={config} updateConfig={updateConfig} />
</TabPanel>
<TabPanel>
<CustomProviders config={config} updateConfig={updateConfig} />
</TabPanel>
<TabPanel>
<AdvancedPart config={config} updateConfig={updateConfig} />
</TabPanel>
Expand Down
Loading