initial commit
This commit is contained in:
commit
04d294c65b
5
.gitignore
vendored
Executable file
5
.gitignore
vendored
Executable file
@ -0,0 +1,5 @@
|
||||
target
|
||||
.classpath
|
||||
.project
|
||||
.vscode
|
||||
.claude
|
||||
6
config.properties
Normal file
6
config.properties
Normal file
@ -0,0 +1,6 @@
|
||||
#Llama Runner Configuration
|
||||
#Mon Mar 23 12:24:12 CET 2026
|
||||
windowHeight=854
|
||||
windowWidth=587
|
||||
windowX=1973
|
||||
windowY=546
|
||||
45
dependency-reduced-pom.xml
Normal file
45
dependency-reduced-pom.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cz.kamma</groupId>
|
||||
<artifactId>llama-runner</artifactId>
|
||||
<name>Llama Runner</name>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<description>GUI application for running llama-server with customizable parameters</description>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>11</source>
|
||||
<target>11</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer>
|
||||
<mainClass>cz.kamma.llamarunner.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
</project>
|
||||
62
pom.xml
Normal file
62
pom.xml
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>cz.kamma</groupId>
|
||||
<artifactId>llama-runner</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Llama Runner</name>
|
||||
<description>GUI application for running llama-server with customizable parameters</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>11</source>
|
||||
<target>11</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>cz.kamma.llamarunner.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
97
profiles.json
Normal file
97
profiles.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"Nemotron Cascade Q8 160k": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 3080,
|
||||
"parallel": 1,
|
||||
"threads": 99,
|
||||
"flashAttention": true,
|
||||
"kvUnified": true,
|
||||
"cacheTypeK": "q8_0",
|
||||
"cacheTypeV": "q8_0",
|
||||
"temperature": 0.6,
|
||||
"topP": 0.95,
|
||||
"topK": 20,
|
||||
"minP": 0.0,
|
||||
"ctxSize": 160000,
|
||||
"enableThinking": false,
|
||||
"modelPath": "/home/kamma/models/Nemotron-Cascade-2-30B-A3B.Q8_0.gguf",
|
||||
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||
"ngl": -1
|
||||
},
|
||||
"Qwen3.5-q6k-180k": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 3080,
|
||||
"parallel": 1,
|
||||
"threads": 99,
|
||||
"flashAttention": true,
|
||||
"kvUnified": true,
|
||||
"cacheTypeK": "q8_0",
|
||||
"cacheTypeV": "q8_0",
|
||||
"temperature": 0.6,
|
||||
"topP": 0.95,
|
||||
"topK": 20,
|
||||
"minP": 0.0,
|
||||
"ctxSize": 180000,
|
||||
"enableThinking": false,
|
||||
"modelPath": "/home/kamma/models/Qwen3.5-35B-A3B-Q6_K.gguf",
|
||||
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||
"ngl": 999
|
||||
},
|
||||
"QwenCoderNext-160k": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 3080,
|
||||
"parallel": 1,
|
||||
"threads": 99,
|
||||
"flashAttention": true,
|
||||
"kvUnified": true,
|
||||
"cacheTypeK": "q8_0",
|
||||
"cacheTypeV": "q8_0",
|
||||
"temperature": 0.6,
|
||||
"topP": 0.95,
|
||||
"topK": 20,
|
||||
"minP": 0.0,
|
||||
"ctxSize": 160000,
|
||||
"enableThinking": false,
|
||||
"modelPath": "/home/kamma/models/Qwen3-Coder-Next-UD-Q2_K_XL.gguf",
|
||||
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||
"ngl": 999
|
||||
},
|
||||
"Nemotron Cascade 180k": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 3080,
|
||||
"parallel": 1,
|
||||
"threads": 99,
|
||||
"flashAttention": true,
|
||||
"kvUnified": true,
|
||||
"cacheTypeK": "q8_0",
|
||||
"cacheTypeV": "q8_0",
|
||||
"temperature": 0.6,
|
||||
"topP": 0.95,
|
||||
"topK": 20,
|
||||
"minP": 0.0,
|
||||
"ctxSize": 180000,
|
||||
"enableThinking": false,
|
||||
"modelPath": "/home/kamma/models/Nemotron-Cascade-2-30B-A3B.Q5_K_M.gguf",
|
||||
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||
"ngl": 999
|
||||
},
|
||||
"Qwen3.5 q6xl 160k": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 3080,
|
||||
"parallel": 1,
|
||||
"threads": 99,
|
||||
"flashAttention": true,
|
||||
"kvUnified": true,
|
||||
"cacheTypeK": "q8_0",
|
||||
"cacheTypeV": "q8_0",
|
||||
"temperature": 0.6,
|
||||
"topP": 0.95,
|
||||
"topK": 20,
|
||||
"minP": 0.0,
|
||||
"ctxSize": 160000,
|
||||
"enableThinking": false,
|
||||
"modelPath": "/home/kamma/models/Qwen3.5-35B-A3B-UD-Q6_K_XL.gguf",
|
||||
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||
"ngl": -1
|
||||
}
|
||||
}
|
||||
102
src/main/java/cz/kamma/llamarunner/AppConfig.java
Normal file
102
src/main/java/cz/kamma/llamarunner/AppConfig.java
Normal file
@ -0,0 +1,102 @@
|
||||
package cz.kamma.llamarunner;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Application configuration management for storing window size and position.
|
||||
*/
|
||||
public class AppConfig {
|
||||
|
||||
private static final int DEFAULT_WIDTH = 900;
|
||||
private static final int DEFAULT_HEIGHT = 750;
|
||||
private static final int DEFAULT_X = -1; // -1 means center on screen
|
||||
private static final int DEFAULT_Y = -1;
|
||||
|
||||
private int windowWidth;
|
||||
private int windowHeight;
|
||||
private int windowX;
|
||||
private int windowY;
|
||||
private final ConfigLocation configLocation;
|
||||
|
||||
public AppConfig() {
|
||||
this(new ConfigLocation());
|
||||
}
|
||||
|
||||
public AppConfig(ConfigLocation configLocation) {
|
||||
this.configLocation = configLocation;
|
||||
windowWidth = DEFAULT_WIDTH;
|
||||
windowHeight = DEFAULT_HEIGHT;
|
||||
windowX = DEFAULT_X;
|
||||
windowY = DEFAULT_Y;
|
||||
load();
|
||||
}
|
||||
|
||||
private void load() {
|
||||
if (!configLocation.getConfigPropertiesFile().exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Properties props = new Properties();
|
||||
try (FileInputStream fis = new FileInputStream(configLocation.getConfigPropertiesFile())) {
|
||||
props.load(fis);
|
||||
|
||||
windowWidth = Integer.parseInt(props.getProperty("windowWidth", String.valueOf(DEFAULT_WIDTH)));
|
||||
windowHeight = Integer.parseInt(props.getProperty("windowHeight", String.valueOf(DEFAULT_HEIGHT)));
|
||||
windowX = Integer.parseInt(props.getProperty("windowX", String.valueOf(DEFAULT_X)));
|
||||
windowY = Integer.parseInt(props.getProperty("windowY", String.valueOf(DEFAULT_Y)));
|
||||
} catch (IOException | NumberFormatException e) {
|
||||
// Use default values on error
|
||||
}
|
||||
}
|
||||
|
||||
public void save() {
|
||||
configLocation.ensureDirectoryExists();
|
||||
|
||||
Properties props = new Properties();
|
||||
props.setProperty("windowWidth", String.valueOf(windowWidth));
|
||||
props.setProperty("windowHeight", String.valueOf(windowHeight));
|
||||
props.setProperty("windowX", String.valueOf(windowX));
|
||||
props.setProperty("windowY", String.valueOf(windowY));
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(configLocation.getConfigPropertiesFile())) {
|
||||
props.store(fos, "Llama Runner Configuration");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public int getWindowWidth() {
|
||||
return windowWidth;
|
||||
}
|
||||
|
||||
public void setWindowWidth(int windowWidth) {
|
||||
this.windowWidth = windowWidth;
|
||||
}
|
||||
|
||||
public int getWindowHeight() {
|
||||
return windowHeight;
|
||||
}
|
||||
|
||||
public void setWindowHeight(int windowHeight) {
|
||||
this.windowHeight = windowHeight;
|
||||
}
|
||||
|
||||
public int getWindowX() {
|
||||
return windowX;
|
||||
}
|
||||
|
||||
public void setWindowX(int windowX) {
|
||||
this.windowX = windowX;
|
||||
}
|
||||
|
||||
public int getWindowY() {
|
||||
return windowY;
|
||||
}
|
||||
|
||||
public void setWindowY(int windowY) {
|
||||
this.windowY = windowY;
|
||||
}
|
||||
}
|
||||
74
src/main/java/cz/kamma/llamarunner/CommandBuilder.java
Normal file
74
src/main/java/cz/kamma/llamarunner/CommandBuilder.java
Normal file
@ -0,0 +1,74 @@
|
||||
package cz.kamma.llamarunner;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Builds llama-server command lines from ModelConfig.
|
||||
*/
|
||||
public class CommandBuilder {
|
||||
|
||||
/**
|
||||
* Builds the command string from a ModelConfig.
|
||||
*
|
||||
* @param config the model configuration
|
||||
* @param modelsDirPath the path to the models directory
|
||||
* @return the complete command string
|
||||
*/
|
||||
public String buildCommand(ModelConfig config, String modelsDirPath) {
|
||||
StringBuilder cmd = new StringBuilder("llama-server");
|
||||
cmd.append(" --host ").append(config.getHost());
|
||||
cmd.append(" --port ").append(config.getPort());
|
||||
cmd.append(" --parallel ").append(config.getParallel());
|
||||
cmd.append(" -t ").append(config.getThreads());
|
||||
cmd.append(" -fa ").append(config.isFlashAttention() ? "on" : "off");
|
||||
cmd.append(" --temp ").append(config.getTemperature());
|
||||
cmd.append(" --top-p ").append(config.getTopP());
|
||||
cmd.append(" --top-k ").append(config.getTopK());
|
||||
cmd.append(" --min-p ").append(config.getMinP());
|
||||
|
||||
if (config.isKvUnified()) {
|
||||
cmd.append(" --kv-unified");
|
||||
}
|
||||
|
||||
cmd.append(" --cache-type-k ").append(config.getCacheTypeK());
|
||||
cmd.append(" --cache-type-v ").append(config.getCacheTypeV());
|
||||
|
||||
cmd.append(" --ctx-size ").append(config.getCtxSize());
|
||||
|
||||
if (!config.isFit()) {
|
||||
cmd.append(" -ngl ").append(config.getNgl());
|
||||
}
|
||||
|
||||
if (config.isFit()) {
|
||||
cmd.append(" --fit on");
|
||||
}
|
||||
|
||||
String modelPath = buildModelPath(config.getModelPath(), modelsDirPath);
|
||||
cmd.append(" -m ").append(modelPath);
|
||||
|
||||
String kwargsText = config.getChatTemplateKwargs();
|
||||
if (kwargsText != null && !kwargsText.trim().isEmpty()) {
|
||||
cmd.append(" --chat-template-kwargs \"");
|
||||
cmd.append(kwargsText.replace("\"", "\\\""));
|
||||
cmd.append("\"");
|
||||
}
|
||||
|
||||
return cmd.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the full model path.
|
||||
*/
|
||||
private String buildModelPath(String modelPath, String modelsDirPath) {
|
||||
if (modelPath == null || modelPath.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
File modelFile = new File(modelPath);
|
||||
if (modelFile.isAbsolute()) {
|
||||
return modelPath;
|
||||
}
|
||||
|
||||
return new File(modelsDirPath, modelPath).getAbsolutePath();
|
||||
}
|
||||
}
|
||||
48
src/main/java/cz/kamma/llamarunner/ConfigLocation.java
Normal file
48
src/main/java/cz/kamma/llamarunner/ConfigLocation.java
Normal file
@ -0,0 +1,48 @@
|
||||
package cz.kamma.llamarunner;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Manages configuration file locations in the user's home directory.
|
||||
*/
|
||||
public class ConfigLocation {
|
||||
|
||||
private final File appDir;
|
||||
private final File configPropertiesFile;
|
||||
private final File profilesJsonFile;
|
||||
|
||||
public ConfigLocation() {
|
||||
appDir = new File(".");
|
||||
configPropertiesFile = new File(appDir, "config.properties");
|
||||
profilesJsonFile = new File(appDir, "profiles.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the application directory exists.
|
||||
*/
|
||||
public void ensureDirectoryExists() {
|
||||
if (!appDir.exists()) {
|
||||
appDir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
public File getAppDir() {
|
||||
return appDir;
|
||||
}
|
||||
|
||||
public File getConfigPropertiesFile() {
|
||||
return configPropertiesFile;
|
||||
}
|
||||
|
||||
public File getProfilesJsonFile() {
|
||||
return profilesJsonFile;
|
||||
}
|
||||
|
||||
public String getConfigPropertiesFilePath() {
|
||||
return configPropertiesFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getProfilesJsonFilePath() {
|
||||
return profilesJsonFile.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
123
src/main/java/cz/kamma/llamarunner/ConfigValidation.java
Normal file
123
src/main/java/cz/kamma/llamarunner/ConfigValidation.java
Normal file
@ -0,0 +1,123 @@
|
||||
package cz.kamma.llamarunner;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Validation utilities for configuration inputs.
|
||||
*/
|
||||
public class ConfigValidation {
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
/**
|
||||
* Validates numeric fields for valid ranges.
|
||||
*
|
||||
* @return a list of error messages (empty if valid)
|
||||
*/
|
||||
public static java.util.List<String> validateNumericInputs(
|
||||
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
|
||||
if (port < 1 || port > 65535) {
|
||||
errors.add("Port must be between 1 and 65535");
|
||||
}
|
||||
|
||||
// Validate parallel
|
||||
if (parallel < 1) {
|
||||
errors.add("Parallel must be at least 1");
|
||||
}
|
||||
|
||||
// Validate threads
|
||||
if (threads < 1) {
|
||||
errors.add("Threads must be at least 1");
|
||||
}
|
||||
|
||||
// Validate temperature
|
||||
if (temperature < 0) {
|
||||
errors.add("Temperature must be non-negative");
|
||||
}
|
||||
|
||||
// Validate topP
|
||||
if (topP < 0 || topP > 1) {
|
||||
errors.add("Top P must be between 0 and 1");
|
||||
}
|
||||
|
||||
// Validate topK
|
||||
if (topK < 0) {
|
||||
errors.add("Top K must be non-negative");
|
||||
}
|
||||
|
||||
// Validate minP
|
||||
if (minP < 0 || minP > 1) {
|
||||
errors.add("Min P must be between 0 and 1");
|
||||
}
|
||||
|
||||
// Validate ctxSize
|
||||
if (ctxSize < 0) {
|
||||
errors.add("Context size must be non-negative");
|
||||
}
|
||||
|
||||
// Validate ngl
|
||||
if (ngl < 0) {
|
||||
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";
|
||||
}
|
||||
|
||||
File modelFile = new File(modelPath);
|
||||
if (!modelFile.exists()) {
|
||||
return "Model file does not exist: " + modelPath;
|
||||
}
|
||||
|
||||
if (!modelFile.isFile()) {
|
||||
return "Model path is not a file: " + modelPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
1030
src/main/java/cz/kamma/llamarunner/Main.java
Normal file
1030
src/main/java/cz/kamma/llamarunner/Main.java
Normal file
File diff suppressed because it is too large
Load Diff
129
src/main/java/cz/kamma/llamarunner/ModelConfig.java
Normal file
129
src/main/java/cz/kamma/llamarunner/ModelConfig.java
Normal file
@ -0,0 +1,129 @@
|
||||
package cz.kamma.llamarunner;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Class for storing llama-server configuration.
|
||||
*/
|
||||
public class ModelConfig implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String host;
|
||||
private int port;
|
||||
private int parallel;
|
||||
private int threads;
|
||||
private boolean flashAttention;
|
||||
private boolean kvUnified;
|
||||
private String cacheTypeK;
|
||||
private String cacheTypeV;
|
||||
private double temperature;
|
||||
private double topP;
|
||||
private int topK;
|
||||
private double minP;
|
||||
private int ctxSize;
|
||||
private boolean enableThinking;
|
||||
private String modelPath;
|
||||
private String chatTemplateKwargs;
|
||||
private int ngl;
|
||||
private boolean fit;
|
||||
|
||||
public ModelConfig() {
|
||||
this.host = "0.0.0.0";
|
||||
this.port = 3080;
|
||||
this.parallel = 1;
|
||||
this.threads = 99;
|
||||
this.flashAttention = true;
|
||||
this.kvUnified = true;
|
||||
this.cacheTypeK = "bf16";
|
||||
this.cacheTypeV = "bf16";
|
||||
this.temperature = 0.6;
|
||||
this.topP = 0.95;
|
||||
this.topK = 20;
|
||||
this.minP = 0.00;
|
||||
this.ctxSize = 180000;
|
||||
this.enableThinking = false;
|
||||
this.modelPath = "";
|
||||
this.chatTemplateKwargs = "";
|
||||
this.ngl = 999;
|
||||
this.fit = false;
|
||||
}
|
||||
|
||||
// Getters and setters
|
||||
public String getHost() { return host; }
|
||||
public void setHost(String host) { this.host = host; }
|
||||
|
||||
public int getPort() { return port; }
|
||||
public void setPort(int port) { this.port = port; }
|
||||
|
||||
public int getParallel() { return parallel; }
|
||||
public void setParallel(int parallel) { this.parallel = parallel; }
|
||||
|
||||
public int getThreads() { return threads; }
|
||||
public void setThreads(int threads) { this.threads = threads; }
|
||||
|
||||
public boolean isFlashAttention() { return flashAttention; }
|
||||
public void setFlashAttention(boolean flashAttention) { this.flashAttention = flashAttention; }
|
||||
|
||||
public boolean isKvUnified() { return kvUnified; }
|
||||
public void setKvUnified(boolean kvUnified) { this.kvUnified = kvUnified; }
|
||||
|
||||
public String getCacheTypeK() { return cacheTypeK; }
|
||||
public void setCacheTypeK(String cacheTypeK) { this.cacheTypeK = cacheTypeK; }
|
||||
|
||||
public String getCacheTypeV() { return cacheTypeV; }
|
||||
public void setCacheTypeV(String cacheTypeV) { this.cacheTypeV = cacheTypeV; }
|
||||
|
||||
public double getTemperature() { return temperature; }
|
||||
public void setTemperature(double temperature) { this.temperature = temperature; }
|
||||
|
||||
public double getTopP() { return topP; }
|
||||
public void setTopP(double topP) { this.topP = topP; }
|
||||
|
||||
public int getTopK() { return topK; }
|
||||
public void setTopK(int topK) { this.topK = topK; }
|
||||
|
||||
public double getMinP() { return minP; }
|
||||
public void setMinP(double minP) { this.minP = minP; }
|
||||
|
||||
public int getCtxSize() { return ctxSize; }
|
||||
public void setCtxSize(int ctxSize) { this.ctxSize = ctxSize; }
|
||||
|
||||
public boolean isEnableThinking() { return enableThinking; }
|
||||
public void setEnableThinking(boolean enableThinking) { this.enableThinking = enableThinking; }
|
||||
|
||||
public String getModelPath() { return modelPath; }
|
||||
public void setModelPath(String modelPath) { this.modelPath = modelPath; }
|
||||
|
||||
public String getChatTemplateKwargs() { return chatTemplateKwargs; }
|
||||
public void setChatTemplateKwargs(String chatTemplateKwargs) { this.chatTemplateKwargs = chatTemplateKwargs; }
|
||||
|
||||
public int getNgl() { return ngl; }
|
||||
public void setNgl(int ngl) { this.ngl = ngl; }
|
||||
|
||||
public boolean isFit() { return fit; }
|
||||
public void setFit(boolean fit) { this.fit = fit; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModelConfig{" +
|
||||
"host='" + host + '\'' +
|
||||
", port=" + port +
|
||||
", parallel=" + parallel +
|
||||
", threads=" + threads +
|
||||
", flashAttention=" + flashAttention +
|
||||
", kvUnified=" + kvUnified +
|
||||
", cacheTypeK='" + cacheTypeK + '\'' +
|
||||
", cacheTypeV='" + cacheTypeV + '\'' +
|
||||
", temperature=" + temperature +
|
||||
", topP=" + topP +
|
||||
", topK=" + topK +
|
||||
", minP=" + minP +
|
||||
", ctxSize=" + ctxSize +
|
||||
", enableThinking=" + enableThinking +
|
||||
", modelPath='" + modelPath + '\'' +
|
||||
", chatTemplateKwargs='" + chatTemplateKwargs + '\'' +
|
||||
", ngl=" + ngl +
|
||||
", fit=" + fit +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
137
src/main/java/cz/kamma/llamarunner/ProfileManager.java
Normal file
137
src/main/java/cz/kamma/llamarunner/ProfileManager.java
Normal file
@ -0,0 +1,137 @@
|
||||
package cz.kamma.llamarunner;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* Profile management using JSON file in user's home directory.
|
||||
*/
|
||||
public class ProfileManager {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
private final ConfigLocation configLocation;
|
||||
|
||||
public ProfileManager() {
|
||||
this(new ConfigLocation());
|
||||
}
|
||||
|
||||
public ProfileManager(ConfigLocation configLocation) {
|
||||
this.configLocation = configLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves profile to JSON file.
|
||||
*/
|
||||
public void saveProfile(String name, ModelConfig config) throws IOException {
|
||||
configLocation.ensureDirectoryExists();
|
||||
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||
profiles.put(name, config);
|
||||
|
||||
try (FileWriter writer = new FileWriter(configLocation.getProfilesJsonFile())) {
|
||||
GSON.toJson(profiles, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads profile from JSON file.
|
||||
*/
|
||||
public ModelConfig loadProfile(String name) throws IOException {
|
||||
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||
ModelConfig config = profiles.get(name);
|
||||
if (config == null) {
|
||||
throw new IOException("Profile does not exist: " + name);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes profile from JSON file.
|
||||
*/
|
||||
public void deleteProfile(String name) throws IOException {
|
||||
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||
if (!profiles.containsKey(name)) {
|
||||
throw new IOException("Profile does not exist: " + name);
|
||||
}
|
||||
profiles.remove(name);
|
||||
|
||||
try (FileWriter writer = new FileWriter(configLocation.getProfilesJsonFile())) {
|
||||
GSON.toJson(profiles, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the profiles file.
|
||||
*/
|
||||
public String getProfilesFilePath() {
|
||||
return configLocation.getProfilesJsonFilePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of profile names.
|
||||
*/
|
||||
public List<String> listProfiles() throws IOException {
|
||||
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||
return profiles != null ? new ArrayList<>(profiles.keySet()) : new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if profile exists.
|
||||
*/
|
||||
public boolean profileExists(String name) {
|
||||
try {
|
||||
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||
return profiles.containsKey(name);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a profile from oldName to newName.
|
||||
*/
|
||||
public void renameProfile(String oldName, String newName) throws IOException {
|
||||
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||
if (!profiles.containsKey(oldName)) {
|
||||
throw new IOException("Profile does not exist: " + oldName);
|
||||
}
|
||||
if (oldName.equals(newName)) {
|
||||
throw new IOException("Old and new names must be different.");
|
||||
}
|
||||
if (profiles.containsKey(newName)) {
|
||||
throw new IOException("Profile already exists: " + newName);
|
||||
}
|
||||
|
||||
ModelConfig config = profiles.remove(oldName);
|
||||
profiles.put(newName, config);
|
||||
|
||||
try (FileWriter writer = new FileWriter(configLocation.getProfilesJsonFile())) {
|
||||
GSON.toJson(profiles, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all profiles from JSON file.
|
||||
*/
|
||||
private Map<String, ModelConfig> loadAllProfiles() throws IOException {
|
||||
if (!configLocation.getProfilesJsonFile().exists()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
try (FileReader reader = new FileReader(configLocation.getProfilesJsonFile())) {
|
||||
java.lang.reflect.Type mapType = new TypeToken<Map<String, ModelConfig>>() {}.getType();
|
||||
Map<String, ModelConfig> profiles = GSON.fromJson(reader, mapType);
|
||||
return profiles != null ? profiles : new HashMap<>();
|
||||
} catch (Exception e) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/main/java/cz/kamma/llamarunner/ProfileValidator.java
Normal file
51
src/main/java/cz/kamma/llamarunner/ProfileValidator.java
Normal file
@ -0,0 +1,51 @@
|
||||
package cz.kamma.llamarunner;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Validation utilities for profile names.
|
||||
*/
|
||||
public class ProfileValidator {
|
||||
|
||||
private static final int MAX_NAME_LENGTH = 100;
|
||||
private static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");
|
||||
|
||||
/**
|
||||
* Validates a profile name.
|
||||
*
|
||||
* @param name the profile name to validate
|
||||
* @return error message if invalid, null if valid
|
||||
*/
|
||||
public static String validateProfileName(String name) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return "Profile name cannot be empty";
|
||||
}
|
||||
|
||||
String trimmedName = name.trim();
|
||||
|
||||
if (trimmedName.length() > MAX_NAME_LENGTH) {
|
||||
return "Profile name must be at most " + MAX_NAME_LENGTH + " characters";
|
||||
}
|
||||
|
||||
if (!VALID_NAME_PATTERN.matcher(trimmedName).matches()) {
|
||||
return "Profile name can only contain letters, numbers, underscores, and hyphens";
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/llama.png
Normal file
BIN
src/main/resources/llama.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Loading…
x
Reference in New Issue
Block a user