Añadido imagenes en records nuevos
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { z } from "zod";
|
||||
import { withAuth, getSessionCredentials } from "../../auth/index.js";
|
||||
import { handleToolError, validateRequired, handleApiResponse } from "../helpers/errorHandler.js";
|
||||
@@ -19,6 +21,81 @@ async function mcpPost(target, actionWs, payload, token, tokenHash) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Si imageUrl apunta a un host local (localhost, 127.0.0.1, acai-app),
|
||||
* descarga el archivo y lo retorna como base64 para incluirlo en el payload.
|
||||
* Esto permite subir imagenes locales a un servidor remoto (modo produccion),
|
||||
* ya que el servidor remoto no tiene acceso a nuestras URLs locales.
|
||||
*
|
||||
* @param {string} imageUrl
|
||||
* @returns {Promise<{fileBase64: string, fileName: string} | null>}
|
||||
* null si la URL no es local (usar imageUrl directamente)
|
||||
*/
|
||||
async function resolveLocalImageAsBase64(imageUrl) {
|
||||
const LOCAL_HOSTS = ["localhost", "127.0.0.1", "acai-app", "host.docker.internal"];
|
||||
|
||||
// Caso 1: Path absoluto del filesystem (e.g. /opt/acai/webs/.../cms/uploads/x.jpg)
|
||||
if (typeof imageUrl === "string" && imageUrl.startsWith("/") && !imageUrl.startsWith("//")) {
|
||||
try {
|
||||
if (fs.existsSync(imageUrl) && fs.statSync(imageUrl).isFile()) {
|
||||
const buffer = fs.readFileSync(imageUrl);
|
||||
return {
|
||||
fileBase64: buffer.toString("base64"),
|
||||
fileName: path.basename(imageUrl),
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[upload] Failed to read filesystem path ${imageUrl}: ${error.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Caso 2: URL HTTP — verificar si es local
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(imageUrl);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!LOCAL_HOSTS.includes(parsed.hostname)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Intento A: descargar via HTTP (funciona cuando el host local es alcanzable)
|
||||
try {
|
||||
const axios = (await import("axios")).default;
|
||||
const response = await axios.get(imageUrl, {
|
||||
responseType: "arraybuffer",
|
||||
timeout: 30000,
|
||||
});
|
||||
const fileBase64 = Buffer.from(response.data).toString("base64");
|
||||
const pathname = parsed.pathname || "/image.jpg";
|
||||
const fileName = pathname.split("/").pop() || "image.jpg";
|
||||
return { fileBase64, fileName };
|
||||
} catch (httpError) {
|
||||
console.error(`[upload] HTTP fetch failed for ${imageUrl}: ${httpError.message}. Trying filesystem fallback.`);
|
||||
}
|
||||
|
||||
// Intento B: resolver el pathname contra ACAI_PROJECT_DIR y leer del disco
|
||||
const projectDir = process.env.ACAI_PROJECT_DIR || "";
|
||||
if (projectDir && parsed.pathname) {
|
||||
try {
|
||||
const localPath = path.join(projectDir, parsed.pathname);
|
||||
if (fs.existsSync(localPath) && fs.statSync(localPath).isFile()) {
|
||||
const buffer = fs.readFileSync(localPath);
|
||||
return {
|
||||
fileBase64: buffer.toString("base64"),
|
||||
fileName: path.basename(localPath),
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[upload] Filesystem fallback failed for ${imageUrl}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function registerUploadRecordImageTool(server) {
|
||||
server.tool(
|
||||
"upload_record_image",
|
||||
@@ -42,11 +119,17 @@ export function registerUploadRecordImageTool(server) {
|
||||
|
||||
const credentials = await getSessionCredentials(extra.sessionId);
|
||||
|
||||
// Upload via mcp_respond.php uploadRecordImage (sends imageUrl, PHP downloads it)
|
||||
// Si la URL es local, descargar y enviar base64 (el servidor remoto no
|
||||
// puede descargarla en modo produccion).
|
||||
const localFile = await resolveLocalImageAsBase64(imageUrl);
|
||||
const uploadPayload = localFile
|
||||
? { tableName, recordId, fieldName, alt, fileBase64: localFile.fileBase64, fileName: localFile.fileName }
|
||||
: { tableName, recordId, fieldName, imageUrl, alt };
|
||||
|
||||
const response = await mcpPost(
|
||||
credentials,
|
||||
"uploadRecordImage",
|
||||
{ tableName, recordId, fieldName, imageUrl, alt },
|
||||
uploadPayload,
|
||||
credentials.token,
|
||||
credentials.tokenHash
|
||||
);
|
||||
@@ -170,10 +253,17 @@ export function registerUploadRecordImageTool(server) {
|
||||
);
|
||||
|
||||
// Step 2: Upload new image
|
||||
// Si la URL es local, descargar y enviar base64 (el servidor remoto no
|
||||
// puede descargarla en modo produccion).
|
||||
const localFile = await resolveLocalImageAsBase64(imageUrl);
|
||||
const uploadPayload = localFile
|
||||
? { tableName, recordId, fieldName, alt, fileBase64: localFile.fileBase64, fileName: localFile.fileName }
|
||||
: { tableName, recordId, fieldName, imageUrl, alt };
|
||||
|
||||
const response = await mcpPost(
|
||||
credentials,
|
||||
"uploadRecordImage",
|
||||
{ tableName, recordId, fieldName, imageUrl, alt },
|
||||
uploadPayload,
|
||||
credentials.token,
|
||||
credentials.tokenHash
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user