diff options
Diffstat (limited to 'packages/ui')
| -rw-r--r-- | packages/ui/src/models/assistant-store.ts.bk (renamed from packages/ui/src/stores/assistant-store.ts) | 0 | ||||
| -rw-r--r-- | packages/ui/src/models/logs-store.ts.bk (renamed from packages/ui/src/stores/logs-store.ts) | 0 | ||||
| -rw-r--r-- | packages/ui/src/models/settings-store.ts.bk (renamed from packages/ui/src/stores/settings-store.ts) | 0 | ||||
| -rw-r--r-- | packages/ui/src/pages/assistant-view.tsx.bk | 485 | ||||
| -rw-r--r-- | packages/ui/src/stores/auth-store.ts | 296 | ||||
| -rw-r--r-- | packages/ui/src/stores/releases-store.ts | 63 | ||||
| -rw-r--r-- | packages/ui/src/stores/ui-store.ts | 42 |
7 files changed, 0 insertions, 886 deletions
diff --git a/packages/ui/src/stores/assistant-store.ts b/packages/ui/src/models/assistant-store.ts.bk index 180031b..180031b 100644 --- a/packages/ui/src/stores/assistant-store.ts +++ b/packages/ui/src/models/assistant-store.ts.bk diff --git a/packages/ui/src/stores/logs-store.ts b/packages/ui/src/models/logs-store.ts.bk index b19f206..b19f206 100644 --- a/packages/ui/src/stores/logs-store.ts +++ b/packages/ui/src/models/logs-store.ts.bk diff --git a/packages/ui/src/stores/settings-store.ts b/packages/ui/src/models/settings-store.ts.bk index 0bfc1e1..0bfc1e1 100644 --- a/packages/ui/src/stores/settings-store.ts +++ b/packages/ui/src/models/settings-store.ts.bk diff --git a/packages/ui/src/pages/assistant-view.tsx.bk b/packages/ui/src/pages/assistant-view.tsx.bk deleted file mode 100644 index 56f827b..0000000 --- a/packages/ui/src/pages/assistant-view.tsx.bk +++ /dev/null @@ -1,485 +0,0 @@ -import { - AlertTriangle, - Bot, - Brain, - ChevronDown, - Loader2, - RefreshCw, - Send, - Settings, - Trash2, -} from "lucide-react"; -import { marked } from "marked"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Separator } from "@/components/ui/separator"; -import { Textarea } from "@/components/ui/textarea"; -import { toNumber } from "@/lib/tsrs-utils"; -import { type Message, useAssistantStore } from "../stores/assistant-store"; -import { useSettingsStore } from "../stores/settings-store"; -import { useUiStore } from "../stores/ui-store"; - -interface ParsedMessage { - thinking: string | null; - content: string; - isThinking: boolean; -} - -function parseMessageContent(content: string): ParsedMessage { - if (!content) return { thinking: null, content: "", isThinking: false }; - - // Support both <thinking> and <think> (DeepSeek uses <think>) - let startTag = "<thinking>"; - let endTag = "</thinking>"; - let startIndex = content.indexOf(startTag); - - if (startIndex === -1) { - startTag = "<think>"; - endTag = "</think>"; - startIndex = content.indexOf(startTag); - } - - // Also check for encoded tags if they weren't decoded properly - if (startIndex === -1) { - startTag = "\u003cthink\u003e"; - endTag = "\u003c/think\u003e"; - startIndex = content.indexOf(startTag); - } - - if (startIndex !== -1) { - const endIndex = content.indexOf(endTag, startIndex); - - if (endIndex !== -1) { - // Completed thinking block - const before = content.substring(0, startIndex); - const thinking = content - .substring(startIndex + startTag.length, endIndex) - .trim(); - const after = content.substring(endIndex + endTag.length); - - return { - thinking, - content: (before + after).trim(), - isThinking: false, - }; - } else { - // Incomplete thinking block (still streaming) - const before = content.substring(0, startIndex); - const thinking = content.substring(startIndex + startTag.length).trim(); - - return { - thinking, - content: before.trim(), - isThinking: true, - }; - } - } - - return { thinking: null, content, isThinking: false }; -} - -function renderMarkdown(content: string): string { - if (!content) return ""; - try { - return marked(content, { breaks: true, gfm: true }) as string; - } catch { - return content; - } -} - -export function AssistantView() { - const { - messages, - isProcessing, - isProviderHealthy, - streamingContent, - init, - checkHealth, - sendMessage, - clearHistory, - } = useAssistantStore(); - const { settings } = useSettingsStore(); - const { setView } = useUiStore(); - - const [input, setInput] = useState(""); - const messagesEndRef = useRef<HTMLDivElement>(null); - const messagesContainerRef = useRef<HTMLDivElement>(null); - - const provider = settings.assistant.llmProvider; - const endpoint = - provider === "ollama" - ? settings.assistant.ollamaEndpoint - : settings.assistant.openaiEndpoint; - const model = - provider === "ollama" - ? settings.assistant.ollamaModel - : settings.assistant.openaiModel; - - const getProviderName = (): string => { - if (provider === "ollama") { - return `Ollama (${model})`; - } else if (provider === "openai") { - return `OpenAI (${model})`; - } - return provider; - }; - - const getProviderHelpText = (): string => { - if (provider === "ollama") { - return `Please ensure Ollama is installed and running at ${endpoint}.`; - } else if (provider === "openai") { - return "Please check your OpenAI API key in Settings > AI Assistant."; - } - return ""; - }; - - const scrollToBottom = useCallback(() => { - if (messagesContainerRef.current) { - setTimeout(() => { - if (messagesContainerRef.current) { - messagesContainerRef.current.scrollTop = - messagesContainerRef.current.scrollHeight; - } - }, 0); - } - }, []); - - useEffect(() => { - init(); - }, [init]); - - useEffect(() => { - if (messages.length > 0 || isProcessing) { - scrollToBottom(); - } - }, [messages.length, isProcessing, scrollToBottom]); - - const handleSubmit = async () => { - if (!input.trim() || isProcessing) return; - const text = input; - setInput(""); - await sendMessage(text, settings.assistant.enabled, provider, endpoint); - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - handleSubmit(); - } - }; - - const renderMessage = (message: Message, index: number) => { - const isUser = message.role === "user"; - const parsed = parseMessageContent(message.content); - - return ( - <div - key={index} - className={`flex ${isUser ? "justify-end" : "justify-start"} mb-4`} - > - <div - className={`max-w-[80%] rounded-2xl px-4 py-3 ${ - isUser - ? "bg-indigo-500 text-white rounded-br-none" - : "bg-zinc-800 text-zinc-100 rounded-bl-none" - }`} - > - {!isUser && parsed.thinking && ( - <div className="mb-3 max-w-full overflow-hidden"> - <details className="group" open={parsed.isThinking}> - <summary className="list-none cursor-pointer flex items-center gap-2 text-zinc-500 hover:text-zinc-300 transition-colors text-xs font-medium select-none bg-black/20 p-2 rounded-lg border border-white/5 w-fit mb-2 outline-none"> - <Brain className="h-3 w-3" /> - <span>Thinking Process</span> - <ChevronDown className="h-3 w-3 transition-transform duration-200 group-open:rotate-180" /> - </summary> - <div className="pl-3 border-l-2 border-zinc-700 text-zinc-500 text-xs italic leading-relaxed whitespace-pre-wrap font-mono max-h-96 overflow-y-auto custom-scrollbar bg-black/10 p-2 rounded-r-md"> - {parsed.thinking} - {parsed.isThinking && ( - <span className="inline-block w-1.5 h-3 bg-zinc-500 ml-1 animate-pulse align-middle" /> - )} - </div> - </details> - </div> - )} - <div - className="prose prose-invert max-w-none" - dangerouslySetInnerHTML={{ - __html: renderMarkdown(parsed.content), - }} - /> - {!isUser && message.stats && ( - <div className="mt-2 pt-2 border-t border-zinc-700/50"> - <div className="text-xs text-zinc-400"> - {message.stats.evalCount} tokens ·{" "} - {Math.round(toNumber(message.stats.totalDuration) / 1000000)} - ms - </div> - </div> - )} - </div> - </div> - ); - }; - - return ( - <div className="h-full w-full flex flex-col gap-4 p-4 lg:p-8"> - <div className="flex items-center justify-between mb-2"> - <div className="flex items-center gap-3"> - <div className="p-2 bg-indigo-500/20 rounded-lg text-indigo-400"> - <Bot size={24} /> - </div> - <div> - <h2 className="text-2xl font-bold">Game Assistant</h2> - <p className="text-zinc-400 text-sm"> - Powered by {getProviderName()} - </p> - </div> - </div> - - <div className="flex items-center gap-2"> - {!settings.assistant.enabled ? ( - <Badge - variant="outline" - className="bg-zinc-500/10 text-zinc-400 border-zinc-500/20" - > - <AlertTriangle className="h-3 w-3 mr-1" /> - Disabled - </Badge> - ) : !isProviderHealthy ? ( - <Badge - variant="outline" - className="bg-red-500/10 text-red-400 border-red-500/20" - > - <AlertTriangle className="h-3 w-3 mr-1" /> - Offline - </Badge> - ) : ( - <Badge - variant="outline" - className="bg-emerald-500/10 text-emerald-400 border-emerald-500/20" - > - <div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse mr-1" /> - Online - </Badge> - )} - - <Button - variant="ghost" - size="icon" - onClick={checkHealth} - title="Check Connection" - disabled={isProcessing} - > - <RefreshCw - className={`h-4 w-4 ${isProcessing ? "animate-spin" : ""}`} - /> - </Button> - - <Button - variant="ghost" - size="icon" - onClick={clearHistory} - title="Clear History" - disabled={isProcessing} - > - <Trash2 className="h-4 w-4" /> - </Button> - - <Button - variant="ghost" - size="icon" - onClick={() => setView("settings")} - title="Settings" - > - <Settings className="h-4 w-4" /> - </Button> - </div> - </div> - - {/* Chat Area */} - <div className="flex-1 bg-black/20 border border-white/5 rounded-xl overflow-hidden flex flex-col relative"> - {/* Warning when assistant is disabled */} - {!settings.assistant.enabled && ( - <div className="absolute top-4 left-1/2 transform -translate-x-1/2 z-10"> - <Card className="bg-yellow-500/10 border-yellow-500/20"> - <CardContent className="p-3 flex items-center gap-2"> - <AlertTriangle className="h-4 w-4 text-yellow-500" /> - <span className="text-yellow-500 text-sm font-medium"> - Assistant is disabled. Enable it in Settings > AI - Assistant. - </span> - </CardContent> - </Card> - </div> - )} - - {/* Provider offline warning */} - {settings.assistant.enabled && !isProviderHealthy && ( - <div className="absolute top-4 left-1/2 transform -translate-x-1/2 z-10"> - <Card className="bg-red-500/10 border-red-500/20"> - <CardContent className="p-3 flex items-center gap-2"> - <AlertTriangle className="h-4 w-4 text-red-500" /> - <div className="flex flex-col"> - <span className="text-red-500 text-sm font-medium"> - Assistant is offline - </span> - <span className="text-red-400 text-xs"> - {getProviderHelpText()} - </span> - </div> - </CardContent> - </Card> - </div> - )} - - {/* Messages Container */} - <ScrollArea className="flex-1 p-4 lg:p-6" ref={messagesContainerRef}> - {messages.length === 0 ? ( - <div className="flex flex-col items-center justify-center h-full text-zinc-400 gap-4 mt-8"> - <div className="p-4 bg-zinc-800/50 rounded-full"> - <Bot className="h-12 w-12" /> - </div> - <h3 className="text-xl font-medium">How can I help you today?</h3> - <p className="text-center max-w-md text-sm"> - I can analyze your game logs, diagnose crashes, or explain mod - features. - {!settings.assistant.enabled && ( - <span className="block mt-2 text-yellow-500"> - Assistant is disabled. Enable it in{" "} - <button - type="button" - onClick={() => setView("settings")} - className="text-indigo-400 hover:underline" - > - Settings > AI Assistant - </button> - . - </span> - )} - </p> - <div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-2 max-w-lg"> - <Button - variant="outline" - className="text-left h-auto py-3" - onClick={() => - setInput("How do I fix Minecraft crashing on launch?") - } - disabled={isProcessing} - > - <div className="text-sm"> - How do I fix Minecraft crashing on launch? - </div> - </Button> - <Button - variant="outline" - className="text-left h-auto py-3" - onClick={() => - setInput("What's the best way to improve FPS?") - } - disabled={isProcessing} - > - <div className="text-sm"> - What's the best way to improve FPS? - </div> - </Button> - <Button - variant="outline" - className="text-left h-auto py-3" - onClick={() => - setInput( - "Can you help me install Fabric for Minecraft 1.20.4?", - ) - } - disabled={isProcessing} - > - <div className="text-sm"> - Can you help me install Fabric for 1.20.4? - </div> - </Button> - <Button - variant="outline" - className="text-left h-auto py-3" - onClick={() => - setInput("What mods do you recommend for performance?") - } - disabled={isProcessing} - > - <div className="text-sm"> - What mods do you recommend for performance? - </div> - </Button> - </div> - </div> - ) : ( - <> - {messages.map((message, index) => renderMessage(message, index))} - {isProcessing && streamingContent && ( - <div className="flex justify-start mb-4"> - <div className="max-w-[80%] bg-zinc-800 text-zinc-100 rounded-2xl rounded-bl-none px-4 py-3"> - <div - className="prose prose-invert max-w-none" - dangerouslySetInnerHTML={{ - __html: renderMarkdown(streamingContent), - }} - /> - <div className="flex items-center gap-1 mt-2 text-xs text-zinc-400"> - <Loader2 className="h-3 w-3 animate-spin" /> - <span>Assistant is typing...</span> - </div> - </div> - </div> - )} - </> - )} - <div ref={messagesEndRef} /> - </ScrollArea> - - <Separator /> - - {/* Input Area */} - <div className="p-3 lg:p-4"> - <div className="flex gap-2"> - <Textarea - placeholder={ - settings.assistant.enabled - ? "Ask about your game..." - : "Assistant is disabled. Enable it in Settings to use." - } - value={input} - onChange={(e) => setInput(e.target.value)} - onKeyDown={handleKeyDown} - className="min-h-11 max-h-50 resize-none border-zinc-700 bg-zinc-900/50 focus:bg-zinc-900/80" - disabled={!settings.assistant.enabled || isProcessing} - /> - <Button - onClick={handleSubmit} - disabled={ - !settings.assistant.enabled || !input.trim() || isProcessing - } - className="px-6 bg-indigo-600 hover:bg-indigo-700 text-white" - > - {isProcessing ? ( - <Loader2 className="h-4 w-4 animate-spin" /> - ) : ( - <Send className="h-4 w-4" /> - )} - </Button> - </div> - <div className="mt-2 flex items-center justify-between"> - <div className="text-xs text-zinc-500"> - {settings.assistant.enabled - ? "Press Enter to send, Shift+Enter for new line" - : "Enable the assistant in Settings to use"} - </div> - <div className="text-xs text-zinc-500"> - Model: {model} • Provider: {provider} - </div> - </div> - </div> - </div> - </div> - ); -} diff --git a/packages/ui/src/stores/auth-store.ts b/packages/ui/src/stores/auth-store.ts deleted file mode 100644 index bf7e3c5..0000000 --- a/packages/ui/src/stores/auth-store.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { invoke } from "@tauri-apps/api/core"; -import { listen, type UnlistenFn } from "@tauri-apps/api/event"; -import { open } from "@tauri-apps/plugin-shell"; -import { toast } from "sonner"; -import { create } from "zustand"; -import type { Account, DeviceCodeResponse } from "../types/bindings/auth"; - -interface AuthState { - // State - currentAccount: Account | null; - isLoginModalOpen: boolean; - isLogoutConfirmOpen: boolean; - loginMode: "select" | "offline" | "microsoft"; - offlineUsername: string; - deviceCodeData: DeviceCodeResponse | null; - msLoginLoading: boolean; - msLoginStatus: string; - - // Private state - pollInterval: ReturnType<typeof setInterval> | null; - isPollingRequestActive: boolean; - authProgressUnlisten: UnlistenFn | null; - - // Actions - checkAccount: () => Promise<void>; - openLoginModal: () => void; - openLogoutConfirm: () => void; - cancelLogout: () => void; - confirmLogout: () => Promise<void>; - closeLoginModal: () => void; - resetLoginState: () => void; - performOfflineLogin: () => Promise<void>; - startMicrosoftLogin: () => Promise<void>; - checkLoginStatus: (deviceCode: string) => Promise<void>; - stopPolling: () => void; - cancelMicrosoftLogin: () => void; - setLoginMode: (mode: "select" | "offline" | "microsoft") => void; - setOfflineUsername: (username: string) => void; -} - -export const useAuthStore = create<AuthState>((set, get) => ({ - // Initial state - currentAccount: null, - isLoginModalOpen: false, - isLogoutConfirmOpen: false, - loginMode: "select", - offlineUsername: "", - deviceCodeData: null, - msLoginLoading: false, - msLoginStatus: "Waiting for authorization...", - - // Private state - pollInterval: null, - isPollingRequestActive: false, - authProgressUnlisten: null, - - // Actions - checkAccount: async () => { - try { - const acc = await invoke<Account | null>("get_active_account"); - set({ currentAccount: acc }); - } catch (error) { - console.error("Failed to check account:", error); - } - }, - - openLoginModal: () => { - const { currentAccount } = get(); - if (currentAccount) { - // Show custom logout confirmation dialog - set({ isLogoutConfirmOpen: true }); - return; - } - get().resetLoginState(); - set({ isLoginModalOpen: true }); - }, - - openLogoutConfirm: () => { - set({ isLogoutConfirmOpen: true }); - }, - - cancelLogout: () => { - set({ isLogoutConfirmOpen: false }); - }, - - confirmLogout: async () => { - set({ isLogoutConfirmOpen: false }); - try { - await invoke("logout"); - set({ currentAccount: null }); - } catch (error) { - console.error("Logout failed:", error); - } - }, - - closeLoginModal: () => { - get().stopPolling(); - set({ isLoginModalOpen: false }); - }, - - resetLoginState: () => { - set({ - loginMode: "select", - offlineUsername: "", - deviceCodeData: null, - msLoginLoading: false, - msLoginStatus: "Waiting for authorization...", - }); - }, - - performOfflineLogin: async () => { - const { offlineUsername } = get(); - if (!offlineUsername.trim()) return; - - try { - const account = await invoke<Account>("login_offline", { - username: offlineUsername, - }); - set({ - currentAccount: account, - isLoginModalOpen: false, - offlineUsername: "", - }); - } catch (error) { - // Keep UI-friendly behavior consistent with prior code - alert("Login failed: " + String(error)); - } - }, - - startMicrosoftLogin: async () => { - // Prepare UI state - set({ - msLoginLoading: true, - msLoginStatus: "Waiting for authorization...", - loginMode: "microsoft", - deviceCodeData: null, - }); - - // Listen to general launcher logs so we can display progress to the user. - // The backend emits logs via "launcher-log"; using that keeps this store decoupled - // from a dedicated auth event channel (backend may reuse launcher-log). - try { - const unlisten = await listen("launcher-log", (event) => { - const payload = event.payload; - // Normalize payload to string if possible - const message = - typeof payload === "string" - ? payload - : (payload?.toString?.() ?? JSON.stringify(payload)); - set({ msLoginStatus: message }); - }); - set({ authProgressUnlisten: unlisten }); - } catch (err) { - console.warn("Failed to attach launcher-log listener:", err); - } - - try { - const deviceCodeData = await invoke<DeviceCodeResponse>( - "start_microsoft_login", - ); - set({ deviceCodeData }); - - if (deviceCodeData) { - // Try to copy user code to clipboard for convenience (best-effort) - try { - await navigator.clipboard?.writeText(deviceCodeData.userCode ?? ""); - } catch (err) { - // ignore clipboard errors - console.debug("Clipboard copy failed:", err); - } - - // Open verification URI in default browser - try { - if (deviceCodeData.verificationUri) { - await open(deviceCodeData.verificationUri); - } - } catch (err) { - console.debug("Failed to open verification URI:", err); - } - - // Start polling for completion - // `interval` from the bindings is a bigint (seconds). Convert safely to number. - const intervalSeconds = - deviceCodeData.interval !== undefined && - deviceCodeData.interval !== null - ? Number(deviceCodeData.interval) - : 5; - const intervalMs = intervalSeconds * 1000; - const pollInterval = setInterval( - () => get().checkLoginStatus(deviceCodeData.deviceCode), - intervalMs, - ); - set({ pollInterval }); - } - } catch (error) { - toast.error(`Failed to start Microsoft login: ${error}`); - set({ loginMode: "select" }); - // cleanup listener if present - const { authProgressUnlisten } = get(); - if (authProgressUnlisten) { - authProgressUnlisten(); - set({ authProgressUnlisten: null }); - } - } finally { - set({ msLoginLoading: false }); - } - }, - - checkLoginStatus: async (deviceCode: string) => { - const { isPollingRequestActive } = get(); - if (isPollingRequestActive) return; - - set({ isPollingRequestActive: true }); - - try { - const account = await invoke<Account>("complete_microsoft_login", { - deviceCode, - }); - - // On success, stop polling and cleanup listener - get().stopPolling(); - const { authProgressUnlisten } = get(); - if (authProgressUnlisten) { - authProgressUnlisten(); - set({ authProgressUnlisten: null }); - } - - set({ - currentAccount: account, - isLoginModalOpen: false, - }); - } catch (error: unknown) { - const errStr = String(error); - if (errStr.includes("authorization_pending")) { - // Still waiting — keep polling - } else { - set({ msLoginStatus: "Error: " + errStr }); - - if ( - errStr.includes("expired_token") || - errStr.includes("access_denied") - ) { - // Terminal errors — stop polling and reset state - get().stopPolling(); - const { authProgressUnlisten } = get(); - if (authProgressUnlisten) { - authProgressUnlisten(); - set({ authProgressUnlisten: null }); - } - alert("Login failed: " + errStr); - set({ loginMode: "select" }); - } - } - } finally { - set({ isPollingRequestActive: false }); - } - }, - - stopPolling: () => { - const { pollInterval, authProgressUnlisten } = get(); - if (pollInterval) { - try { - clearInterval(pollInterval); - } catch (err) { - console.debug("Failed to clear poll interval:", err); - } - set({ pollInterval: null }); - } - if (authProgressUnlisten) { - try { - authProgressUnlisten(); - } catch (err) { - console.debug("Failed to unlisten auth progress:", err); - } - set({ authProgressUnlisten: null }); - } - }, - - cancelMicrosoftLogin: () => { - get().stopPolling(); - set({ - deviceCodeData: null, - msLoginLoading: false, - msLoginStatus: "", - loginMode: "select", - }); - }, - - setLoginMode: (mode: "select" | "offline" | "microsoft") => { - set({ loginMode: mode }); - }, - - setOfflineUsername: (username: string) => { - set({ offlineUsername: username }); - }, -})); diff --git a/packages/ui/src/stores/releases-store.ts b/packages/ui/src/stores/releases-store.ts deleted file mode 100644 index 56afa08..0000000 --- a/packages/ui/src/stores/releases-store.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { invoke } from "@tauri-apps/api/core"; -import { create } from "zustand"; -import type { GithubRelease } from "@/types/bindings/core"; - -interface ReleasesState { - // State - releases: GithubRelease[]; - isLoading: boolean; - isLoaded: boolean; - error: string | null; - - // Actions - loadReleases: () => Promise<void>; - setReleases: (releases: GithubRelease[]) => void; - setIsLoading: (isLoading: boolean) => void; - setIsLoaded: (isLoaded: boolean) => void; - setError: (error: string | null) => void; -} - -export const useReleasesStore = create<ReleasesState>((set, get) => ({ - // Initial state - releases: [], - isLoading: false, - isLoaded: false, - error: null, - - // Actions - loadReleases: async () => { - const { isLoaded, isLoading } = get(); - - // If already loaded or currently loading, skip to prevent duplicate requests - if (isLoaded || isLoading) return; - - set({ isLoading: true, error: null }); - - try { - const releases = await invoke<GithubRelease[]>("get_github_releases"); - set({ releases, isLoaded: true }); - } catch (e) { - const error = e instanceof Error ? e.message : String(e); - console.error("Failed to load releases:", e); - set({ error }); - } finally { - set({ isLoading: false }); - } - }, - - setReleases: (releases) => { - set({ releases }); - }, - - setIsLoading: (isLoading) => { - set({ isLoading }); - }, - - setIsLoaded: (isLoaded) => { - set({ isLoaded }); - }, - - setError: (error) => { - set({ error }); - }, -})); diff --git a/packages/ui/src/stores/ui-store.ts b/packages/ui/src/stores/ui-store.ts deleted file mode 100644 index 89b9191..0000000 --- a/packages/ui/src/stores/ui-store.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { create } from "zustand"; - -export type ViewType = "home" | "versions" | "settings" | "guide" | "instances"; - -interface UIState { - // State - currentView: ViewType; - showConsole: boolean; - appVersion: string; - - // Actions - toggleConsole: () => void; - setView: (view: ViewType) => void; - setAppVersion: (version: string) => void; -} - -export const useUIStore = create<UIState>((set) => ({ - // Initial state - currentView: "home", - showConsole: false, - appVersion: "...", - - // Actions - toggleConsole: () => { - set((state) => ({ showConsole: !state.showConsole })); - }, - - setView: (view: ViewType) => { - set({ currentView: view }); - }, - - setAppVersion: (version: string) => { - set({ appVersion: version }); - }, -})); - -// Provide lowercase alias for compatibility with existing imports. -// Use a function wrapper to ensure the named export exists as a callable value -// at runtime (some bundlers/tree-shakers may remove simple aliases). -export function useUiStore() { - return useUIStore(); -} |