OwnGPT.v2 / client /src /store /useAppStore.js
parthib07's picture
Upload 199 files
212c959 verified
import { create } from 'zustand'
import { getToken, saveToken } from '../utils/api'
const THEME_KEY = 'owngpt_theme'
const PROVIDER_KEY = 'owngpt_provider'
const MODEL_KEY = 'owngpt_model'
const MODE_KEY = 'owngpt_mode'
const SIDEBAR_KEY = 'owngpt_sidebar_open'
const VALID_THEME_PREFERENCES = new Set(['system', 'light', 'dark'])
const THEME_COLORS = {
dark: '#212121',
light: '#ffffff',
}
const MODE_CONFIG = {
chat: {
id: 'chat',
label: 'Chat',
description: 'Balanced conversation for general help.',
},
research: {
id: 'research',
label: 'Research',
description: 'Biases the assistant toward search-heavy responses.',
},
coding: {
id: 'coding',
label: 'Coding',
description: 'Focuses on implementation, debugging, and code generation.',
},
document: {
id: 'document',
label: 'Document Q&A',
description: 'Optimized for file-grounded answers and summaries.',
},
}
export const CHAT_MODES = Object.values(MODE_CONFIG)
function createId() {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID()
}
return `id-${Math.random().toString(36).slice(2, 11)}`
}
function readStorage(key, fallback) {
if (typeof window === 'undefined') return fallback
const value = window.localStorage.getItem(key)
return value ?? fallback
}
function getSystemTheme() {
if (
typeof window !== 'undefined' &&
typeof window.matchMedia === 'function' &&
window.matchMedia('(prefers-color-scheme: light)').matches
) {
return 'light'
}
return 'dark'
}
function readTheme() {
const storedTheme = readStorage(THEME_KEY, 'system')
if (VALID_THEME_PREFERENCES.has(storedTheme)) return storedTheme
return 'system'
}
function resolveTheme(themePreference) {
return themePreference === 'system' ? getSystemTheme() : themePreference
}
function readBoolean(key, fallback) {
const value = readStorage(key, String(fallback))
return value === 'true'
}
function writeStorage(key, value) {
if (typeof window === 'undefined') return
window.localStorage.setItem(key, String(value))
}
function applyTheme(themePreference) {
if (typeof document === 'undefined') return
const resolvedTheme = resolveTheme(themePreference)
document.documentElement.setAttribute('data-theme', resolvedTheme)
document.documentElement.setAttribute('data-theme-preference', themePreference)
document.documentElement.style.colorScheme = resolvedTheme
const themeColor = document.querySelector('meta[name="theme-color"]')
if (themeColor) {
themeColor.setAttribute('content', THEME_COLORS[resolvedTheme] || THEME_COLORS.dark)
}
return resolvedTheme
}
const initialThemePreference = readTheme()
const initialTheme = applyTheme(initialThemePreference)
export function encodeSharePayload(payload) {
if (typeof window === 'undefined') return ''
return window.btoa(unescape(encodeURIComponent(JSON.stringify(payload))))
}
export function decodeSharePayload(hash) {
if (typeof window === 'undefined' || !hash.startsWith('#share=')) return null
try {
const raw = hash.replace('#share=', '')
return JSON.parse(decodeURIComponent(escape(window.atob(raw))))
} catch {
return null
}
}
export const useAppStore = create((set, get) => ({
token: getToken(),
theme: initialTheme,
themePreference: initialThemePreference,
sidebarOpen: readBoolean(SIDEBAR_KEY, true),
currentSessionId: null,
currentSessionTitle: 'New Chat',
provider: readStorage(PROVIDER_KEY, 'groq'),
model: readStorage(MODEL_KEY, 'llama-3.3-70b-versatile'),
mode: readStorage(MODE_KEY, 'chat'),
composerText: '',
attachments: [],
authMode: null,
activeModal: null,
modalPayload: null,
sessionSearch: '',
sidePanel: null,
selectedFile: null,
sharedConversation: null,
toasts: [],
speakingMessageId: null,
setToken: (token) => {
saveToken(token)
set({ token })
},
hydrateToken: () => {
set({ token: getToken() })
},
setThemePreference: (themePreference) => {
const nextPreference = VALID_THEME_PREFERENCES.has(themePreference) ? themePreference : 'system'
writeStorage(THEME_KEY, nextPreference)
const theme = applyTheme(nextPreference)
set({ themePreference: nextPreference, theme })
},
setTheme: (themePreference) => get().setThemePreference(themePreference),
syncSystemTheme: () => {
if (get().themePreference !== 'system') return
const theme = applyTheme('system')
set({ theme })
},
setSidebarOpen: (sidebarOpen) => {
writeStorage(SIDEBAR_KEY, sidebarOpen)
set({ sidebarOpen })
},
toggleSidebar: () => {
const next = !get().sidebarOpen
writeStorage(SIDEBAR_KEY, next)
set({ sidebarOpen: next })
},
setCurrentSession: (currentSessionId, currentSessionTitle = 'New Chat') =>
set({
currentSessionId,
currentSessionTitle,
selectedFile: null,
sidePanel: null,
sharedConversation: null,
}),
renameCurrentSession: (currentSessionTitle) => set({ currentSessionTitle }),
startNewChat: () =>
set({
currentSessionId: null,
currentSessionTitle: 'New Chat',
composerText: '',
attachments: [],
selectedFile: null,
sidePanel: null,
sharedConversation: null,
}),
setComposerText: (composerText) => set({ composerText }),
addAttachment: (attachment) =>
set((state) => ({
attachments: [
...state.attachments,
{
...attachment,
localId: attachment.localId || createId(),
},
],
})),
removeAttachment: (localId) =>
set((state) => ({
attachments: state.attachments.filter((attachment) => attachment.localId !== localId),
})),
clearAttachments: () => set({ attachments: [] }),
setMode: (mode) => {
writeStorage(MODE_KEY, mode)
set({ mode })
},
setProviderModel: (provider, model) => {
writeStorage(PROVIDER_KEY, provider)
writeStorage(MODEL_KEY, model)
set({ provider, model })
},
openAuth: (authMode = 'login') => set({ activeModal: 'auth', authMode }),
closeAuth: () => set({ activeModal: null, authMode: null }),
openModal: (activeModal, modalPayload = null) => set({ activeModal, modalPayload }),
closeModal: () => set({ activeModal: null, modalPayload: null }),
setSessionSearch: (sessionSearch) => set({ sessionSearch }),
setSidePanel: (sidePanel) => set({ sidePanel }),
previewFile: (selectedFile) => set({ selectedFile, sidePanel: 'file' }),
clearSelectedFile: () => set({ selectedFile: null, sidePanel: null }),
setSharedConversation: (sharedConversation) =>
set({
sharedConversation,
currentSessionId: null,
currentSessionTitle: sharedConversation?.title || 'Shared Conversation',
}),
clearSharedConversation: () => set({ sharedConversation: null }),
addToast: ({ title, description = '', variant = 'info', duration = 4200 }) => {
const id = createId()
set((state) => ({
toasts: [...state.toasts, { id, title, description, variant, duration }],
}))
if (typeof window !== 'undefined') {
window.setTimeout(() => {
get().removeToast(id)
}, duration)
}
return id
},
removeToast: (id) =>
set((state) => ({
toasts: state.toasts.filter((toast) => toast.id !== id),
})),
setSpeakingMessageId: (speakingMessageId) => set({ speakingMessageId }),
}))