Refactor task handling (adding Task abstraction)

This commit is contained in:
Dominik Jain 2025-09-17 19:36:12 +02:00
parent 8275735999
commit 93e98f5e08
5 changed files with 63 additions and 65 deletions

View File

@ -1,6 +1,6 @@
import { WebSocket, WebSocketServer } from "ws";
import { PluginTask } from "./PluginTask";
import { PluginTaskResponse, PluginTaskResult } from "@penpot-mcp/common";
import {WebSocket, WebSocketServer} from "ws";
import {PluginTask} from "./PluginTask";
import {PluginTaskResponse} from "@penpot-mcp/common";
/**
* Provides the connection to the Penpot MCP Plugin via WebSocket

View File

@ -6,7 +6,7 @@
*
* @template TParams - The strongly-typed parameters for this task
*/
import { PluginTaskRequest, PluginTaskResult } from "@penpot-mcp/common";
import { PluginTaskRequest, PluginTaskResult } from '@penpot-mcp/common';
import { randomUUID } from "crypto";
/**

View File

@ -1,31 +1,22 @@
/**
* Abstract base class for task handlers in the Penpot MCP plugin.
*
* @template TParams - The type of parameters this handler expects
* Represents a task received from the MCP server in the Penpot MCP plugin
*/
export abstract class TaskHandler<TParams = any> {
/** The task identifier this handler is responsible for */
abstract readonly task: string;
export class Task<TParams = any> {
/**
* Checks if this handler can process the given task.
*
* @param task - The task identifier to check
* @returns True if this handler applies to the given task
* @param requestId Unique identifier for the task request
* @param taskType The type of the task to execute
* @param params Task parameters/arguments
*/
applies(task: string): boolean {
return this.task === task;
}
constructor(public requestId: string, public taskType: string, public params: TParams) {}
/**
* Sends a task response back to the MCP server.
*/
public static sendTaskResponse(taskId: string, success: boolean, data: any = undefined, error: any = undefined): void {
protected sendResponse(success: boolean, data: any = undefined, error: any = undefined): void {
const response = {
type: "task-response",
response: {
id: taskId,
id: this.requestId,
success: success,
data: data,
error: error,
@ -37,19 +28,38 @@ export abstract class TaskHandler<TParams = any> {
console.log("Sent task response:", response);
}
public static sendTaskSuccess(taskId: string, data: any = undefined): void {
this.sendTaskResponse(taskId, true, data);
public sendSuccess(data: any = undefined): void {
this.sendResponse(true, data);
}
public static sendTaskError(taskId: string, error: string): void {
this.sendTaskResponse(taskId, false, undefined, error);
public sendError(error: string): void {
this.sendResponse(false, undefined, error);
}
}
/**
* Abstract base class for task handlers in the Penpot MCP plugin.
*
* @template TParams - The type of parameters this handler expects
*/
export abstract class TaskHandler<TParams = any> {
/** The task type this handler is responsible for */
abstract readonly taskType: string;
/**
* Checks if this handler can process the given task.
*
* @param task - The task identifier to check
* @returns True if this handler applies to the given task
*/
isApplicableTo(task: Task): boolean {
return this.taskType === task.taskType;
}
/**
* Handles the task with the provided parameters.
*
* @param taskId - The unique ID of the task request
* @param params - The parameters for the task
* @param task - The task to be handled
*/
abstract handle(taskId: string, params: TParams): void;
abstract handle(task: Task<TParams>): void;
}

View File

@ -1,5 +1,5 @@
import {PrintTextTaskHandler} from "./task-handlers/PrintTextTaskHandler";
import {TaskHandler} from "./TaskHandler";
import {Task, TaskHandler} from "./TaskHandler";
/**
* Registry of all available task handlers.
@ -40,22 +40,25 @@ penpot.ui.onMessage<string | { id: string; task: string; params: any }>((message
*/
function handlePluginTaskRequest(request: { id: string; task: string; params: any }): void {
console.log("Executing plugin task:", request.task, request.params);
const task = new Task(request.id, request.task, request.params);
// Find the appropriate handler
const handler = taskHandlers.find(h => h.applies(request.task));
const handler = taskHandlers.find(h => h.isApplicableTo(task));
if (handler) {
try {
// Cast the params to the expected type and handle the task
handler.handle(request.id, request.params);
console.log("Processing task with handler:", handler);
handler.handle(task);
console.log("Task handled successfully:", task);
} catch (error) {
console.error(`Error handling task '${request.task}':`, error);
console.error("Error creating text:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error";
TaskHandler.sendTaskError(request.id, `Error handling task: ${errorMessage}`);
task.sendError(`Error handling task: ${errorMessage}`);
}
} else {
console.warn("Unknown plugin task:", request.task);
TaskHandler.sendTaskError(request.id, `Unknown task type: ${request.task}`);
task.sendError(`Unknown task type: ${request.task}`);
}
}

View File

@ -1,46 +1,31 @@
import {TaskHandler} from "../TaskHandler";
import {Task, TaskHandler} from "../TaskHandler";
import {PrintTextTaskParams} from "../../../common/src";
/**
* Task handler for printing text to Penpot.
*/
export class PrintTextTaskHandler extends TaskHandler<PrintTextTaskParams> {
readonly task = "printText";
readonly taskType = "printText";
/**
* Handles the printText task by creating text in Penpot.
*
* @param taskId - The unique ID of the task request
* @param params - The parameters containing the text to create
*/
handle(taskId: string, params: PrintTextTaskParams): void {
if (!params.text) {
console.error("printText task requires 'text' parameter");
TaskHandler.sendTaskError(taskId, "printText task requires 'text' parameter");
return;
handle(task: Task<PrintTextTaskParams>): void {
if (!task.params.text) {
throw new Error("printText task requires 'text' parameter");
}
try {
const text = penpot.createText(params.text);
const text = penpot.createText(task.params.text);
if (text) {
// Center the text in the viewport
text.x = penpot.viewport.center.x;
text.y = penpot.viewport.center.y;
if (text) {
// Center the text in the viewport
text.x = penpot.viewport.center.x;
text.y = penpot.viewport.center.y;
// Select the newly created text
penpot.selection = [text];
// Select the newly created text
penpot.selection = [text];
console.log("Successfully created text:", params.text);
TaskHandler.sendTaskSuccess(taskId, { textId: text.id });
} else {
console.error("Failed to create text element");
TaskHandler.sendTaskError(taskId, "Failed to create text element");
}
} catch (error) {
console.error("Error creating text:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error";
TaskHandler.sendTaskError(taskId, `Error creating text: ${errorMessage}`);
console.log("Successfully created text:", task.params.text);
task.sendSuccess({ textId: text.id });
} else {
throw new Error("Failed to create text element");
}
}
}