| import { writable, derived, get } from 'svelte/store'; |
| import { auth as authApi, setup as setupApi, features as featuresApi } from './api.js'; |
|
|
| |
|
|
| function createAuthStore() { |
| const { subscribe, set, update } = writable({ |
| user: null, |
| token: null, |
| refreshToken: null, |
| loading: true, |
| initialized: false, |
| }); |
|
|
| return { |
| subscribe, |
|
|
| async init() { |
| if (typeof localStorage === 'undefined') { |
| update(s => ({ ...s, loading: false, initialized: true })); |
| return; |
| } |
| const token = localStorage.getItem('mac_token'); |
| const refreshToken = localStorage.getItem('mac_refresh'); |
| if (!token) { |
| update(s => ({ ...s, loading: false, initialized: true })); |
| return; |
| } |
| try { |
| const user = await authApi.me(); |
| update(s => ({ ...s, user, token, refreshToken, loading: false, initialized: true })); |
| } catch { |
| localStorage.removeItem('mac_token'); |
| localStorage.removeItem('mac_refresh'); |
| update(s => ({ ...s, loading: false, initialized: true })); |
| } |
| }, |
|
|
| async login(identifier, password) { |
| const data = await authApi.login(identifier, password); |
| localStorage.setItem('mac_token', data.access_token); |
| if (data.refresh_token) localStorage.setItem('mac_refresh', data.refresh_token); |
| update(s => ({ ...s, user: data.user, token: data.access_token, refreshToken: data.refresh_token })); |
| return data; |
| }, |
|
|
| async logout() { |
| const rt = localStorage.getItem('mac_refresh'); |
| try { if (rt) await authApi.logout(); } catch {} |
| localStorage.removeItem('mac_token'); |
| localStorage.removeItem('mac_refresh'); |
| set({ user: null, token: null, refreshToken: null, loading: false, initialized: true }); |
| }, |
|
|
| setUser(user) { |
| update(s => ({ ...s, user })); |
| }, |
| }; |
| } |
|
|
| export const authStore = createAuthStore(); |
| export const user = derived(authStore, $a => $a.user); |
| export const isAdmin = derived(authStore, $a => $a.user?.role === 'admin'); |
| export const isFacultyOrAdmin = derived(authStore, $a => ['faculty', 'admin'].includes($a.user?.role)); |
| export const isLoggedIn = derived(authStore, $a => !!$a.user); |
|
|
| |
|
|
| export const setupStore = writable({ is_first_run: null, checked: false }); |
|
|
| export async function checkSetup() { |
| try { |
| const data = await setupApi.status(); |
| setupStore.set({ ...data, checked: true }); |
| return data; |
| } catch { |
| setupStore.set({ is_first_run: false, checked: true }); |
| } |
| } |
|
|
| |
|
|
| export const featureStore = writable({ flags: {}, roles: {}, loaded: false }); |
|
|
| export async function loadFeatures() { |
| try { |
| const data = await featuresApi.status(); |
| featureStore.set({ ...data, loaded: true }); |
| } catch { |
| featureStore.set({ flags: {}, roles: {}, loaded: true }); |
| } |
| } |
|
|
| export function hasFeature(key) { |
| return derived(featureStore, $f => !!$f.flags[key]); |
| } |
|
|
| |
|
|
| export const sidebarOpen = writable(true); |
| export const theme = writable('light'); |
|
|
| |
| export const globalLoading = writable(false); |
| export const loadingMessage = writable(''); |
| export const locale = writable('en'); |
|
|
| export const toast = writable(null); |
|
|
| let toastTimer = null; |
| export function showToast(message, type = 'info', duration = 4000) { |
| if (toastTimer) clearTimeout(toastTimer); |
| toast.set({ message, type, id: Date.now() }); |
| toastTimer = setTimeout(() => toast.set(null), duration); |
| } |
|
|
| |
|
|
| export const chatStore = writable({ |
| conversations: [], |
| activeId: null, |
| streaming: false, |
| }); |
|
|
| export function newConversation() { |
| const id = crypto.randomUUID(); |
| chatStore.update(s => ({ |
| ...s, |
| conversations: [{ id, title: 'New Chat', messages: [] }, ...s.conversations], |
| activeId: id, |
| })); |
| return id; |
| } |
|
|
| export function appendMessage(convId, message) { |
| chatStore.update(s => ({ |
| ...s, |
| conversations: s.conversations.map(c => |
| c.id === convId |
| ? { ...c, messages: [...c.messages, message], |
| title: c.title === 'New Chat' && message.role === 'user' |
| ? message.content.slice(0, 40) + (message.content.length > 40 ? 'β¦' : '') |
| : c.title } |
| : c |
| ), |
| })); |
| } |
|
|
| export function updateLastMessage(convId, patch) { |
| chatStore.update(s => ({ |
| ...s, |
| conversations: s.conversations.map(c => |
| c.id === convId |
| ? { ...c, messages: c.messages.map((m, i) => |
| i === c.messages.length - 1 ? { ...m, ...patch } : m) } |
| : c |
| ), |
| })); |
| } |
|
|