Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fun IdeaBottomToolbar(
currentConfigName: String? = null,
onConfigSelect: (NamedModelConfig) -> Unit = {},
onConfigureClick: () -> Unit = {},
onAddNewConfig: () -> Unit = {},
modifier: Modifier = Modifier
) {
Row(
Expand All @@ -59,7 +60,8 @@ fun IdeaBottomToolbar(
availableConfigs = availableConfigs,
currentConfigName = currentConfigName,
onConfigSelect = onConfigSelect,
onConfigureClick = onConfigureClick
onConfigureClick = onConfigureClick,
onAddNewConfig = onAddNewConfig
)

// Token usage indicator (subtle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fun IdeaModelSelector(
currentConfigName: String?,
onConfigSelect: (NamedModelConfig) -> Unit,
onConfigureClick: () -> Unit,
onAddNewConfig: () -> Unit,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
Expand Down Expand Up @@ -131,7 +132,32 @@ fun IdeaModelSelector(
separator()
}

// Configure button
// Add New Config button
selectableItem(
selected = false,
onClick = {
onAddNewConfig()
expanded = false
}
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = IdeaComposeIcons.Add,
contentDescription = null,
tint = JewelTheme.globalColors.text.normal,
modifier = Modifier.size(16.dp)
)
Text(
text = "Add New Config",
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The IDEA implementation uses a hardcoded English string "Add New Config" instead of using the i18n string system. The UI version (mpp-ui) properly uses Strings.addNewConfig for internationalization support. For consistency with the codebase's i18n patterns and to support multiple languages, this should use an i18n string.

Since the i18n strings have already been added to the common I18n.kt file (Strings.addNewConfig), you should use that string here instead of the hardcoded "Add New Config".

Copilot uses AI. Check for mistakes.
style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp)
)
}
}
Comment on lines +135 to +158
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use i18n for the "Add New Config" label.

Line 154 contains a hardcoded string. For consistency with the i18n infrastructure added in this PR (see Strings.addNewConfig in I18n.kt), the label should use the localization key.

Apply this diff to use the i18n string:

                        Text(
-                            text = "Add New Config",
+                            text = Strings.addNewConfig,
                            style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp)
                        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Add New Config button
selectableItem(
selected = false,
onClick = {
onAddNewConfig()
expanded = false
}
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = IdeaComposeIcons.Add,
contentDescription = null,
tint = JewelTheme.globalColors.text.normal,
modifier = Modifier.size(16.dp)
)
Text(
text = "Add New Config",
style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp)
)
}
}
// Add New Config button
selectableItem(
selected = false,
onClick = {
onAddNewConfig()
expanded = false
}
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = IdeaComposeIcons.Add,
contentDescription = null,
tint = JewelTheme.globalColors.text.normal,
modifier = Modifier.size(16.dp)
)
Text(
text = Strings.addNewConfig,
style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp)
)
}
}
🤖 Prompt for AI Agents
In mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaModelSelector.kt
around lines 135-158 there is a hardcoded label "Add New Config"; replace that
literal with the i18n key by using Strings.addNewConfig (importing the Strings
object from I18n.kt if needed) so the Text composable uses the localized string
instead of the hardcoded English text; ensure the correct accessor is used
consistent with the project's i18n pattern (e.g. Strings.addNewConfig or the
project's stringResource wrapper) and keep the rest of the Text styling
unchanged.


// Configure button (edit current config)
selectableItem(
selected = false,
onClick = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class SwingBottomToolbar(
private var availableConfigs: List<NamedModelConfig> = emptyList()
private var onConfigSelect: (NamedModelConfig) -> Unit = {}
private var onConfigureClick: () -> Unit = {}
private var onAddNewConfig: () -> Unit = {}
private var isProcessing = false
private var isEnhancing = false

Expand All @@ -52,6 +53,16 @@ class SwingBottomToolbar(
}
add(modelComboBox)

// Add New Config button
val addConfigButton = JButton(AllIcons.General.Add).apply {
toolTipText = "Add New Config"
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

[nitpick] The tooltip text "Add New Config" doesn't match the menu item text used in other implementations. The UI version uses "Add New Config" (from Strings.addNewConfig) and IDEA also uses "Add New Config". However, looking at the UI version's i18n files, the Chinese translation is "新增配置" (line 29 of AutoDevStrings_zh.properties).

For better consistency and clarity, consider using a more descriptive tooltip like "Add New Model Configuration" or ensure it matches the actual menu item text exactly.

Suggested change
toolTipText = "Add New Config"
toolTipText = "Add New Model Configuration"

Copilot uses AI. Check for mistakes.
preferredSize = Dimension(28, 28)
isBorderPainted = false
isContentAreaFilled = false
addActionListener { onAddNewConfig() }
}
add(addConfigButton)

tokenLabel.foreground = JBUI.CurrentTheme.Label.disabledForeground()
add(tokenLabel)
}
Expand Down Expand Up @@ -138,5 +149,9 @@ class SwingBottomToolbar(
fun setOnConfigureClick(callback: () -> Unit) {
onConfigureClick = callback
}

fun setOnAddNewConfig(callback: () -> Unit) {
onAddNewConfig = callback
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ fun IdeaAgentApp(
var isExecuting by remember { mutableStateOf(false) }
var currentPlan by remember { mutableStateOf<AgentPlan?>(null) }
var showConfigDialog by remember { mutableStateOf(false) }
var isNewConfig by remember { mutableStateOf(false) }
var mcpPreloadingMessage by remember { mutableStateOf("") }
var configWrapper by remember { mutableStateOf<AutoDevConfigWrapper?>(null) }
var currentModelConfig by remember { mutableStateOf<ModelConfig?>(null) }
Expand All @@ -89,6 +90,9 @@ fun IdeaAgentApp(
IdeaLaunchedEffect(viewModel, project = project) {
viewModel.showConfigDialog.collect { showConfigDialog = it }
}
IdeaLaunchedEffect(viewModel, project = project) {
viewModel.isNewConfig.collect { isNewConfig = it }
}
IdeaLaunchedEffect(viewModel, project = project) {
viewModel.mcpPreloadingMessage.collect { mcpPreloadingMessage = it }
}
Expand Down Expand Up @@ -217,7 +221,8 @@ fun IdeaAgentApp(
viewModel.setActiveConfig(config.name)
},
currentPlan = currentPlan,
onConfigureClick = { viewModel.setShowConfigDialog(true) }
onConfigureClick = { viewModel.showEditConfigDialog() },
onAddNewConfig = { viewModel.showAddNewConfigDialog() }
)
}
)
Expand Down Expand Up @@ -266,7 +271,8 @@ fun IdeaAgentApp(
onConfigSelect = { config ->
viewModel.setActiveConfig(config.name)
},
onConfigureClick = { viewModel.setShowConfigDialog(true) }
onConfigureClick = { viewModel.showEditConfigDialog() },
onAddNewConfig = { viewModel.showAddNewConfigDialog() }
)
}
)
Expand Down Expand Up @@ -304,12 +310,13 @@ fun IdeaAgentApp(
// DialogWrapper must be created on EDT, so we use invokeLater
DisposableEffect(showConfigDialog) {
if (showConfigDialog) {
val dialogConfig = currentModelConfig ?: ModelConfig()
val dialogConfig = if (isNewConfig) ModelConfig() else (currentModelConfig ?: ModelConfig())
val dialogConfigName = if (isNewConfig) null else currentConfigName
com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater {
IdeaModelConfigDialogWrapper.show(
project = project,
currentConfig = dialogConfig,
currentConfigName = currentConfigName,
currentConfigName = dialogConfigName,
onSave = { configName, newModelConfig ->
// If creating a new config (not editing current), ensure unique name
val existingNames = availableConfigs.map { it.name }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class IdeaAgentViewModel(
// Show config dialog
private val _showConfigDialog = MutableStateFlow(false)
val showConfigDialog: StateFlow<Boolean> = _showConfigDialog.asStateFlow()
private val _isNewConfig = MutableStateFlow(false)
val isNewConfig: StateFlow<Boolean> = _isNewConfig.asStateFlow()

// Current execution job (for cancellation)
private var currentJob: Job? = null
Expand Down Expand Up @@ -587,6 +589,22 @@ class IdeaAgentViewModel(
_showConfigDialog.value = show
}

/**
* Show config dialog for adding a new config.
*/
fun showAddNewConfigDialog() {
_isNewConfig.value = true
_showConfigDialog.value = true
}

/**
* Show config dialog for editing current config.
*/
fun showEditConfigDialog() {
_isNewConfig.value = false
_showConfigDialog.value = true
}

/**
* Set the active configuration by name
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ fun IdeaDevInInputArea(
currentConfigName: String? = null,
onConfigSelect: (NamedModelConfig) -> Unit = {},
onConfigureClick: () -> Unit = {},
onAddNewConfig: () -> Unit = {},
currentPlan: AgentPlan? = null
) {
val scope = rememberIdeaCoroutineScope(project)
Expand Down Expand Up @@ -116,6 +117,11 @@ fun IdeaDevInInputArea(
onDispose { }
}

DisposableEffect(onAddNewConfig) {
swingInputArea?.setOnAddNewConfig(onAddNewConfig)
onDispose { }
}

DisposableEffect(currentPlan) {
swingInputArea?.setCurrentPlan(currentPlan)
onDispose { }
Expand All @@ -140,6 +146,7 @@ fun IdeaDevInInputArea(
it.setCurrentConfigName(currentConfigName)
it.setOnConfigSelect(onConfigSelect)
it.setOnConfigureClick(onConfigureClick)
it.setOnAddNewConfig(onAddNewConfig)
it.setCurrentPlan(currentPlan)
}
},
Expand Down Expand Up @@ -327,6 +334,10 @@ class SwingDevInInputArea(
bottomToolbar.setOnConfigureClick(callback)
}

fun setOnAddNewConfig(callback: () -> Unit) {
bottomToolbar.setOnAddNewConfig(callback)
}

fun setCurrentPlan(plan: AgentPlan?) {
currentPlan = plan
// TODO: Add plan summary bar support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ common.back=Back
common.confirm=Confirm
common.configure=Configure
common.loading=Loading
common.add=Add

# Chat UI
chat.title=AutoDev
Expand All @@ -25,6 +26,7 @@ modelConfig.temperature=Temperature
modelConfig.maxTokens=Max Tokens
modelConfig.advancedParameters=Advanced Parameters
modelConfig.configureModel=Configure Model
modelConfig.addNewConfig=Add New Config
modelConfig.noSavedConfigs=No saved configurations
modelConfig.enterModel=Enter or select model name
modelConfig.modelHint=Select from list or type custom model name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ common.back=返回
common.confirm=确认
common.configure=配置
common.loading=加载中
common.add=添加

# Chat UI
chat.title=AutoDev
Expand All @@ -25,6 +26,7 @@ modelConfig.temperature=温度
modelConfig.maxTokens=最大令牌数
modelConfig.advancedParameters=高级参数
modelConfig.configureModel=配置模型...
modelConfig.addNewConfig=新增配置
modelConfig.noSavedConfigs=没有保存的配置
modelConfig.enterModel=输入或选择模型名称
modelConfig.modelHint=从列表中选择或输入自定义模型名称
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import kotlinx.coroutines.launch
fun ModelSelector(onConfigChange: (ModelConfig) -> Unit = {}) {
var expanded by remember { mutableStateOf(false) }
var showConfigDialog by remember { mutableStateOf(false) }
var isNewConfig by remember { mutableStateOf(false) }

var availableConfigs by remember { mutableStateOf<List<NamedModelConfig>>(emptyList()) }
var currentConfigName by remember { mutableStateOf<String?>(null) }
Expand Down Expand Up @@ -135,10 +136,28 @@ fun ModelSelector(onConfigChange: (ModelConfig) -> Unit = {}) {
HorizontalDivider()
}

// Configure button
// Add New Config button
DropdownMenuItem(
text = { Text(Strings.addNewConfig) },
onClick = {
isNewConfig = true
showConfigDialog = true
expanded = false
},
leadingIcon = {
Icon(
imageVector = AutoDevComposeIcons.Add,
contentDescription = Strings.add,
modifier = Modifier.size(18.dp)
)
}
)

// Configure button (edit current config)
DropdownMenuItem(
text = { Text(Strings.configureModel) },
onClick = {
isNewConfig = false
showConfigDialog = true
expanded = false
},
Expand All @@ -154,9 +173,12 @@ fun ModelSelector(onConfigChange: (ModelConfig) -> Unit = {}) {

if (showConfigDialog) {
ModelConfigDialog(
currentConfig = currentConfig?.toModelConfig() ?: ModelConfig(),
currentConfigName = currentConfigName,
onDismiss = { showConfigDialog = false },
currentConfig = if (isNewConfig) ModelConfig() else (currentConfig?.toModelConfig() ?: ModelConfig()),
currentConfigName = if (isNewConfig) null else currentConfigName,
onDismiss = {
showConfigDialog = false
isNewConfig = false
},
onSave = { configName, newModelConfig ->
scope.launch {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ object Strings {
val confirm: String get() = AutoDevStrings.common_confirm.toString()
val configure: String get() = AutoDevStrings.common_configure.toString()
val loading: String get() = AutoDevStrings.common_loading.toString()
val add: String get() = AutoDevStrings.common_add.toString()

// Chat UI
val chatTitle: String get() = AutoDevStrings.chat_title.toString()
Expand All @@ -85,6 +86,7 @@ object Strings {
val maxTokens: String get() = AutoDevStrings.modelConfig_maxTokens.toString()
val advancedParameters: String get() = AutoDevStrings.modelConfig_advancedParameters.toString()
val configureModel: String get() = AutoDevStrings.modelConfig_configureModel.toString()
val addNewConfig: String get() = AutoDevStrings.modelConfig_addNewConfig.toString()
val noSavedConfigs: String get() = AutoDevStrings.modelConfig_noSavedConfigs.toString()
val enterModel: String get() = AutoDevStrings.modelConfig_enterModel.toString()
val enterApiKey: String get() = AutoDevStrings.modelConfig_enterApiKey.toString()
Expand Down
45 changes: 45 additions & 0 deletions mpp-vscode/src/providers/chat-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
message.data?.completionIndex as number
);
break;
case 'saveModelConfig':
// Save model configuration
await this.handleSaveModelConfig(message.data);
break;
}
});

Expand All @@ -192,6 +196,47 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
}
}

/**
* Save model configuration
*/
private async handleSaveModelConfig(data: any): Promise<void> {
try {
const config: LLMConfig = {
name: data.name as string,
provider: data.provider as LLMProvider,
apiKey: data.apiKey as string,
model: data.model as string,
baseUrl: data.baseUrl as string | undefined,
temperature: data.temperature as number | undefined,
maxTokens: data.maxTokens as number | undefined,
};

// Check for duplicate names if it's a new config
if (data.isNewConfig) {
const wrapper = await ConfigManager.load();
const existingNames = wrapper.getAllConfigs().map(c => c.name);

if (existingNames.includes(config.name)) {
// Generate unique name
config.name = ConfigManager.generateUniqueConfigName(config.name, existingNames);
}
}

// Save config
await ConfigManager.saveConfig(config, true);

// Reload config and notify webview
this.configWrapper = await ConfigManager.load();
this.sendConfigUpdate();

vscode.window.showInformationMessage(`Configuration "${config.name}" saved successfully`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
vscode.window.showErrorMessage(`Failed to save configuration: ${errorMessage}`);
this.log(`Failed to save model config: ${errorMessage}`);
}
}

/**
* Select a different config
*/
Expand Down
Loading
Loading