mateclaw/mateclaw-server/src/main/java/vip/mate/tool/builtin/PptxRenderTool.java

150 lines
6.0 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vip.mate.tool.builtin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import vip.mate.tool.document.FilenameSanitizer;
import vip.mate.tool.document.GeneratedFileCache;
import vip.mate.tool.document.GeneratedFileLink;
import vip.mate.tool.document.MarkdownInputResolver;
import vip.mate.tool.document.MarkdownInputResolver.Resolved;
import vip.mate.tool.document.MarkdownInputResolver.ResolveException;
import vip.mate.tool.document.MarkdownPptxRenderer;
/**
* Render a brand-new .pptx deck from Markdown, in-process via Apache POI.
* The LLM produces a Marp-style markdown body where {@code ---} separates
* slides, {@code # / ## / ###} is the slide title, and {@code - item} are
* bullets.
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class PptxRenderTool {
private static final String PPTX_MIME =
"application/vnd.openxmlformats-officedocument.presentationml.presentation";
private final MarkdownPptxRenderer renderer;
private final GeneratedFileCache cache;
@Tool(description = """
Render a NEW .pptx slide deck from Markdown and return a one-time download URL.
Use for creating presentations: pitch decks, project plans, talks, briefings.
Markdown convention (Marp-style):
- `---` on its own line separates slides.
- The first `# / ## / ###` of a slide becomes its title.
- Lines starting with `-` or `*` become bullet points.
- Other non-blank lines become plain paragraphs.
- `<!-- speaker note -->` HTML comments become speaker notes.
Example:
# My Presentation
By Author Name
---
## Topic 1
- Point one
- Point two
- Point three
<!-- Remember to mention the timeline here. -->
---
## Conclusion
Thanks!
For markdown bodies larger than ~5 KB, prefer renderPptxFromFile (read
from disk) — passing huge markdown as a tool argument burns LLM tokens.
Returns a markdown link the user can click to download the file.
The link is valid for 10 minutes.
""")
public String renderPptx(
@ToolParam(description = "Slide content in Marp-style Markdown ('---' between slides)")
String markdown,
@ToolParam(description = "Output filename without extension, e.g. 'pitch-deck'")
String filename,
@ToolParam(description = "Aspect ratio: '16:9' (default, widescreen) or '4:3' (legacy)", required = false)
String aspectRatio) {
if (markdown == null || markdown.isBlank()) {
return "错误markdown 参数为空,无法生成演示文稿。";
}
String displayName = FilenameSanitizer.sanitize(filename, "presentation", ".pptx") + ".pptx";
String ratio = resolveRatio(aspectRatio);
try {
long t0 = System.currentTimeMillis();
byte[] bytes = renderer.render(markdown, ratio);
log.info("[PptxRender] generated {} ({} bytes, {}ms)",
displayName, bytes.length, System.currentTimeMillis() - t0);
return GeneratedFileLink.resultZh(bytes, displayName, PPTX_MIME, cache, "演示文稿");
} catch (Exception e) {
log.error("[PptxRender] render failed for {}: {}", displayName, e.getMessage(), e);
return "渲染失败:" + e.getMessage();
}
}
@Tool(description = """
Render a .pptx deck from a markdown FILE on disk and return a one-time download URL.
Use this instead of `renderPptx` when the markdown body is large (>5 KB) — the
LLM does not need to repeat its own previous output as a tool argument.
Typical workflow:
1. write_file(path="deck.md", content="# Title\\n\\n---\\n\\n## Topic\\n\\n- ...")
2. renderPptxFromFile(filePath="deck.md", filename="pitch-deck")
3. return the download link to the user
The markdown file is read with UTF-8. Path resolution honors the workspace
boundary (same rules as read_file / write_file).
Same supported Marp-style markdown subset as renderPptx (`---` slide breaks,
`# / ##` titles, `-` / `*` bullets, `<!-- ... -->` speaker notes).
""")
public String renderPptxFromFile(
@ToolParam(description = "Absolute or workspace-relative path to a markdown file")
String filePath,
@ToolParam(description = "Output filename without extension, e.g. 'pitch-deck'")
String filename,
@ToolParam(description = "Aspect ratio: '16:9' (default) or '4:3'", required = false)
String aspectRatio) {
Resolved input;
try {
input = MarkdownInputResolver.readSingle(filePath);
} catch (ResolveException e) {
return "Error: " + e.getMessage();
}
String displayName = FilenameSanitizer.sanitize(filename, "presentation", ".pptx") + ".pptx";
String ratio = resolveRatio(aspectRatio);
try {
long t0 = System.currentTimeMillis();
byte[] bytes = renderer.render(input.markdown(), ratio);
log.info("[PptxRender] generated {} ({} bytes from {} bytes md, {}ms)",
displayName, bytes.length, input.totalBytes(),
System.currentTimeMillis() - t0);
return GeneratedFileLink.resultEn(bytes, displayName, PPTX_MIME, cache, "Presentation", 1);
} catch (Exception e) {
log.error("[PptxRender] render failed for {} (source: {}): {}",
displayName, input.sources().get(0), e.getMessage(), e);
return "Render failed: " + e.getMessage();
}
}
private static String resolveRatio(String aspectRatio) {
return (aspectRatio == null || aspectRatio.isBlank()) ? "16:9" : aspectRatio.trim();
}
}