diff --git a/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java b/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java
index 1b06eef0..491eb05f 100644
--- a/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java
+++ b/mateclaw-server/src/main/java/vip/mate/agent/AgentGraphBuilder.java
@@ -69,6 +69,7 @@ import vip.mate.tool.guard.service.ToolGuardService;
import vip.mate.workspace.conversation.ConversationService;
import vip.mate.approval.ApprovalWorkflowService;
import vip.mate.channel.web.ChatStreamTracker;
+import vip.mate.wiki.service.WikiContextService;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -114,6 +115,7 @@ public class AgentGraphBuilder {
private final WorkspaceFileService workspaceFileService;
private final vip.mate.agent.context.ConversationWindowManager conversationWindowManager;
private final vip.mate.llm.chatgpt.ChatGPTResponsesClient chatGPTResponsesClient;
+ private final WikiContextService wikiContextService;
/**
* 根据 AgentEntity 构建完整的 Agent 实例
@@ -611,7 +613,10 @@ public class AgentGraphBuilder {
""";
}
- return basePrompt + skillEnhancement + toolGuidance + searchGuidance;
+ // Wiki 知识库上下文注入
+ String wikiContext = wikiContextService.buildWikiContext(entity.getId());
+
+ return basePrompt + skillEnhancement + toolGuidance + searchGuidance + wikiContext;
}
// ==================== 模型选项构建 ====================
diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/WikiAutoConfiguration.java b/mateclaw-server/src/main/java/vip/mate/wiki/WikiAutoConfiguration.java
new file mode 100644
index 00000000..8757fb61
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/WikiAutoConfiguration.java
@@ -0,0 +1,16 @@
+package vip.mate.wiki;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * Wiki 知识库模块自动配置
+ *
+ * @author MateClaw Team
+ */
+@Configuration
+@EnableAsync
+@EnableConfigurationProperties(WikiProperties.class)
+public class WikiAutoConfiguration {
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java b/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java
new file mode 100644
index 00000000..9aa9a51d
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java
@@ -0,0 +1,38 @@
+package vip.mate.wiki;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Wiki 知识库配置
+ *
+ * @author MateClaw Team
+ */
+@Data
+@ConfigurationProperties(prefix = "mate.wiki")
+public class WikiProperties {
+
+ /** 是否启用 Wiki 知识库功能 */
+ private boolean enabled = true;
+
+ /** LLM 单次处理最大字符数(超过则分块) */
+ private int maxChunkSize = 30000;
+
+ /** 注入 agent prompt 的最大字符数 */
+ private int maxContextChars = 10000;
+
+ /** 单个原始材料最多生成的 Wiki 页面数 */
+ private int maxPagesPerRaw = 15;
+
+ /** 上传后是否自动触发处理 */
+ private boolean autoProcessOnUpload = true;
+
+ /** 上传文件存储目录 */
+ private String uploadDir = "./data/wiki-uploads";
+
+ /** 目录扫描最大文件数 */
+ private int maxScanFiles = 500;
+
+ /** 扫描时跳过大于此大小的文件(字节),默认 50MB */
+ private long maxScanFileSize = 50 * 1024 * 1024;
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/config/WikiSchemaMigration.java b/mateclaw-server/src/main/java/vip/mate/wiki/config/WikiSchemaMigration.java
new file mode 100644
index 00000000..c5bbdcbb
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/config/WikiSchemaMigration.java
@@ -0,0 +1,113 @@
+package vip.mate.wiki.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+/**
+ * Wiki 模块 Schema 迁移
+ *
+ * 确保 Wiki 相关表存在(兼容已有部署)。
+ *
+ * @author MateClaw Team
+ */
+@Slf4j
+@Component
+@Order(202)
+@RequiredArgsConstructor
+public class WikiSchemaMigration implements ApplicationRunner {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ @Override
+ public void run(ApplicationArguments args) {
+ createKnowledgeBaseTable();
+ createRawMaterialTable();
+ createPageTable();
+ migrateKnowledgeBaseColumns();
+ log.info("[WikiSchemaMigration] Wiki schema migration completed");
+ }
+
+ private void createKnowledgeBaseTable() {
+ jdbcTemplate.execute("""
+ CREATE TABLE IF NOT EXISTS mate_wiki_knowledge_base (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(128) NOT NULL,
+ description TEXT,
+ agent_id BIGINT,
+ config_content CLOB,
+ status VARCHAR(32) NOT NULL DEFAULT 'active',
+ page_count INT NOT NULL DEFAULT 0,
+ raw_count INT NOT NULL DEFAULT 0,
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+ )
+ """);
+ jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_wiki_kb_agent ON mate_wiki_knowledge_base(agent_id)");
+ }
+
+ private void createRawMaterialTable() {
+ jdbcTemplate.execute("""
+ CREATE TABLE IF NOT EXISTS mate_wiki_raw_material (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ title VARCHAR(256) NOT NULL,
+ source_type VARCHAR(32) NOT NULL DEFAULT 'text',
+ source_path VARCHAR(512),
+ original_content CLOB,
+ extracted_text CLOB,
+ content_hash VARCHAR(64),
+ file_size BIGINT NOT NULL DEFAULT 0,
+ processing_status VARCHAR(32) NOT NULL DEFAULT 'pending',
+ last_processed_at DATETIME,
+ error_message VARCHAR(512),
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0
+ )
+ """);
+ jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_wiki_raw_kb ON mate_wiki_raw_material(kb_id)");
+ jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_wiki_raw_status ON mate_wiki_raw_material(kb_id, processing_status)");
+ }
+
+ private void createPageTable() {
+ jdbcTemplate.execute("""
+ CREATE TABLE IF NOT EXISTS mate_wiki_page (
+ id BIGINT NOT NULL PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ slug VARCHAR(256) NOT NULL,
+ title VARCHAR(256) NOT NULL,
+ content CLOB,
+ summary VARCHAR(1024),
+ outgoing_links CLOB,
+ source_raw_ids CLOB,
+ version INT NOT NULL DEFAULT 1,
+ last_updated_by VARCHAR(32) NOT NULL DEFAULT 'ai',
+ create_time DATETIME NOT NULL,
+ update_time DATETIME NOT NULL,
+ deleted INT NOT NULL DEFAULT 0,
+ CONSTRAINT uk_wiki_page_kb_slug UNIQUE (kb_id, slug)
+ )
+ """);
+ jdbcTemplate.execute("CREATE INDEX IF NOT EXISTS idx_wiki_page_kb ON mate_wiki_page(kb_id)");
+ }
+
+ /**
+ * 增量字段迁移(兼容已有部署)
+ */
+ private void migrateKnowledgeBaseColumns() {
+ try {
+ jdbcTemplate.execute("ALTER TABLE mate_wiki_knowledge_base ADD COLUMN IF NOT EXISTS source_directory VARCHAR(512)");
+ } catch (Exception e) {
+ // MySQL 不支持 ADD COLUMN IF NOT EXISTS,忽略已存在的错误
+ if (!e.getMessage().contains("Duplicate column")) {
+ log.warn("[WikiSchemaMigration] Failed to add source_directory column: {}", e.getMessage());
+ }
+ }
+ }
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java b/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java
new file mode 100644
index 00000000..c0965f31
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java
@@ -0,0 +1,275 @@
+package vip.mate.wiki.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import vip.mate.common.result.R;
+import vip.mate.wiki.WikiProperties;
+import vip.mate.wiki.event.WikiProcessingEvent;
+import vip.mate.wiki.model.WikiKnowledgeBaseEntity;
+import vip.mate.wiki.model.WikiPageEntity;
+import vip.mate.wiki.model.WikiRawMaterialEntity;
+import vip.mate.wiki.service.WikiDirectoryScanService;
+import vip.mate.wiki.service.WikiKnowledgeBaseService;
+import vip.mate.wiki.service.WikiPageService;
+import vip.mate.wiki.service.WikiProcessingService;
+import vip.mate.wiki.service.WikiRawMaterialService;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Wiki 知识库接口
+ *
+ * @author MateClaw Team
+ */
+@Tag(name = "Wiki 知识库")
+@RestController
+@RequestMapping("/api/v1/wiki")
+@RequiredArgsConstructor
+public class WikiController {
+
+ private final WikiKnowledgeBaseService kbService;
+ private final WikiRawMaterialService rawService;
+ private final WikiPageService pageService;
+ private final WikiProcessingService processingService;
+ private final WikiDirectoryScanService scanService;
+ private final WikiProperties properties;
+ private final ApplicationEventPublisher eventPublisher;
+
+ // ==================== Knowledge Base ====================
+
+ @Operation(summary = "获取所有知识库")
+ @GetMapping("/knowledge-bases")
+ public R> listKBs() {
+ return R.ok(kbService.listAll());
+ }
+
+ @Operation(summary = "获取知识库详情")
+ @GetMapping("/knowledge-bases/{id}")
+ public R getKB(@PathVariable Long id) {
+ WikiKnowledgeBaseEntity kb = kbService.getById(id);
+ if (kb == null) return R.fail("Knowledge base not found");
+ return R.ok(kb);
+ }
+
+ @Operation(summary = "按 Agent 获取知识库")
+ @GetMapping("/knowledge-bases/agent/{agentId}")
+ public R> listKBsByAgent(@PathVariable Long agentId) {
+ return R.ok(kbService.listByAgentId(agentId));
+ }
+
+ @Operation(summary = "创建知识库")
+ @PostMapping("/knowledge-bases")
+ public R createKB(@RequestBody Map body) {
+ String name = (String) body.get("name");
+ String description = (String) body.get("description");
+ Long agentId = body.get("agentId") != null ? Long.valueOf(body.get("agentId").toString()) : null;
+ return R.ok(kbService.create(name, description, agentId));
+ }
+
+ @Operation(summary = "更新知识库")
+ @PutMapping("/knowledge-bases/{id}")
+ public R updateKB(@PathVariable Long id, @RequestBody Map body) {
+ String name = (String) body.get("name");
+ String description = (String) body.get("description");
+ Long agentId = body.get("agentId") != null ? Long.valueOf(body.get("agentId").toString()) : null;
+ return R.ok(kbService.update(id, name, description, agentId));
+ }
+
+ @Operation(summary = "删除知识库")
+ @DeleteMapping("/knowledge-bases/{id}")
+ public R deleteKB(@PathVariable Long id) {
+ kbService.delete(id);
+ return R.ok();
+ }
+
+ @Operation(summary = "获取知识库配置")
+ @GetMapping("/knowledge-bases/{id}/config")
+ public R