Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swap configs #1050

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
50 changes: 27 additions & 23 deletions screenpipe-app-tauri/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,36 @@ import { initOpenTelemetry } from "@/lib/opentelemetry";
import { OnboardingProvider } from "@/lib/hooks/use-onboarding";
import { ChangelogDialogProvider } from "@/lib/hooks/use-changelog-dialog";
import { forwardRef } from "react";
import { store } from "@/lib/hooks/use-settings";
import { StoreProvider } from "easy-peasy";
import { store as SettingsStore } from "@/lib/hooks/use-settings";
import { profilesStore as ProfilesStore } from "@/lib/hooks/use-profiles";

export const Providers = forwardRef<HTMLDivElement, { children: React.ReactNode }>(
({ children }, ref) => {
useEffect(() => {
if (typeof window !== "undefined") {
const isDebug = process.env.TAURI_ENV_DEBUG === "true";
if (isDebug) return;
posthog.init("phc_Bt8GoTBPgkCpDrbaIZzJIEYt0CrJjhBiuLaBck1clce", {
api_host: "https://eu.i.posthog.com",
person_profiles: "identified_only",
capture_pageview: false,
});
initOpenTelemetry("82688", new Date().toISOString());
}
}, []);
export const Providers = forwardRef<
HTMLDivElement,
{ children: React.ReactNode }
>(({ children }, ref) => {
useEffect(() => {
if (typeof window !== "undefined") {
const isDebug = process.env.TAURI_ENV_DEBUG === "true";
if (isDebug) return;
posthog.init("phc_Bt8GoTBPgkCpDrbaIZzJIEYt0CrJjhBiuLaBck1clce", {
api_host: "https://eu.i.posthog.com",
person_profiles: "identified_only",
capture_pageview: false,
});
initOpenTelemetry("82688", new Date().toISOString());
}
}, []);

return (
<StoreProvider store={store}>
<OnboardingProvider>
<ChangelogDialogProvider>
<PostHogProvider client={posthog}>{children}</PostHogProvider>
</ChangelogDialogProvider>
</OnboardingProvider>
</StoreProvider>
<SettingsStore.Provider>
<ProfilesStore.Provider>
<OnboardingProvider>
<ChangelogDialogProvider>
<PostHogProvider client={posthog}>{children}</PostHogProvider>
</ChangelogDialogProvider>
</OnboardingProvider>
</ProfilesStore.Provider>
</SettingsStore.Provider>
);
});

Expand Down
151 changes: 140 additions & 11 deletions screenpipe-app-tauri/components/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,83 @@
"use client";

import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useSettings } from "@/lib/hooks/use-settings";
import {
Settings2,
Brain,
Video,
Keyboard,
User,
ArrowLeft,
ChevronDown,
Plus,
Trash2,
Check,
} from "lucide-react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "./ui/dialog";
import { DialogHeader, DialogTitle } from "./ui/dialog";
import { cn } from "@/lib/utils";
import { RecordingSettings } from "./recording-settings";
import { AccountSection } from "./settings/account-section";
import ShortcutSection from "./settings/shortcut-section";
import AISection from "./settings/ai-section";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "./ui/dropdown-menu";
import { Input } from "./ui/input";
import { Button } from "./ui/button";
import { relaunch } from "@tauri-apps/plugin-process";
import { invoke } from "@tauri-apps/api/core";
import { useProfiles } from "@/lib/hooks/use-profiles";
import { toast } from "./ui/use-toast";

type SettingsSection = "ai" | "shortcuts" | "recording" | "account";

export function Settings() {
// const { settings, switchProfile, deleteProfile } = useSettings();
const {
profiles,
activeProfile,
createProfile,
deleteProfile,
setActiveProfile,
} = useProfiles();
const [activeSection, setActiveSection] =
useState<SettingsSection>("account");
const [isCreatingProfile, setIsCreatingProfile] = useState(false);
const [newProfileName, setNewProfileName] = useState("");

const handleProfileChange = async () => {
toast({
title: "Restarting Screenpipe",
description: "Please wait while we restart Screenpipe",
});
await invoke("kill_all_sreenpipes");

await new Promise((resolve) => setTimeout(resolve, 1000));

await invoke("spawn_screenpipe");

await new Promise((resolve) => setTimeout(resolve, 1000));
relaunch();
};

const handleCreateProfile = async () => {
if (newProfileName.trim()) {
console.log("creating profile", newProfileName.trim());
createProfile(newProfileName.trim());
setActiveProfile(newProfileName.trim());
setNewProfileName("");
setIsCreatingProfile(false);
handleProfileChange();
}
};

const handleSwitchProfile = async (profileName: string) => {
setActiveProfile(profileName);
handleProfileChange();
};

const renderSection = () => {
switch (activeSection) {
Expand All @@ -42,6 +92,10 @@ export function Settings() {
}
};

useEffect(() => {
console.log(profiles, "profiles");
}, [profiles]);

return (
<div className="flex h-full">
{/* Sidebar */}
Expand All @@ -50,6 +104,81 @@ export function Settings() {
<DialogTitle className="text-2xl font-bold">settings</DialogTitle>
</DialogHeader>

{/* Profile Selector */}
<div className="px-4 py-3 border-b">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="w-full justify-between font-mono text-sm"
>
{activeProfile}
<ChevronDown className="h-4 w-4 opacity-50" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
{profiles?.map((profile) => (
<DropdownMenuItem
key={profile}
className="justify-between"
onSelect={() => handleSwitchProfile(profile)}
>
<span className="font-mono">{profile}</span>
{activeProfile === profile && <Check className="h-4 w-4" />}
{profile !== "default" && (
<Trash2
className="h-4 w-4 opacity-50 hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
deleteProfile(profile);
}}
/>
)}
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
{isCreatingProfile ? (
<div className="p-2">
<form
onSubmit={(e) => {
e.preventDefault();
handleCreateProfile();
}}
className="flex gap-2"
>
<Input
value={newProfileName}
onChange={(e) => setNewProfileName(e.target.value)}
placeholder="profile name"
className="h-8 font-mono"
autoFocus
/>
<Button
type="submit"
size="sm"
disabled={!newProfileName.trim()}
>
<Check className="h-4 w-4" />
</Button>
</form>
</div>
) : (
<DropdownMenuItem
onSelect={(e) => {
e.preventDefault();
setIsCreatingProfile(true);
}}
className="gap-2"
>
<Plus className="h-4 w-4" />
<span>new profile</span>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>

{/* Existing Settings Navigation */}
<div className="flex flex-col space-y-1 p-4">
{[
{
Expand Down Expand Up @@ -90,7 +219,7 @@ export function Settings() {
</div>
</div>

{/* Content - Updated styles */}
{/* Content */}
<div className="flex-1 flex flex-col h-full max-h-[80vh]">
<div className="flex-1 overflow-y-auto px-4">
<div className="max-h-full">{renderSection()}</div>
Expand Down
117 changes: 117 additions & 0 deletions screenpipe-app-tauri/lib/hooks/use-profiles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Action, action, persist } from "easy-peasy";
import { LazyStore } from "@tauri-apps/plugin-store";
import { localDataDir } from "@tauri-apps/api/path";
import { flattenObject, unflattenObject } from "../utils";
import { createContextStore } from "easy-peasy";

export interface ProfilesModel {
activeProfile: string;
profiles: string[];
setActiveProfile: Action<ProfilesModel, string>;
createProfile: Action<ProfilesModel, string>;
deleteProfile: Action<ProfilesModel, string>;
}

let profilesStorePromise: Promise<LazyStore> | null = null;

/**
* @warning Do not change autoSave to true, it causes race conditions
*/
const getProfilesStore = async () => {
if (!profilesStorePromise) {
profilesStorePromise = (async () => {
const dir = await localDataDir();
console.log(dir, "dir");
return new LazyStore(`${dir}/screenpipe/profiles.bin`, {
autoSave: false,
});
})();
}
return profilesStorePromise;
};

const profilesStorage = {
getItem: async (_key: string) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
const tauriStore = await getProfilesStore();
const allKeys = await tauriStore.keys();
const values: Record<string, any> = {};

for (const k of allKeys) {
values[k] = await tauriStore.get(k);
}

return unflattenObject(values);
},

setItem: async (_key: string, value: any) => {
const tauriStore = await getProfilesStore();
const flattenedValue = flattenObject(value);

const existingKeys = await tauriStore.keys();
for (const key of existingKeys) {
await tauriStore.delete(key);
}

for (const [key, val] of Object.entries(flattenedValue)) {
await tauriStore.set(key, val);
}

await tauriStore.save();
},
removeItem: async (_key: string) => {
const tauriStore = await getProfilesStore();
const keys = await tauriStore.keys();
for (const key of keys) {
await tauriStore.delete(key);
}
await tauriStore.save();
},
};

export const profilesStore = createContextStore<ProfilesModel>(
persist(
{
activeProfile: "default",
profiles: ["default"],
setActiveProfile: action((state, payload) => {
state.activeProfile = payload;
}),
createProfile: action((state, payload) => {
state.profiles.push(payload);
}),
deleteProfile: action((state, payload) => {
state.profiles = state.profiles.filter(
(profile) => profile !== payload
);
}),
},
{
storage: profilesStorage,
}
)
);

export const useProfiles = () => {
const { profiles, activeProfile } = profilesStore.useStoreState((state) => ({
activeProfile: state.activeProfile,
profiles: state.profiles,
}));
const setActiveProfile = profilesStore.useStoreActions(
(actions) => actions.setActiveProfile
);
const createProfile = profilesStore.useStoreActions(
(actions) => actions.createProfile
);
const deleteProfile = profilesStore.useStoreActions(
(actions) => actions.deleteProfile
);

return {
profiles,
activeProfile,
setActiveProfile,
createProfile,
deleteProfile,
};
};
Loading
Loading