/** * Industry Store — Zustand store for industry configuration * * Manages industry configs fetched from SaaS, cached locally for * offline fallback. Injected into ButlerRouter for dynamic keyword routing. */ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import type { IndustryFullConfig, AccountIndustryItem } from '../lib/saas-client'; import { saasClient } from '../lib/saas-client'; // ============ Types ============ interface IndustryState { /** User's authorized industries */ accountIndustries: AccountIndustryItem[]; /** Full configs for authorized industries (keyed by industry id) */ configs: Record; /** Last sync timestamp */ lastSynced: string | null; /** Loading state */ isLoading: boolean; /** Error message */ error: string | null; } interface IndustryActions { /** Fetch user industries + full configs from SaaS */ fetchIndustries: () => Promise; /** Get all loaded industry keywords (for trigger system) */ getAllKeywords: () => string[]; /** Clear all data */ clear: () => void; } type IndustryStore = IndustryState & IndustryActions; // ============ Store ============ export const useIndustryStore = create()( persist( (set, get) => ({ accountIndustries: [], configs: {}, lastSynced: null, isLoading: false, error: null, fetchIndustries: async () => { set({ isLoading: true, error: null }); try { // Step 1: Get user's authorized industries const accountIndustries = await saasClient.getMyIndustries(); // Step 2: Fetch full config for each authorized industry const configs: Record = {}; for (const item of accountIndustries) { try { const fullConfig = await saasClient.getIndustryFullConfig(item.industry_id); configs[item.industry_id] = fullConfig; } catch (err) { // Non-fatal: one industry failing shouldn't block others console.warn(`[industryStore] Failed to fetch config for ${item.industry_id}:`, err); } } set({ accountIndustries, configs, lastSynced: new Date().toISOString(), isLoading: false, }); // Step 3: Push to Rust ButlerRouter via Tauri invoke try { const { invoke } = await import('@tauri-apps/api/core'); const industryConfigs = Object.values(configs).map((c) => ({ id: c.id, name: c.name, keywords: c.keywords, system_prompt: c.system_prompt, })); await invoke('viking_load_industry_keywords', { configs: JSON.stringify(industryConfigs) }); } catch (err) { // Tauri not available (browser mode) — ignore } } catch (err) { set({ error: err instanceof Error ? err.message : String(err), isLoading: false, }); } }, getAllKeywords: () => { const { configs } = get(); const allKeywords: string[] = []; for (const config of Object.values(configs)) { allKeywords.push(...config.keywords); } return allKeywords; }, clear: () => { set({ accountIndustries: [], configs: {}, lastSynced: null, isLoading: false, error: null, }); }, }), { name: 'zclaw-industry-store', // Only persist configs and industries (not loading/error state) partialize: (state) => ({ accountIndustries: state.accountIndustries, configs: state.configs, lastSynced: state.lastSynced, }), }, ), );