Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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
35 changes: 33 additions & 2 deletions mpp-vscode/webview/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import { Timeline } from './components/Timeline';
import { ChatInput } from './components/ChatInput';
import { ModelConfig } from './components/ModelSelector';
import { ModelConfigDialog } from './components/ModelConfigDialog';
import { SelectedFile } from './components/FileChip';
import { CompletionItem } from './components/CompletionPopup';
import { PlanData } from './components/plan';
Expand Down Expand Up @@ -74,6 +75,11 @@ const App: React.FC = () => {
// Omnibar state
const [showOmnibar, setShowOmnibar] = useState(false);

// Model config dialog state
const [showModelConfigDialog, setShowModelConfigDialog] = useState(false);
const [modelConfigDialogConfig, setModelConfigDialogConfig] = useState<ModelConfig | null>(null);
const [isNewConfig, setIsNewConfig] = useState(false);

const { postMessage, onMessage, isVSCode } = useVSCode();

// Handle messages from extension
Expand Down Expand Up @@ -327,8 +333,22 @@ const App: React.FC = () => {

// Handle open config
const handleOpenConfig = useCallback(() => {
postMessage({ type: 'openConfig' });
}, [postMessage]);
const currentConfig = configState.availableConfigs.find(c => c.name === configState.currentConfigName);
if (currentConfig) {
setModelConfigDialogConfig(currentConfig);
setIsNewConfig(false);
setShowModelConfigDialog(true);
} else {
postMessage({ type: 'openConfig' });
}
}, [postMessage, configState]);

// Handle add new config
const handleAddNewConfig = useCallback(() => {
setModelConfigDialogConfig(null);
setIsNewConfig(true);
setShowModelConfigDialog(true);
}, []);

// Handle stop execution
const handleStop = useCallback(() => {
Expand Down Expand Up @@ -470,6 +490,7 @@ const App: React.FC = () => {
onStop={handleStop}
onConfigSelect={handleConfigSelect}
onConfigureClick={handleOpenConfig}
onAddNewConfig={handleAddNewConfig}
onMcpConfigClick={handleMcpConfigClick}
onPromptOptimize={handlePromptOptimize}
onGetCompletions={handleGetCompletions}
Expand All @@ -484,6 +505,16 @@ const App: React.FC = () => {
totalTokens={totalTokens}
currentPlan={currentPlan}
/>

{/* Model Config Dialog */}
{showModelConfigDialog && (
<ModelConfigDialog
isOpen={showModelConfigDialog}
onClose={() => setShowModelConfigDialog(false)}
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.

When the dialog is closed via onClose, the isNewConfig state is not reset. This could lead to unexpected behavior if the user opens the "Configure Model" dialog after closing the "Add New Config" dialog, as the isNewConfig flag would still be true.

The UI version properly handles this by resetting isNewConfig to false in the onDismiss handler (see mpp-ui ModelSelector.kt lines 178-181). Consider adding similar state cleanup:

onClose={() => {
  setShowModelConfigDialog(false);
  setIsNewConfig(false);
  setModelConfigDialogConfig(null);
}}
Suggested change
onClose={() => setShowModelConfigDialog(false)}
onClose={() => {
setShowModelConfigDialog(false);
setIsNewConfig(false);
setModelConfigDialogConfig(null);
}}

Copilot uses AI. Check for mistakes.
currentConfig={modelConfigDialogConfig || undefined}
isNewConfig={isNewConfig}
/>
)}
</div>
);
};
Expand Down
3 changes: 3 additions & 0 deletions mpp-vscode/webview/src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface ChatInputProps {
onStop?: () => void;
onConfigSelect?: (config: ModelConfig) => void;
onConfigureClick?: () => void;
onAddNewConfig?: () => void;
onMcpConfigClick?: () => void;
onPromptOptimize?: (prompt: string) => Promise<string>;
onGetCompletions?: (text: string, cursorPosition: number) => void;
Expand All @@ -34,6 +35,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
onStop,
onConfigSelect,
onConfigureClick,
onAddNewConfig,
onMcpConfigClick,
onPromptOptimize,
onGetCompletions,
Expand Down Expand Up @@ -225,6 +227,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
currentConfigName={currentConfigName}
onConfigSelect={onConfigSelect || (() => {})}
onConfigureClick={onConfigureClick || (() => {})}
onAddNewConfig={onAddNewConfig}
/>
{/* Token usage indicator */}
{totalTokens != null && totalTokens > 0 && (
Expand Down
Loading
Loading