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.
158 lines
5.8 KiB
TypeScript
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"`
|
|
}
|
|
})
|
|
}
|