'use client'; import { create } from 'zustand'; import { loadServerData, saveServerData, SERVER_DATA_CACHE_MS } from '@/lib/server-data'; import { api } from '@/lib/api'; import { useMemberStore } from './useMemberStore'; import { mergeDailySnapshotCacheForSave, mergeInsightCacheForSave, upsertDailySnapshot, upsertInsight, type XiaobaoRiskInsightCacheItem, } from '@/lib/xiaobao-risk-cache'; import type { XiaobaoRiskSnapshot } from '@/lib/xiaobao-risk-trend'; interface XiaobaoRiskState { snapshots: XiaobaoRiskSnapshot[]; insights: XiaobaoRiskInsightCacheItem[]; pendingInsightKeys: string[]; insightRequestAttempts: Record; riskDataLoaded: boolean; error?: string; fetchRiskData: () => Promise; saveSnapshot: (item: XiaobaoRiskSnapshot) => Promise; saveInsight: (item: XiaobaoRiskInsightCacheItem) => Promise; beginInsightUpdate: (key: string) => void; finishInsightUpdate: (key: string) => void; } async function loadSnapshots(): Promise { try { const rows = await loadServerData('xiaobao-risk-snapshots'); return Array.isArray(rows) ? rows : []; } catch {} return null; } async function loadInsights(): Promise { try { const rows = await loadServerData('xiaobao-risk-insights'); return Array.isArray(rows) ? rows : []; } catch {} return null; } let snapshotSaveQueue: Promise = Promise.resolve(); let insightSaveQueue: Promise = Promise.resolve(); let lastRiskDataFetchAt = 0; export const useXiaobaoRiskStore = create((set, get) => ({ snapshots: [], insights: [], pendingInsightKeys: [], insightRequestAttempts: {}, riskDataLoaded: false, error: undefined, fetchRiskData: async () => { if (get().riskDataLoaded && Date.now() - lastRiskDataFetchAt < SERVER_DATA_CACHE_MS) return; set({ riskDataLoaded: false }); const [snapshots, insights] = await Promise.all([loadSnapshots(), loadInsights()]); if (get().riskDataLoaded && Date.now() - lastRiskDataFetchAt < SERVER_DATA_CACHE_MS) return; lastRiskDataFetchAt = Date.now(); set({ ...(snapshots ? { snapshots } : {}), ...(insights ? { insights } : {}), riskDataLoaded: snapshots !== null && insights !== null, error: snapshots === null || insights === null ? '小宝预警缓存加载失败' : undefined, }); }, saveSnapshot: async (item) => { const optimistic = upsertDailySnapshot(get().snapshots, item); set({ snapshots: optimistic, error: undefined }); const task = snapshotSaveQueue.then(async () => { const remote = await loadServerData('xiaobao-risk-snapshots', { force: true }); const snapshots = mergeDailySnapshotCacheForSave(get().snapshots, Array.isArray(remote) ? remote : [], item); set({ snapshots, error: undefined }); await saveServerData('xiaobao-risk-snapshots', snapshots); void notifyRiskManagers(item).catch(() => {}); }); snapshotSaveQueue = task.catch(() => undefined); try { await task; } catch (error) { set({ error: '小宝预警快照保存失败' }); throw error; } }, saveInsight: async (item) => { const optimistic = upsertInsight(get().insights, item); set({ insights: optimistic, error: undefined }); const task = insightSaveQueue.then(async () => { const remote = await loadServerData('xiaobao-risk-insights', { force: true }); const insights = mergeInsightCacheForSave(get().insights, Array.isArray(remote) ? remote : [], item); set({ insights, error: undefined }); await saveServerData('xiaobao-risk-insights', insights); }); insightSaveQueue = task.catch(() => undefined); try { await task; } catch (error) { set({ error: '小宝预警解读保存失败' }); throw error; } }, beginInsightUpdate: (key) => { if (!key) return; if (get().pendingInsightKeys.includes(key)) return; set({ pendingInsightKeys: [...get().pendingInsightKeys, key], insightRequestAttempts: { ...get().insightRequestAttempts, [key]: new Date().toISOString(), }, }); }, finishInsightUpdate: (key) => { if (!key) return; set({ pendingInsightKeys: get().pendingInsightKeys.filter((item) => item !== key) }); }, })); async function notifyRiskManagers(item: XiaobaoRiskSnapshot) { if (!['at_risk', 'likely_delayed', 'blocked'].includes(item.riskLevel)) return; const memberStore = useMemberStore.getState(); if (!memberStore.loaded) { await memberStore.fetchMembers().catch(() => undefined); } const state = useMemberStore.getState(); const roleMap = new Map(state.roles.map((role) => [role.id, role])); const recipients = state.members.filter((member) => { const permissions = roleMap.get(member.roleId)?.permissions ?? []; return permissions.includes('*') || permissions.includes('xiaobao.warning:manage'); }); await Promise.all(recipients.map((member) => api.post('/notifications', { recipientId: member.id, actorId: 'xiaobao', type: 'risk_alert', title: '小宝预警更新', body: `版本 ${item.versionId} 当前风险 ${item.riskLevel},风险分 ${item.riskScore}`, resourceType: 'version', resourceId: item.versionId, versionId: item.versionId, metadata: { riskLevel: item.riskLevel, riskScore: item.riskScore, riskSignature: `${item.versionId}:${item.date}:${item.riskScore}:${item.riskLevel}`, }, }).catch(() => undefined))); }