import { create } from 'zustand'; import type { Contact } from '../types'; import * as api from '../api/client'; /** 中间栏的呈现方式:列表(紧凑,答"跟谁在聊")或卡片(答"在聊什么、进展如何") */ export type ContactView = 'list' | 'card'; const VIEW_KEY = 'agentmail.contactView'; /** * 视图偏好存 localStorage。 * * 这是纯展示偏好,不值得为它建一张表、加一个 API —— 而每次刷新都退回默认视图 * 会让人反复点同一个按钮。读取时容错:localStorage 在隐私模式下可能抛异常。 */ function loadView(): ContactView { try { return localStorage.getItem(VIEW_KEY) === 'card' ? 'card' : 'list'; } catch { return 'list'; } } interface ContactState { contacts: Contact[]; archivedContacts: Contact[]; showArchived: boolean; loading: boolean; error: string | null; /** 正在等待归档确认的地址 */ pendingArchive: string | null; /** 中间栏呈现方式(持久化到 localStorage) */ view: ContactView; setView: (v: ContactView) => void; fetchContacts: () => Promise; fetchArchived: () => Promise; toggleArchivedView: () => void; requestArchive: (address: string) => void; cancelArchive: () => void; archive: (contact: Contact) => Promise; /** SSE session_archived 到达时本地即时移除 */ removeSessionLocally: (sessionId: string) => void; } export const useContactStore = create((set, get) => ({ contacts: [], archivedContacts: [], showArchived: false, loading: false, error: null, pendingArchive: null, view: loadView(), setView: v => { set({ view: v }); try { localStorage.setItem(VIEW_KEY, v); } catch { /* 存不下只是下次回到默认视图,不该让切换本身失败 */ } }, fetchContacts: async () => { set({ loading: true, error: null }); try { const { contacts } = await api.listContacts(false); set({ contacts: contacts || [], loading: false }); } catch (err) { set({ error: err instanceof Error ? err.message : String(err), loading: false }); } }, fetchArchived: async () => { try { const { contacts } = await api.listContacts(true); set({ archivedContacts: contacts || [] }); } catch (err) { set({ error: err instanceof Error ? err.message : String(err) }); } }, toggleArchivedView: () => { const next = !get().showArchived; set({ showArchived: next }); if (next) get().fetchArchived(); }, requestArchive: address => set({ pendingArchive: address }), cancelArchive: () => set({ pendingArchive: null }), archive: async contact => { try { await api.archiveContact({ session_id: contact.session_id }); // 本地即时移除,不等 SSE set(state => ({ contacts: state.contacts.filter(c => c.session_id !== contact.session_id), pendingArchive: null })); if (get().showArchived) get().fetchArchived(); } catch (err) { set({ error: err instanceof Error ? err.message : String(err), pendingArchive: null }); } }, removeSessionLocally: sessionId => set(state => ({ contacts: state.contacts.filter(c => c.session_id !== sessionId) })) }));