diff --git a/src/lib/stores/profiles.svelte.ts b/src/lib/stores/profiles.svelte.ts new file mode 100644 index 0000000..a913be7 --- /dev/null +++ b/src/lib/stores/profiles.svelte.ts @@ -0,0 +1,123 @@ +import { invoke } from "@tauri-apps/api/core"; + +import { toasts } from "$lib/stores/toasts.svelte.ts"; +import { errorMessage } from "$lib/utils/errors.js"; +import { hasTauriRuntime } from "$lib/utils/runtime.js"; + +// Matches the SQLite-seeded Default profile UUID (see migration v6). Used as +// the in-memory default for `activeProfileId` on boot. Cross-session +// persistence of the selection is out of scope for Phase 2 — see Task 14 +// plan notes. +export const DEFAULT_PROFILE_ID = "00000000-0000-0000-0000-000000000001"; + +// DTOs mirror the serde-camelCase shape emitted by the Tauri commands added +// in Task 12. Kept local to this store until a second consumer appears. +export interface ProfileDto { + id: string; + name: string; + initialPrompt: string; + createdAt: string; +} + +export interface ProfileTermDto { + id: string; + profileId: string; + term: string; + note: string; + createdAt: string; +} + +let profilesState = $state([]); +let activeProfileIdState = $state(DEFAULT_PROFILE_ID); + +export const profilesStore = { + get profiles(): ProfileDto[] { + return profilesState; + }, + get activeProfileId(): string { + return activeProfileIdState; + }, + get active(): ProfileDto | null { + return profilesState.find((p) => p.id === activeProfileIdState) ?? null; + }, + + async load(): Promise { + if (!hasTauriRuntime()) return; + try { + profilesState = await invoke("list_profiles_cmd"); + } catch (err) { + toasts.error("Failed to load profiles", errorMessage(err)); + } + }, + + setActive(id: string): void { + if (profilesState.find((p) => p.id === id)) { + activeProfileIdState = id; + } + }, + + async create(name: string, initialPrompt: string): Promise { + if (!hasTauriRuntime()) return null; + try { + const row = await invoke("create_profile_cmd", { + name, + initialPrompt, + }); + profilesState = [...profilesState, row]; + return row; + } catch (err) { + toasts.error("Failed to create profile", errorMessage(err)); + return null; + } + }, + + async update(id: string, name: string, initialPrompt: string): Promise { + if (!hasTauriRuntime()) return; + try { + await invoke("update_profile_cmd", { id, name, initialPrompt }); + profilesState = profilesState.map((p) => + p.id === id ? { ...p, name, initialPrompt } : p, + ); + } catch (err) { + toasts.error("Failed to update profile", errorMessage(err)); + } + }, + + async delete(id: string): Promise { + if (id === DEFAULT_PROFILE_ID) { + toasts.error("Default profile cannot be deleted"); + return; + } + if (!hasTauriRuntime()) return; + try { + await invoke("delete_profile_cmd", { id }); + profilesState = profilesState.filter((p) => p.id !== id); + if (activeProfileIdState === id) activeProfileIdState = DEFAULT_PROFILE_ID; + } catch (err) { + toasts.error("Failed to delete profile", errorMessage(err)); + } + }, + + async listTerms(profileId: string): Promise { + if (!hasTauriRuntime()) return []; + return invoke("list_profile_terms_cmd", { profileId }); + }, + + async addTerm( + profileId: string, + term: string, + note = "", + ): Promise { + if (!hasTauriRuntime()) return null; + return invoke("add_profile_term_cmd", { + profileId, + term, + note, + }); + }, + + async deleteTerm(termId: string): Promise { + if (!hasTauriRuntime()) return; + await invoke("delete_profile_term_cmd", { id: termId }); + }, +};