File size: 4,897 Bytes
f589dab | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | /**
* stores/extensionStore.ts
*
* Zustand store with chrome.storage.sync persistence.
* Cross-context state: popup β background β content scripts
* all read from / write to chrome.storage.sync, so the store
* stays in sync across the extension's execution contexts.
*/
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
// ββ Types βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
export type HighlightMode = 'minimal' | 'normal' | 'advanced';
export type WsStatus = 'connected' | 'reconnecting' | 'offline';
export interface AnalysisResult {
claimHash: string;
claimText: string;
color: 'green' | 'yellow' | 'red' | 'purple';
confidence: number; // 0-100
verdict: string;
explanation: string;
sources: string[];
trustScore: number;
cached: boolean;
processingMs: number;
}
export interface ExtensionState {
// User preferences
enabled: boolean;
mode: HighlightMode;
// Connection
wsStatus: WsStatus;
backendUrl: string;
clientId: string;
// Results cache (in-memory, not persisted)
results: Record<string, AnalysisResult>; // keyed by claimHash
// Stats
totalAnalyzed: number;
totalFlagged: number;
// Actions
setEnabled: (v: boolean) => void;
setMode: (m: HighlightMode) => void;
setWsStatus: (s: WsStatus) => void;
setBackendUrl: (url: string) => void;
addResults: (results: AnalysisResult[]) => void;
clearResults: () => void;
}
// ββ chrome.storage.sync adapter for zustand/persist βββββββββββββββββββββββββββ
const chromeStorageAdapter = createJSONStorage(() => ({
getItem: (key: string): Promise<string | null> => {
return new Promise((resolve) => {
if (typeof chrome === 'undefined' || !chrome.storage) {
resolve(localStorage.getItem(key));
return;
}
chrome.storage.sync.get([key], (result) => {
resolve(result[key] ?? null);
});
});
},
setItem: (key: string, value: string): Promise<void> => {
return new Promise((resolve) => {
if (typeof chrome === 'undefined' || !chrome.storage) {
localStorage.setItem(key, value);
resolve();
return;
}
chrome.storage.sync.set({ [key]: value }, resolve);
});
},
removeItem: (key: string): Promise<void> => {
return new Promise((resolve) => {
if (typeof chrome === 'undefined' || !chrome.storage) {
localStorage.removeItem(key);
resolve();
return;
}
chrome.storage.sync.remove([key], resolve);
});
},
}));
// ββ Generate stable client ID βββββββββββββββββββββββββββββββββββββββββββββββββ
function generateClientId(): string {
const arr = new Uint8Array(8);
crypto.getRandomValues(arr);
return 'ext-' + Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
}
// ββ Store βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
export const useExtensionStore = create<ExtensionState>()(
persist(
(set, get) => ({
enabled: true,
mode: 'normal',
wsStatus: 'offline',
backendUrl: import.meta.env.VITE_WS_URL || 'ws://localhost:7860',
clientId: generateClientId(),
results: {},
totalAnalyzed: 0,
totalFlagged: 0,
setEnabled: (v) => set({ enabled: v }),
setMode: (m) => set({ mode: m }),
setWsStatus: (s) => set({ wsStatus: s }),
setBackendUrl: (url) => set({ backendUrl: url }),
addResults: (newResults) => {
const { results, totalAnalyzed, totalFlagged } = get();
const updated = { ...results };
let flaggedDelta = 0;
for (const r of newResults) {
updated[r.claimHash] = r;
if (r.color === 'red' || r.color === 'purple') flaggedDelta++;
}
set({
results: updated,
totalAnalyzed: totalAnalyzed + newResults.length,
totalFlagged: totalFlagged + flaggedDelta,
});
},
clearResults: () => set({ results: {}, totalAnalyzed: 0, totalFlagged: 0 }),
}),
{
name: 'fact-engine-store',
storage: chromeStorageAdapter,
// Only persist user preferences, not runtime state
partialize: (state) => ({
enabled: state.enabled,
mode: state.mode,
backendUrl: state.backendUrl,
clientId: state.clientId,
totalAnalyzed: state.totalAnalyzed,
totalFlagged: state.totalFlagged,
}),
}
)
);
|