Files
agent-ui/server/routes/themes.ts
josedario87 645f51a74e refactor: Modularize server into separate concerns
Split monolithic index.ts (~1400 lines) into modular structure:
- config.ts: Server configuration and constants
- db/: Database initialization, migrations, and seeds
- routes/: API handlers by domain (themes, canvas, components, etc.)
- services/: Terminal WebSocket server
- utils/: CORS helpers

Entry point now only coordinates initialization.
2026-02-13 13:01:18 -06:00

158 lines
5.8 KiB
TypeScript

import { db } from '../db'
import { jsonResponse, errorResponse, corsHeaders } from '../utils/cors'
function parseTheme(row: any) {
return {
...row,
is_default: !!row.is_default,
is_system: !!row.is_system,
variables: JSON.parse(row.variables),
metadata: row.metadata ? JSON.parse(row.metadata) : null
}
}
export async function handleThemes(req: Request) {
if (req.method === 'GET') {
const rows = db.query('SELECT * FROM themes ORDER BY is_system DESC, is_default DESC, name ASC').all()
const themes = (rows as any[]).map(parseTheme)
return jsonResponse(themes)
}
if (req.method === 'POST') {
const body = await req.json()
const id = body.id || `theme-${Date.now()}`
const stmt = db.prepare(`
INSERT OR REPLACE INTO themes
(id, name, description, is_default, is_system, variables, metadata, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
`)
stmt.run(
id,
body.name,
body.description || '',
body.is_default ? 1 : 0,
body.is_system ? 1 : 0,
JSON.stringify(body.variables),
JSON.stringify(body.metadata || {})
)
return jsonResponse({ success: true, id })
}
return null
}
export function handleActiveTheme() {
const row = db.query('SELECT * FROM themes WHERE is_default = 1 LIMIT 1').get() as any
if (!row) {
return errorResponse('No active theme', 404)
}
return jsonResponse(parseTheme(row))
}
export function handleDesignTokens() {
const row = db.query('SELECT variables FROM themes WHERE is_default = 1 LIMIT 1').get() as { variables: string } | null
const tokens = row ? JSON.parse(row.variables) : {}
return jsonResponse({
version: '1.0.0',
description: 'Design tokens for Agent UI components. Use these CSS variables for consistent styling.',
usage: 'Use var(--token-name) in CSS, e.g., var(--bg-primary)',
tokens,
guidelines: {
backgrounds: 'Use bg-primary for main areas, bg-secondary for cards/panels, bg-tertiary for nested elements',
text: 'Use text-primary for headings, text-secondary for body, text-muted for hints',
accent: 'Use accent for interactive elements, accent-hover for hover states, accent-muted for backgrounds',
semantic: 'Use success/warning/error/info for status indicators with their -bg variants for backgrounds',
spacing: 'Use radius-sm (4px) for small elements, radius-md (8px) for cards, radius-lg (12px) for modals',
effects: 'Use transition-fast for micro-interactions, shadow-md for elevated elements'
},
examples: {
button: 'background: var(--accent); color: var(--accent-text); border-radius: var(--radius-md); transition: var(--transition-fast);',
card: 'background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);',
input: 'background: var(--bg-primary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: var(--radius-md);',
badge: 'background: var(--accent-muted); color: var(--accent); padding: 0.25rem 0.5rem; border-radius: var(--radius-full);'
}
})
}
export async function handleThemeById(req: Request, id: string, action?: string) {
// POST /api/themes/:id/default - Set as default
if (action === 'default' && req.method === 'POST') {
db.run('UPDATE themes SET is_default = 0')
db.run('UPDATE themes SET is_default = 1 WHERE id = ?', [id])
return jsonResponse({ success: true })
}
// PUT /api/themes/:id - Update theme
if (req.method === 'PUT' && !action) {
const theme = db.query('SELECT * FROM themes WHERE id = ?').get(id) as any
if (!theme) {
return errorResponse('Theme not found', 404)
}
const body = await req.json()
const updates: string[] = []
const values: any[] = []
if (body.name !== undefined) { updates.push('name = ?'); values.push(body.name) }
if (body.description !== undefined) { updates.push('description = ?'); values.push(body.description) }
if (body.variables !== undefined) { updates.push('variables = ?'); values.push(JSON.stringify(body.variables)) }
if (body.metadata !== undefined) { updates.push('metadata = ?'); values.push(JSON.stringify(body.metadata)) }
if (updates.length > 0) {
updates.push('updated_at = CURRENT_TIMESTAMP')
values.push(id)
const sql = `UPDATE themes SET ${updates.join(', ')} WHERE id = ?`
db.run(sql, values)
}
return jsonResponse({ success: true, id })
}
// GET /api/themes/:id - Get theme
if (req.method === 'GET' && !action) {
const row = db.query('SELECT * FROM themes WHERE id = ?').get(id) as any
if (!row) {
return errorResponse('Theme not found', 404)
}
return jsonResponse(parseTheme(row))
}
// DELETE /api/themes/:id - Delete theme
if (req.method === 'DELETE' && !action) {
const theme = db.query('SELECT is_system FROM themes WHERE id = ?').get(id) as { is_system: number } | null
if (theme?.is_system) {
return errorResponse('Cannot delete system theme', 403)
}
db.run('DELETE FROM themes WHERE id = ?', [id])
return jsonResponse({ success: true })
}
return null
}
export function handleThemeExport(id: string) {
const row = db.query('SELECT * FROM themes WHERE id = ?').get(id) as any
if (!row) {
return errorResponse('Theme not found', 404)
}
const exportData = {
name: row.name,
description: row.description,
variables: JSON.parse(row.variables),
metadata: {
...(row.metadata ? JSON.parse(row.metadata) : {}),
exported_at: new Date().toISOString()
}
}
return new Response(JSON.stringify(exportData, null, 2), {
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="${row.name.toLowerCase().replace(/\s+/g, '-')}-theme.json"`
}
})
}