Enhance App model management: add model settings and argument handling

This commit is contained in:
extio-de
2026-05-30 11:07:11 +02:00
parent 7b83576c04
commit b44c988d5a
6 changed files with 149 additions and 26 deletions

View File

@@ -1 +1 @@
{"apps":[{"path":"file:///home/sb/lm/koboldcpp-rocm/koboldcpp.py","interpreter":"file:///usr/bin/python","arguments":[],"appArguments":[{"argument":"--usecublas","optional":true,"default_":true},{"argument":"mmq","optional":true,"default_":false},{"argument":"lowvram","optional":true,"default_":false},{"argument":"0","optional":true,"default_":true},{"argument":"--gpulayers","optional":true,"default_":true},{"argument":"GPU_LAYERS","optional":true,"default_":true},{"argument":"--smartcontext","optional":true,"default_":false},{"argument":"--noshift","optional":true,"default_":false},{"argument":"--contextsize","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"--launch","optional":false,"default_":true},{"argument":"--host","optional":true,"default_":false},{"argument":"0.0.0.0","optional":true,"default_":false},{"argument":"--threads","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"--model","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}]},{"path":"file:///home/sb/lm/llama.cpp/llama-server","interpreter":null,"arguments":[],"appArguments":[{"argument":"-nkvo","optional":true,"default_":false},{"argument":"--parallel","optional":true,"default_":false},{"argument":"2","optional":true,"default_":false},{"argument":"4","optional":true,"default_":false},{"argument":"--host","optional":true,"default_":true},{"argument":"0.0.0.0","optional":true,"default_":true},{"argument":"--port","optional":false,"default_":true},{"argument":"5001","optional":false,"default_":true},{"argument":"-t","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"-ngl","optional":false,"default_":true},{"argument":"GPU_LAYERS","optional":false,"default_":true},{"argument":"--ctx-size","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"-m","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}]},{"path":"file:///home/sb/lm/SillyTavern/start.sh","interpreter":null,"arguments":[],"appArguments":[]},{"path":"file:///home/sb/lm/ollama-oai-bridge/start.sh","interpreter":null,"arguments":[],"appArguments":[]}]}
{"apps":[{"path":"file:///home/sb/lm/koboldcpp-rocm/koboldcpp.py","interpreter":"file:///usr/bin/python","arguments":[],"appArguments":[{"argument":"--usecublas","optional":true,"default_":true},{"argument":"mmq","optional":true,"default_":false},{"argument":"lowvram","optional":true,"default_":false},{"argument":"0","optional":true,"default_":true},{"argument":"--gpulayers","optional":true,"default_":true},{"argument":"GPU_LAYERS","optional":true,"default_":true},{"argument":"--smartcontext","optional":true,"default_":false},{"argument":"--noshift","optional":true,"default_":false},{"argument":"--contextsize","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"--launch","optional":false,"default_":true},{"argument":"--host","optional":true,"default_":false},{"argument":"0.0.0.0","optional":true,"default_":false},{"argument":"--threads","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"--model","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}],"modelSettings":[]},{"path":"file:///home/sb/lm/llama.cpp/llama-server","interpreter":null,"arguments":[],"appArguments":[{"argument":"-nkvo","optional":true,"default_":false},{"argument":"--parallel","optional":true,"default_":false},{"argument":"2","optional":true,"default_":false},{"argument":"4","optional":true,"default_":false},{"argument":"--host","optional":true,"default_":true},{"argument":"0.0.0.0","optional":true,"default_":true},{"argument":"--port","optional":false,"default_":true},{"argument":"5001","optional":false,"default_":true},{"argument":"-t","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"-ngl","optional":false,"default_":true},{"argument":"GPU_LAYERS","optional":false,"default_":true},{"argument":"--ctx-size","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"-m","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}],"modelSettings":[{"modelPath":"/mnt/data/data/lm/models/gguf/m/Qwen3.6-35B-A3B-UD-Q5_K_XL-mtp.gguf","appArguments":[{"argument":"-nkvo","optional":true,"default_":false},{"argument":"--parallel","optional":true,"default_":true},{"argument":"2","optional":true,"default_":true},{"argument":"4","optional":true,"default_":false},{"argument":"--host","optional":true,"default_":true},{"argument":"0.0.0.0","optional":true,"default_":true},{"argument":"--port","optional":false,"default_":true},{"argument":"5001","optional":false,"default_":true},{"argument":"-t","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"-ngl","optional":false,"default_":true},{"argument":"GPU_LAYERS","optional":false,"default_":true},{"argument":"--ctx-size","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"-m","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}]}]},{"path":"file:///home/sb/lm/SillyTavern/start.sh","interpreter":null,"arguments":[],"appArguments":[],"modelSettings":[]},{"path":"file:///home/sb/lm/ollama-oai-bridge/start.sh","interpreter":null,"arguments":[],"appArguments":[],"modelSettings":[]}]}

Binary file not shown.

View File

@@ -1 +1 @@
{"models":[{"path":"/home/sb/lm/models/Meta-Llama-3.1-8B-Instruct-Q8_0.gguf","contextSize":32768,"maxContextSize":32768,"gpuLayers":99,"threads":6,"promptTemplate":"llama-3"}]}
{"models":[{"path":"/mnt/data/data/lm/models/gguf/m/Qwen3.6-35B-A3B-UD-Q5_K_XL-mtp.gguf","contextSize":16000,"maxContextSize":128000,"gpuLayers":99,"threads":8,"temperature":0.8,"topP":0.95,"topK":40,"minP":0.05,"promptTemplate":"","ctime":1779609453785},{"path":"/home/sb/lm/models/Meta-Llama-3.1-8B-Instruct-Q8_0.gguf","contextSize":32768,"maxContextSize":32768,"gpuLayers":99,"threads":6,"temperature":0.8,"topP":0.95,"topK":40,"minP":0.05,"promptTemplate":"llama-3","ctime":0}]}

View File

@@ -2,6 +2,8 @@ package de.extio.lm_launcher;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -9,7 +11,13 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
record App(Path path, Path interpreter, @Deprecated List<String> arguments, List<AppArgument> appArguments) {
record App(Path path, Path interpreter, @Deprecated List<String> arguments, List<AppArgument> appArguments, List<AppModelSettings> modelSettings) {
public App {
arguments = arguments == null ? new ArrayList<>() : arguments;
appArguments = appArguments == null ? new ArrayList<>() : appArguments;
modelSettings = modelSettings == null ? new ArrayList<>() : modelSettings;
}
static Map<String, Function<Model, String>> PLACEHOLDERS = Map.of(
"MODEL", m -> m.path(),
@@ -32,6 +40,31 @@ record App(Path path, Path interpreter, @Deprecated List<String> arguments, List
showAll);
}
List<AppArgument> argumentsForModel(final String modelPath) {
if (modelPath == null || modelPath.isBlank()) {
return copyArguments(this.appArguments);
}
final AppModelSettings savedSettings = this.findModelSettings(modelPath);
if (savedSettings == null) {
return copyArguments(this.appArguments);
}
return this.mergeArguments(savedSettings.appArguments());
}
void saveArgumentsForModel(final String modelPath, final List<AppArgument> selectedArguments) {
if (modelPath == null || modelPath.isBlank()) {
return;
}
final AppModelSettings updatedSettings = new AppModelSettings(modelPath, copyArguments(selectedArguments));
for (int i = 0; i < this.modelSettings.size(); i++) {
if (this.modelSettings.get(i).modelPath().equals(modelPath)) {
this.modelSettings.set(i, updatedSettings);
return;
}
}
this.modelSettings.add(updatedSettings);
}
private String argumentsToStringByArgs(final Stream<AppArgument> args, boolean showAll) {
return args
.filter(arg -> showAll || (arg.optional() ? arg.default_() : true))
@@ -39,6 +72,50 @@ record App(Path path, Path interpreter, @Deprecated List<String> arguments, List
.collect(Collectors.joining(" "));
}
private List<AppArgument> mergeArguments(final List<AppArgument> savedArguments) {
if (savedArguments == null || savedArguments.isEmpty()) {
return copyArguments(this.appArguments);
}
final Map<String, Integer> occurrences = new HashMap<>();
final List<AppArgument> mergedArguments = new ArrayList<>(this.appArguments.size());
for (final AppArgument baseArgument : this.appArguments) {
final int occurrenceIndex = occurrences.getOrDefault(baseArgument.argument(), 0);
occurrences.put(baseArgument.argument(), occurrenceIndex + 1);
final AppArgument savedArgument = nthArgument(savedArguments, baseArgument.argument(), occurrenceIndex);
mergedArguments.add(savedArgument == null
? new AppArgument(baseArgument.argument(), baseArgument.optional(), baseArgument.default_())
: new AppArgument(baseArgument.argument(), baseArgument.optional(), baseArgument.optional() ? savedArgument.default_() : baseArgument.default_()));
}
return mergedArguments;
}
private AppModelSettings findModelSettings(final String modelPath) {
return this.modelSettings.stream()
.filter(settings -> settings.modelPath().equals(modelPath))
.findFirst()
.orElse(null);
}
private static AppArgument nthArgument(final List<AppArgument> arguments, final String argument, final int occurrenceIndex) {
int matchIndex = 0;
for (final AppArgument candidate : arguments) {
if (!candidate.argument().equals(argument)) {
continue;
}
if (matchIndex == occurrenceIndex) {
return candidate;
}
matchIndex++;
}
return null;
}
private static List<AppArgument> copyArguments(final List<AppArgument> arguments) {
return arguments.stream()
.map(argument -> new AppArgument(argument.argument(), argument.optional(), argument.default_()))
.collect(Collectors.toCollection(ArrayList::new));
}
void run(final Model model, final List<AppArgument> argumentOverrides) throws IOException, IllegalArgumentException {
String interpreterStr = "";
if (this.interpreter() != null) {
@@ -62,4 +139,11 @@ record App(Path path, Path interpreter, @Deprecated List<String> arguments, List
null,
this.path().toAbsolutePath().getParent().toFile());
}
static record AppModelSettings(String modelPath, List<AppArgument> appArguments) {
public AppModelSettings {
appArguments = appArguments == null ? new ArrayList<>() : appArguments;
}
}
}

View File

@@ -65,10 +65,13 @@ public class Data {
// Migration
for (int i = 0; i < appData.apps.size(); i++) {
final App app = appData.apps.get(i);
if (app.appArguments() == null) {
if (app.appArguments().isEmpty() && !app.arguments().isEmpty()) {
final List<AppArgument> arguments = new ArrayList<>(app.arguments().size());
app.arguments().stream().map(argument -> new AppArgument(argument, false, true)).forEach(arguments::add);
appData.apps.set(i, new App(app.path(), app.interpreter(), new ArrayList<>(), arguments));
appData.apps.set(i, new App(app.path(), app.interpreter(), new ArrayList<>(), arguments, new ArrayList<>(app.modelSettings())));
}
else if (!app.arguments().isEmpty()) {
appData.apps.set(i, new App(app.path(), app.interpreter(), new ArrayList<>(), new ArrayList<>(app.appArguments()), new ArrayList<>(app.modelSettings())));
}
}
}
@@ -147,6 +150,16 @@ public class Data {
.orElse(null);
}
static void upsertModel(final Model model) {
for (int i = 0; i < modelData.models().size(); i++) {
if (modelData.models().get(i).path().equals(model.path())) {
modelData.models().set(i, model);
return;
}
}
modelData.models().add(model);
}
static Model defaultModel(final String path) {
long ctime = 0;
try {

View File

@@ -225,15 +225,7 @@ public class LaunchFrame extends JFrame {
this.list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
this.list.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
if (this.table.getCellEditor() != null) {
this.table.getCellEditor().stopCellEditing();
}
this.appArgumentsTableModel.setRowCount(0);
final DefaultListSelectionModel selectionModel = (DefaultListSelectionModel) e.getSource();
if (selectionModel.getMinSelectionIndex() > -1) {
final App app = Data.appData.apps().get(selectionModel.getMinSelectionIndex());
app.appArguments().forEach(arg -> this.appArgumentsTableModel.addRow(new Object[] { arg }));
}
LaunchFrame.this.refreshSelectedAppArguments();
}
});
this.scrollPane.setViewportView(this.list);
@@ -279,6 +271,7 @@ public class LaunchFrame extends JFrame {
final Model model = saved != null ? saved : Data.defaultModel(p.toString());
this.refreshModelSliders(model);
}
LaunchFrame.this.refreshSelectedAppArguments();
});
this.scrollPane_1.setViewportView(this.modelTree);
@@ -469,7 +462,7 @@ public class LaunchFrame extends JFrame {
interpreter = fileChooser.getSelectedFile().toPath();
}
final App app = new App(executable, interpreter, new ArrayList<>(), new ArrayList<>());
final App app = new App(executable, interpreter, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
Data.appData.apps().add(app);
final AppPropertiesDialog appPropertiesDialog = new AppPropertiesDialog(LaunchFrame.this, app, () -> {
@@ -537,7 +530,7 @@ public class LaunchFrame extends JFrame {
}
final App app = Data.appData.apps().get(index);
Data.appData.apps().add(index, new App(app.path(), app.interpreter(), new ArrayList<>(app.arguments()), new ArrayList<>(app.appArguments())));
Data.appData.apps().add(index, new App(app.path(), app.interpreter(), new ArrayList<>(app.arguments()), new ArrayList<>(app.appArguments()), new ArrayList<>(app.modelSettings())));
Data.saveApps();
LaunchFrame.this.refresh();
@@ -581,19 +574,19 @@ public class LaunchFrame extends JFrame {
template.ctime());
}
final List<AppArgument> argumentOverrides = new ArrayList<>();
if (LaunchFrame.this.table.getCellEditor() != null) {
LaunchFrame.this.table.getCellEditor().stopCellEditing();
}
for (int i = 0; i < LaunchFrame.this.appArgumentsTableModel.getRowCount(); i++) {
final AppArgument appArgument = (AppArgument) LaunchFrame.this.appArgumentsTableModel.getValueAt(i, 0);
if (appArgument.optional()) {
argumentOverrides.add(appArgument);
}
}
final List<AppArgument> selectedArguments = LaunchFrame.this.currentTableArguments();
final List<AppArgument> argumentOverrides = selectedArguments.stream().filter(AppArgument::optional).toList();
try {
app.run(model, argumentOverrides);
if (model != null) {
Data.upsertModel(model);
Data.saveModels();
app.saveArgumentsForModel(selectedPath, selectedArguments);
Data.saveApps();
LaunchFrame.this.refreshModelSliders(model);
LaunchFrame.this.refreshSelectedAppArguments();
}
}
catch (final IllegalArgumentException iae) {
JOptionPane.showMessageDialog(LaunchFrame.this, iae.getMessage(), "Error Message", JOptionPane.WARNING_MESSAGE);
@@ -631,6 +624,38 @@ public class LaunchFrame extends JFrame {
return null;
}
private App getSelectedApp() {
final int selectedIndex = this.list.getSelectedIndex();
if (selectedIndex < 0) {
return null;
}
return Data.appData.apps().get(selectedIndex);
}
private void refreshSelectedAppArguments() {
if (this.table.getCellEditor() != null) {
this.table.getCellEditor().stopCellEditing();
}
this.appArgumentsTableModel.setRowCount(0);
final App selectedApp = this.getSelectedApp();
if (selectedApp == null) {
return;
}
selectedApp.argumentsForModel(this.getSelectedModelPath())
.forEach(argument -> this.appArgumentsTableModel.addRow(new Object[] { argument }));
}
private List<AppArgument> currentTableArguments() {
if (this.table.getCellEditor() != null) {
this.table.getCellEditor().stopCellEditing();
}
final List<AppArgument> selectedArguments = new ArrayList<>(this.appArgumentsTableModel.getRowCount());
for (int i = 0; i < this.appArgumentsTableModel.getRowCount(); i++) {
selectedArguments.add((AppArgument) this.appArgumentsTableModel.getValueAt(i, 0));
}
return selectedArguments;
}
private void refreshModelSliders(final Model model) {
this.slider_context.setMaximum(model.maxContextSize());
this.slider_context.setMajorTickSpacing(Math.max(1, model.maxContextSize() / 4));
@@ -712,6 +737,7 @@ public class LaunchFrame extends JFrame {
this.modelTree.setModel(new DefaultTreeModel(root));
}
this.refreshSelectedAppArguments();
this.repaint();
}