Skip to content

Commit

Permalink
feat: new settings in app, notification when unhealthy
Browse files Browse the repository at this point in the history
  • Loading branch information
louis030195 committed Aug 1, 2024
1 parent 7d37c19 commit 38676ec
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 80 deletions.
12 changes: 3 additions & 9 deletions .github/workflows/release-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,27 +163,21 @@ jobs:
projectPath: "./examples/apps/screenpipe-app-tauri"
tauriScript: bunx tauri -v

- name: Get version from Cargo.toml
id: get_version
run: |
VERSION=$(grep '^version =' examples/apps/screenpipe-app-tauri/src-tauri/Cargo.toml | sed 's/.*= "\(.*\)"/\1/')
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
- name: Create CrabNebula Cloud Release Draft
uses: crabnebula-dev/[email protected]
with:
command: release draft ${{ secrets.CN_APP_SLUG }} --framework tauri ${{ steps.get_version.outputs.VERSION }}
command: release draft ${{ secrets.CN_APP_SLUG }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }}

- name: Upload Assets to CrabNebula Cloud
uses: crabnebula-dev/[email protected]
with:
command: release upload ${{ secrets.CN_APP_SLUG }} --framework tauri ${{ steps.get_version.outputs.VERSION }}
command: release upload ${{ secrets.CN_APP_SLUG }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }}
path: ./examples/apps/screenpipe-app-tauri/src-tauri

- name: Publish CrabNebula Cloud Release
uses: crabnebula-dev/[email protected]
with:
command: release publish ${{ secrets.CN_APP_SLUG }} --framework tauri ${{ steps.get_version.outputs.VERSION }}
command: release publish ${{ secrets.CN_APP_SLUG }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { CodeBlock } from "@/components/ui/codeblock";
import { MemoizedReactMarkdown } from "./markdown";
import { invoke } from "@tauri-apps/api/core";
import { spinner } from "./spinner";
import {
isPermissionGranted,
sendNotification,
} from "@tauri-apps/plugin-notification";

interface HealthCheckResponse {
status: string;
Expand All @@ -35,6 +39,9 @@ const HealthStatus = ({ className }: { className?: string }) => {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isStopping, setIsStopping] = useState(false);
const [isStarting, setIsStarting] = useState(false);
const [lastNotificationTime, setLastNotificationTime] = useState<
number | null
>(null);

const fetchHealth = async () => {
try {
Expand Down Expand Up @@ -77,6 +84,36 @@ const HealthStatus = ({ className }: { className?: string }) => {
return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
const checkAndNotify = async () => {
if (health && health.status === "Unhealthy") {
const now = Date.now();
const lastNotification = localStorage.getItem(
"lastUnhealthyNotification"
);
const lastNotificationTime = lastNotification
? parseInt(lastNotification, 10)
: 0;

if (now - lastNotificationTime > 3600000) {
// 1 hour in milliseconds
const permissionGranted = await isPermissionGranted();
if (permissionGranted) {
sendNotification({
title: "Screenpipe Status Alert",
body: "Screenpipe is currently unhealthy. Please try to restart. It could also be useful to press 'Stop' in the status badge just in case.",
});
localStorage.setItem("lastUnhealthyNotification", now.toString());
setLastNotificationTime(now);
}
}
}
};

checkAndNotify();
}, [health]);

const logCommands = `# Stream the log:
tail -f $HOME/.screenpipe/screenpipe.log
Expand Down
69 changes: 60 additions & 9 deletions examples/apps/screenpipe-app-tauri/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,16 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Badge } from "@/components/ui/badge";
import { PrettyLink } from "./pretty-link";
import { MemoizedReactMarkdown } from "./markdown";
import { Separator } from "@/components/ui/separator";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { CodeBlock } from "./ui/codeblock";
import { ChatMessageActions } from "./chat-message-actions";
import { useCopyToClipboard } from "@/lib/hooks/use-copy-to-clipboard";
import { cn } from "@/lib/utils";
import { IconCheck, IconCopy } from "./ui/icons";
import { invoke } from "@tauri-apps/api/core";
import { spinner } from "./spinner";

export function Settings({ className }: { className?: string }) {
const { settings, updateSettings } = useSettings();
const [localSettings, setLocalSettings] = React.useState(settings);
const [isLoading, setIsLoading] = React.useState(false);

const handleApiKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setLocalSettings({ ...localSettings, openaiApiKey: e.target.value });
Expand All @@ -48,6 +41,34 @@ export function Settings({ className }: { className?: string }) {
updateSettings({ ...localSettings, useOllama: checked });
};

const handleCloudAudioToggle = async (checked: boolean) => {
if (isLoading) return;
setIsLoading(true);
try {
setLocalSettings({ ...localSettings, useCloudAudio: checked });
await updateSettings({ ...localSettings, useCloudAudio: checked });
// Restart screenpipe with new settings
await restartScreenpipe();
// user feedback
await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) {
console.error("Failed to update cloud audio setting:", error);
} finally {
setIsLoading(false);
}
};

const restartScreenpipe = async () => {
try {
await invoke("kill_all_sreenpipes");
// sleep 1s
await new Promise((resolve) => setTimeout(resolve, 1000));
await invoke("spawn_screenpipe");
} catch (error) {
console.error("Failed to restart screenpipe:", error);
}
};

React.useEffect(() => {
setLocalSettings(settings);
}, [settings]);
Expand All @@ -65,7 +86,7 @@ export function Settings({ className }: { className?: string }) {
Settings
</Button>
</DialogTrigger>
<DialogContent className="max-w-[80vw] w-full max-h-[100vh] h-full overflow-y-auto">
<DialogContent className="max-w-[80vw] w-full max-h-[80vh] h-full overflow-y-auto">
<DialogHeader>
<DialogTitle>Settings</DialogTitle>
<DialogDescription>
Expand Down Expand Up @@ -210,6 +231,36 @@ export function Settings({ className }: { className?: string }) {
</TooltipContent>
</Tooltip>
</TooltipProvider>

<div className="flex flex-col items-center space-y-2">
<div className="flex items-center space-x-4">
<Switch
id="use-cloud-audio"
checked={localSettings.useCloudAudio}
onCheckedChange={handleCloudAudioToggle}
disabled={isLoading}
/>
<Label
htmlFor="use-cloud-audio"
className="flex items-center space-x-2"
>
Use Cloud Audio Processing
</Label>
{isLoading && (
<div className="ml-4 h-[24px] flex flex-row items-center flex-1 space-y-2 overflow-hidden px-1">
{spinner}
</div>
)}
</div>
<div className="text-sm text-muted-foreground mt-1 text-center">
<p>
Toggle to use cloud-based audio processing instead of local
processing. Cloud processing may provide better accuracy but
requires an internet connection. This will restart
screenpipe background process.
</p>
</div>
</div>
</CardContent>
</Card>
</div>
Expand Down
77 changes: 57 additions & 20 deletions examples/apps/screenpipe-app-tauri/lib/hooks/use-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,81 @@
import { useState, useEffect } from "react";
import { Store } from "@tauri-apps/plugin-store";
import { homeDir } from "@tauri-apps/api/path";
import { join } from "@tauri-apps/api/path";

interface Settings {
openaiApiKey: string;
useOllama: boolean;
isLoading: boolean;
useCli: boolean;
useCloudAudio: boolean;
}

let store: Store | null = null;

export function useSettings() {
const [settings, setSettings] = useState<Settings>({
openaiApiKey: "",
useOllama: false,
isLoading: true,
useCli: false,
useCloudAudio: true,
});

useEffect(() => {
const loadSettings = () => {
const savedKey = localStorage.getItem("openaiApiKey") || "";
const savedUseOllama = localStorage.getItem("useOllama") === "true";
const savedUseCli = localStorage.getItem("useCli") === "true";
setSettings({
openaiApiKey: savedKey,
useOllama: savedUseOllama,
isLoading: false,
useCli: savedUseCli,
});
const initStore = async () => {
const home = await homeDir();
const storePath = await join(home, ".screenpipe", "store.bin");
store = new Store(storePath);
};

const loadSettings = async () => {
if (!store) {
await initStore();
}

try {
await store!.load();
const savedKey = ((await store!.get("openaiApiKey")) as string) || "";
const savedUseOllama =
((await store!.get("useOllama")) as boolean) || false;
const savedUseCloudAudio =
((await store!.get("useCloudAudio")) as boolean) ?? true;

setSettings({
openaiApiKey: savedKey,
useOllama: savedUseOllama,
isLoading: false,
useCloudAudio: savedUseCloudAudio,
});
} catch (error) {
console.error("Failed to load settings:", error);
setSettings((prevSettings) => ({ ...prevSettings, isLoading: false }));
}
};
loadSettings();
}, []);

const updateSettings = (newSettings: Partial<Settings>) => {
setSettings((prevSettings) => {
const updatedSettings = { ...prevSettings, ...newSettings };
localStorage.setItem("openaiApiKey", updatedSettings.openaiApiKey);
localStorage.setItem("useOllama", updatedSettings.useOllama.toString());
localStorage.setItem("useCli", updatedSettings.useCli.toString());
return updatedSettings;
});
const updateSettings = async (newSettings: Partial<Settings>) => {
if (!store) {
await initStore();
}

try {
const updatedSettings = { ...settings, ...newSettings };
await store!.set("openaiApiKey", updatedSettings.openaiApiKey);
await store!.set("useOllama", updatedSettings.useOllama);
await store!.set("useCloudAudio", updatedSettings.useCloudAudio);
await store!.save();
setSettings(updatedSettings);
} catch (error) {
console.error("Failed to update settings:", error);
}
};

return { settings, updateSettings };
}

async function initStore() {
const home = await homeDir();
const storePath = await join(home, ".screenpipe", "store.bin");
store = new Store(storePath);
}
2 changes: 1 addition & 1 deletion examples/apps/screenpipe-app-tauri/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "screenpipe-app"
version = "0.1.2"
version = "0.1.3"
description = ""
authors = ["you"]
license = ""
Expand Down
Loading

0 comments on commit 38676ec

Please sign in to comment.