commit f95067a44c546b8a47ee7c2f328b9bf1c4523fa3 Author: extio-de Date: Sun Oct 20 20:38:51 2024 +0200 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d9f3f3 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +echo # lm-launcher diff --git a/apps.json b/apps.json new file mode 100644 index 0000000..7c3acac --- /dev/null +++ b/apps.json @@ -0,0 +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":[]}]} diff --git a/jackson-annotations-2.17.2.jar b/jackson-annotations-2.17.2.jar new file mode 100644 index 0000000..c13bcb9 Binary files /dev/null and b/jackson-annotations-2.17.2.jar differ diff --git a/jackson-core-2.17.2.jar b/jackson-core-2.17.2.jar new file mode 100644 index 0000000..34be902 Binary files /dev/null and b/jackson-core-2.17.2.jar differ diff --git a/jackson-databind-2.17.2.jar b/jackson-databind-2.17.2.jar new file mode 100644 index 0000000..3750b8c Binary files /dev/null and b/jackson-databind-2.17.2.jar differ diff --git a/lm-launcher.jar b/lm-launcher.jar new file mode 100644 index 0000000..8765f81 Binary files /dev/null and b/lm-launcher.jar differ diff --git a/lm-launcher.properties b/lm-launcher.properties new file mode 100644 index 0000000..9987859 --- /dev/null +++ b/lm-launcher.properties @@ -0,0 +1,2 @@ +models=/home/sb/lm/models/ +console=/usr/bin/konsole -e CMD diff --git a/models.json b/models.json new file mode 100644 index 0000000..1554b04 --- /dev/null +++ b/models.json @@ -0,0 +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"}]} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..1b5ee21 --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +java -jar lm-launcher.jar diff --git a/src/de/extio/lm_launcher/App.java b/src/de/extio/lm_launcher/App.java new file mode 100644 index 0000000..76fea95 --- /dev/null +++ b/src/de/extio/lm_launcher/App.java @@ -0,0 +1,61 @@ +package de.extio.lm_launcher; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +record App(Path path, Path interpreter, @Deprecated List arguments, List appArguments) { + + static Map> PLACEHOLDERS = Map.of( + "MODEL", m -> m.path(), + "CONTEXT_SIZE", m -> String.valueOf(m.contextSize()), + "THREADS", m -> String.valueOf(m.threads()), + "GPU_LAYERS", m -> String.valueOf(m.gpuLayers())); + + String argumentsToString(boolean showAll) { + return this.argumentsToStringByArgs(this.appArguments.stream(), showAll); + } + + String argumentsToString(final List argumentOverrides, boolean showAll) { + return this.argumentsToStringByArgs(this.appArguments + .stream() + .map(arg -> argumentOverrides.stream().filter(a -> a.argument().equals(arg.argument())).findFirst().orElse(arg)), + showAll); + } + + private String argumentsToStringByArgs(final Stream args, boolean showAll) { + return args + .filter(arg -> showAll || (arg.optional() ? arg.default_() : true)) + .map(AppArgument::argument) + .collect(Collectors.joining(" ")); + } + + void run(final Model model, final List argumentOverrides) throws IOException, IllegalArgumentException { + String interpreterStr = ""; + if (this.interpreter() != null) { + interpreterStr = this.interpreter().toString().concat(" "); + } + + String consoleCmdStr = Data.props.getProperty("console") + .replace("CMD", interpreterStr + this.path().toString() + " " + this.argumentsToString(argumentOverrides, false)); + + if (PLACEHOLDERS.keySet().stream().anyMatch(consoleCmdStr::contains)) { + if (model == null) { + throw new IllegalArgumentException("Select a model first"); + } + for (final Entry> placeholder : PLACEHOLDERS.entrySet()) { + consoleCmdStr = consoleCmdStr.replace(placeholder.getKey(), placeholder.getValue().apply(model)); + } + } + + Runtime.getRuntime().exec( + consoleCmdStr.split("\\s+"), + null, + this.path().toAbsolutePath().getParent().toFile()); + } +} diff --git a/src/de/extio/lm_launcher/AppArgument.java b/src/de/extio/lm_launcher/AppArgument.java new file mode 100644 index 0000000..489f661 --- /dev/null +++ b/src/de/extio/lm_launcher/AppArgument.java @@ -0,0 +1,5 @@ +package de.extio.lm_launcher; + +public record AppArgument(String argument, boolean optional, boolean default_) { + +} diff --git a/src/de/extio/lm_launcher/AppPropertiesDialog.java b/src/de/extio/lm_launcher/AppPropertiesDialog.java new file mode 100644 index 0000000..2a26496 --- /dev/null +++ b/src/de/extio/lm_launcher/AppPropertiesDialog.java @@ -0,0 +1,222 @@ +package de.extio.lm_launcher; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EventObject; +import java.util.List; + +import javax.swing.AbstractCellEditor; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.border.EmptyBorder; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; + +public class AppPropertiesDialog extends JDialog { + + private static final long serialVersionUID = 1L; + + private final JPanel contentPanel = new JPanel(); + + JTextField textField; + + JScrollPane scrollPane; + + JTable table; + + DefaultTableModel model = new DefaultTableModel(); + + JPanel panel; + + JLabel lblNewLabel; + + JButton btnUpdate; + + static App app; + + private final Runnable onClose; + + /** + * Create the dialog. + */ + public AppPropertiesDialog(final Frame parent, final App app, final Runnable onClose) { + super(parent); + + AppPropertiesDialog.app = app; + this.onClose = onClose; + + this.setBounds(100, 100, 681, 350); + this.getContentPane().setLayout(new BorderLayout()); + this.contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); + this.getContentPane().add(this.contentPanel, BorderLayout.CENTER); + this.contentPanel.setLayout(new BorderLayout(0, 0)); + { + this.scrollPane = new JScrollPane(); + this.contentPanel.add(this.scrollPane, BorderLayout.CENTER); + { + this.model.addColumn("Parameter"); + this.model.addColumn("Optional"); + this.model.addColumn("Default"); + + this.table = new JTable(this.model); + this.table.setRowSelectionAllowed(false); + this.table.setFillsViewportHeight(true); + this.table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + + JCheckBoxRenderer renderer = new JCheckBoxRenderer(); + JCheckBoxEditor editor = new JCheckBoxEditor(); + this.table.getColumnModel().getColumn(1).setCellRenderer(renderer); + this.table.getColumnModel().getColumn(1).setCellEditor(editor); + renderer = new JCheckBoxRenderer(); + editor = new JCheckBoxEditor(); + this.table.getColumnModel().getColumn(2).setCellRenderer(renderer); + this.table.getColumnModel().getColumn(2).setCellEditor(editor); + + this.scrollPane.setViewportView(this.table); + } + } + { + this.panel = new JPanel(); + this.contentPanel.add(this.panel, BorderLayout.NORTH); + this.panel.setLayout(new BorderLayout(0, 0)); + { + this.textField = new JTextField(); + this.panel.add(this.textField); + this.textField.setColumns(10); + } + { + this.lblNewLabel = new JLabel("Enter the arguments. Placeholders: " + String.join(", ", App.PLACEHOLDERS.keySet())); + this.panel.add(this.lblNewLabel, BorderLayout.NORTH); + } + { + this.btnUpdate = new JButton("Update"); + this.btnUpdate.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + AppPropertiesDialog.this.refreshTable(); + } + }); + this.panel.add(this.btnUpdate, BorderLayout.EAST); + } + } + { + final JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); + this.getContentPane().add(buttonPane, BorderLayout.SOUTH); + { + final JButton okButton = new JButton("OK"); + okButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + AppPropertiesDialog.this.setVisible(false); + AppPropertiesDialog.this.onClose.run(); + AppPropertiesDialog.this.dispose(); + } + }); + okButton.setActionCommand("OK"); + buttonPane.add(okButton); + this.getRootPane().setDefaultButton(okButton); + } + } + + this.textField.setText(app.argumentsToString(true)); + + this.refreshTable(); + } + + private void refreshTable() { + this.model.setRowCount(0); + + final List arguments = Arrays.asList(this.textField.getText().split("\\s+")); + final List appArgs = new ArrayList<>(); + for (int i = 0; i < arguments.size(); i++) { + final String argument = arguments.get(i); + if (argument.isBlank()) { + continue; + } + + final long existingRecords = AppPropertiesDialog.app.appArguments().stream().filter(arg -> arg.argument().equals(argument)).count() - 1; + final long newRecordIndex = appArgs.stream().filter(arg -> arg.argument().equals(argument)).count(); + + final AppArgument appArgument = AppPropertiesDialog.app.appArguments() + .stream() + .filter(arg -> arg.argument().equals(argument)) + .skip(Math.max(0, Math.min(newRecordIndex, existingRecords))) + .findFirst() + .orElseGet(() -> new AppArgument(argument, false, true)); + appArgs.add(appArgument); + + this.model.addRow(new Object[] { appArgument.argument(), appArgument.optional(), appArgument.default_() }); + } + + AppPropertiesDialog.app.appArguments().clear(); + AppPropertiesDialog.app.appArguments().addAll(appArgs); + + this.table.revalidate(); + this.table.repaint(); + } + + static class JCheckBoxRenderer extends JCheckBox implements TableCellRenderer { + + @Override + public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { + this.setSelected((boolean) value); + return this; + } + } + + static class JCheckBoxEditor extends AbstractCellEditor implements TableCellEditor { + + private final JCheckBox checkBox = new JCheckBox() { + + { + this.addActionListener(e -> { + final AppArgument existing = AppPropertiesDialog.app.appArguments().get(JCheckBoxEditor.this.row); + if (JCheckBoxEditor.this.column == 1) { + AppPropertiesDialog.app.appArguments().set(JCheckBoxEditor.this.row, new AppArgument(existing.argument(), this.isSelected(), existing.default_())); + } + else { + AppPropertiesDialog.app.appArguments().set(JCheckBoxEditor.this.row, new AppArgument(existing.argument(), existing.optional(), this.isSelected())); + } + }); + } + }; + + private int row; + + private int column; + + @Override + public Component getTableCellEditorComponent(final JTable table, final Object value, final boolean isSelected, final int row, final int column) { + this.row = row; + this.column = column; + this.checkBox.setSelected((Boolean) value); + return this.checkBox; + } + + @Override + public Object getCellEditorValue() { + return this.checkBox.isSelected(); + } + + @Override + public boolean isCellEditable(final EventObject e) { + return true; + } + } +} diff --git a/src/de/extio/lm_launcher/Data.java b/src/de/extio/lm_launcher/Data.java new file mode 100644 index 0000000..ee9c093 --- /dev/null +++ b/src/de/extio/lm_launcher/Data.java @@ -0,0 +1,115 @@ +package de.extio.lm_launcher; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Properties; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class Data { + + static final Properties props; + + static ModelData modelData; + + static AppData appData; + + static { + props = new Properties(); + try { + props.load(new FileInputStream("lm-launcher.properties")); + } + catch (final IOException e) { + e.printStackTrace(); + } + + final ObjectMapper objectMapper = new ObjectMapper(); + + try { + modelData = objectMapper.readValue(new File("models.json"), ModelData.class); + } + catch (final IOException e) { + e.printStackTrace(); + } + if (modelData == null || modelData.models == null) { + modelData = new ModelData(new ArrayList<>()); + } + // Migration + for (int i = 0; i < modelData.models().size(); i++) { + final Model model = modelData.models.get(i); + if (model.maxContextSize() < model.contextSize() || model.promptTemplate() == null) { + modelData.models.set(i, new Model(model.path(), model.contextSize(), model.contextSize(), model.gpuLayers(), model.threads(), Objects.requireNonNullElse(model.promptTemplate(), ""))); + } + } + + try { + appData = objectMapper.readValue(new File("apps.json"), AppData.class); + } + catch (final IOException e) { + e.printStackTrace(); + } + if (appData == null || appData.apps == null) { + appData = new AppData(new ArrayList<>()); + } + // Migration + for (int i = 0; i < appData.apps.size(); i++) { + final App app = appData.apps.get(i); + if (app.appArguments() == null) { + final List 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)); + } + } + } + + static void saveModels() { + final ObjectMapper objectMapper = new ObjectMapper(); + try { + objectMapper.writeValue(new File("models.json"), modelData); + } + catch (final IOException e) { + e.printStackTrace(); + } + } + + static void saveApps() { + final ObjectMapper objectMapper = new ObjectMapper(); + try { + objectMapper.writeValue(new File("apps.json"), appData); + } + catch (final IOException e) { + e.printStackTrace(); + } + } + + static List scanModels() { + final List result = new ArrayList<>(); + try { + Files + .list(Path.of(props.getProperty("models"))) + .filter(p -> p.getFileName().toString().endsWith(".gguf")) + .map(Path::toAbsolutePath) + .map(Path::toString) + .sorted() + .forEach(result::add); + } + catch (final IOException e) { + e.printStackTrace(); + } + return result; + } + + static record ModelData(List models) { + + } + + static record AppData(List apps) { + + } +} diff --git a/src/de/extio/lm_launcher/LaunchFrame.java b/src/de/extio/lm_launcher/LaunchFrame.java new file mode 100644 index 0000000..ba09f6f --- /dev/null +++ b/src/de/extio/lm_launcher/LaunchFrame.java @@ -0,0 +1,609 @@ +package de.extio.lm_launcher; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractCellEditor; +import javax.swing.Box; +import javax.swing.DefaultListModel; +import javax.swing.DefaultListSelectionModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSlider; +import javax.swing.JSplitPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.WindowConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; + +public class LaunchFrame extends JFrame { + + private static final long serialVersionUID = 1L; + + private final JPanel contentPane; + + JPanel panel; + + JPanel panel_1; + + JButton btnAdd; + + JButton btnEdit; + + JSplitPane splitPane; + + JScrollPane scrollPane; + + JScrollPane scrollPane_1; + + JList list; + + JList list_1; + + JButton btnDelete; + + JButton btnClone; + + JPanel panel_2; + + JButton btnAdd_1; + + JButton btnEdit_1; + + JButton btnDelete_1; + + JButton btnClone_1; + + JButton btnRun_1; + + DefaultListModel listModel; + + DefaultListModel list1Model; + + Component horizontalStrut; + + JSplitPane splitPane_1; + + JScrollPane scrollPane_2; + + JTable table; + + DefaultTableModel appArgumentsTableModel = new DefaultTableModel() { + + @Override + public boolean isCellEditable(final int row, final int column) { + return ((AppArgument) this.getValueAt(row, column)).optional(); + }; + + }; + + JSplitPane splitPane_2; + + JPanel panel_3; + + JSlider slider_context; + + JLabel lblContextLength; + + Component verticalStrut; + + JLabel lblThreads; + + JSlider slider_threads; + + JLabel lblGpuLayers; + + JSlider slider_gpu; + + JLabel lblPromptTemplate; + + JLabel lblPromptTemplate_1; + + /** + * Create the frame. + */ + public LaunchFrame() { + this.setTitle("LM Launcher"); + this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + this.setBounds(100, 100, 400, 400); + this.setPreferredSize(new Dimension(650, 650)); + this.contentPane = new JPanel(); + this.contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); + + this.setContentPane(this.contentPane); + this.contentPane.setLayout(new BorderLayout(0, 0)); + { + this.panel = new JPanel(); + this.contentPane.add(this.panel, BorderLayout.SOUTH); + { + this.btnAdd = new JButton("Add"); + this.btnAdd.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + final SelectModelDialog smDialog = new SelectModelDialog(LaunchFrame.this, path -> { + final ModelPropertiesDialog mpDialog = new ModelPropertiesDialog(LaunchFrame.this, path, null, model -> { + Data.modelData.models().add(model); + Data.modelData.models().sort((o1, o2) -> { + return o1.path().compareTo(o2.path()); + }); + try { + Data.saveModels(); + } + finally { + LaunchFrame.this.refresh(); + } + }); + mpDialog.setLocationRelativeTo(LaunchFrame.this); + mpDialog.setVisible(true); + }); + smDialog.setLocationRelativeTo(LaunchFrame.this); + smDialog.setVisible(true); + } + }); + this.panel.add(this.btnAdd); + } + { + this.btnEdit = new JButton("Edit"); + this.btnEdit.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + final int index = LaunchFrame.this.list_1.getSelectedIndex(); + if (index == -1) { + JOptionPane.showMessageDialog(LaunchFrame.this, "Error: Select a model first", "Error Message", JOptionPane.ERROR_MESSAGE); + return; + } + final Model editModel = Data.modelData.models().get(index); + final ModelPropertiesDialog mpDialog = new ModelPropertiesDialog(LaunchFrame.this, editModel.path(), editModel, model -> { + Data.modelData.models().set(index, model); + try { + Data.saveModels(); + } + finally { + LaunchFrame.this.refresh(); + } + }); + mpDialog.setLocationRelativeTo(LaunchFrame.this); + mpDialog.setVisible(true); + } + }); + this.panel.add(this.btnEdit); + } + { + this.btnDelete = new JButton("Delete"); + this.btnDelete.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + final int index = LaunchFrame.this.list_1.getSelectedIndex(); + if (index == -1) { + JOptionPane.showMessageDialog(LaunchFrame.this, "Error: Select a model first", "Error Message", JOptionPane.ERROR_MESSAGE); + return; + } + Data.modelData.models().remove(index); + try { + Data.saveModels(); + } + finally { + LaunchFrame.this.refresh(); + } + } + }); + this.panel.add(this.btnDelete); + } + { + this.btnClone = new JButton("Clone"); + this.btnClone.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + final int index = LaunchFrame.this.list_1.getSelectedIndex(); + if (index == -1) { + JOptionPane.showMessageDialog(LaunchFrame.this, "Error: Select a model first", "Error Message", JOptionPane.ERROR_MESSAGE); + return; + } + final Model model = Data.modelData.models().get(index); + Data.modelData.models().add(index, new Model(model.path(), model.contextSize(), model.maxContextSize(), model.gpuLayers(), model.threads(), model.promptTemplate())); + try { + Data.saveModels(); + } + finally { + LaunchFrame.this.refresh(); + } + } + }); + this.panel.add(this.btnClone); + } + } + { + this.panel_1 = new JPanel(); + this.contentPane.add(this.panel_1, BorderLayout.CENTER); + this.panel_1.setLayout(new BorderLayout(0, 0)); + { + this.splitPane = new JSplitPane(); + this.splitPane.setResizeWeight(0.5); + this.splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); + this.panel_1.add(this.splitPane); + { + this.splitPane_1 = new JSplitPane(); + this.splitPane_1.setResizeWeight(0.75); + this.splitPane.setLeftComponent(this.splitPane_1); + this.scrollPane = new JScrollPane(); + this.splitPane_1.setLeftComponent(this.scrollPane); + + this.listModel = new DefaultListModel<>(); + this.list = new JList(this.listModel); + 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 })); + } + } + }); + this.scrollPane.setViewportView(this.list); + { + this.scrollPane_2 = new JScrollPane(); + this.splitPane_1.setRightComponent(this.scrollPane_2); + { + this.appArgumentsTableModel.addColumn("Arguments"); + + this.table = new JTable(this.appArgumentsTableModel); + this.table.setRowSelectionAllowed(false); + this.table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + this.table.getColumnModel().getColumn(0).setCellRenderer(new AppArgumentsJCheckBoxRenderer(this.appArgumentsTableModel)); + this.table.getColumnModel().getColumn(0).setCellEditor(new AppArgumentsJCheckBoxEditor()); + + this.scrollPane_2.setViewportView(this.table); + } + } + } + { + this.splitPane_2 = new JSplitPane(); + this.splitPane_2.setResizeWeight(0.75); + this.splitPane.setRightComponent(this.splitPane_2); + + this.scrollPane_1 = new JScrollPane(); + this.splitPane_2.setLeftComponent(this.scrollPane_1); + this.list1Model = new DefaultListModel<>(); + this.list_1 = new JList(this.list1Model); + this.list_1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + this.list_1.getSelectionModel().addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + final DefaultListSelectionModel selectionModel = (DefaultListSelectionModel) e.getSource(); + if (selectionModel.getMinSelectionIndex() > -1) { + final Model model = Data.modelData.models().get(selectionModel.getMinSelectionIndex()); + + this.slider_context.setMaximum(model.maxContextSize()); + this.slider_context.setMajorTickSpacing(model.maxContextSize() / 4); + this.slider_context.setMinorTickSpacing(model.maxContextSize() / 8); + this.slider_context.setValue(model.contextSize()); + this.slider_context.setLabelTable(this.slider_context.createStandardLabels(model.maxContextSize() / 4)); + this.slider_context.setSnapToTicks(true); + this.slider_context.repaint(); + + this.slider_threads.setValue(model.threads()); + + this.slider_gpu.setValue(model.gpuLayers()); + + this.lblPromptTemplate_1.setText(model.promptTemplate()); + } + } + }); + this.scrollPane_1.setViewportView(this.list_1); + { + this.panel_3 = new JPanel(); + this.splitPane_2.setRightComponent(this.panel_3); + this.panel_3.setLayout(new GridLayout(0, 1, 0, 0)); + { + this.lblContextLength = new JLabel("Context Length"); + this.lblContextLength.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblContextLength); + } + { + this.slider_context = new JSlider(); + this.slider_context.setValue(0); + this.slider_context.setMaximum(131072); + this.slider_context.setMajorTickSpacing(65536); + this.slider_context.setMinorTickSpacing(8192); + this.slider_context.setSnapToTicks(true); + this.slider_context.setPaintLabels(true); + this.slider_context.setPaintTicks(true); + this.panel_3.add(this.slider_context); + } + { + this.lblThreads = new JLabel("Threads"); + this.lblThreads.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblThreads); + } + { + this.slider_threads = new JSlider(); + this.slider_threads.setSnapToTicks(true); + this.slider_threads.setValue(0); + this.slider_threads.setPaintLabels(true); + this.slider_threads.setPaintTicks(true); + this.slider_threads.setMajorTickSpacing(8); + this.slider_threads.setMinorTickSpacing(2); + this.slider_threads.setMaximum(24); + this.panel_3.add(this.slider_threads); + } + { + this.lblGpuLayers = new JLabel("GPU Layers"); + this.lblGpuLayers.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblGpuLayers); + } + { + this.slider_gpu = new JSlider(); + this.slider_gpu.setPaintLabels(true); + this.slider_gpu.setMajorTickSpacing(16); + this.slider_gpu.setMinorTickSpacing(4); + this.slider_gpu.setValue(100); + this.slider_gpu.setPaintTicks(true); + this.panel_3.add(this.slider_gpu); + } + { + this.lblPromptTemplate = new JLabel("Prompt Template"); + this.panel_3.add(this.lblPromptTemplate); + } + { + this.lblPromptTemplate_1 = new JLabel("-"); + this.lblPromptTemplate_1.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblPromptTemplate_1); + } + { + this.verticalStrut = Box.createVerticalStrut(20); + this.panel_3.add(this.verticalStrut); + } + } + } + } + } + { + this.panel_2 = new JPanel(); + this.contentPane.add(this.panel_2, BorderLayout.NORTH); + { + this.btnAdd_1 = new JButton("Add"); + this.btnAdd_1.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Select executable"); + fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); + if (fileChooser.showOpenDialog(LaunchFrame.this) == JFileChooser.APPROVE_OPTION) { + final Path executable = fileChooser.getSelectedFile().toPath(); + + Path interpreter = null; + fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Select interpreter or hit cancel if you don't need it"); + fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); + if (fileChooser.showOpenDialog(LaunchFrame.this) == JFileChooser.APPROVE_OPTION) { + interpreter = fileChooser.getSelectedFile().toPath(); + } + + final App app = new App(executable, interpreter, new ArrayList<>(), new ArrayList<>()); + + final AppPropertiesDialog appPropertiesDialog = new AppPropertiesDialog(LaunchFrame.this, app, () -> { + Data.saveApps(); + LaunchFrame.this.refresh(); + }); + appPropertiesDialog.setLocationRelativeTo(LaunchFrame.this); + appPropertiesDialog.setVisible(true); + } + } + }); + this.panel_2.add(this.btnAdd_1); + } + { + this.btnEdit_1 = new JButton("Edit"); + this.btnEdit_1.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + final int index = LaunchFrame.this.list.getSelectedIndex(); + if (index == -1) { + JOptionPane.showMessageDialog(LaunchFrame.this, "Error: Select an app first", "Error Message", JOptionPane.ERROR_MESSAGE); + return; + } + final App app = Data.appData.apps().get(index); + + final AppPropertiesDialog appPropertiesDialog = new AppPropertiesDialog(LaunchFrame.this, app, () -> { + Data.saveApps(); + LaunchFrame.this.refresh(); + }); + appPropertiesDialog.setLocationRelativeTo(LaunchFrame.this); + appPropertiesDialog.setVisible(true); + } + }); + this.panel_2.add(this.btnEdit_1); + } + { + this.btnDelete_1 = new JButton("Delete"); + this.btnDelete_1.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + final int index = LaunchFrame.this.list.getSelectedIndex(); + if (index == -1) { + JOptionPane.showMessageDialog(LaunchFrame.this, "Error: Select an app first", "Error Message", JOptionPane.ERROR_MESSAGE); + return; + } + Data.appData.apps().remove(index); + Data.saveApps(); + LaunchFrame.this.refresh(); + } + }); + this.panel_2.add(this.btnDelete_1); + } + { + this.btnClone_1 = new JButton("Clone"); + this.btnClone_1.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + final int index = LaunchFrame.this.list.getSelectedIndex(); + if (index == -1) { + JOptionPane.showMessageDialog(LaunchFrame.this, "Error: Select an app first", "Error Message", JOptionPane.ERROR_MESSAGE); + return; + } + + 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.saveApps(); + LaunchFrame.this.refresh(); + } + }); + this.panel_2.add(this.btnClone_1); + } + { + this.horizontalStrut = Box.createHorizontalStrut(20); + this.panel_2.add(this.horizontalStrut); + } + { + this.btnRun_1 = new JButton("Run"); + this.btnRun_1.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + final int AppIndex = LaunchFrame.this.list.getSelectedIndex(); + if (AppIndex == -1) { + JOptionPane.showMessageDialog(LaunchFrame.this, "Error: Select an app first", "Error Message", JOptionPane.ERROR_MESSAGE); + return; + } + final App app = Data.appData.apps().get(AppIndex); + + Model model = null; + final int modelIndex = LaunchFrame.this.list_1.getSelectedIndex(); + if (modelIndex > -1) { + final Model template = Data.modelData.models().get(modelIndex); + model = new Model( + template.path(), + LaunchFrame.this.slider_context.getValue(), + template.maxContextSize(), + LaunchFrame.this.slider_gpu.getValue(), + LaunchFrame.this.slider_threads.getValue(), + template.promptTemplate()); + } + + final List 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); + } + } + + try { + app.run(model, argumentOverrides); + } + catch (final IllegalArgumentException iae) { + JOptionPane.showMessageDialog(LaunchFrame.this, iae.getMessage(), "Error Message", JOptionPane.WARNING_MESSAGE); + } + catch (final IOException ioa) { + JOptionPane.showMessageDialog(LaunchFrame.this, "Error: Could not execute program\n" + ioa.getMessage(), "Error Message", JOptionPane.ERROR_MESSAGE); + } + } + }); + this.panel_2.add(this.btnRun_1); + } + } + + this.pack(); + this.repaint(); + this.splitPane.setDividerLocation(0.33); + this.splitPane_1.setDividerLocation(0.66); + this.splitPane_2.setDividerLocation(0.66); + + this.refresh(); + } + + private void refresh() { + this.listModel.clear(); + Data.appData.apps() + .stream() + .map(app -> app.path().getFileName() + " " + app.path().getParent() + " " + app.interpreter()) + .forEach(this.listModel::addElement); + + this.list1Model.clear(); + Data.modelData.models() + .stream() + .map(model -> Path.of(model.path()).getFileName() + "; ctx " + model.contextSize() + "/" + model.maxContextSize()) + .forEach(this.list1Model::addElement); + + if (this.table.getCellEditor() != null) { + this.table.getCellEditor().stopCellEditing(); + } + this.appArgumentsTableModel.setRowCount(0); + + this.repaint(); + } + + static class AppArgumentsJCheckBoxRenderer extends JCheckBox implements TableCellRenderer { + + private final DefaultTableModel model; + + public AppArgumentsJCheckBoxRenderer(final DefaultTableModel model) { + super(); + this.model = model; + } + + @Override + public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { + this.setText(((AppArgument) value).argument()); + this.setSelected(((AppArgument) value).optional() ? ((AppArgument) value).default_() : true); + this.setEnabled(this.model.isCellEditable(row, column)); + return this; + } + } + + static class AppArgumentsJCheckBoxEditor extends AbstractCellEditor implements TableCellEditor { + + private final JCheckBox checkBox = new JCheckBox(); + + private AppArgument appArgument; + + @Override + public Component getTableCellEditorComponent(final JTable table, final Object value, final boolean isSelected, final int row, final int column) { + this.appArgument = (AppArgument) value; + this.checkBox.setText(this.appArgument.argument()); + this.checkBox.setSelected(this.appArgument.optional() ? this.appArgument.default_() : true); + return this.checkBox; + } + + @Override + public Object getCellEditorValue() { + return new AppArgument(this.appArgument.argument(), this.appArgument.optional(), this.checkBox.isSelected()); + } + + } +} diff --git a/src/de/extio/lm_launcher/Main.java b/src/de/extio/lm_launcher/Main.java new file mode 100644 index 0000000..b4bc684 --- /dev/null +++ b/src/de/extio/lm_launcher/Main.java @@ -0,0 +1,25 @@ +package de.extio.lm_launcher; + +import java.awt.EventQueue; + +public class Main { + + public static void main(final String[] args) { + System.setProperty("awt.useSystemAAFontSettings", "on"); + System.setProperty("swing.aatext", "true"); + + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + try { + final LaunchFrame frame = new LaunchFrame(); + frame.setVisible(true); + } + catch (final Exception e) { + e.printStackTrace(); + } + } + }); + } +} diff --git a/src/de/extio/lm_launcher/Model.java b/src/de/extio/lm_launcher/Model.java new file mode 100644 index 0000000..a9b4229 --- /dev/null +++ b/src/de/extio/lm_launcher/Model.java @@ -0,0 +1,4 @@ +package de.extio.lm_launcher; + +record Model(String path, int contextSize, int maxContextSize, int gpuLayers, int threads, String promptTemplate) { +} diff --git a/src/de/extio/lm_launcher/ModelPropertiesDialog.java b/src/de/extio/lm_launcher/ModelPropertiesDialog.java new file mode 100644 index 0000000..5123bc3 --- /dev/null +++ b/src/de/extio/lm_launcher/ModelPropertiesDialog.java @@ -0,0 +1,155 @@ +package de.extio.lm_launcher; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.nio.file.Path; +import java.util.function.Consumer; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.WindowConstants; +import javax.swing.border.EmptyBorder; + +public class ModelPropertiesDialog extends JDialog { + + private static final long serialVersionUID = 1L; + + private final JPanel contentPanel = new JPanel(); + + JLabel lblName; + + JLabel lblModelname; + + JLabel lblMaxContextLength; + + JTextField textField; + + JLabel lblGpuLayers; + + JTextField textField_1; + + JLabel lblThreads; + + JTextField textField_2; + JLabel lblContextLength; + JTextField textField_3; + JLabel lblPromptTemplate; + JTextField txtPrompttemplate; + + /** + * Create the dialog. + */ + public ModelPropertiesDialog(final Frame parent, final String modelPath, final Model editModel, final Consumer modelConsumer) { + super(parent); + this.setTitle("Model properties"); + this.setModalityType(ModalityType.APPLICATION_MODAL); + this.setModal(true); + this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + this.setBounds(100, 100, 678, 191); + this.getContentPane().setLayout(new BorderLayout()); + this.contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); + this.getContentPane().add(this.contentPanel, BorderLayout.CENTER); + this.contentPanel.setLayout(new GridLayout(0, 2, 0, 0)); + { + this.lblName = new JLabel("Name"); + this.contentPanel.add(this.lblName); + } + { + this.lblModelname = new JLabel("ModelName"); + this.contentPanel.add(this.lblModelname); + } + { + this.lblContextLength = new JLabel("Context Length"); + contentPanel.add(this.lblContextLength); + } + { + this.textField_3 = new JTextField(); + this.textField_3.setText("16000"); + contentPanel.add(this.textField_3); + this.textField_3.setColumns(10); + } + { + this.lblMaxContextLength = new JLabel("Max Context Length"); + this.contentPanel.add(this.lblMaxContextLength); + } + { + this.textField = new JTextField(); + this.textField.setText("128000"); + this.contentPanel.add(this.textField); + this.textField.setColumns(10); + } + { + this.lblThreads = new JLabel("Threads"); + this.contentPanel.add(this.lblThreads); + } + { + this.textField_2 = new JTextField(); + this.textField_2.setText("8"); + this.contentPanel.add(this.textField_2); + this.textField_2.setColumns(10); + } + { + this.lblGpuLayers = new JLabel("GPU Layers"); + this.contentPanel.add(this.lblGpuLayers); + } + { + this.textField_1 = new JTextField(); + this.textField_1.setText("99"); + this.contentPanel.add(this.textField_1); + this.textField_1.setColumns(10); + } + { + final JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); + this.getContentPane().add(buttonPane, BorderLayout.SOUTH); + { + final JButton okButton = new JButton("OK"); + okButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + ModelPropertiesDialog.this.setVisible(false); + modelConsumer.accept( + new Model(modelPath, + Integer.parseInt(ModelPropertiesDialog.this.textField_3.getText()), + Integer.parseInt(ModelPropertiesDialog.this.textField.getText()), + Integer.parseInt(ModelPropertiesDialog.this.textField_1.getText()), + Integer.parseInt(ModelPropertiesDialog.this.textField_2.getText()), + ModelPropertiesDialog.this.txtPrompttemplate.getText())); + ModelPropertiesDialog.this.dispose(); + } + }); + okButton.setActionCommand("OK"); + buttonPane.add(okButton); + this.getRootPane().setDefaultButton(okButton); + } + } + + this.lblModelname.setText(Path.of(modelPath).getFileName().toString()); + { + this.lblPromptTemplate = new JLabel("Prompt Template"); + contentPanel.add(this.lblPromptTemplate); + } + { + this.txtPrompttemplate = new JTextField(); + contentPanel.add(this.txtPrompttemplate); + this.txtPrompttemplate.setColumns(10); + } + if (editModel != null) { + this.textField.setText(String.valueOf(editModel.maxContextSize())); + this.textField_1.setText(String.valueOf(editModel.gpuLayers())); + this.textField_2.setText(String.valueOf(editModel.threads())); + this.textField_3.setText(String.valueOf(editModel.contextSize())); + this.txtPrompttemplate.setText(editModel.promptTemplate()); + } + } + +} diff --git a/src/de/extio/lm_launcher/SelectModelDialog.java b/src/de/extio/lm_launcher/SelectModelDialog.java new file mode 100644 index 0000000..1eddb8b --- /dev/null +++ b/src/de/extio/lm_launcher/SelectModelDialog.java @@ -0,0 +1,101 @@ +package de.extio.lm_launcher; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.function.Consumer; + +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.WindowConstants; +import javax.swing.border.EmptyBorder; + +public class SelectModelDialog extends JDialog { + + private static final long serialVersionUID = 1L; + + private final JPanel contentPanel = new JPanel(); + + JScrollPane scrollPane; + + JList list; + + DefaultListModel listModel; + + /** + * Create the dialog. + */ + public SelectModelDialog(final Frame parent, final Consumer modelPathConsumer) { + super(parent); + this.setTitle("Select Model"); + this.setModalityType(ModalityType.APPLICATION_MODAL); + this.setModal(true); + this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + this.setBounds(100, 100, 578, 251); + this.getContentPane().setLayout(new BorderLayout()); + this.contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); + this.getContentPane().add(this.contentPanel, BorderLayout.CENTER); + this.contentPanel.setLayout(new BorderLayout(0, 0)); + { + this.scrollPane = new JScrollPane(); + this.contentPanel.add(this.scrollPane, BorderLayout.CENTER); + { + this.listModel = new DefaultListModel<>(); + this.list = new JList(this.listModel); + this.list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + this.scrollPane.setViewportView(this.list); + } + } + { + final JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); + this.getContentPane().add(buttonPane, BorderLayout.SOUTH); + { + final JButton okButton = new JButton("OK"); + okButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + if (SelectModelDialog.this.list.getSelectedIndex() == -1) { + JOptionPane.showMessageDialog(null, "Error: Select a model first", "Error Message", JOptionPane.ERROR_MESSAGE); + return; + } + SelectModelDialog.this.setVisible(false); + modelPathConsumer.accept(SelectModelDialog.this.listModel.getElementAt(SelectModelDialog.this.list.getSelectedIndex())); + SelectModelDialog.this.dispose(); + } + }); + okButton.setActionCommand("OK"); + buttonPane.add(okButton); + this.getRootPane().setDefaultButton(okButton); + } + { + final JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent e) { + SelectModelDialog.this.dispose(); + } + }); + cancelButton.setActionCommand("Cancel"); + buttonPane.add(cancelButton); + } + } + this.initialize(); + } + + private void initialize() { + Data.scanModels().forEach(this.listModel::addElement); + } + +}