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
-
+
+
{/* 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}
)}
+
);
}