aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui/src/stores/settings.svelte.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/ui/src/stores/settings.svelte.ts')
-rw-r--r--packages/ui/src/stores/settings.svelte.ts570
1 files changed, 570 insertions, 0 deletions
diff --git a/packages/ui/src/stores/settings.svelte.ts b/packages/ui/src/stores/settings.svelte.ts
new file mode 100644
index 0000000..5d20050
--- /dev/null
+++ b/packages/ui/src/stores/settings.svelte.ts
@@ -0,0 +1,570 @@
+import { invoke } from "@tauri-apps/api/core";
+import { convertFileSrc } from "@tauri-apps/api/core";
+import { listen, type UnlistenFn } from "@tauri-apps/api/event";
+import type {
+ JavaCatalog,
+ JavaDownloadProgress,
+ JavaDownloadSource,
+ JavaInstallation,
+ JavaReleaseInfo,
+ LauncherConfig,
+ ModelInfo,
+ PendingJavaDownload,
+} from "../types";
+import { uiState } from "./ui.svelte";
+
+export class SettingsState {
+ settings = $state<LauncherConfig>({
+ min_memory: 1024,
+ max_memory: 2048,
+ java_path: "java",
+ width: 854,
+ height: 480,
+ download_threads: 32,
+ enable_gpu_acceleration: false,
+ enable_visual_effects: true,
+ active_effect: "constellation",
+ theme: "dark",
+ custom_background_path: undefined,
+ log_upload_service: "paste.rs",
+ pastebin_api_key: undefined,
+ assistant: {
+ enabled: true,
+ llm_provider: "ollama",
+ ollama_endpoint: "http://localhost:11434",
+ ollama_model: "llama3",
+ openai_api_key: undefined,
+ openai_endpoint: "https://api.openai.com/v1",
+ openai_model: "gpt-3.5-turbo",
+ system_prompt:
+ "You are a helpful Minecraft expert assistant. You help players with game issues, mod installation, performance optimization, and gameplay tips. Analyze any game logs provided and give concise, actionable advice.",
+ response_language: "auto",
+ tts_enabled: false,
+ tts_provider: "disabled",
+ },
+ use_shared_caches: false,
+ keep_legacy_per_instance_storage: true,
+ feature_flags: {
+ demo_user: false,
+ quick_play_enabled: false,
+ quick_play_path: undefined,
+ quick_play_singleplayer: true,
+ quick_play_multiplayer_server: undefined,
+ },
+ });
+
+ // Convert background path to proper asset URL
+ get backgroundUrl(): string | undefined {
+ if (this.settings.custom_background_path) {
+ return convertFileSrc(this.settings.custom_background_path);
+ }
+ return undefined;
+ }
+ javaInstallations = $state<JavaInstallation[]>([]);
+ isDetectingJava = $state(false);
+
+ // Java download modal state
+ showJavaDownloadModal = $state(false);
+ selectedDownloadSource = $state<JavaDownloadSource>("adoptium");
+
+ // Java catalog state
+ javaCatalog = $state<JavaCatalog | null>(null);
+ isLoadingCatalog = $state(false);
+ catalogError = $state("");
+
+ // Version selection state
+ selectedMajorVersion = $state<number | null>(null);
+ selectedImageType = $state<"jre" | "jdk">("jre");
+ showOnlyRecommended = $state(true);
+ searchQuery = $state("");
+
+ // Download progress state
+ isDownloadingJava = $state(false);
+ downloadProgress = $state<JavaDownloadProgress | null>(null);
+ javaDownloadStatus = $state("");
+
+ // Pending downloads
+ pendingDownloads = $state<PendingJavaDownload[]>([]);
+
+ // AI Model lists
+ ollamaModels = $state<ModelInfo[]>([]);
+ openaiModels = $state<ModelInfo[]>([]);
+ isLoadingOllamaModels = $state(false);
+ isLoadingOpenaiModels = $state(false);
+ ollamaModelsError = $state("");
+ openaiModelsError = $state("");
+
+ // Config Editor state
+ showConfigEditor = $state(false);
+ rawConfigContent = $state("");
+ configFilePath = $state("");
+ configEditorError = $state("");
+
+ // Event listener cleanup
+ private progressUnlisten: UnlistenFn | null = null;
+
+ async openConfigEditor() {
+ this.configEditorError = "";
+ try {
+ const path = await invoke<string>("get_config_path");
+ const content = await invoke<string>("read_raw_config");
+ this.configFilePath = path;
+ this.rawConfigContent = content;
+ this.showConfigEditor = true;
+ } catch (e) {
+ console.error("Failed to open config editor:", e);
+ uiState.setStatus(`Failed to open config: ${e}`);
+ }
+ }
+
+ async saveRawConfig(content: string, closeAfterSave = true) {
+ try {
+ await invoke("save_raw_config", { content });
+ // Reload settings to ensure UI is in sync
+ await this.loadSettings();
+ if (closeAfterSave) {
+ this.showConfigEditor = false;
+ }
+ uiState.setStatus("Configuration saved successfully!");
+ } catch (e) {
+ console.error("Failed to save config:", e);
+ this.configEditorError = String(e);
+ }
+ }
+
+ closeConfigEditor() {
+ this.showConfigEditor = false;
+ this.rawConfigContent = "";
+ this.configEditorError = "";
+ }
+
+ // Computed: filtered releases based on selection
+ get filteredReleases(): JavaReleaseInfo[] {
+ if (!this.javaCatalog) return [];
+
+ let releases = this.javaCatalog.releases;
+
+ // Filter by major version if selected
+ if (this.selectedMajorVersion !== null) {
+ releases = releases.filter((r) => r.major_version === this.selectedMajorVersion);
+ }
+
+ // Filter by image type
+ releases = releases.filter((r) => r.image_type === this.selectedImageType);
+
+ // Filter by recommended (LTS) versions
+ if (this.showOnlyRecommended) {
+ releases = releases.filter((r) => r.is_lts);
+ }
+
+ // Filter by search query
+ if (this.searchQuery.trim()) {
+ const query = this.searchQuery.toLowerCase();
+ releases = releases.filter(
+ (r) =>
+ r.release_name.toLowerCase().includes(query) ||
+ r.version.toLowerCase().includes(query) ||
+ r.major_version.toString().includes(query),
+ );
+ }
+
+ return releases;
+ }
+
+ // Computed: available major versions for display
+ get availableMajorVersions(): number[] {
+ if (!this.javaCatalog) return [];
+ let versions = [...this.javaCatalog.available_major_versions];
+
+ // Filter by LTS if showOnlyRecommended is enabled
+ if (this.showOnlyRecommended) {
+ versions = versions.filter((v) => this.javaCatalog!.lts_versions.includes(v));
+ }
+
+ // Sort descending (newest first)
+ return versions.sort((a, b) => b - a);
+ }
+
+ // Get installation status for a release: 'installed' | 'download'
+ getInstallStatus(release: JavaReleaseInfo): "installed" | "download" {
+ // Find installed Java that matches the major version and image type (by path pattern)
+ const matchingInstallations = this.javaInstallations.filter((inst) => {
+ // Check if this is a DropOut-managed Java (path contains temurin-XX-jre/jdk pattern)
+ const pathLower = inst.path.toLowerCase();
+ const pattern = `temurin-${release.major_version}-${release.image_type}`;
+ return pathLower.includes(pattern);
+ });
+
+ // If any matching installation exists, it's installed
+ return matchingInstallations.length > 0 ? "installed" : "download";
+ }
+
+ // Computed: selected release details
+ get selectedRelease(): JavaReleaseInfo | null {
+ if (!this.javaCatalog || this.selectedMajorVersion === null) return null;
+ return (
+ this.javaCatalog.releases.find(
+ (r) =>
+ r.major_version === this.selectedMajorVersion && r.image_type === this.selectedImageType,
+ ) || null
+ );
+ }
+
+ async loadSettings() {
+ try {
+ const result = await invoke<LauncherConfig>("get_settings");
+ this.settings = result;
+ // Force dark mode
+ if (this.settings.theme !== "dark") {
+ this.settings.theme = "dark";
+ this.saveSettings();
+ }
+ // Ensure custom_background_path is reactive
+ if (!this.settings.custom_background_path) {
+ this.settings.custom_background_path = undefined;
+ }
+ } catch (e) {
+ console.error("Failed to load settings:", e);
+ }
+ }
+
+ async saveSettings() {
+ try {
+ // Ensure we clean up any invalid paths before saving
+ if (this.settings.custom_background_path === "") {
+ this.settings.custom_background_path = undefined;
+ }
+
+ await invoke("save_settings", { config: this.settings });
+ uiState.setStatus("Settings saved!");
+ } catch (e) {
+ console.error("Failed to save settings:", e);
+ uiState.setStatus("Error saving settings: " + e);
+ }
+ }
+
+ async detectJava() {
+ this.isDetectingJava = true;
+ try {
+ this.javaInstallations = await invoke("detect_java");
+ if (this.javaInstallations.length === 0) {
+ uiState.setStatus("No Java installations found");
+ } else {
+ uiState.setStatus(`Found ${this.javaInstallations.length} Java installation(s)`);
+ }
+ } catch (e) {
+ console.error("Failed to detect Java:", e);
+ uiState.setStatus("Error detecting Java: " + e);
+ } finally {
+ this.isDetectingJava = false;
+ }
+ }
+
+ selectJava(path: string) {
+ this.settings.java_path = path;
+ }
+
+ async openJavaDownloadModal() {
+ this.showJavaDownloadModal = true;
+ this.javaDownloadStatus = "";
+ this.catalogError = "";
+ this.downloadProgress = null;
+
+ // Setup progress event listener
+ await this.setupProgressListener();
+
+ // Load catalog
+ await this.loadJavaCatalog(false);
+
+ // Check for pending downloads
+ await this.loadPendingDownloads();
+ }
+
+ async closeJavaDownloadModal() {
+ if (!this.isDownloadingJava) {
+ this.showJavaDownloadModal = false;
+ // Cleanup listener
+ if (this.progressUnlisten) {
+ this.progressUnlisten();
+ this.progressUnlisten = null;
+ }
+ }
+ }
+
+ private async setupProgressListener() {
+ if (this.progressUnlisten) {
+ this.progressUnlisten();
+ }
+
+ this.progressUnlisten = await listen<JavaDownloadProgress>(
+ "java-download-progress",
+ (event) => {
+ this.downloadProgress = event.payload;
+ this.javaDownloadStatus = event.payload.status;
+
+ if (event.payload.status === "Completed") {
+ this.isDownloadingJava = false;
+ setTimeout(async () => {
+ await this.detectJava();
+ uiState.setStatus(`Java installed successfully!`);
+ }, 500);
+ } else if (event.payload.status === "Error") {
+ this.isDownloadingJava = false;
+ }
+ },
+ );
+ }
+
+ async loadJavaCatalog(forceRefresh: boolean) {
+ this.isLoadingCatalog = true;
+ this.catalogError = "";
+
+ try {
+ const command = forceRefresh ? "refresh_java_catalog" : "fetch_java_catalog";
+ this.javaCatalog = await invoke<JavaCatalog>(command);
+
+ // Auto-select first LTS version
+ if (this.selectedMajorVersion === null && this.javaCatalog.lts_versions.length > 0) {
+ // Select most recent LTS (21 or highest)
+ const ltsVersions = [...this.javaCatalog.lts_versions].sort((a, b) => b - a);
+ this.selectedMajorVersion = ltsVersions[0];
+ }
+ } catch (e) {
+ console.error("Failed to load Java catalog:", e);
+ this.catalogError = `Failed to load Java catalog: ${e}`;
+ } finally {
+ this.isLoadingCatalog = false;
+ }
+ }
+
+ async refreshCatalog() {
+ await this.loadJavaCatalog(true);
+ uiState.setStatus("Java catalog refreshed");
+ }
+
+ async loadPendingDownloads() {
+ try {
+ this.pendingDownloads = await invoke<PendingJavaDownload[]>("get_pending_java_downloads");
+ } catch (e) {
+ console.error("Failed to load pending downloads:", e);
+ }
+ }
+
+ selectMajorVersion(version: number) {
+ this.selectedMajorVersion = version;
+ }
+
+ async downloadJava() {
+ if (!this.selectedRelease || !this.selectedRelease.is_available) {
+ uiState.setStatus("Selected Java version is not available for this platform");
+ return;
+ }
+
+ this.isDownloadingJava = true;
+ this.javaDownloadStatus = "Starting download...";
+ this.downloadProgress = null;
+
+ try {
+ const result: JavaInstallation = await invoke("download_adoptium_java", {
+ majorVersion: this.selectedMajorVersion,
+ imageType: this.selectedImageType,
+ customPath: null,
+ });
+
+ this.settings.java_path = result.path;
+ await this.detectJava();
+
+ setTimeout(() => {
+ this.showJavaDownloadModal = false;
+ uiState.setStatus(`Java ${this.selectedMajorVersion} is ready to use!`);
+ }, 1500);
+ } catch (e) {
+ console.error("Failed to download Java:", e);
+ this.javaDownloadStatus = `Download failed: ${e}`;
+ } finally {
+ this.isDownloadingJava = false;
+ }
+ }
+
+ async cancelDownload() {
+ try {
+ await invoke("cancel_java_download");
+ this.isDownloadingJava = false;
+ this.javaDownloadStatus = "Download cancelled";
+ this.downloadProgress = null;
+ await this.loadPendingDownloads();
+ } catch (e) {
+ console.error("Failed to cancel download:", e);
+ }
+ }
+
+ async resumeDownloads() {
+ if (this.pendingDownloads.length === 0) return;
+
+ this.isDownloadingJava = true;
+ this.javaDownloadStatus = "Resuming download...";
+
+ try {
+ const installed = await invoke<JavaInstallation[]>("resume_java_downloads");
+ if (installed.length > 0) {
+ this.settings.java_path = installed[0].path;
+ await this.detectJava();
+ uiState.setStatus(`Resumed and installed ${installed.length} Java version(s)`);
+ }
+ await this.loadPendingDownloads();
+ } catch (e) {
+ console.error("Failed to resume downloads:", e);
+ this.javaDownloadStatus = `Resume failed: ${e}`;
+ } finally {
+ this.isDownloadingJava = false;
+ }
+ }
+
+ // Format bytes to human readable
+ formatBytes(bytes: number): string {
+ if (bytes === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
+ }
+
+ // Format seconds to human readable
+ formatTime(seconds: number): string {
+ if (seconds === 0 || !isFinite(seconds)) return "--";
+ if (seconds < 60) return `${Math.round(seconds)}s`;
+ if (seconds < 3600) {
+ const mins = Math.floor(seconds / 60);
+ const secs = Math.round(seconds % 60);
+ return `${mins}m ${secs}s`;
+ }
+ const hours = Math.floor(seconds / 3600);
+ const mins = Math.floor((seconds % 3600) / 60);
+ return `${hours}h ${mins}m`;
+ }
+
+ // Format date string
+ formatDate(dateStr: string | null): string {
+ if (!dateStr) return "--";
+ try {
+ const date = new Date(dateStr);
+ return date.toLocaleDateString("en-US", {
+ year: "2-digit",
+ month: "2-digit",
+ day: "2-digit",
+ });
+ } catch {
+ return "--";
+ }
+ }
+
+ // Legacy compatibility
+ get availableJavaVersions(): number[] {
+ return this.availableMajorVersions;
+ }
+
+ // AI Model loading methods
+ async loadOllamaModels() {
+ this.isLoadingOllamaModels = true;
+ this.ollamaModelsError = "";
+
+ try {
+ const models = await invoke<ModelInfo[]>("list_ollama_models", {
+ endpoint: this.settings.assistant.ollama_endpoint,
+ });
+ this.ollamaModels = models;
+
+ // If no model is selected or selected model isn't available, select the first one
+ if (models.length > 0) {
+ const currentModel = this.settings.assistant.ollama_model;
+ const modelExists = models.some((m) => m.id === currentModel);
+ if (!modelExists) {
+ this.settings.assistant.ollama_model = models[0].id;
+ }
+ }
+ } catch (e) {
+ console.error("Failed to load Ollama models:", e);
+ this.ollamaModelsError = String(e);
+ this.ollamaModels = [];
+ } finally {
+ this.isLoadingOllamaModels = false;
+ }
+ }
+
+ async loadOpenaiModels() {
+ if (!this.settings.assistant.openai_api_key) {
+ this.openaiModelsError = "API key required";
+ this.openaiModels = [];
+ return;
+ }
+
+ this.isLoadingOpenaiModels = true;
+ this.openaiModelsError = "";
+
+ try {
+ const models = await invoke<ModelInfo[]>("list_openai_models");
+ this.openaiModels = models;
+
+ // If no model is selected or selected model isn't available, select the first one
+ if (models.length > 0) {
+ const currentModel = this.settings.assistant.openai_model;
+ const modelExists = models.some((m) => m.id === currentModel);
+ if (!modelExists) {
+ this.settings.assistant.openai_model = models[0].id;
+ }
+ }
+ } catch (e) {
+ console.error("Failed to load OpenAI models:", e);
+ this.openaiModelsError = String(e);
+ this.openaiModels = [];
+ } finally {
+ this.isLoadingOpenaiModels = false;
+ }
+ }
+
+ // Computed: get model options for current provider
+ get currentModelOptions(): { value: string; label: string; details?: string }[] {
+ const provider = this.settings.assistant.llm_provider;
+
+ if (provider === "ollama") {
+ if (this.ollamaModels.length === 0) {
+ // Return fallback options if no models loaded
+ return [
+ { value: "llama3", label: "Llama 3" },
+ { value: "llama3.1", label: "Llama 3.1" },
+ { value: "llama3.2", label: "Llama 3.2" },
+ { value: "mistral", label: "Mistral" },
+ { value: "gemma2", label: "Gemma 2" },
+ { value: "qwen2.5", label: "Qwen 2.5" },
+ { value: "phi3", label: "Phi-3" },
+ { value: "codellama", label: "Code Llama" },
+ ];
+ }
+ return this.ollamaModels.map((m) => ({
+ value: m.id,
+ label: m.name,
+ details: m.size ? `${m.size}${m.details ? ` - ${m.details}` : ""}` : m.details,
+ }));
+ } else if (provider === "openai") {
+ if (this.openaiModels.length === 0) {
+ // Return fallback options if no models loaded
+ return [
+ { value: "gpt-4o", label: "GPT-4o" },
+ { value: "gpt-4o-mini", label: "GPT-4o Mini" },
+ { value: "gpt-4-turbo", label: "GPT-4 Turbo" },
+ { value: "gpt-4", label: "GPT-4" },
+ { value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" },
+ ];
+ }
+ return this.openaiModels.map((m) => ({
+ value: m.id,
+ label: m.name,
+ details: m.details,
+ }));
+ }
+
+ return [];
+ }
+}
+
+export const settingsState = new SettingsState();