mateclaw/mateclaw-server/pom.xml
matevip b4ebab65c7 feat(tool): docx image embedding + multi-file render
Two follow-up improvements on top of renderDocxFromFile so the docx
pipeline can handle real long-form deliverables instead of just
prose-only memos.

Image embedding (P1).
MarkdownDocxRenderer now recognizes single-line ![alt](path) markdown
and embeds the referenced file via POI's XWPFRun.addPicture():
- PNG / JPG / GIF / BMP read straight from disk
- SVG rasterized via Apache Batik (PNGTranscoder, target width 1400px)
  before embedding — OOXML stores raster images, so any vector source
  needs conversion. Batik runs in-JVM, no rsvg-convert / cairo on host.
- Pictures are pinned to roughly the printable page width (≈ 5.77 in
  for A4 minus default 1800-twip margins) and given a 4:3 height
  fallback. Mixing images inline with other paragraph text is not
  supported by design — the markdown subset assumes one image per
  block paragraph. Inline images would require splitting paragraphs
  across runs with explicit positioning, well beyond what this
  renderer covers.
- Failure modes (missing file, unsupported format, Batik blowing up)
  emit an italicised "[image: alt — reason]" placeholder so the rest
  of the document still renders; the agent can read its own log to
  see why the picture didn't make it.
- Adds two transitive deps via pom: batik-transcoder + batik-codec at
  1.18, ~10 MB combined. Worth it given the alternative is shelling
  out to system tooling.

Multi-file render (P2-lite).
New tool renderDocxFromFiles(List<String> filePaths, filename, pageSize)
reads several markdown files in order and renders one combined docx.
Lets the agent split a 30-page proposal into cover.md / ch1.md /
ch2.md / appendix.md and produce a single deliverable in one tool
call. Each path goes through WorkspacePathGuard.validatePath; any
empty or unreadable file aborts with a typed error so the agent
fixes its file list before retrying. Files are joined with a blank
line — no separator markup is injected, headings carry over cleanly.

I deliberately did NOT build the heavier mutable-docx state
("appendDocxChapter / finalizeDocx") flavor of P2: the multi-file
form covers the same workflow with no per-conversation state to
clean up, and the agent can iterate by rewriting the chapter file
and re-running the tool. Stateful append can come later if a
streaming use case actually shows up.

renderDocx and renderDocxFromFile @Tool descriptions updated to point
the agent at renderDocxFromFile for >5 KB markdown and to advertise
the new image-embedding capability.
2026-04-27 08:42:03 +08:00

499 lines
20 KiB
XML
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.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>vip.mate</groupId>
<artifactId>mateclaw-server</artifactId>
<version>1.1.137-SNAPSHOT</version>
<packaging>jar</packaging>
<name>MateClaw Server</name>
<description>MateClaw - Java+Vue Personal AI Assistant powered by Spring AI Alibaba</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.13</version>
<relativePath/>
</parent>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Spring AI 1.1.4 正式版 -->
<spring-ai.version>1.1.4</spring-ai.version>
<!-- Spring AI Alibaba 1.1.2.2(对应 Spring AI 1.1.x -->
<spring-ai-alibaba.version>1.1.2.2</spring-ai-alibaba.version>
<mybatis-plus.version>3.5.16</mybatis-plus.version>
<hutool.version>5.8.26</hutool.version>
<springdoc.version>2.8.16</springdoc.version>
<jjwt.version>0.12.6</jjwt.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring AI BOM统一管理 spring-ai-* 版本) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringDoc OpenAPI BOM统一管理 springdoc-* 版本) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-bom</artifactId>
<version>${springdoc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- ===== MateClaw Plugin API ===== -->
<dependency>
<groupId>vip.mate</groupId>
<artifactId>mateclaw-plugin-api</artifactId>
<version>1.1.0-SNAPSHOT</version>
</dependency>
<!-- ===== Web MVC不引入 WebFlux避免自动切换为响应式模式 ===== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ===== Actuator — exposes Spring AI observation metrics (gen_ai.*) ===== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- ===== Spring AI Alibaba DashScope ===== -->
<!--
1.1.2.2 需单独指定版本,不在 BOM 中
内置 DashScope ChatModel / EmbeddingModel / ImageModel
-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${spring-ai-alibaba.version}</version>
<!-- 排除 webflux 传递依赖,保持 MVC 模式 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- ===== Spring AI Alibaba Graph CoreStateGraph 工作流引擎) ===== -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-graph-core</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<!-- ===== Spring AI OpenAI Compatible ===== -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai</artifactId>
</dependency>
<!-- ===== Spring AI AnthropicClaude 模型支持) ===== -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic</artifactId>
</dependency>
<!-- ===== Spring AI MCP Client动态 MCP server 连接管理) ===== -->
<!--
使用 spring-ai-mcp-client-spring-boot-starter 引入 MCP 核心库,
但禁用自动配置(我们自己管理 McpSyncClient 生命周期)
-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- ===== H2 内嵌数据库(开发环境) ===== -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- ===== MySQL 驱动(生产环境) ===== -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- ===== MyBatis Plus不引入 JPA避免双 ORM 冲突) ===== -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MyBatis Plus 分页插件3.5.16 拆分为独立模块) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- ===== Spring Security ===== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- ===== JJWT (JSON Web Token) ===== -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- ===== SpringDoc OpenAPISwagger UI for Spring MVC ===== -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- ===== Hutool 工具库 ===== -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- ===== 钉钉 Stream SDKWebSocket 长连接,无需公网 IP ===== -->
<dependency>
<groupId>com.dingtalk.open</groupId>
<artifactId>dingtalk-stream</artifactId>
<version>1.3.5</version>
</dependency>
<!-- ===== 飞书 / Lark Open API SDKWebSocket 长连接 + 事件分发) ===== -->
<dependency>
<groupId>com.larksuite.oapi</groupId>
<artifactId>oapi-sdk</artifactId>
<version>2.5.3</version>
</dependency>
<!-- ===== Caffeine Cache用于 skill runtime 缓存) ===== -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- ===== SnakeYAML用于 SKILL.md frontmatter 解析) ===== -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- ===== Lombok ===== -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- ===== QR Code Generation (ZXing) ===== -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.3</version>
</dependency>
<!-- ===== Playwright (Browser Automation) ===== -->
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.52.0</version>
</dependency>
<!-- ===== JDADiscord Bot Gateway WebSocket 长连接) ===== -->
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>5.2.3</version>
<exclusions>
<!-- 排除 audio 相关依赖MateClaw 不需要语音功能) -->
<exclusion>
<groupId>club.minnced</groupId>
<artifactId>opus-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- ===== Spring WebSocketTalk Mode ===== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- ===== Slack SDKSocket Mode + Web API ===== -->
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>slack-api-client</artifactId>
<version>1.44.2</version>
</dependency>
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>bolt-socket-mode</artifactId>
<version>1.44.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus.bundles</groupId>
<artifactId>tyrus-standalone-client</artifactId>
<version>2.2.0</version>
</dependency>
<!-- ===== Apache POI (in-process .docx generation) ===== -->
<!--
Used by DocxRenderTool to render Markdown into a .docx in-JVM,
replacing the Node.js docx-js subprocess (3-5 min cold install).
-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.4.1</version>
</dependency>
<!-- ===== Apache Batik (SVG rasterization for docx image embedding) ===== -->
<!--
Used by MarkdownDocxRenderer to convert ![alt](*.svg) image references
into PNG bytes that POI can embed via XWPFRun.addPicture(). Without this,
agents that produce architecture diagrams as inline SVG cannot get them
into the final .docx. Rasterization runs in-JVM (no rsvg-convert / cairo
dependency on the host).
-->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.18</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.18</version>
</dependency>
<!-- ===== jsoup (HTML cleanup for Wiki ingest, RFC-051 PR-1c) ===== -->
<!--
Used by WikiContentNormalizer to strip nav/footer/script/style/aside
and ad-class nodes from URL/HTML uploads before chunking. Small
(~430KB), no transitive deps, JVM-only — safe for the desktop bundle.
-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.18.3</version>
</dependency>
<!-- ===== Apache Tika (RFC-051 PR-?: Java-side last-resort extractor) ===== -->
<!--
Wired as the FINAL fallback in DocumentExtractTool's PDF/DOCX/XLSX/PPTX
chains, after every system command + Python + POI-based path has failed.
Used in production primarily by Windows users without Python or poppler
installed; otherwise idle.
Pinned to the precise format modules called out in RFC-051 §5.2 — we
deliberately avoid `tika-parsers-standard-package`, which pulls in mail,
audio, archive, RTF / ODT, scientific, etc. (~80MB). Current footprint:
tika-core (~700KB) + tika-parser-pdf-module (PDFBox ~5MB) +
tika-parser-microsoft-module (POI-scratchpad ~10MB) ≈ 16MB.
-->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parser-pdf-module</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parser-microsoft-module</artifactId>
<version>3.0.0</version>
</dependency>
<!-- ===== Database Migration (Flyway) ===== -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<!-- ===== Spring Boot Test ===== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<!--
Dependency repositories, with US + CN mirrors listed side by side so builds
are reasonable on either continent. Maven tries repositories in the order
they are declared — the first one that resolves an artifact wins.
IDs are deliberately distinct from the super-POM's `central` id so that
mirror rules in settings.xml (if any) don't silently redirect them. Keep
the fastest-by-default first; switch order via a local ~/.m2/settings.xml
or pass `-Paliyun-first` when building from inside China.
-->
<repositories>
<!-- Primary: Maven Central direct — fast from US/EU backbones. -->
<repository>
<id>maven-central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
<!-- Fallback 1: Google Cloud's Maven Central mirror (global CDN edge). -->
<repository>
<id>google-maven-central</id>
<name>Google Maven Central Mirror</name>
<url>https://maven-central.storage-download.googleapis.com/maven2</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
<!-- Fallback 2: Aliyun public — fast from China, full Central mirror. -->
<repository>
<id>aliyun-public</id>
<name>Aliyun Public</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
<!-- Spring milestones / snapshots — direct from Spring (US). -->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
<!-- Aliyun Spring mirror — fallback for CN builds. -->
<repository>
<id>aliyun-spring</id>
<name>Aliyun Spring Mirror</name>
<url>https://maven.aliyun.com/repository/spring</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
<!-- Plugin lookups follow the same multi-region fallback. -->
<pluginRepositories>
<pluginRepository>
<id>maven-central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</pluginRepository>
<pluginRepository>
<id>google-maven-central</id>
<name>Google Maven Central Mirror</name>
<url>https://maven-central.storage-download.googleapis.com/maven2</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</pluginRepository>
<pluginRepository>
<id>aliyun-public</id>
<name>Aliyun Public</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
<!--
Profile: swap the primary repo order when building from China so Aliyun
is tried first. Activate with `mvn -Paliyun-first ...`.
-->
<profiles>
<profile>
<id>aliyun-first</id>
<repositories>
<repository>
<id>aliyun-public-first</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
<repository>
<id>aliyun-spring-first</id>
<url>https://maven.aliyun.com/repository/spring</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun-public-first</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</project>