feat: Add multi-canvas system with project canvas support
- Add project_canvas and canvas_components tables for persistent canvas storage - Add ProjectCanvas store with full CRUD operations - Add ProjectCanvasPage for rendering saved canvas with components - Add ProjectsPage for managing canvas list (create, clone, delete) - Add HomePage that loads default canvas or falls back to dynamic canvas - Add toolbar support for displaying canvas as pages with custom icons - Add component usage validation to prevent deletion of components in use - Add MCP tools for canvas management (list, create, update, delete, clone) - Update router with /canvas/:id and /projects routes - Update Toolbar to show dynamic canvas pages from database
This commit is contained in:
246
frontend/src/services/tools/projectCanvasTools.ts
Normal file
246
frontend/src/services/tools/projectCanvasTools.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { registerTool, unregisterTools } from '../webmcp'
|
||||
import { useProjectCanvasStore } from '../../stores/projectCanvas'
|
||||
|
||||
export const PROJECT_CANVAS_TOOLS = [
|
||||
'list_canvases',
|
||||
'create_canvas',
|
||||
'get_canvas',
|
||||
'update_canvas',
|
||||
'delete_canvas',
|
||||
'clone_canvas',
|
||||
'add_component_to_canvas',
|
||||
'remove_component_from_canvas',
|
||||
'get_canvas_components'
|
||||
]
|
||||
|
||||
export function registerProjectCanvasTools() {
|
||||
const store = useProjectCanvasStore()
|
||||
|
||||
// list_canvases
|
||||
registerTool(
|
||||
'list_canvases',
|
||||
'Lista todos los canvas disponibles (proyectos, sistema)',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['all', 'project', 'system'],
|
||||
description: 'Filtrar por tipo de canvas'
|
||||
}
|
||||
}
|
||||
},
|
||||
async (args: { type?: string }) => {
|
||||
await store.fetchCanvases()
|
||||
let canvases = store.canvases
|
||||
|
||||
if (args.type === 'project') {
|
||||
canvases = store.projectCanvases
|
||||
} else if (args.type === 'system') {
|
||||
canvases = store.systemCanvases
|
||||
}
|
||||
|
||||
return JSON.stringify(canvases.map(c => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
type: c.type,
|
||||
description: c.description,
|
||||
is_system: c.is_system
|
||||
})), null, 2)
|
||||
}
|
||||
)
|
||||
|
||||
// create_canvas
|
||||
registerTool(
|
||||
'create_canvas',
|
||||
'Crea un nuevo project canvas',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Nombre del canvas' },
|
||||
description: { type: 'string', description: 'Descripcion del canvas' },
|
||||
theme_id: { type: 'string', description: 'ID del tema a usar (opcional)' },
|
||||
config: {
|
||||
type: 'object',
|
||||
description: 'Configuracion del canvas (layout, settings, permissions)'
|
||||
}
|
||||
},
|
||||
required: ['name']
|
||||
},
|
||||
async (args: { name: string; description?: string; theme_id?: string; config?: object }) => {
|
||||
const id = await store.createCanvas({
|
||||
name: args.name,
|
||||
description: args.description,
|
||||
theme_id: args.theme_id,
|
||||
config: args.config as any,
|
||||
type: 'project'
|
||||
})
|
||||
|
||||
if (id) {
|
||||
return `Canvas "${args.name}" creado con ID: ${id}`
|
||||
}
|
||||
return `Error al crear canvas: ${store.error}`
|
||||
}
|
||||
)
|
||||
|
||||
// get_canvas
|
||||
registerTool(
|
||||
'get_canvas',
|
||||
'Obtiene los detalles de un canvas por ID',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'ID del canvas' }
|
||||
},
|
||||
required: ['id']
|
||||
},
|
||||
async (args: { id: string }) => {
|
||||
const canvas = await store.fetchCanvasById(args.id)
|
||||
if (!canvas) {
|
||||
return `Canvas con ID "${args.id}" no encontrado`
|
||||
}
|
||||
return JSON.stringify(canvas, null, 2)
|
||||
}
|
||||
)
|
||||
|
||||
// update_canvas
|
||||
registerTool(
|
||||
'update_canvas',
|
||||
'Actualiza un canvas existente',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'ID del canvas a actualizar' },
|
||||
name: { type: 'string', description: 'Nuevo nombre' },
|
||||
description: { type: 'string', description: 'Nueva descripcion' },
|
||||
theme_id: { type: 'string', description: 'Nuevo tema' },
|
||||
config: { type: 'object', description: 'Nueva configuracion' }
|
||||
},
|
||||
required: ['id']
|
||||
},
|
||||
async (args: { id: string; name?: string; description?: string; theme_id?: string; config?: object }) => {
|
||||
const { id, ...data } = args
|
||||
const success = await store.updateCanvas(id, data as any)
|
||||
|
||||
if (success) {
|
||||
return `Canvas "${id}" actualizado`
|
||||
}
|
||||
return `Error al actualizar canvas: ${store.error}`
|
||||
}
|
||||
)
|
||||
|
||||
// delete_canvas
|
||||
registerTool(
|
||||
'delete_canvas',
|
||||
'Elimina un canvas (no se pueden eliminar canvas del sistema)',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'ID del canvas a eliminar' }
|
||||
},
|
||||
required: ['id']
|
||||
},
|
||||
async (args: { id: string }) => {
|
||||
const success = await store.deleteCanvas(args.id)
|
||||
|
||||
if (success) {
|
||||
return `Canvas "${args.id}" eliminado`
|
||||
}
|
||||
return `Error al eliminar canvas: ${store.error}`
|
||||
}
|
||||
)
|
||||
|
||||
// clone_canvas
|
||||
registerTool(
|
||||
'clone_canvas',
|
||||
'Clona un canvas existente (incluyendo sus componentes)',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'ID del canvas a clonar' },
|
||||
name: { type: 'string', description: 'Nombre para el nuevo canvas (opcional)' }
|
||||
},
|
||||
required: ['id']
|
||||
},
|
||||
async (args: { id: string; name?: string }) => {
|
||||
const newId = await store.cloneCanvas(args.id, args.name)
|
||||
|
||||
if (newId) {
|
||||
return `Canvas clonado con nuevo ID: ${newId}`
|
||||
}
|
||||
return `Error al clonar canvas: ${store.error}`
|
||||
}
|
||||
)
|
||||
|
||||
// add_component_to_canvas
|
||||
registerTool(
|
||||
'add_component_to_canvas',
|
||||
'Agrega un componente guardado a un canvas',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
canvas_id: { type: 'string', description: 'ID del canvas' },
|
||||
component_id: { type: 'string', description: 'ID del componente a agregar' },
|
||||
props: { type: 'object', description: 'Props para el componente en este canvas' },
|
||||
position: { type: 'number', description: 'Posicion del componente (orden de renderizado)' }
|
||||
},
|
||||
required: ['canvas_id', 'component_id']
|
||||
},
|
||||
async (args: { canvas_id: string; component_id: string; props?: object; position?: number }) => {
|
||||
const success = await store.addComponentToCanvas(
|
||||
args.canvas_id,
|
||||
args.component_id,
|
||||
args.props as Record<string, any>,
|
||||
args.position
|
||||
)
|
||||
|
||||
if (success) {
|
||||
return `Componente "${args.component_id}" agregado al canvas "${args.canvas_id}"`
|
||||
}
|
||||
return 'Error al agregar componente al canvas'
|
||||
}
|
||||
)
|
||||
|
||||
// remove_component_from_canvas
|
||||
registerTool(
|
||||
'remove_component_from_canvas',
|
||||
'Remueve un componente de un canvas',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
canvas_id: { type: 'string', description: 'ID del canvas' },
|
||||
component_id: { type: 'string', description: 'ID del componente a remover' }
|
||||
},
|
||||
required: ['canvas_id', 'component_id']
|
||||
},
|
||||
async (args: { canvas_id: string; component_id: string }) => {
|
||||
const success = await store.removeComponentFromCanvas(args.canvas_id, args.component_id)
|
||||
|
||||
if (success) {
|
||||
return `Componente "${args.component_id}" removido del canvas "${args.canvas_id}"`
|
||||
}
|
||||
return 'Error al remover componente del canvas'
|
||||
}
|
||||
)
|
||||
|
||||
// get_canvas_components
|
||||
registerTool(
|
||||
'get_canvas_components',
|
||||
'Obtiene los componentes de un canvas con sus definiciones',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
canvas_id: { type: 'string', description: 'ID del canvas' }
|
||||
},
|
||||
required: ['canvas_id']
|
||||
},
|
||||
async (args: { canvas_id: string }) => {
|
||||
await store.fetchCanvasComponents(args.canvas_id)
|
||||
return JSON.stringify(store.activeCanvasComponents, null, 2)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function unregisterProjectCanvasTools() {
|
||||
unregisterTools(PROJECT_CANVAS_TOOLS)
|
||||
}
|
||||
Reference in New Issue
Block a user