diff options
| -rw-r--r-- | ui/src/stores/settings.svelte.ts | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts index 12e4a1c..8a90736 100644 --- a/ui/src/stores/settings.svelte.ts +++ b/ui/src/stores/settings.svelte.ts @@ -8,6 +8,7 @@ import type { JavaInstallation, JavaReleaseInfo, LauncherConfig, + ModelInfo, PendingJavaDownload, } from "../types"; import { uiState } from "./ui.svelte"; @@ -27,6 +28,20 @@ export class SettingsState { 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", + }, }); // Convert background path to proper asset URL @@ -62,9 +77,58 @@ export class SettingsState { // 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 []; @@ -389,6 +453,109 @@ export class SettingsState { 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(); |