Files
agent-ui/frontend/src/stores/theme.ts
josedario87 902029c805 feat: Add HTTPS/Traefik support with centralized endpoints
- Create traefik/agent-ui.yml with full routing config for domain z590.nucleoriofrio.com
- Add frontend/src/config/endpoints.ts for automatic HTTP/HTTPS detection
- Update all hardcoded localhost URLs to use relative paths
- WebSocket connections auto-detect wss:// vs ws:// based on page protocol
- Configure path-based WebSocket routing (/ws/terminal, /ws/mcp, /ws/status, /ws/whisper)
- Add commented IP whitelist middleware for future security
2026-02-14 03:20:51 -06:00

322 lines
7.9 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// =====================
// Types
// =====================
export interface ThemeVariables {
colors: Record<string, string>
text: Record<string, string>
accent: Record<string, string>
semantic: Record<string, string>
spacing: Record<string, string>
typography: Record<string, string>
effects: Record<string, string>
}
export interface ThemeMetadata {
author?: string
version?: string
tags?: string[]
base?: string | null
exported_at?: string | null
}
export interface Theme {
id: string
name: string
description?: string
is_default: boolean
is_system: boolean
variables: ThemeVariables
metadata?: ThemeMetadata
created_at?: string
updated_at?: string
}
export interface DesignTokens {
version: string
description: string
usage: string
tokens: ThemeVariables
guidelines: Record<string, string>
examples: Record<string, string>
}
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
const API_URL = ''
// =====================
// Store
// =====================
export const useThemeStore = defineStore('theme', () => {
// State
const themes = ref<Theme[]>([])
const activeTheme = ref<Theme | null>(null)
const previewTheme = ref<ThemeVariables | null>(null)
const designTokens = ref<DesignTokens | null>(null)
const loading = ref(false)
const saving = ref(false)
const error = ref<string | null>(null)
// Getters
const currentVariables = computed(() => {
if (previewTheme.value) return previewTheme.value
return activeTheme.value?.variables || null
})
const systemThemes = computed(() =>
themes.value.filter(t => t.is_system)
)
const userThemes = computed(() =>
themes.value.filter(t => !t.is_system)
)
const hasUnsavedChanges = computed(() => previewTheme.value !== null)
const flattenedVariables = computed(() => {
const vars = currentVariables.value
if (!vars) return {}
const flat: Record<string, string> = {}
for (const category of Object.keys(vars)) {
for (const [key, value] of Object.entries(vars[category as keyof ThemeVariables])) {
flat[`--${key}`] = value
}
}
return flat
})
// Actions
async function fetchThemes() {
loading.value = true
error.value = null
try {
const res = await fetch(`${API_URL}/api/themes`)
themes.value = await res.json()
const defaultTheme = themes.value.find(t => t.is_default)
if (defaultTheme) {
activeTheme.value = defaultTheme
applyTheme(defaultTheme.variables)
}
} catch (e) {
error.value = 'Error loading themes'
console.error(e)
} finally {
loading.value = false
}
}
async function fetchDesignTokens() {
try {
const res = await fetch(`${API_URL}/api/design-tokens`)
designTokens.value = await res.json()
} catch (e) {
console.error('Error fetching design tokens:', e)
}
}
async function saveTheme(theme: Partial<Theme> & { name: string; variables: ThemeVariables }) {
saving.value = true
try {
const res = await fetch(`${API_URL}/api/themes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(theme)
})
const result = await res.json()
await fetchThemes()
return result
} catch (e) {
error.value = 'Error saving theme'
throw e
} finally {
saving.value = false
}
}
async function updateTheme(id: string, data: { name?: string; description?: string; variables?: ThemeVariables; metadata?: ThemeMetadata }) {
saving.value = true
try {
const res = await fetch(`${API_URL}/api/themes/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
const result = await res.json()
if (result.error) {
error.value = result.error
return null
}
await fetchThemes()
return result
} catch (e) {
error.value = 'Error updating theme'
throw e
} finally {
saving.value = false
}
}
async function deleteTheme(id: string) {
try {
const res = await fetch(`${API_URL}/api/themes/${id}`, { method: 'DELETE' })
const result = await res.json()
if (result.error) {
error.value = result.error
return false
}
await fetchThemes()
return true
} catch (e) {
error.value = 'Error deleting theme'
return false
}
}
async function setDefaultTheme(id: string) {
try {
await fetch(`${API_URL}/api/themes/${id}/default`, { method: 'POST' })
await fetchThemes()
} catch (e) {
error.value = 'Error setting default theme'
}
}
async function cloneTheme(id: string, newName: string) {
const original = themes.value.find(t => t.id === id)
if (!original) return null
return saveTheme({
name: newName,
description: `Cloned from ${original.name}`,
variables: JSON.parse(JSON.stringify(original.variables)),
metadata: { base: id }
})
}
function applyTheme(variables: ThemeVariables) {
const root = document.documentElement
for (const category of Object.keys(variables)) {
for (const [key, value] of Object.entries(variables[category as keyof ThemeVariables])) {
root.style.setProperty(`--${key}`, value)
}
}
}
function selectTheme(theme: Theme) {
activeTheme.value = theme
previewTheme.value = null
applyTheme(theme.variables)
}
function setPreview(variables: ThemeVariables | null) {
previewTheme.value = variables
if (variables) {
applyTheme(variables)
} else if (activeTheme.value) {
applyTheme(activeTheme.value.variables)
}
}
function updateVariable(category: keyof ThemeVariables, key: string, value: string) {
if (!previewTheme.value && activeTheme.value) {
previewTheme.value = JSON.parse(JSON.stringify(activeTheme.value.variables))
}
if (previewTheme.value && previewTheme.value[category]) {
previewTheme.value[category][key] = value
document.documentElement.style.setProperty(`--${key}`, value)
}
}
function exportTheme(theme: Theme): string {
return JSON.stringify({
name: theme.name,
description: theme.description,
variables: theme.variables,
metadata: {
...theme.metadata,
exported_at: new Date().toISOString()
}
}, null, 2)
}
async function importTheme(jsonString: string) {
try {
const data = JSON.parse(jsonString)
if (!data.name || !data.variables) {
throw new Error('Invalid theme format')
}
return saveTheme({
name: data.name,
description: data.description,
variables: data.variables,
metadata: data.metadata
})
} catch (e) {
error.value = 'Invalid theme file'
throw e
}
}
function resetPreview() {
previewTheme.value = null
if (activeTheme.value) {
applyTheme(activeTheme.value.variables)
}
}
async function commitPreview(name?: string) {
if (!previewTheme.value) return
const themeName = name || activeTheme.value?.name || 'Custom Theme'
const themeId = activeTheme.value?.is_system ? undefined : activeTheme.value?.id
await saveTheme({
id: themeId,
name: themeName,
variables: previewTheme.value
})
previewTheme.value = null
}
return {
// State
themes,
activeTheme,
previewTheme,
designTokens,
loading,
saving,
error,
// Getters
currentVariables,
systemThemes,
userThemes,
hasUnsavedChanges,
flattenedVariables,
// Actions
fetchThemes,
fetchDesignTokens,
saveTheme,
updateTheme,
deleteTheme,
setDefaultTheme,
cloneTheme,
applyTheme,
selectTheme,
setPreview,
updateVariable,
exportTheme,
importTheme,
resetPreview,
commitPreview
}
})