claude refactor

This commit is contained in:
Radek Davidek 2026-03-29 17:55:02 +02:00
parent 1e6b33175a
commit d1358cc6d3
6 changed files with 182 additions and 235 deletions

View File

@ -1,5 +1,5 @@
#Llama Runner Configuration #Llama Runner Configuration
#Sun Mar 29 17:31:07 CEST 2026 #Sun Mar 29 17:54:05 CEST 2026
windowHeight=1189 windowHeight=1189
windowWidth=711 windowWidth=711
windowX=1849 windowX=1849

View File

@ -10,112 +10,73 @@ import com.google.gson.JsonSyntaxException;
*/ */
public class ConfigValidation { public class ConfigValidation {
private static final Gson GSON = new Gson();
/** /**
* Validates numeric fields for valid ranges. * Validates a ModelConfig object.
* *
* @return a list of error messages (empty if valid) * @param config the model config to validate
* @return error message if invalid, null if valid
*/ */
public static java.util.List<String> validateNumericInputs( public static String validateConfig(ModelConfig config) {
String host,
int port,
int parallel,
int threads,
double temperature,
double topP,
int topK,
double minP,
int ctxSize,
int ngl) {
java.util.List<String> errors = new java.util.ArrayList<>();
// Validate port // Validate port
if (port < 1 || port > 65535) { if (config.getPort() < 1 || config.getPort() > 65535) {
errors.add("Port must be between 1 and 65535"); return "Port must be between 1 and 65535";
} }
// Validate parallel // Validate parallel
if (parallel < 1) { if (config.getParallel() < 1) {
errors.add("Parallel must be at least 1"); return "Parallel must be at least 1";
} }
// Validate threads // Validate threads
if (threads < 1) { if (config.getThreads() < 1) {
errors.add("Threads must be at least 1"); return "Threads must be at least 1";
} }
// Validate temperature // Validate temperature
if (temperature < 0) { if (config.getTemperature() < 0) {
errors.add("Temperature must be non-negative"); return "Temperature must be non-negative";
} }
// Validate topP // Validate topP
if (topP < 0 || topP > 1) { if (config.getTopP() < 0 || config.getTopP() > 1) {
errors.add("Top P must be between 0 and 1"); return "Top P must be between 0 and 1";
} }
// Validate topK // Validate topK
if (topK < 0) { if (config.getTopK() < 0) {
errors.add("Top K must be non-negative"); return "Top K must be non-negative";
} }
// Validate minP // Validate minP
if (minP < 0 || minP > 1) { if (config.getMinP() < 0 || config.getMinP() > 1) {
errors.add("Min P must be between 0 and 1"); return "Min P must be between 0 and 1";
} }
// Validate ctxSize // Validate ctxSize
if (ctxSize < 0) { if (config.getCtxSize() < 0) {
errors.add("Context size must be non-negative"); return "Context size must be non-negative";
} }
// Validate ngl // Validate model path
if (ngl < 0) { if (config.getModelPath() == null || config.getModelPath().trim().isEmpty()) {
errors.add("GPU layers must be non-negative");
}
return errors;
}
/**
* Validates JSON string for chatTemplateKwargs.
*
* @param json the JSON string to validate
* @return error message if invalid, null if valid
*/
public static String validateJsonKwargs(String json) {
if (json == null || json.trim().isEmpty()) {
return null;
}
try {
GSON.fromJson(json, Object.class);
return null;
} catch (JsonSyntaxException e) {
return "Invalid JSON format in kwargs";
}
}
/**
* Validates model path exists.
*
* @param modelPath the model file path
* @return error message if file doesn't exist, null if valid
*/
public static String validateModelPath(String modelPath) {
if (modelPath == null || modelPath.trim().isEmpty()) {
return "Model path cannot be empty"; return "Model path cannot be empty";
} }
File modelFile = new File(config.getModelPath());
File modelFile = new File(modelPath);
if (!modelFile.exists()) { if (!modelFile.exists()) {
return "Model file does not exist: " + modelPath; return "Model file does not exist: " + config.getModelPath();
}
if (!modelFile.isFile()) {
return "Model path is not a file: " + config.getModelPath();
} }
if (!modelFile.isFile()) { // Validate JSON kwargs
return "Model path is not a file: " + modelPath; String json = config.getChatTemplateKwargs();
if (json != null && !json.trim().isEmpty()) {
try {
new Gson().fromJson(json, Object.class);
} catch (JsonSyntaxException e) {
return "Invalid JSON format in kwargs";
}
} }
return null; return null;

View File

@ -0,0 +1,95 @@
package cz.kamma.llamarunner;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* Reusable dialog for simple text input.
*/
public class InputDialog {
private final JDialog dialog;
private final String[] result = new String[1];
/**
* Shows a dialog for text input.
*
* @param parent the parent frame
* @param title the dialog title
* @param label the label text
* @param defaultValue the default value
* @return the input text, or null if cancelled
*/
public static String showInputDialog(java.awt.Frame parent, String title, String label, String defaultValue) {
InputDialog dialog = new InputDialog(parent, title, label, defaultValue);
dialog.show();
return dialog.getResult();
}
private InputDialog(java.awt.Frame parent, String title, String label, String defaultValue) {
dialog = new JDialog(parent, title, true);
dialog.setResizable(false);
// Input panel
JPanel inputPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 0;
inputPanel.add(new JLabel(label), gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
JTextField textField = new JTextField(20);
textField.setText(defaultValue);
textField.setCaretColor(Color.WHITE);
inputPanel.add(textField, gbc);
dialog.add(inputPanel, BorderLayout.CENTER);
// Button panel
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0));
JButton okButton = new JButton("OK");
okButton.addActionListener(e -> {
result[0] = textField.getText().trim();
dialog.dispose();
});
buttonPanel.add(okButton);
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(e -> {
result[0] = null;
dialog.dispose();
});
buttonPanel.add(cancelButton);
dialog.add(buttonPanel, BorderLayout.SOUTH);
// Center dialog
dialog.setSize(350, 100);
dialog.setLocationRelativeTo(parent);
}
private void show() {
dialog.setVisible(true);
}
private String getResult() {
if (result[0] == null) {
return null;
}
return result[0].isEmpty() ? null : result[0];
}
}

View File

@ -22,7 +22,6 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JCheckBox; import javax.swing.JCheckBox;
import javax.swing.JComboBox; import javax.swing.JComboBox;
@ -313,16 +312,27 @@ public class Main extends JFrame {
} }
try { try {
String validationError = validateCurrentConfig();
if (validationError != null) {
JOptionPane.showMessageDialog(this, validationError, "Validation Error",
JOptionPane.ERROR_MESSAGE);
return;
}
saveProfileToName(currentProfile); saveProfileToName(currentProfile);
JOptionPane.showMessageDialog(this, "Profile saved!"); JOptionPane.showMessageDialog(this, "Profile saved!");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
JOptionPane.showMessageDialog(this, "Error saving: " + e.getMessage() + "\n" + e.toString(), "Error", JOptionPane.showMessageDialog(this, "Error saving: " + e.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
} }
} }
private String validateCurrentConfig() {
ModelConfig config = buildModelConfig();
return ConfigValidation.validateConfig(config);
}
private void saveProfileToName(String name) throws IOException { private void saveProfileToName(String name) throws IOException {
ModelConfig config = buildModelConfig(); ModelConfig config = buildModelConfig();
profileManager.saveProfile(name, config); profileManager.saveProfile(name, config);
@ -404,58 +414,9 @@ public class Main extends JFrame {
String modelName = (String) modelComboBox.getSelectedItem(); String modelName = (String) modelComboBox.getSelectedItem();
String defaultName = modelName != null ? modelName : ""; String defaultName = modelName != null ? modelName : "";
String[] result = new String[1]; String newName = InputDialog.showInputDialog(this, "Save profile as...", "Profile name:", defaultName);
JDialog dialog = new JDialog(this, "Save profile as...", true); if (newName == null) {
dialog.setLayout(new BorderLayout(8, 8));
dialog.setResizable(false);
// Input panel
JPanel inputPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 0;
inputPanel.add(new JLabel("Profile name:"), gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
JTextField nameField = new JTextField(20);
nameField.setText(defaultName);
nameField.setCaretColor(Color.WHITE);
inputPanel.add(nameField, gbc);
dialog.add(inputPanel, BorderLayout.CENTER);
// Button panel
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0));
JButton okButton = new JButton("OK");
okButton.addActionListener(e -> {
result[0] = nameField.getText().trim();
dialog.dispose();
});
buttonPanel.add(okButton);
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(e -> {
result[0] = null;
dialog.dispose();
});
buttonPanel.add(cancelButton);
dialog.add(buttonPanel, BorderLayout.SOUTH);
// Center dialog
dialog.setSize(350, 100);
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
String newName = result[0];
if (newName == null || newName.isEmpty()) {
return; // Cancelled by user return; // Cancelled by user
} }
@ -531,58 +492,9 @@ public class Main extends JFrame {
return; return;
} }
String[] result = new String[1]; String newName = InputDialog.showInputDialog(this, "Rename profile", "New profile name:", oldName);
JDialog dialog = new JDialog(this, "Rename profile", true); if (newName == null) {
dialog.setLayout(new BorderLayout(8, 8));
dialog.setResizable(false);
// Input panel
JPanel inputPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 0;
inputPanel.add(new JLabel("New profile name:"), gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
JTextField nameField = new JTextField(20);
nameField.setText(oldName);
nameField.setCaretColor(Color.WHITE);
inputPanel.add(nameField, gbc);
dialog.add(inputPanel, BorderLayout.CENTER);
// Button panel
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0));
JButton okButton = new JButton("OK");
okButton.addActionListener(e -> {
result[0] = nameField.getText().trim();
dialog.dispose();
});
buttonPanel.add(okButton);
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(e -> {
result[0] = null;
dialog.dispose();
});
buttonPanel.add(cancelButton);
dialog.add(buttonPanel, BorderLayout.SOUTH);
// Center dialog
dialog.setSize(350, 100);
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
String newName = result[0];
if (newName == null || newName.isEmpty()) {
return; // Cancelled by user return; // Cancelled by user
} }
@ -895,15 +807,7 @@ public class Main extends JFrame {
ctxSizeField = new JTextField("180000", 10); ctxSizeField = new JTextField("180000", 10);
ctxSizeField.setCaretColor(Color.WHITE); ctxSizeField.setCaretColor(Color.WHITE);
ctxSizeField.addActionListener(e -> updateCommandPreview()); ctxSizeField.addActionListener(e -> updateCommandPreview());
// Update preview on any change, not just Enter ctxSizeField.getDocument().addDocumentListener(new PreviewUpdateListener(this::updateCommandPreview));
ctxSizeField.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
@Override
public void insertUpdate(javax.swing.event.DocumentEvent e) { updateCommandPreview(); }
@Override
public void removeUpdate(javax.swing.event.DocumentEvent e) { updateCommandPreview(); }
@Override
public void changedUpdate(javax.swing.event.DocumentEvent e) { updateCommandPreview(); }
});
panel.add(ctxSizeField, gbc); panel.add(ctxSizeField, gbc);
gbc.gridx = 0; gbc.gridx = 0;
@ -916,22 +820,7 @@ public class Main extends JFrame {
gbc.weightx = 1.0; gbc.weightx = 1.0;
kwargsField = new JTextField("{\"enable_thinking\": true}"); kwargsField = new JTextField("{\"enable_thinking\": true}");
kwargsField.setCaretColor(Color.WHITE); kwargsField.setCaretColor(Color.WHITE);
kwargsField.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() { kwargsField.getDocument().addDocumentListener(new PreviewUpdateListener(this::updateCommandPreview));
@Override
public void insertUpdate(javax.swing.event.DocumentEvent e) {
updateCommandPreview();
}
@Override
public void removeUpdate(javax.swing.event.DocumentEvent e) {
updateCommandPreview();
}
@Override
public void changedUpdate(javax.swing.event.DocumentEvent e) {
updateCommandPreview();
}
});
panel.add(kwargsField, gbc); panel.add(kwargsField, gbc);
return panel; return panel;
@ -987,22 +876,7 @@ public class Main extends JFrame {
} }
private javax.swing.event.DocumentListener createNglDocumentListener() { private javax.swing.event.DocumentListener createNglDocumentListener() {
return new javax.swing.event.DocumentListener() { return new PreviewUpdateListener(this::updateCommandPreview);
@Override
public void insertUpdate(javax.swing.event.DocumentEvent e) {
updateCommandPreview();
}
@Override
public void removeUpdate(javax.swing.event.DocumentEvent e) {
updateCommandPreview();
}
@Override
public void changedUpdate(javax.swing.event.DocumentEvent e) {
updateCommandPreview();
}
};
} }
private void copyCommandToClipboard() { private void copyCommandToClipboard() {

View File

@ -0,0 +1,31 @@
package cz.kamma.llamarunner;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* DocumentListener that updates command preview on text changes.
*/
public class PreviewUpdateListener implements DocumentListener {
private final Runnable updateCallback;
public PreviewUpdateListener(Runnable updateCallback) {
this.updateCallback = updateCallback;
}
@Override
public void insertUpdate(DocumentEvent e) {
updateCallback.run();
}
@Override
public void removeUpdate(DocumentEvent e) {
updateCallback.run();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateCallback.run();
}
}

View File

@ -34,18 +34,4 @@ public class ProfileValidator {
return null; return null;
} }
/** }
* Checks if a profile name is unique among existing profiles.
*
* @param newName the new profile name
* @param existingProfiles list of existing profile names
* @return true if unique, false otherwise
*/
public static boolean isProfileNameUnique(String newName, java.util.List<String> existingProfiles) {
if (existingProfiles == null) {
return true;
}
return !existingProfiles.stream()
.anyMatch(name -> name.equalsIgnoreCase(newName));
}
}