mcp_bridge_fixed.jsxā¢14.8 kB
// mcp_bridge_fixed.jsx
// Fixed version for reliable file-based command handling
(function mcpBridge() {
// ===== JSON2 Polyfill =====
if(typeof JSON!=='object'){JSON={};}
(function(){'use strict';function f(n){return n<10?'0'+n:n;}
if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf();};}
var cx,escapable,gap,indent,meta,rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==='string'){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
if(typeof JSON.stringify!=='function'){escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});};}
if(typeof JSON.parse!=='function'){cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');};}}());
// ===== END JSON2 Polyfill =====
// --- State & Config ---
var lastModifiedLayer = null;
var config = {
commandFile: "C:/ae_temp/command.json",
resultFile: "C:/ae_temp/result.json",
checkInterval: 1000, // Check for commands every second
maxRetries: 3
};
// --- UI Panel ---
var panel = this instanceof Panel ? this : new Window("palette", "MCP Bridge Fixed", undefined, { resizeable: true });
panel.orientation = "column";
panel.alignChildren = ["fill", "top"];
// Add connection status group
var statusGroup = panel.add("group");
statusGroup.orientation = "row";
var statusText = statusGroup.add("statictext", undefined, "Status: File Mode");
// Add control buttons
var buttonGroup = panel.add("group");
buttonGroup.orientation = "row";
var checkButton = buttonGroup.add("button", undefined, "Check for Command");
var autoCheckBox = buttonGroup.add("checkbox", undefined, "Auto-Check");
var logBox = panel.add("edittext", [0, 0, 400, 200], "", { multiline: true, readonly: true, scrollable: true });
function log(message) {
var timestamp = new Date().toLocaleTimeString();
logBox.text = timestamp + ": " + message + "\n" + logBox.text;
}
function updateStatus(message) {
statusText.text = "Status: " + message;
}
function writeResult(resultData) {
try {
var resultFile = new File(config.resultFile);
resultFile.open("w");
resultFile.write(JSON.stringify(resultData, null, 2));
resultFile.close();
log("Result file written successfully.");
return true;
} catch (e) {
log("FATAL: Could not write result file: " + e.toString());
return false;
}
}
// --- Action Functions ---
function getActiveComp() {
var comp = app.project.activeItem;
if (!comp || !(comp instanceof CompItem)) {
for (var i = 1; i <= app.project.numItems; i++) {
if (app.project.item(i) instanceof CompItem) {
comp = app.project.item(i);
return comp;
}
}
}
if (!comp) {
log("No comp found. Creating a default one.");
comp = app.project.items.addComp("Default Composition", 1920, 1080, 1.0, 10, 30);
lastModifiedLayer = null;
}
return comp;
}
function createComposition(params) {
app.beginUndoGroup("MCP: Create Composition");
var compName = params.name || "New Composition";
var duration = parseFloat(params.duration) || 10;
var newComp = app.project.items.addComp(compName, 1920, 1080, 1.0, duration, 30);
lastModifiedLayer = null;
app.endUndoGroup();
return { status: "success", message: "Composition '" + compName + "' created." };
}
function hexToRgb(hex) {
try {
// Remove # if present
hex = hex.replace('#', '');
// Handle named colors
var namedColors = {
'red': 'FF0000',
'green': '00FF00',
'blue': '0000FF',
'yellow': 'FFFF00',
'purple': '800080',
'orange': 'FFA500',
'black': '000000',
'white': 'FFFFFF',
'gray': '808080',
'grey': '808080'
};
if (namedColors[hex.toLowerCase()]) {
hex = namedColors[hex.toLowerCase()];
}
// Parse hex values
var r = parseInt(hex.substring(0, 2), 16) / 255;
var g = parseInt(hex.substring(2, 4), 16) / 255;
var b = parseInt(hex.substring(4, 6), 16) / 255;
// Validate values
if (isNaN(r) || isNaN(g) || isNaN(b)) {
throw new Error("Invalid color values");
}
return [r, g, b];
} catch (e) {
log("Color conversion error: " + e.toString());
// Return default white color if conversion fails
return [1, 1, 1];
}
}
function createTextLayer(params) {
app.beginUndoGroup("MCP: Create Text Layer");
try {
var comp = getActiveComp();
var textLayer = comp.layers.addText(params.text || "New Text");
var textProp = textLayer.property("Source Text");
var textDocument = textProp.value;
// Set font size if provided
if (params.fontSize) {
textDocument.fontSize = parseFloat(params.fontSize);
}
// Set font color if provided
if (params.color) {
var rgbColor = hexToRgb(params.color);
textDocument.fillColor = rgbColor;
}
// Set font family if provided
if (params.fontFamily) {
textDocument.font = params.fontFamily;
}
// Apply the changes
textProp.setValue(textDocument);
// Set position if provided
if (params.position && params.position.length === 2) {
textLayer.position.setValue([params.position[0], params.position[1], 0]);
} else {
// Center the text by default
textLayer.position.setValue([comp.width / 2, comp.height / 2, 0]);
}
lastModifiedLayer = textLayer;
app.endUndoGroup();
return { status: "success", message: "Text layer created successfully", layerName: textLayer.name };
} catch (e) {
app.endUndoGroup();
return { status: "error", message: "Error creating text layer: " + e.toString() };
}
}
function runCustomCode(params) {
app.beginUndoGroup("MCP: Run Custom Code");
try {
// Safer approach: validate and print code first
var code = params.code;
if (!code || typeof code !== "string") {
throw new Error("Invalid or empty code provided");
}
log("Executing custom code: " + code.substring(0, 100) + "...");
// Create a new Function from the code string and execute it
var customFunction = new Function("app", "comp", "project", code);
// Get the active composition for context
var comp = getActiveComp();
// Execute the function with After Effects context
var result = customFunction(app, comp, app.project);
app.endUndoGroup();
return {
status: "success",
message: "Custom code executed successfully",
result: result ? result.toString() : "No result returned"
};
} catch (e) {
app.endUndoGroup();
return {
status: "error",
message: "Error executing custom code: " + e.toString()
};
}
}
function processCommand(commandData) {
try {
if (!commandData || !commandData.action) {
return { status: "error", message: "Invalid command data or missing action" };
}
var action = commandData.action;
var params = commandData.params || {};
log("Processing action: " + action);
switch (action) {
case "create_text_layer":
return createTextLayer(params);
case "create_composition":
return createComposition(params);
case "run_custom_code":
return runCustomCode(params);
default:
return { status: "error", message: "Unknown action: " + action };
}
} catch (e) {
return { status: "error", message: "Error processing command: " + e.toString() };
}
}
function checkForCommand() {
log("Manual check triggered.");
var commandFile = new File(config.commandFile);
if (commandFile.exists) {
log("Command file found!");
try {
$.sleep(200); // Wait 200ms to ensure file is fully written
commandFile.open("r");
var content = commandFile.read();
commandFile.close();
// Remove the command file
commandFile.remove();
try {
var commandData = JSON.parse(content);
log("Action: " + commandData.action);
var result = processCommand(commandData);
writeResult(result);
} catch (jsonError) {
log("Error parsing command JSON: " + jsonError.toString());
writeResult({
status: "error",
message: "Error parsing command JSON: " + jsonError.toString()
});
}
} catch (e) {
log("Error during manual check: " + e.toString());
writeResult({
status: "error",
message: "Error during manual check: " + e.toString()
});
}
} else {
log("No command file found.");
}
}
// Set up auto-check timer
var timer = null;
function startAutoCheck() {
if (timer) {
timer.stop();
}
timer = app.scheduleTask('checkForCommand()', config.checkInterval, true);
log("Auto-check started. Checking every " + (config.checkInterval / 1000) + " seconds.");
}
function stopAutoCheck() {
if (timer) {
timer.stop();
timer = null;
log("Auto-check stopped.");
}
}
// Set up UI event handlers
checkButton.onClick = checkForCommand;
autoCheckBox.onClick = function() {
if (autoCheckBox.value) {
startAutoCheck();
} else {
stopAutoCheck();
}
};
// Initialize
try {
// Create temp directory if it doesn't exist
var tempFolder = new Folder("C:/ae_temp");
if (!tempFolder.exists) {
tempFolder.create();
}
updateStatus("File Mode Active");
log("Running in file mode. Click 'Check for Command' to process commands.");
} catch (e) {
log("Initialization error: " + e.toString());
updateStatus("Error");
}
panel.onResizing = panel.onResize = function() { this.layout.resize(); };
if (panel instanceof Window) { panel.center(); panel.show(); }
})();