Files
agent-ui/server/routes/components.ts
josedario87 9f9f335439 feat: Auto-save components, soft delete, tags, compact WCO header
- Auto-save rendered Vue components to DB on render_vue_component
- Soft delete (archive) instead of hard delete for components
- Tags support for component categorization
- Gallery limited to 10 most recent items per section
- Upsert with ON CONFLICT for component saves
- PUT endpoint for partial component updates
- Collapsible toolbar with animated toggle button
- Window Controls Overlay support for PWA titlebar
- Compact header mode (32px) with hidden dot toggle
- Dynamic theme-color meta sync for Windows titlebar
2026-02-15 02:54:27 -06:00

141 lines
4.0 KiB
TypeScript

import { db } from '../db'
import { jsonResponse, errorResponse } from '../utils/cors'
export async function handleComponents(req: Request) {
const url = new URL(req.url)
if (req.method === 'GET') {
const includeArchived = url.searchParams.get('include_archived') === 'true'
const limit = parseInt(url.searchParams.get('limit') || '0') || 0
let sql = 'SELECT * FROM vue_components'
const params: any[] = []
if (!includeArchived) {
sql += " WHERE (status = 'active' OR status IS NULL)"
}
sql += ' ORDER BY updated_at DESC'
if (limit > 0) {
sql += ' LIMIT ?'
params.push(limit)
}
const rows = db.query(sql).all(...params)
return jsonResponse(rows)
}
if (req.method === 'POST') {
const body = await req.json()
const id = body.id || `comp-${Date.now()}`
const tags = body.tags ? JSON.stringify(body.tags) : null
const stmt = db.prepare(`
INSERT INTO vue_components (id, name, template, setup, style, props, imports, tags, status, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active', CURRENT_TIMESTAMP)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
template = excluded.template,
setup = excluded.setup,
style = excluded.style,
props = excluded.props,
imports = excluded.imports,
tags = COALESCE(excluded.tags, tags),
updated_at = CURRENT_TIMESTAMP
`)
stmt.run(
id,
body.name,
body.template,
body.setup || '',
body.style || '',
JSON.stringify(body.props || []),
JSON.stringify(body.imports || []),
tags
)
return jsonResponse({ success: true, id })
}
if (req.method === 'DELETE') {
db.run("UPDATE vue_components SET status = 'archived', updated_at = CURRENT_TIMESTAMP")
return jsonResponse({ success: true })
}
return null
}
export async function handleComponentById(req: Request, id: string) {
if (req.method === 'GET') {
const row = db.query('SELECT * FROM vue_components WHERE id = ?').get(id)
if (!row) {
return errorResponse('Component not found', 404)
}
return jsonResponse(row)
}
if (req.method === 'PUT') {
const body = await req.json()
const fields: string[] = []
const params: any[] = []
const allowedFields = ['name', 'template', 'setup', 'style', 'props', 'imports', 'tags', 'status']
for (const field of allowedFields) {
if (body[field] !== undefined) {
if (field === 'props' || field === 'imports' || field === 'tags') {
fields.push(`${field} = ?`)
params.push(JSON.stringify(body[field]))
} else {
fields.push(`${field} = ?`)
params.push(body[field])
}
}
}
if (fields.length === 0) {
return errorResponse('No fields to update', 400)
}
fields.push('updated_at = CURRENT_TIMESTAMP')
params.push(id)
db.run(`UPDATE vue_components SET ${fields.join(', ')} WHERE id = ?`, params)
return jsonResponse({ success: true })
}
if (req.method === 'DELETE') {
// Check if component is in use by any canvas (warn only)
const usage = db.query(`
SELECT pc.id, pc.name
FROM canvas_components cc
JOIN project_canvas pc ON cc.canvas_id = pc.id
WHERE cc.component_id = ?
`).all(id) as { id: string; name: string }[]
db.run("UPDATE vue_components SET status = 'archived', updated_at = CURRENT_TIMESTAMP WHERE id = ?", [id])
return jsonResponse({
success: true,
warning: usage.length > 0
? `Component is used by: ${usage.map(u => u.name).join(', ')}`
: undefined
})
}
return null
}
export function handleComponentUsage(componentId: string) {
const usage = db.query(`
SELECT pc.id, pc.name, pc.type
FROM canvas_components cc
JOIN project_canvas pc ON cc.canvas_id = pc.id
WHERE cc.component_id = ?
`).all(componentId) as { id: string; name: string; type: string }[]
return jsonResponse({
componentId,
usedBy: usage,
canDelete: usage.length === 0
})
}