Skip to content

Commit 982f989

Browse files
committed
feat(llm): enhance model management UI with additional fields for Model and Temperature, and improve validation
1 parent 86cf62f commit 982f989

File tree

1 file changed

+134
-41
lines changed

1 file changed

+134
-41
lines changed

core/src/main/kotlin/cc/unitmesh/devti/settings/SimplifiedLLMSettingComponent.kt

Lines changed: 134 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
6060
// Category section panel for dynamic visibility
6161
private var categoryPanel: JPanel? = null
6262

63-
// Model management - simplified table with only Name and ID
64-
private val llmTableModel = object : DefaultTableModel(arrayOf("Name", "ID", "Delete"), 0) {
63+
// Model management - table with Name, Model, Streaming, Temperature, Delete
64+
private val llmTableModel = object : DefaultTableModel(arrayOf("Name", "Model", "Streaming", "Temperature", "Delete"), 0) {
6565
override fun isCellEditable(row: Int, column: Int): Boolean {
66-
return column == 2 // Only delete column is "editable" (clickable)
66+
return column == 4 // Only delete column is "editable" (clickable)
6767
}
6868
}
6969
private val llmTable = JTable(llmTableModel)
@@ -140,19 +140,21 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
140140
val row = llmTable.rowAtPoint(e.point)
141141
val column = llmTable.columnAtPoint(e.point)
142142

143-
// Only allow double-click on Name or ID columns (not Delete column)
144-
if (row >= 0 && column < 2) {
143+
// Only allow double-click on Name, Model, Streaming, Temperature columns (not Delete column)
144+
if (row >= 0 && column < 4) {
145145
editLLMAtRow(row)
146146
}
147147
}
148148
}
149149
})
150150

151151
// Set column widths
152-
llmTable.columnModel.getColumn(0).preferredWidth = 200 // Name
153-
llmTable.columnModel.getColumn(1).preferredWidth = 300 // ID
154-
llmTable.columnModel.getColumn(2).preferredWidth = 80 // Delete
155-
llmTable.columnModel.getColumn(2).maxWidth = 80
152+
llmTable.columnModel.getColumn(0).preferredWidth = 150 // Name
153+
llmTable.columnModel.getColumn(1).preferredWidth = 200 // Model
154+
llmTable.columnModel.getColumn(2).preferredWidth = 80 // Streaming
155+
llmTable.columnModel.getColumn(3).preferredWidth = 100 // Temperature
156+
llmTable.columnModel.getColumn(4).preferredWidth = 80 // Delete
157+
llmTable.columnModel.getColumn(4).maxWidth = 80
156158
}
157159

158160
private fun markAsModified() {
@@ -192,7 +194,11 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
192194
private val tokenField = JBTextField()
193195
private val maxTokensField = JBTextField()
194196
private val modelTypeComboBox = JComboBox(ModelType.values())
197+
198+
// Explicit model parameters
199+
private val modelField = JBTextField()
195200
private val streamCheckbox = JBCheckBox("Use streaming response", true)
201+
private val temperatureField = JBTextField()
196202

197203
// Custom headers and body fields
198204
private val headersArea = JTextArea(3, 40)
@@ -212,7 +218,24 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
212218
modelTypeComboBox.selectedItem = existingLlm.modelType
213219
streamCheckbox.isSelected = existingLlm.customRequest.stream
214220

215-
// Initialize custom headers and body
221+
// Extract model and temperature from body
222+
val modelValue = existingLlm.customRequest.body["model"]?.let {
223+
when (it) {
224+
is JsonPrimitive -> it.content
225+
else -> it.toString().removeSurrounding("\"")
226+
}
227+
} ?: ""
228+
modelField.text = modelValue
229+
230+
val temperatureValue = existingLlm.customRequest.body["temperature"]?.let {
231+
when (it) {
232+
is JsonPrimitive -> it.content
233+
else -> it.toString()
234+
}
235+
} ?: "0.0"
236+
temperatureField.text = temperatureValue
237+
238+
// Initialize custom headers and body (excluding model and temperature)
216239
headersArea.text = if (existingLlm.customRequest.headers.isNotEmpty()) {
217240
buildJsonObject {
218241
existingLlm.customRequest.headers.forEach { (key, value) ->
@@ -223,29 +246,34 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
223246
"{}"
224247
}
225248

226-
bodyArea.text = if (existingLlm.customRequest.body.isNotEmpty()) {
227-
// Convert body map to JSON string
249+
// Body without model and temperature (they are now explicit fields)
250+
val bodyWithoutModelTemp = existingLlm.customRequest.body.filterKeys {
251+
it != "model" && it != "temperature" && it != "stream"
252+
}
253+
bodyArea.text = if (bodyWithoutModelTemp.isNotEmpty()) {
228254
buildJsonObject {
229-
existingLlm.customRequest.body.forEach { (key, value) ->
255+
bodyWithoutModelTemp.forEach { (key, value) ->
230256
put(key, value)
231257
}
232258
}.toString()
233259
} else {
234-
"""{"model": "gpt-3.5-turbo", "temperature": 0.0}"""
260+
"{}"
235261
}
236262
} else {
237263
// Default values for new LLM
238264
maxTokensField.text = "4096"
265+
modelField.text = "gpt-3.5-turbo"
266+
temperatureField.text = "0.0"
239267
headersArea.text = "{}"
240-
bodyArea.text = """{"model": "gpt-3.5-turbo", "temperature": 0.0}"""
268+
bodyArea.text = "{}"
241269
}
242270

243271
init()
244272
}
245273

246274
override fun createCenterPanel(): JPanel {
247275
val panel = JPanel(BorderLayout())
248-
panel.preferredSize = Dimension(600, 600)
276+
panel.preferredSize = Dimension(600, 700)
249277

250278
val formBuilder = FormBuilder.createFormBuilder()
251279
.addLabeledComponent(JBLabel("Name:"), nameField)
@@ -254,14 +282,20 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
254282
.addLabeledComponent(JBLabel("Token (optional):"), tokenField)
255283
.addLabeledComponent(JBLabel("Max Tokens:"), maxTokensField)
256284
.addLabeledComponent(JBLabel("Model Type:"), modelTypeComboBox)
257-
.addComponent(streamCheckbox)
258285
.addSeparator()
259286

287+
// Model parameters section
288+
formBuilder.addLabeledComponent(JBLabel("Model Parameters"), JPanel(), 1, false)
289+
formBuilder.addLabeledComponent(JBLabel("Model:"), modelField)
290+
formBuilder.addComponent(streamCheckbox)
291+
formBuilder.addLabeledComponent(JBLabel("Temperature:"), temperatureField)
292+
formBuilder.addSeparator()
293+
260294
// Custom headers section
261295
formBuilder.addLabeledComponent(JBLabel("Custom Headers (JSON):"), JScrollPane(headersArea))
262296

263-
// Custom body section
264-
formBuilder.addLabeledComponent(JBLabel("Request Body (JSON):"), JScrollPane(bodyArea))
297+
// Custom body section (additional fields)
298+
formBuilder.addLabeledComponent(JBLabel("Additional Request Body (JSON):"), JScrollPane(bodyArea))
265299

266300
formBuilder.addSeparator()
267301

@@ -276,8 +310,8 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
276310
}
277311

278312
private fun testConnection() {
279-
if (nameField.text.isBlank() || urlField.text.isBlank()) {
280-
testResultLabel.text = "Name and URL are required"
313+
if (nameField.text.isBlank() || urlField.text.isBlank() || modelField.text.isBlank()) {
314+
testResultLabel.text = "Name, URL, and Model are required"
281315
testResultLabel.foreground = JBColor.RED
282316
return
283317
}
@@ -291,13 +325,22 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
291325
return
292326
}
293327

328+
// Validate temperature
329+
val temperature = try {
330+
temperatureField.text.toDouble()
331+
} catch (e: NumberFormatException) {
332+
testResultLabel.text = "Temperature must be a valid number"
333+
testResultLabel.foreground = JBColor.RED
334+
return
335+
}
336+
294337
testResultLabel.text = "Testing connection..."
295338
testResultLabel.foreground = JBColor.BLUE
296339

297340
val scope = CoroutineScope(CoroutineName("testConnection"))
298341
scope.launch {
299342
try {
300-
// Parse custom headers and body
343+
// Parse custom headers
301344
val headers = try {
302345
if (headersArea.text.trim().isNotEmpty() && headersArea.text.trim() != "{}") {
303346
Json.decodeFromString<Map<String, String>>(headersArea.text)
@@ -312,25 +355,34 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
312355
return@launch
313356
}
314357

315-
val body = try {
316-
if (bodyArea.text.trim().isNotEmpty()) {
358+
// Parse additional body fields and combine with explicit parameters
359+
val additionalBody = try {
360+
if (bodyArea.text.trim().isNotEmpty() && bodyArea.text.trim() != "{}") {
317361
val jsonElement = Json.parseToJsonElement(bodyArea.text)
318362
if (jsonElement is JsonObject) {
319363
jsonElement.toMap()
320364
} else {
321-
mapOf("model" to JsonPrimitive(nameField.text), "temperature" to JsonPrimitive(0.0))
365+
emptyMap()
322366
}
323367
} else {
324-
mapOf("model" to JsonPrimitive(nameField.text), "temperature" to JsonPrimitive(0.0))
368+
emptyMap()
325369
}
326370
} catch (e: Exception) {
327371
SwingUtilities.invokeLater {
328-
testResultLabel.text = "Invalid body JSON: ${e.message}"
372+
testResultLabel.text = "Invalid additional body JSON: ${e.message}"
329373
testResultLabel.foreground = JBColor.RED
330374
}
331375
return@launch
332376
}
333377

378+
// Combine explicit parameters with additional body
379+
val body = mutableMapOf<String, JsonElement>().apply {
380+
put("model", JsonPrimitive(modelField.text))
381+
put("temperature", JsonPrimitive(temperature))
382+
put("stream", JsonPrimitive(streamCheckbox.isSelected))
383+
putAll(additionalBody)
384+
}
385+
334386
// Create a temporary LLM config for testing
335387
val customRequest = CustomRequest(
336388
headers = headers,
@@ -378,8 +430,8 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
378430
}
379431

380432
override fun doOKAction() {
381-
if (nameField.text.isBlank() || urlField.text.isBlank()) {
382-
Messages.showErrorDialog("Name and URL are required", "Validation Error")
433+
if (nameField.text.isBlank() || urlField.text.isBlank() || modelField.text.isBlank()) {
434+
Messages.showErrorDialog("Name, URL, and Model are required", "Validation Error")
383435
return
384436
}
385437

@@ -391,8 +443,16 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
391443
return
392444
}
393445

446+
// Validate temperature
447+
val temperature = try {
448+
temperatureField.text.toDouble()
449+
} catch (e: NumberFormatException) {
450+
Messages.showErrorDialog("Temperature must be a valid number", "Validation Error")
451+
return
452+
}
453+
394454
try {
395-
// Parse custom headers and body
455+
// Parse custom headers
396456
val headers = try {
397457
if (headersArea.text.trim().isNotEmpty() && headersArea.text.trim() != "{}") {
398458
Json.decodeFromString<Map<String, String>>(headersArea.text)
@@ -404,22 +464,31 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
404464
return
405465
}
406466

407-
val body = try {
408-
if (bodyArea.text.trim().isNotEmpty()) {
467+
// Parse additional body fields and combine with explicit parameters
468+
val additionalBody = try {
469+
if (bodyArea.text.trim().isNotEmpty() && bodyArea.text.trim() != "{}") {
409470
val jsonElement = Json.parseToJsonElement(bodyArea.text)
410471
if (jsonElement is JsonObject) {
411472
jsonElement.toMap()
412473
} else {
413-
mapOf("model" to JsonPrimitive(nameField.text), "temperature" to JsonPrimitive(0.0))
474+
emptyMap()
414475
}
415476
} else {
416-
mapOf("model" to JsonPrimitive(nameField.text), "temperature" to JsonPrimitive(0.0))
477+
emptyMap()
417478
}
418479
} catch (e: Exception) {
419-
Messages.showErrorDialog("Invalid body JSON: ${e.message}", "Validation Error")
480+
Messages.showErrorDialog("Invalid additional body JSON: ${e.message}", "Validation Error")
420481
return
421482
}
422483

484+
// Combine explicit parameters with additional body
485+
val body = mutableMapOf<String, JsonElement>().apply {
486+
put("model", JsonPrimitive(modelField.text))
487+
put("temperature", JsonPrimitive(temperature))
488+
put("stream", JsonPrimitive(streamCheckbox.isSelected))
489+
putAll(additionalBody)
490+
}
491+
423492
// Get existing LLMs
424493
val existingLlms = try {
425494
LlmConfig.load().toMutableList()
@@ -575,23 +644,41 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
575644
val githubModels = manager.getSupportedModels(forceRefresh = false)
576645
val userModels = LlmConfig.load()
577646

578-
// Add GitHub Copilot models (read-only) - simplified display
647+
// Add GitHub Copilot models (read-only)
579648
githubModels?.forEach { model ->
580649
llmTableModel.addRow(
581650
arrayOf(
582-
"Github: ${model.id}", // Name - show as "Github: model.id"
583-
model.id, // ID
651+
"Github: ${model.id}", // Name
652+
model.id, // Model
653+
"true", // Streaming (GitHub models use streaming by default)
654+
"0.1", // Temperature (GitHub models default temperature)
584655
"" // Delete (empty for read-only models)
585656
)
586657
)
587658
}
588659

589-
// Add custom LLMs (editable) - simplified display
660+
// Add custom LLMs (editable)
590661
userModels.forEach { llm ->
662+
val modelValue = llm.customRequest.body["model"]?.let {
663+
when (it) {
664+
is JsonPrimitive -> it.content
665+
else -> it.toString().removeSurrounding("\"")
666+
}
667+
} ?: ""
668+
669+
val temperatureValue = llm.customRequest.body["temperature"]?.let {
670+
when (it) {
671+
is JsonPrimitive -> it.content
672+
else -> it.toString()
673+
}
674+
} ?: "0.0"
675+
591676
llmTableModel.addRow(
592677
arrayOf(
593678
llm.name, // Name
594-
llm.name, // ID (use name as ID for custom models)
679+
modelValue, // Model
680+
llm.customRequest.stream.toString(), // Streaming
681+
temperatureValue, // Temperature
595682
"Delete" // Delete button placeholder
596683
)
597684
)
@@ -768,11 +855,17 @@ class SimplifiedLLMSettingComponent(private val settings: AutoDevSettingsState)
768855
// Add category panel (visibility controlled dynamically)
769856
formBuilder.addComponent(categoryPanel!!)
770857

858+
// Create a properly sized scroll pane for the table
859+
val tableScrollPane = JScrollPane(llmTable).apply {
860+
preferredSize = Dimension(600, 200)
861+
minimumSize = Dimension(400, 150)
862+
}
863+
771864
formBuilder
772865
// Model Management Section
773866
.addLabeledComponent(JBLabel("Model Management"), JPanel(), 1, false)
774867
.addComponent(buttonPanel)
775-
.addComponentFillVertically(JScrollPane(llmTable), 0)
868+
.addComponentFillVertically(tableScrollPane, 0)
776869
.addComponentFillVertically(JPanel(), 0)
777870

778871
// Set initial visibility

0 commit comments

Comments
 (0)