(null)
const currentCategoryVariables = computed(() => {
@@ -58,6 +62,26 @@ async function confirmClone() {
}
}
+function handleEditTheme(theme: any) {
+ editingTheme.value = theme
+ editName.value = theme.name
+ editDescription.value = theme.description || ''
+ showEditModal.value = true
+}
+
+async function confirmEdit() {
+ if (editingTheme.value && editName.value.trim()) {
+ await store.updateTheme(editingTheme.value.id, {
+ name: editName.value.trim(),
+ description: editDescription.value.trim() || undefined
+ })
+ showEditModal.value = false
+ editingTheme.value = null
+ editName.value = ''
+ editDescription.value = ''
+ }
+}
+
function handleSetDefault(id: string) {
store.setDefaultTheme(id)
}
@@ -171,6 +195,7 @@ onMounted(() => {
@select="(t) => { handleSelectTheme(t); mobileDropdownOpen = false }"
@clone="handleCloneTheme"
@setDefault="handleSetDefault"
+ @edit="handleEditTheme"
/>
@@ -184,6 +209,7 @@ onMounted(() => {
@delete="handleDeleteTheme"
@clone="handleCloneTheme"
@setDefault="handleSetDefault"
+ @edit="handleEditTheme"
/>
@@ -208,6 +234,7 @@ onMounted(() => {
@delete="handleDeleteTheme"
@clone="handleCloneTheme"
@setDefault="handleSetDefault"
+ @edit="handleEditTheme"
/>
@@ -222,6 +249,7 @@ onMounted(() => {
@delete="handleDeleteTheme"
@clone="handleCloneTheme"
@setDefault="handleSetDefault"
+ @edit="handleEditTheme"
/>
@@ -366,6 +394,33 @@ onMounted(() => {
+
+
+
+
+
Edit Theme
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -820,6 +875,35 @@ onMounted(() => {
border-color: var(--accent);
}
+.form-group {
+ margin-bottom: 1rem;
+}
+
+.form-group label {
+ display: block;
+ font-size: 0.8rem;
+ font-weight: 500;
+ color: var(--text-secondary);
+ margin-bottom: 0.375rem;
+}
+
+.modal textarea {
+ width: 100%;
+ padding: 0.75rem 1rem;
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ color: var(--text-primary);
+ font-size: 0.95rem;
+ font-family: inherit;
+ resize: vertical;
+}
+
+.modal textarea:focus {
+ outline: none;
+ border-color: var(--accent);
+}
+
.modal-actions {
display: flex;
gap: 0.75rem;
diff --git a/frontend/src/services/tools/themeTools.ts b/frontend/src/services/tools/themeTools.ts
index 4f2b96a..8a76743 100644
--- a/frontend/src/services/tools/themeTools.ts
+++ b/frontend/src/services/tools/themeTools.ts
@@ -6,6 +6,7 @@ export const THEME_TOOLS = [
'get_active_theme',
'set_theme_variable',
'save_theme',
+ 'update_theme',
'list_themes',
'switch_theme',
'set_default_theme',
@@ -212,6 +213,85 @@ export function registerThemeTools() {
}
)
+ // update_theme
+ registerTool(
+ 'update_theme',
+ 'Actualiza un tema existente (nombre, descripción o variables)',
+ {
+ type: 'object',
+ properties: {
+ theme: {
+ type: 'string',
+ description: 'Nombre o ID del tema a actualizar'
+ },
+ name: {
+ type: 'string',
+ description: 'Nuevo nombre para el tema (opcional)'
+ },
+ description: {
+ type: 'string',
+ description: 'Nueva descripción para el tema (opcional)'
+ },
+ saveCurrentVariables: {
+ type: 'boolean',
+ description: 'Si es true, guarda las variables actuales (con los cambios de set_theme_variable) en este tema'
+ }
+ },
+ required: ['theme']
+ },
+ async (args: { theme: string; name?: string; description?: string; saveCurrentVariables?: boolean }) => {
+ try {
+ const themeStore = useThemeStore()
+ await themeStore.fetchThemes()
+
+ // Find theme by ID or name
+ const theme = themeStore.themes.find(t =>
+ t.id === args.theme || t.name.toLowerCase() === args.theme.toLowerCase()
+ )
+
+ if (!theme) {
+ const available = themeStore.themes.map(t => t.name).join(', ')
+ return `Tema "${args.theme}" no encontrado.\nDisponibles: ${available}`
+ }
+
+ if (theme.is_system) {
+ return `No se puede modificar "${theme.name}" porque es un tema del sistema. Usa save_theme para crear una copia.`
+ }
+
+ // Build update data
+ const updateData: { name?: string; description?: string; variables?: any } = {}
+
+ if (args.name) {
+ updateData.name = args.name
+ }
+ if (args.description !== undefined) {
+ updateData.description = args.description
+ }
+ if (args.saveCurrentVariables) {
+ const variablesToSave = themeStore.previewTheme || themeStore.activeTheme?.variables
+ if (variablesToSave) {
+ updateData.variables = variablesToSave
+ }
+ }
+
+ if (Object.keys(updateData).length === 0) {
+ return 'No se especificaron cambios. Usa name, description o saveCurrentVariables.'
+ }
+
+ await themeStore.updateTheme(theme.id, updateData)
+
+ const changes = []
+ if (args.name) changes.push(`nombre: "${args.name}"`)
+ if (args.description !== undefined) changes.push('descripción actualizada')
+ if (args.saveCurrentVariables) changes.push('variables guardadas')
+
+ return `Tema "${theme.name}" actualizado:\n ${changes.join('\n ')}`
+ } catch (e: any) {
+ return `Error al actualizar tema: ${e.message}`
+ }
+ }
+ )
+
// list_themes
registerTool(
'list_themes',
diff --git a/frontend/src/stores/theme.ts b/frontend/src/stores/theme.ts
index 9e462a9..e2d6edc 100644
--- a/frontend/src/stores/theme.ts
+++ b/frontend/src/stores/theme.ts
@@ -138,6 +138,29 @@ export const useThemeStore = defineStore('theme', () => {
}
}
+ 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' })
@@ -281,6 +304,7 @@ export const useThemeStore = defineStore('theme', () => {
fetchThemes,
fetchDesignTokens,
saveTheme,
+ updateTheme,
deleteTheme,
setDefaultTheme,
cloneTheme,
diff --git a/server/index.ts b/server/index.ts
index ef34f99..180d132 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -393,6 +393,47 @@ Bun.serve({
return Response.json({ success: true }, { headers: corsHeaders })
}
+ // PUT /api/themes/:id - Actualizar un tema existente
+ if (req.method === 'PUT' && !action) {
+ const theme = db.query('SELECT * FROM themes WHERE id = ?').get(id) as any
+ if (!theme) {
+ return Response.json({ error: 'Theme not found' }, { status: 404, headers: corsHeaders })
+ }
+
+ const body = await req.json()
+
+ // Build update query dynamically based on provided fields
+ 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 Response.json({ success: true, id }, { headers: corsHeaders })
+ }
+
// GET /api/themes/:id - Obtener un tema
if (req.method === 'GET' && !action) {
const row = db.query('SELECT * FROM themes WHERE id = ?').get(id) as any