From b107283cbd36718d4e9920448d95b1e40f72b272 Mon Sep 17 00:00:00 2001 From: SapientialM Date: Sat, 2 Aug 2025 18:24:04 +0800 Subject: [PATCH 1/2] feat(jmanus): 1. Added a PPT generation tool and its corresponding Agent, currently only supporting the generation of PPTs with text and titles without applying templates. 2. Fixed the issue of incorrect TOOL_TOOL definition in the tool prompt. --- spring-ai-alibaba-jmanus/pom.xml | 17 ++ .../dynamic/agent/model/enums/AgentEnum.java | 3 +- .../prompt/model/enums/PromptEnum.java | 16 +- .../manus/planning/PlanningFactory.java | 5 + .../pptGenerator/IPptGeneratorService.java | 92 ++++++ .../pptGenerator/PptGeneratorOperator.java | 153 ++++++++++ .../pptGenerator/PptGeneratorService.java | 263 ++++++++++++++++++ .../manus/tool/pptGenerator/PptInput.java | 112 ++++++++ .../prompts/en/descriptions.properties | 6 +- ...pt-generator-operator-tool-description.txt | 12 + ...ppt-generator-operator-tool-parameters.txt | 50 ++++ .../en/ppt_generator_agent/agent-config.yml | 27 ++ .../zh/ppt_generator_agent/agent-config.yml | 27 ++ .../prompts/zh/descriptions.properties | 6 +- ...pt-generator-operator-tool-description.txt | 12 + ...ppt-generator-operator-tool-parameters.txt | 50 ++++ 16 files changed, 841 insertions(+), 10 deletions(-) create mode 100644 spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/IPptGeneratorService.java create mode 100644 spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorOperator.java create mode 100644 spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorService.java create mode 100644 spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptInput.java create mode 100644 spring-ai-alibaba-jmanus/src/main/resources/prompts/en/tool/ppt-generator-operator-tool-description.txt create mode 100644 spring-ai-alibaba-jmanus/src/main/resources/prompts/en/tool/ppt-generator-operator-tool-parameters.txt create mode 100644 spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/en/ppt_generator_agent/agent-config.yml create mode 100644 spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/zh/ppt_generator_agent/agent-config.yml create mode 100644 spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/tool/ppt-generator-operator-tool-description.txt create mode 100644 spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/tool/ppt-generator-operator-tool-parameters.txt diff --git a/spring-ai-alibaba-jmanus/pom.xml b/spring-ai-alibaba-jmanus/pom.xml index 3d0cf204e9..902e39a92e 100644 --- a/spring-ai-alibaba-jmanus/pom.xml +++ b/spring-ai-alibaba-jmanus/pom.xml @@ -255,6 +255,23 @@ mockito-junit-jupiter test + + + + org.apache.poi + poi-ooxml + 5.2.3 + + + org.apache.poi + poi + 5.2.3 + + + commons-io + commons-io + 2.15.1 + diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/dynamic/agent/model/enums/AgentEnum.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/dynamic/agent/model/enums/AgentEnum.java index c8c6d64123..e0692dcaaa 100644 --- a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/dynamic/agent/model/enums/AgentEnum.java +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/dynamic/agent/model/enums/AgentEnum.java @@ -26,7 +26,8 @@ public enum AgentEnum { MAPREDUCE_DATA_PREPARE_AGENT("MAPREDUCE_DATA_PREPARE_AGENT", "mapreduce_data_prepare_agent"), MAPREDUCE_FIN_AGENT("MAPREDUCE_FIN_AGENT", "mapreduce_fin_agent"), MAPREDUCE_MAP_TASK_AGENT("MAPREDUCE_MAP_TASK_AGENT", "mapreduce_map_task_agent"), - MAPREDUCE_REDUCE_TASK_AGENT("MAPREDUCE_REDUCE_TASK_AGENT", "mapreduce_reduce_task_agent"); + MAPREDUCE_REDUCE_TASK_AGENT("MAPREDUCE_REDUCE_TASK_AGENT", "mapreduce_reduce_task_agent"), + PPT_GENERATOR_AGENT("PPT_GENERATOR_AGENT", "ppt_generator_agent"); private String agentName; diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/dynamic/prompt/model/enums/PromptEnum.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/dynamic/prompt/model/enums/PromptEnum.java index 5340853e77..64c5c57b38 100644 --- a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/dynamic/prompt/model/enums/PromptEnum.java +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/dynamic/prompt/model/enums/PromptEnum.java @@ -94,10 +94,10 @@ public enum PromptEnum { "tool/doc-loader-tool-parameters.txt"), // File Merge Tool - FILE_MERGE_TOOL_DESCRIPTION("FILE_MERGE_TOOL_DESCRIPTION", MessageType.SYSTEM, PromptType.TOOL_DESCRIPTION, true, - "tool/file-merge-tool-description.txt"), - FILE_MERGE_TOOL_PARAMETERS("FILE_MERGE_TOOL_PARAMETERS", MessageType.SYSTEM, PromptType.TOOL_PARAMETER, true, - "tool/file-merge-tool-parameters.txt"), + FILE_MERGE_TOOL_TOOL_DESCRIPTION("FILE_MERGE_TOOL_TOOL_DESCRIPTION", MessageType.SYSTEM, + PromptType.TOOL_DESCRIPTION, true, "tool/file-merge-tool-description.txt"), + FILE_MERGE_TOOL_TOOL_PARAMETERS("FILE_MERGE_TOOL_TOOL_PARAMETERS", MessageType.SYSTEM, PromptType.TOOL_PARAMETER, + true, "tool/file-merge-tool-parameters.txt"), // Data Split Tool DATA_SPLIT_TOOL_DESCRIPTION("DATA_SPLIT_TOOL_DESCRIPTION", MessageType.SYSTEM, PromptType.TOOL_DESCRIPTION, true, @@ -127,7 +127,13 @@ public enum PromptEnum { TERMINATE_TOOL_DESCRIPTION("TERMINATE_TOOL_DESCRIPTION", MessageType.SYSTEM, PromptType.TOOL_DESCRIPTION, true, "tool/terminate-tool-description.txt"), TERMINATE_TOOL_PARAMETERS("TERMINATE_TOOL_PARAMETERS", MessageType.SYSTEM, PromptType.TOOL_PARAMETER, true, - "tool/terminate-tool-parameters.txt"); + "tool/terminate-tool-parameters.txt"), + + // PPT Generator Tool + PPTGENERATOROPERATOR_TOOL_DESCRIPTION("PPTGENERATOROPERATOR_TOOL_DESCRIPTION", MessageType.SYSTEM, + PromptType.TOOL_DESCRIPTION, true, "tool/ppt-generator-operator-tool-description.txt"), + PPTGENERATOROPERATOR_TOOL_PARAMETERS("PPTGENERATOROPERATOR_TOOL_PARAMETERS", MessageType.SYSTEM, + PromptType.TOOL_PARAMETER, true, "tool/ppt-generator-operator-tool-parameters.txt"); private String promptName; diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/planning/PlanningFactory.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/planning/PlanningFactory.java index 334c55d2cd..62bc86aba2 100644 --- a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/planning/PlanningFactory.java +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/planning/PlanningFactory.java @@ -83,6 +83,7 @@ import com.alibaba.cloud.ai.example.manus.tool.mapreduce.ReduceOperationTool; import com.alibaba.cloud.ai.example.manus.tool.textOperator.TextFileOperator; import com.alibaba.cloud.ai.example.manus.tool.textOperator.TextFileService; +import com.alibaba.cloud.ai.example.manus.tool.pptGenerator.PptGeneratorOperator; import com.alibaba.cloud.ai.example.manus.workflow.SummaryWorkflow; import com.fasterxml.jackson.databind.ObjectMapper; @@ -150,6 +151,9 @@ public class PlanningFactory implements IPlanningFactory { @Lazy private CronService cronService; + @Autowired + private PptGeneratorOperator pptGeneratorOperator; + public PlanningFactory(ChromeDriverService chromeDriverService, PlanExecutionRecorder recorder, ManusProperties manusProperties, TextFileService textFileService, McpService mcpService, SmartContentSavingService innerStorageService, UnifiedDirectoryManager unifiedDirectoryManager, @@ -241,6 +245,7 @@ public Map toolCallbackMap(String planId, String ro unifiedDirectoryManager, terminateColumns)); toolDefinitions.add(new FinalizeTool(planId, manusProperties, sharedStateManager, unifiedDirectoryManager)); toolDefinitions.add(new CronTool(cronService, objectMapper, toolPromptManager)); + toolDefinitions.add(pptGeneratorOperator); List functionCallbacks = mcpService.getFunctionCallbacks(planId); for (McpServiceEntity toolCallback : functionCallbacks) { diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/IPptGeneratorService.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/IPptGeneratorService.java new file mode 100644 index 0000000000..54274f6961 --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/IPptGeneratorService.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.example.manus.tool.pptGenerator; + +import java.io.IOException; + +import com.alibaba.cloud.ai.example.manus.config.ManusProperties; +import com.alibaba.cloud.ai.example.manus.tool.textOperator.FileState; + +/** + * PPT generator service interface, providing PPT file operation management functions + */ +public interface IPptGeneratorService { + + /** + * Get the file state + * @param planId Plan ID + * @return File state + */ + FileState getFileState(String planId); + + /** + * Update the file state + * @param planId Plan ID + * @param filePath File path + * @param operationResult Operation result + */ + void updateFileState(String planId, String filePath, String operationResult); + + /** + * Get the current file path + * @param planId Plan ID + * @return Current file path + */ + String getCurrentFilePath(String planId); + + /** + * Get the last operation result + * @param planId Plan ID + * @return Last operation result + */ + String getLastOperationResult(String planId); + + /** + * Validate PPT file path + * @param planId Plan ID + * @param filePath File path + * @return Validated absolute path + * @throws IOException IO exception + */ + String validatePptFilePath(String planId, String filePath) throws IOException; + + /** + * Check if the file type is supported + * @param filePath File path + * @return True if supported, false otherwise + */ + boolean isSupportedPptFileType(String filePath); + + /** + * Get the file extension + * @param filePath File path + * @return File extension + */ + String getFileExtension(String filePath); + + /** + * Clean up plan resources + * @param planId Plan ID + */ + void cleanupForPlan(String planId); + + /** + * Get the Manus configuration properties + * @return Manus configuration properties + */ + ManusProperties getManusProperties(); + +} diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorOperator.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorOperator.java new file mode 100644 index 0000000000..c655409923 --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorOperator.java @@ -0,0 +1,153 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.example.manus.tool.pptGenerator; + +import com.alibaba.cloud.ai.example.manus.tool.AbstractBaseTool; +import com.alibaba.cloud.ai.example.manus.tool.ToolPromptManager; +import com.alibaba.cloud.ai.example.manus.tool.code.ToolExecuteResult; +import com.alibaba.cloud.ai.example.manus.tool.filesystem.UnifiedDirectoryManager; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class PptGeneratorOperator extends AbstractBaseTool { + + private static final Logger log = LoggerFactory.getLogger(PptGeneratorOperator.class); + + private final PptGeneratorService pptGeneratorService; + + private final ObjectMapper objectMapper; + + private final ToolPromptManager toolPromptManager; + + private final UnifiedDirectoryManager unifiedDirectoryManager; + + private static final String TOOL_NAME = "ppt_generator_operator"; + + public PptGeneratorOperator(PptGeneratorService pptGeneratorService, ObjectMapper objectMapper, + ToolPromptManager toolPromptManager, UnifiedDirectoryManager unifiedDirectoryManager) { + this.pptGeneratorService = pptGeneratorService; + this.objectMapper = objectMapper; + this.toolPromptManager = toolPromptManager; + this.unifiedDirectoryManager = unifiedDirectoryManager; + } + + /** + * Run the tool (accepts JSON string input) + */ + @Override + public ToolExecuteResult run(PptInput input) { + log.info("PptGeneratorOperator input: action={}, outputPath={}, title={}", input.getAction(), + input.getOutputPath(), input.getTitle()); + try { + String planId = this.currentPlanId; + + if (!"create".equals(input.getAction())) { + pptGeneratorService.updateFileState(planId, input.getOutputPath(), + "Error: Unsupported operations: " + input.getAction()); + return new ToolExecuteResult( + "Unsupported operations: " + input.getAction() + ",Only supports the 'create' operation"); + } + + // Validate the path. This will throw an exception if the path is invalid. + String resultPath = validateAndProcessPath(planId, input.getOutputPath()); + input.setOutputPath(resultPath); + + // Update the file state to processing. + pptGeneratorService.updateFileState(planId, resultPath, "Processing: 正在生成PPT文件"); + + String path = pptGeneratorService.createPpt(input); + + // Update the file state to success. + pptGeneratorService.updateFileState(planId, path, "Success: PPT file generated successfully"); + + return new ToolExecuteResult("PPT file generated successfully, save path: " + path); + } + catch (IllegalArgumentException e) { + String planId = this.currentPlanId; + pptGeneratorService.updateFileState(planId, input.getOutputPath(), + "Error: Parameter validation failed: " + e.getMessage()); + return new ToolExecuteResult("Parameter validation failed: " + e.getMessage()); + } + catch (Exception e) { + log.error("PPT generation failed", e); + String planId = this.currentPlanId; + pptGeneratorService.updateFileState(planId, input.getOutputPath(), + "Error: PPT generation failed: " + e.getMessage()); + return new ToolExecuteResult("PPT generation failed: " + e.getMessage()); + } + } + + /** + * Validate and process the output path. + * @param planId Plan ID + * @param outputPath Output path + * @return Processed absolute path + * @throws IOException IO exception + */ + private String validateAndProcessPath(String planId, String outputPath) throws IOException { + // Even if the path is empty, call the validation method, which will throw an + // IllegalArgumentException. + return pptGeneratorService.validatePptFilePath(planId, outputPath); + } + + @Override + public String getName() { + return TOOL_NAME; + } + + @Override + public String getDescription() { + return toolPromptManager.getToolDescription("pptGeneratorOperator"); + } + + @Override + public String getParameters() { + return toolPromptManager.getToolParameters("pptGeneratorOperator"); + } + + @Override + public Class getInputType() { + return PptInput.class; + } + + @Override + public String getServiceGroup() { + return "default-service-group"; + } + + @Override + public void cleanup(String planId) { + // Clean up file state. + pptGeneratorService.cleanupForPlan(planId); + log.info("Cleaning up PPT generator resources for plan: {}", planId); + } + + @Override + public String getCurrentToolStateString() { + String planId = this.currentPlanId; + if (planId != null) { + return String.format("PPT Generator - Current File: %s, Last Operation: %s", + pptGeneratorService.getCurrentFilePath(planId), pptGeneratorService.getLastOperationResult(planId)); + } + return "PPT Generator is ready"; + } + +} diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorService.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorService.java new file mode 100644 index 0000000000..41ed70168f --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorService.java @@ -0,0 +1,263 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.example.manus.tool.pptGenerator; + +import com.alibaba.cloud.ai.example.manus.tool.pptGenerator.PptInput.SlideContent; +import com.alibaba.cloud.ai.example.manus.config.ManusProperties; +import com.alibaba.cloud.ai.example.manus.tool.filesystem.UnifiedDirectoryManager; +import com.alibaba.cloud.ai.example.manus.tool.textOperator.FileState; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.xslf.usermodel.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.awt.Rectangle; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class PptGeneratorService implements IPptGeneratorService { + + private static final Logger log = LoggerFactory.getLogger(PptGeneratorService.class); + + @Autowired + private ManusProperties manusProperties; + + @Autowired + private UnifiedDirectoryManager unifiedDirectoryManager; + + // File state management. + private final ConcurrentHashMap fileStates = new ConcurrentHashMap<>(); + + // Supported PPT file extensions. + private static final Set SUPPORTED_EXTENSIONS = new HashSet<>(Set.of(".pptx", ".ppt")); + + /** + * Create a PPT file. + * @param pptInput PPT input parameters. + * @return Path of the generated PPT file. + * @throws IOException IO exception. + */ + public String createPpt(PptInput pptInput) throws IOException { + // Validate input. + if (pptInput == null) { + throw new IllegalArgumentException("PPT input cannot be null"); + } + if (StringUtils.isBlank(pptInput.getOutputPath())) { + throw new IllegalArgumentException("Output path cannot be blank"); + } + if (StringUtils.isBlank(pptInput.getTitle())) { + throw new IllegalArgumentException("Title cannot be blank"); + } + + // Create PPT document. + XMLSlideShow presentation = new XMLSlideShow(); + + try { + // Create title slide. + XSLFSlideMaster titleMaster = presentation.getSlideMasters().get(0); + XSLFSlideLayout titleLayout = titleMaster.getLayout(SlideLayout.TITLE); + if (titleLayout == null) { + log.error("PPT template is missing the required layout (TITLE)"); + throw new IllegalStateException("PPT template is missing the required layout (TITLE)"); + } + XSLFSlide titleSlide = presentation.createSlide(titleLayout); + + // Set title and subtitle. + XSLFTextShape titleShape = titleSlide.getPlaceholder(0); + if (titleShape != null) { + titleShape.setText(pptInput.getTitle()); + } + + XSLFTextShape subtitleShape = titleSlide.getPlaceholder(1); + if (subtitleShape != null && StringUtils.isNotBlank(pptInput.getSubtitle())) { + subtitleShape.setText(pptInput.getSubtitle()); + } + + // Create content slide. + XSLFSlideLayout contentLayout = titleMaster.getLayout(SlideLayout.TITLE_AND_CONTENT); + if (contentLayout == null) { + log.error("PPT template is missing the required layout (TITLE_AND_CONTENT)"); + throw new IllegalStateException("PPT template is missing the required layout (TITLE_AND_CONTENT)"); + } + + List slideContents = pptInput.getSlideContents(); + if (slideContents != null && !slideContents.isEmpty()) { + for (int i = 0; i < slideContents.size(); i++) { + SlideContent slideContent = slideContents.get(i); + if (slideContent == null) { + continue; + } + + // Validate slide content. + if (StringUtils.isBlank(slideContent.getTitle()) + && StringUtils.isBlank(slideContent.getContent())) { + log.warn("Skip empty slide content, index: {}", i); + continue; + } + + XSLFSlide contentSlide = presentation.createSlide(contentLayout); + + // Set slide title. + XSLFTextShape contentTitle = contentSlide.getPlaceholder(0); + if (contentTitle != null && StringUtils.isNotBlank(slideContent.getTitle())) { + contentTitle.setText(slideContent.getTitle()); + } + + // Set slide content. + XSLFTextShape contentBody = contentSlide.getPlaceholder(1); + if (contentBody != null && StringUtils.isNotBlank(slideContent.getContent())) { + contentBody.setText(slideContent.getContent()); + } + + // Insert image (if specified). + if (StringUtils.isNotBlank(slideContent.getImagePath())) { + File imageFile = new File(slideContent.getImagePath()); + if (imageFile.exists()) { + try (FileInputStream fis = new FileInputStream(imageFile)) { + byte[] pictureData = IOUtils.toByteArray(fis); + XSLFPictureData picture = presentation.addPicture(pictureData, + XSLFPictureData.PictureType.JPEG); + XSLFPictureShape pictureShape = contentSlide.createPicture(picture); + // Set image position and size. + pictureShape.setAnchor(new Rectangle(50, 150, 400, 300)); + } + catch (IOException e) { + log.warn("Failed to load image: {}", imageFile.getAbsolutePath(), e); + } + } + else { + log.warn("Specified image file does not exist: {}", imageFile.getAbsolutePath()); + } + } + } + } + + // Save PPT file. + File outputFile = new File(pptInput.getOutputPath()); + try (FileOutputStream out = new FileOutputStream(outputFile)) { + presentation.write(out); + } + + log.info("PPT created successfully: {}", pptInput.getOutputPath()); + return pptInput.getOutputPath(); + } + finally { + // Ensure resources are properly released. + try { + presentation.close(); + } + catch (IOException e) { + log.warn("Failed to close PPT document", e); + } + } + } + + // PptGeneratorService interface implementation. + + @Override + public FileState getFileState(String planId) { + return fileStates.computeIfAbsent(planId, k -> new FileState()); + } + + @Override + public void updateFileState(String planId, String filePath, String operationResult) { + FileState fileState = getFileState(planId); + fileState.setCurrentFilePath(filePath); + fileState.setLastOperationResult(operationResult); + log.info("Updated PPT file state for plan {}: path={}, result={}", planId, filePath, operationResult); + } + + @Override + public String getCurrentFilePath(String planId) { + return getFileState(planId).getCurrentFilePath(); + } + + @Override + public String getLastOperationResult(String planId) { + return getFileState(planId).getLastOperationResult(); + } + + @Override + public String validatePptFilePath(String planId, String filePath) throws IOException { + // 1. Basic validation. + if (StringUtils.isBlank(filePath)) { + throw new IllegalArgumentException("File path cannot be blank"); + } + + // 2. File type validation. + if (!isSupportedPptFileType(filePath)) { + throw new IllegalArgumentException("Unsupported file type: " + getFileExtension(filePath)); + } + + // 3. Path normalization. + Path requestedPath = Path.of(filePath).normalize(); + + // 4. Security validation - prevent path traversal attacks. + if (requestedPath.toString().contains("../") || requestedPath.isAbsolute()) { + throw new SecurityException("Illegal path: absolute path or parent directory reference is not allowed"); + } + + // 5. Limit output directory range. + Path baseDir = Path.of("extensions/pptGenerator").normalize(); + + // 6. Get the final absolute path. + Path absolutePath = unifiedDirectoryManager.getSpecifiedDirectory(baseDir.resolve(requestedPath).toString()); + + // 7. Ensure the directory exists. + Files.createDirectories(absolutePath.getParent()); + + return absolutePath.toString(); + } + + @Override + public boolean isSupportedPptFileType(String filePath) { + String fileExtension = getFileExtension(filePath); + return SUPPORTED_EXTENSIONS.contains(fileExtension.toLowerCase()); + } + + @Override + public String getFileExtension(String filePath) { + if (filePath == null || filePath.trim().isEmpty()) { + return ""; + } + int lastDotIndex = filePath.lastIndexOf('.'); + return lastDotIndex > 0 ? filePath.substring(lastDotIndex) : ""; + } + + @Override + public void cleanupForPlan(String planId) { + fileStates.remove(planId); + log.info("Cleaned up PPT generator file state for plan: {}", planId); + } + + @Override + public ManusProperties getManusProperties() { + return manusProperties; + } + +} diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptInput.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptInput.java new file mode 100644 index 0000000000..68de93128b --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptInput.java @@ -0,0 +1,112 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.example.manus.tool.pptGenerator; + +import java.util.List; + +public class PptInput { + + private String action; + + @com.fasterxml.jackson.annotation.JsonProperty("output_path") + private String outputPath; + + private String title; + + private String subtitle; + + @com.fasterxml.jackson.annotation.JsonProperty("slide_contents") + private List slideContents; + + public static class SlideContent { + + private String title; + + private String content; + + @com.fasterxml.jackson.annotation.JsonProperty("image_path") + private String imagePath; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getImagePath() { + return imagePath; + } + + public void setImagePath(String imagePath) { + this.imagePath = imagePath; + } + + } + + public PptInput() { + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getOutputPath() { + return outputPath; + } + + public void setOutputPath(String outputPath) { + this.outputPath = outputPath; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSubtitle() { + return subtitle; + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + public List getSlideContents() { + return slideContents; + } + + public void setSlideContents(List slideContents) { + this.slideContents = slideContents; + } + +} diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/descriptions.properties b/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/descriptions.properties index 3a5c35ce61..5c6eaa3669 100644 --- a/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/descriptions.properties +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/descriptions.properties @@ -30,8 +30,8 @@ INNER_STORAGE_CONTENT_TOOL_TOOL_DESCRIPTION=Description for internal storage con INNER_STORAGE_CONTENT_TOOL_TOOL_PARAMETERS=Parameter definition JSON for internal storage content tool DOC_LOADER_TOOL_DESCRIPTION=Description for document loader tool DOC_LOADER_TOOL_PARAMETERS=Parameter definition JSON for document loader tool -FILE_MERGE_TOOL_DESCRIPTION=Description for file merge tool -FILE_MERGE_TOOL_PARAMETERS=Parameter definition JSON for file merge tool +FILE_MERGE_TOOL_TOOL_DESCRIPTION=Description for file merge tool +FILE_MERGE_TOOL_TOOL_PARAMETERS=Parameter definition JSON for file merge tool DATA_SPLIT_TOOL_DESCRIPTION=Description for data split tool DATA_SPLIT_TOOL_PARAMETERS=Parameter definition JSON for data split tool MAP_OUTPUT_TOOL_DESCRIPTION=Description for map output tool @@ -42,3 +42,5 @@ FINALIZE_TOOL_DESCRIPTION=Description for finalize tool FINALIZE_TOOL_PARAMETERS=Parameter definition JSON for finalize tool TERMINATE_TOOL_DESCRIPTION=Description for terminate tool TERMINATE_TOOL_PARAMETERS=Parameter definition JSON for terminate tool +PPT_GENERATOR_TOOL_DESCRIPTION=Description for PPT generator tool +PPT_GENERATOR_TOOL_PARAMETERS=Parameter definition JSON for PPT generator tool diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/tool/ppt-generator-operator-tool-description.txt b/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/tool/ppt-generator-operator-tool-description.txt new file mode 100644 index 0000000000..0b10363032 --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/tool/ppt-generator-operator-tool-description.txt @@ -0,0 +1,12 @@ +Tool for creating and generating PPT files. + +Supported operations: +- create: Create a new PPT file, requires output_path and title parameters, subtitle and slide_contents are optional + +Supported PPT file types: +- .pptx +- .ppt + +When creating PPT, you can set: +- Title and subtitle +- Multiple slides, each slide can contain title, content and image path diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/tool/ppt-generator-operator-tool-parameters.txt b/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/tool/ppt-generator-operator-tool-parameters.txt new file mode 100644 index 0000000000..5482d0ac9c --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/en/tool/ppt-generator-operator-tool-parameters.txt @@ -0,0 +1,50 @@ +{ + "oneOf": [ + { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "create" + }, + "output_path": { + "type": "string", + "description": "Path to save the generated PPT file" + }, + "title": { + "type": "string", + "description": "Title of the PPT" + }, + "subtitle": { + "type": "string", + "description": "Subtitle of the PPT (optional)" + }, + "slide_contents": { + "type": "array", + "description": "List of slide contents (optional)", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Slide title" + }, + "content": { + "type": "string", + "description": "Slide content" + }, + "image_path": { + "type": "string", + "description": "Image path in the slide (optional)" + } + }, + "required": ["title", "content"], + "additionalProperties": false + } + } + }, + "required": ["action", "output_path", "title"], + "additionalProperties": false + } + ] +} diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/en/ppt_generator_agent/agent-config.yml b/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/en/ppt_generator_agent/agent-config.yml new file mode 100644 index 0000000000..b44cb46c02 --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/en/ppt_generator_agent/agent-config.yml @@ -0,0 +1,27 @@ +# PPT Generator Agent Configuration +agentName: PPT_GENERATOR_AGENT +agentDescription: A professional PowerPoint presentation generation agent capable of automatically creating PPT files with a title slide and multiple content slides, supporting both text and images. +availableToolKeys: + - ppt_generator_operator + - inner_storage_content_tool + - text_file_operator + - terminate + +# Next Step Prompt Configuration +nextStepPrompt: | + You are a professional PPT generation operator. + + Use the ppt_generator_operator tool to create a PPT file that includes a title slide and multiple content slides. + + This agent supports the following features: + - Create a new PPT presentation + - Set the title slide (including title and subtitle) + - Add multiple content slides, each slide may include: + - Slide title + - Text content + + To extract content from existing text files or documents, please use the text_file_operator or inner_storage_content_tool. + + Note: + - When specifying the path for generating the PPT, do not use an absolute address. + - Please return the path of the generated PPT file to the user. diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/zh/ppt_generator_agent/agent-config.yml b/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/zh/ppt_generator_agent/agent-config.yml new file mode 100644 index 0000000000..06460b75fb --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/zh/ppt_generator_agent/agent-config.yml @@ -0,0 +1,27 @@ +# PPT生成代理配置 +agentName: PPT_GENERATOR_AGENT +agentDescription: 一个专业的PowerPoint演示文稿生成代理,能够自动创建包含标题页和多个内容页的PPT文件,内容支持文本与图片。 +availableToolKeys: + - ppt_generator_operator + - inner_storage_content_tool + - text_file_operator + - terminate + +# 下一步操作提示配置 +nextStepPrompt: | + 您是一名专业的PPT生成操作员。 + + 请使用 ppt_generator_operator 工具创建一个包含标题页和多个内容幻灯片的PPT文件。 + + 本代理支持以下功能: + - 创建新PPT演示文稿 + - 设置标题页(包含标题与副标题) + - 添加多个内容幻灯片,每页可包含: + - 幻灯片标题 + - 文本内容 + + 如需从已有文本或文档中提取内容,请使用 text_file_operator 或 inner_storage_content_tool。 + + 注意: + - 在指定生成PPT路径时不要使用绝对地址 + - 请返回给用户生成的PPT文件路径 diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/descriptions.properties b/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/descriptions.properties index 3281b7ff1f..8440d043c2 100644 --- a/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/descriptions.properties +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/descriptions.properties @@ -30,8 +30,8 @@ INNER_STORAGE_CONTENT_TOOL_TOOL_DESCRIPTION=\u5185\u90E8\u5B58\u50A8\u5185\u5BB9 INNER_STORAGE_CONTENT_TOOL_TOOL_PARAMETERS=\u5185\u90E8\u5B58\u50A8\u5185\u5BB9\u5DE5\u5177\u7684\u53C2\u6570\u5B9A\u4E49JSON DOC_LOADER_TOOL_DESCRIPTION=\u6587\u6863\u52A0\u8F7D\u5DE5\u5177\u7684\u63CF\u8FF0\u4FE1\u606F DOC_LOADER_TOOL_PARAMETERS=\u6587\u6863\u52A0\u8F7D\u5DE5\u5177\u7684\u53C2\u6570\u5B9A\u4E49JSON -FILE_MERGE_TOOL_DESCRIPTION=\u6587\u4EF6\u5408\u5E76\u5DE5\u5177\u7684\u63CF\u8FF0\u4FE1\u606F -FILE_MERGE_TOOL_PARAMETERS=\u6587\u4EF6\u5408\u5E76\u5DE5\u5177\u7684\u53C2\u6570\u5B9A\u4E49JSON +FILE_MERGE_TOOL_TOOL_DESCRIPTION=\u6587\u4EF6\u5408\u5E76\u5DE5\u5177\u7684\u63CF\u8FF0\u4FE1\u606F +FILE_MERGE_TOOL_TOOL_PARAMETERS=\u6587\u4EF6\u5408\u5E76\u5DE5\u5177\u7684\u53C2\u6570\u5B9A\u4E49JSON DATA_SPLIT_TOOL_DESCRIPTION=\u6570\u636E\u5206\u5272\u5DE5\u5177\u7684\u63CF\u8FF0\u4FE1\u606F DATA_SPLIT_TOOL_PARAMETERS=\u6570\u636E\u5206\u5272\u5DE5\u5177\u7684\u53C2\u6570\u5B9A\u4E49JSON MAP_OUTPUT_TOOL_DESCRIPTION=\u6620\u5C04\u8F93\u51FA\u5DE5\u5177\u7684\u63CF\u8FF0\u4FE1\u606F @@ -42,3 +42,5 @@ FINALIZE_TOOL_DESCRIPTION=\u5B8C\u6210\u5DE5\u5177\u7684\u63CF\u8FF0\u4FE1\u606F FINALIZE_TOOL_PARAMETERS=\u5B8C\u6210\u5DE5\u5177\u7684\u53C2\u6570\u5B9A\u4E49JSON TERMINATE_TOOL_DESCRIPTION=\u7EC8\u6B62\u5DE5\u5177\u7684\u63CF\u8FF0\u4FE1\u606F TERMINATE_TOOL_PARAMETERS=\u7EC8\u6B62\u5DE5\u5177\u7684\u53C2\u6570\u5B9A\u4E49JSON +PPTGENERATOROPERATOR_TOOL_DESCRIPTION=PPT\u751F\u6210\u5668\u5DE5\u5177\u7684\u63CF\u8FF0\u4FE1\u606F +PPTGENERATOROPERATOR_TOOL_PARAMETERS=PPT\u751F\u6210\u5668\u5DE5\u5177\u7684\u53C2\u6570\u5B9A\u4E49JSON diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/tool/ppt-generator-operator-tool-description.txt b/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/tool/ppt-generator-operator-tool-description.txt new file mode 100644 index 0000000000..902de42bf3 --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/tool/ppt-generator-operator-tool-description.txt @@ -0,0 +1,12 @@ +用于创建和生成PPT文件的工具。 + +支持的操作: +- create:创建新的PPT文件,需要output_path、title参数,可选subtitle和slide_contents参数 + +支持的PPT文件类型包括: +- .pptx +- .ppt + +创建PPT时可以设置: +- 标题和副标题 +- 多张幻灯片,每张幻灯片可包含标题、内容和图片路径 diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/tool/ppt-generator-operator-tool-parameters.txt b/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/tool/ppt-generator-operator-tool-parameters.txt new file mode 100644 index 0000000000..fe26285f27 --- /dev/null +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/zh/tool/ppt-generator-operator-tool-parameters.txt @@ -0,0 +1,50 @@ +{ + "oneOf": [ + { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "create" + }, + "output_path": { + "type": "string", + "description": "生成的PPT文件保存路径" + }, + "title": { + "type": "string", + "description": "PPT的标题" + }, + "subtitle": { + "type": "string", + "description": "PPT的副标题(可选)" + }, + "slide_contents": { + "type": "array", + "description": "幻灯片内容列表(可选)", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "幻灯片标题" + }, + "content": { + "type": "string", + "description": "幻灯片内容" + }, + "image_path": { + "type": "string", + "description": "幻灯片中的图片路径(可选)" + } + }, + "required": ["title", "content"], + "additionalProperties": false + } + } + }, + "required": ["action", "output_path", "title"], + "additionalProperties": false + } + ] +} From 1e87e59d099f74412dddddfb221bdaf43a6dfa5e Mon Sep 17 00:00:00 2001 From: SapientialM Date: Sat, 2 Aug 2025 18:40:02 +0800 Subject: [PATCH 2/2] fix(jmanus): fixed the check for Chinese content and the check for Linter --- .../example/manus/tool/pptGenerator/PptGeneratorOperator.java | 2 +- .../startup-agents/en/ppt_generator_agent/agent-config.yml | 2 +- .../startup-agents/zh/ppt_generator_agent/agent-config.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorOperator.java b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorOperator.java index c655409923..531d13e602 100644 --- a/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorOperator.java +++ b/spring-ai-alibaba-jmanus/src/main/java/com/alibaba/cloud/ai/example/manus/tool/pptGenerator/PptGeneratorOperator.java @@ -71,7 +71,7 @@ public ToolExecuteResult run(PptInput input) { input.setOutputPath(resultPath); // Update the file state to processing. - pptGeneratorService.updateFileState(planId, resultPath, "Processing: 正在生成PPT文件"); + pptGeneratorService.updateFileState(planId, resultPath, "Processing: Generating PPT file"); String path = pptGeneratorService.createPpt(input); diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/en/ppt_generator_agent/agent-config.yml b/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/en/ppt_generator_agent/agent-config.yml index b44cb46c02..bf57ac2629 100644 --- a/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/en/ppt_generator_agent/agent-config.yml +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/en/ppt_generator_agent/agent-config.yml @@ -24,4 +24,4 @@ nextStepPrompt: | Note: - When specifying the path for generating the PPT, do not use an absolute address. - - Please return the path of the generated PPT file to the user. + - Please return the path of the generated PPT file to the user. \ No newline at end of file diff --git a/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/zh/ppt_generator_agent/agent-config.yml b/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/zh/ppt_generator_agent/agent-config.yml index 06460b75fb..fa5e3df857 100644 --- a/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/zh/ppt_generator_agent/agent-config.yml +++ b/spring-ai-alibaba-jmanus/src/main/resources/prompts/startup-agents/zh/ppt_generator_agent/agent-config.yml @@ -24,4 +24,4 @@ nextStepPrompt: | 注意: - 在指定生成PPT路径时不要使用绝对地址 - - 请返回给用户生成的PPT文件路径 + - 请返回给用户生成的PPT文件路径 \ No newline at end of file