diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c5a1174..fd0ed33 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -11,7 +11,7 @@ "app": { "windows": [ { - "title": "ClawPal", + "title": "ClawPal by zhixian", "width": 1200, "height": 820, "minWidth": 1024, diff --git a/src/App.tsx b/src/App.tsx index dc3cd46..e8eb3fc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,4 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { check } from "@tauri-apps/plugin-updater"; -import { relaunch } from "@tauri-apps/plugin-process"; import { Home } from "./pages/Home"; import { Recipes } from "./pages/Recipes"; import { Cook } from "./pages/Cook"; @@ -82,29 +80,6 @@ export function App() { setToasts((prev) => prev.filter((t) => t.id !== id)); }, []); - // App self-update (manual check from sidebar) - const [checkingAppUpdate, setCheckingAppUpdate] = useState(false); - - const handleCheckForUpdates = useCallback(async () => { - setCheckingAppUpdate(true); - try { - const update = await check(); - if (update) { - const confirmed = window.confirm(`ClawPal v${update.version} is available. Update and restart now?`); - if (confirmed) { - await update.downloadAndInstall(); - await relaunch(); - } - } else { - showToast("You're on the latest version"); - } - } catch (e) { - console.error("Update check failed:", e); - showToast(`Update check failed: ${e}`, "error"); - } finally { - setCheckingAppUpdate(false); - } - }, [showToast]); const handleInstanceSelect = useCallback((id: string) => { setActiveInstance(id); @@ -311,16 +286,26 @@ export function App() { > Settings - +
+ { e.preventDefault(); api.openUrl("https://clawpal.zhixian.io"); }} + > + Website + + ยท + { e.preventDefault(); api.openUrl("https://x.com/zhixianio"); }} + > + @zhixian + +
+ {/* Dirty config action bar */} {dirty && (
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index acaed65..76ac1d5 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,6 +1,4 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { check } from "@tauri-apps/plugin-updater"; -import { relaunch } from "@tauri-apps/plugin-process"; import { api } from "../lib/api"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -74,11 +72,6 @@ export function Home({ const [backingUp, setBackingUp] = useState(false); const [backupMessage, setBackupMessage] = useState(""); - // ClawPal app self-update state - const [appUpdate, setAppUpdate] = useState<{ version: string; body?: string } | null>(null); - const [appUpdateChecking, setAppUpdateChecking] = useState(false); - const [appUpdating, setAppUpdating] = useState(false); - const [appUpdateProgress, setAppUpdateProgress] = useState(null); // Create agent dialog const [showCreateAgent, setShowCreateAgent] = useState(false); @@ -201,46 +194,6 @@ export function Home({ return () => clearTimeout(timer); }, [isRemote, isConnected, instanceId]); - // ClawPal app self-update check - useEffect(() => { - setAppUpdateChecking(true); - check() - .then((update) => { - if (update) { - setAppUpdate({ version: update.version, body: update.body }); - } - }) - .catch((e) => console.error("Failed to check app update:", e)) - .finally(() => setAppUpdateChecking(false)); - }, []); - - const handleAppUpdate = useCallback(async () => { - setAppUpdating(true); - setAppUpdateProgress(0); - try { - const update = await check(); - if (!update) return; - let totalBytes = 0; - let downloadedBytes = 0; - await update.downloadAndInstall((event) => { - if (event.event === "Started" && event.data.contentLength) { - totalBytes = event.data.contentLength; - } else if (event.event === "Progress") { - downloadedBytes += event.data.chunkLength; - if (totalBytes > 0) { - setAppUpdateProgress(Math.round((downloadedBytes / totalBytes) * 100)); - } - } else if (event.event === "Finished") { - setAppUpdateProgress(100); - } - }); - await relaunch(); - } catch (e) { - console.error("App update failed:", e); - setAppUpdating(false); - setAppUpdateProgress(null); - } - }, []); const handleDeleteAgent = (agentId: string) => { if (isRemote && !isConnected) return; @@ -301,46 +254,6 @@ export function Home({ )}
- {/* ClawPal app self-update */} - {(appUpdateChecking || appUpdate) && ( - <> - App Update -
- {appUpdateChecking && ( - Checking for app updates... - )} - {!appUpdateChecking && appUpdate && !appUpdating && ( - <> - - ClawPal v{appUpdate.version} available - - - - )} - {appUpdating && ( - <> - - {appUpdateProgress !== null && appUpdateProgress < 100 - ? `Downloading... ${appUpdateProgress}%` - : appUpdateProgress === 100 - ? "Installing..." - : "Preparing..."} - - {appUpdateProgress !== null && appUpdateProgress < 100 && ( -
-
-
- )} - - )} -
- - )} Default Model
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 81d01c6..c15166d 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,5 +1,8 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { FormEvent } from "react"; +import { check } from "@tauri-apps/plugin-updater"; +import { relaunch } from "@tauri-apps/plugin-process"; +import { getVersion } from "@tauri-apps/api/app"; import { api } from "@/lib/api"; import { useInstance } from "@/lib/instance-context"; import type { ModelCatalogProvider, ModelProfile, ProviderAuthSuggestion, ResolvedApiKey } from "@/lib/types"; @@ -125,6 +128,61 @@ export function Settings({ onDataChange }: { onDataChange?: () => void }) { const [catalogRefreshed, setCatalogRefreshed] = useState(false); + // ClawPal app version & self-update + const [appVersion, setAppVersion] = useState(""); + const [appUpdate, setAppUpdate] = useState<{ version: string; body?: string } | null>(null); + const [appUpdateChecking, setAppUpdateChecking] = useState(false); + const [appUpdating, setAppUpdating] = useState(false); + const [appUpdateProgress, setAppUpdateProgress] = useState(null); + + useEffect(() => { + getVersion().then(setAppVersion).catch(() => {}); + }, []); + + const handleCheckForUpdates = useCallback(async () => { + setAppUpdateChecking(true); + setAppUpdate(null); + try { + const update = await check(); + if (update) { + setAppUpdate({ version: update.version, body: update.body }); + } + } catch (e) { + console.error("Update check failed:", e); + } finally { + setAppUpdateChecking(false); + } + }, []); + + const handleAppUpdate = useCallback(async () => { + setAppUpdating(true); + setAppUpdateProgress(0); + try { + const update = await check(); + if (!update) return; + let totalBytes = 0; + let downloadedBytes = 0; + await update.downloadAndInstall((event) => { + if (event.event === "Started" && event.data.contentLength) { + totalBytes = event.data.contentLength; + } else if (event.event === "Progress") { + downloadedBytes += event.data.chunkLength; + if (totalBytes > 0) { + setAppUpdateProgress(Math.round((downloadedBytes / totalBytes) * 100)); + } + } else if (event.event === "Finished") { + setAppUpdateProgress(100); + } + }); + await relaunch(); + } catch (e) { + console.error("App update failed:", e); + setAppUpdating(false); + setAppUpdateProgress(null); + } + }, []); + + // Extract profiles from remote config on first load useEffect(() => { if (!isRemote || !isConnected) return; @@ -291,6 +349,7 @@ export function Settings({ onDataChange }: { onDataChange?: () => void }) { )}
+
{/* Create / Edit form */} @@ -413,6 +472,56 @@ export function Settings({ onDataChange }: { onDataChange?: () => void }) { + {/* Current Version */} + + + Current Version + + +
+ {appVersion ? `v${appVersion}` : "..."} + +
+ {!appUpdateChecking && appUpdate && !appUpdating && ( +
+ + v{appUpdate.version} available + + +
+ )} + {appUpdating && ( +
+ + {appUpdateProgress !== null && appUpdateProgress < 100 + ? `Downloading... ${appUpdateProgress}%` + : appUpdateProgress === 100 + ? "Installing..." + : "Preparing..."} + + {appUpdateProgress !== null && appUpdateProgress < 100 && ( +
+
+
+ )} +
+ )} + + +
+ {/* Profiles list */} @@ -494,6 +603,7 @@ export function Settings({ onDataChange }: { onDataChange?: () => void }) { {message && (

{message}

)} + ); }