metadata = new HashMap<>();
-
- metadata.put("projectName", projectRoot.getFileName().toString());
- metadata.put("projectType", projectType.name());
- metadata.put("primaryLanguage", projectType.getPrimaryLanguage());
- metadata.put("packageManager", projectType.getPackageManager());
- metadata.put("analysisTimestamp", System.currentTimeMillis());
-
- // 检查版本控制
- if (Files.exists(projectRoot.resolve(".git"))) {
- metadata.put("versionControl", "Git");
- }
-
- // 检查CI/CD配置
- if (Files.exists(projectRoot.resolve(".github"))) {
- metadata.put("cicd", "GitHub Actions");
- } else if (Files.exists(projectRoot.resolve(".gitlab-ci.yml"))) {
- metadata.put("cicd", "GitLab CI");
- }
-
- // 检查Docker支持
- if (Files.exists(projectRoot.resolve("Dockerfile"))) {
- metadata.put("containerization", "Docker");
- }
-
- return metadata;
- }
-
- /**
- * 生成编辑上下文
- */
- public String buildEditContext(Path projectRoot, String editDescription) {
- logger.debug("Building edit context for: {}", projectRoot);
-
- ProjectContext context = analyzeProject(projectRoot);
-
- StringBuilder contextBuilder = new StringBuilder();
- contextBuilder.append("=== EDIT CONTEXT ===\n");
- contextBuilder.append("Edit Request: ").append(editDescription).append("\n\n");
- contextBuilder.append(context.generateContextSummary());
-
- return contextBuilder.toString();
- }
-
- // 辅助方法
- private boolean shouldSkipDirectory(String dirName) {
- return dirName.equals(".git") || dirName.equals("node_modules") ||
- dirName.equals("target") || dirName.equals("build") ||
- dirName.equals("dist") || dirName.equals("__pycache__") ||
- dirName.startsWith(".");
- }
-
- private String getFileExtension(String fileName) {
- int lastDot = fileName.lastIndexOf('.');
- return lastDot > 0 ? fileName.substring(lastDot) : "";
- }
-
- private boolean isCodeFile(String extension, ProjectType projectType) {
- return extension.equals(".java") || extension.equals(".js") || extension.equals(".ts") ||
- extension.equals(".py") || extension.equals(".html") || extension.equals(".css") ||
- extension.equals(".jsx") || extension.equals(".tsx") || extension.equals(".vue") ||
- extension.equals(".go") || extension.equals(".rs") || extension.equals(".php") ||
- extension.equals(".cs") || extension.equals(".cpp") || extension.equals(".c");
- }
-
- private boolean isCommentLine(String line, String extension) {
- switch (extension) {
- case ".java":
- case ".js":
- case ".ts":
- case ".jsx":
- case ".tsx":
- case ".css":
- return line.startsWith("//") || line.startsWith("/*") || line.startsWith("*");
- case ".py":
- return line.startsWith("#");
- case ".html":
- return line.startsWith("
-
- `;
- return container;
- }
-
- // 添加到页面
- appendToPage() {
- messagesContainer.appendChild(this.container);
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
- }
-
- // 显示连接状态
- showConnectionStatus(status) {
- const statusElement = this.container.querySelector('.connection-status');
- if (statusElement) {
- statusElement.textContent = status;
- statusElement.className = `connection-status ${status === '已连接' ? 'connected' : 'error'}`;
- }
- }
-
- // 添加工具开始执行
- addToolStart(logEvent) {
- // 移除等待状态卡片(如果存在)
- removeWaitingToolCard();
-
- const toolCard = this.createToolCard(logEvent);
- const content = this.container.querySelector('.tool-log-content');
- content.appendChild(toolCard);
-
- this.toolCards.set(logEvent.toolName, toolCard);
- this.scrollToBottom();
- }
-
- // 更新工具执行成功
- updateToolSuccess(logEvent) {
- const toolCard = this.toolCards.get(logEvent.toolName);
- if (toolCard) {
- this.updateToolCard(toolCard, logEvent, 'success');
- }
- }
-
- // 更新工具执行失败
- updateToolError(logEvent) {
- const toolCard = this.toolCards.get(logEvent.toolName);
- if (toolCard) {
- this.updateToolCard(toolCard, logEvent, 'error');
- }
- }
-
- // 创建工具卡片
- createToolCard(logEvent) {
- const card = document.createElement('div');
- card.className = 'tool-card running';
- card.innerHTML = `
-
- 📁 ${this.getFileName(logEvent.filePath)}
- ${logEvent.message}
- 开始时间: ${logEvent.timestamp}
- `;
- return card;
- }
-
- // 更新工具卡片
- updateToolCard(toolCard, logEvent, status) {
- toolCard.className = `tool-card ${status}`;
-
- const statusElement = toolCard.querySelector('.tool-status');
- const messageElement = toolCard.querySelector('.tool-message');
- const timeElement = toolCard.querySelector('.tool-time');
-
- if (status === 'success') {
- statusElement.innerHTML = '✅ 完成';
- statusElement.className = 'tool-status success';
- } else if (status === 'error') {
- statusElement.innerHTML = '❌ 失败';
- statusElement.className = 'tool-status error';
- }
-
- messageElement.textContent = logEvent.message;
-
- if (logEvent.executionTime) {
- timeElement.textContent = `完成时间: ${logEvent.timestamp} (耗时: ${logEvent.executionTime}ms)`;
- }
-
- this.scrollToBottom();
- }
-
- // 显示任务完成
- showTaskComplete() {
- const header = this.container.querySelector('.tool-log-header');
- header.innerHTML = `
- 🎉 任务执行完成
- 已完成
- `;
- }
-
- // 淡出效果
- fadeOut() {
- this.container.style.transition = 'opacity 1s ease-out';
- this.container.style.opacity = '0.5';
-
- setTimeout(() => {
- if (this.container.parentNode) {
- this.container.parentNode.removeChild(this.container);
- }
- }, 10000); // 10秒后移除
- }
-
- // 滚动到底部
- scrollToBottom() {
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
- }
-
- // 获取文件名
- getFileName(filePath) {
- if (!filePath) return '未知文件';
- const parts = filePath.split(/[/\\]/);
- return parts[parts.length - 1] || filePath;
- }
-}
diff --git a/ruoyi-extend/ruoyi-ai-copilot/src/main/resources/templates/index.html b/ruoyi-extend/ruoyi-ai-copilot/src/main/resources/templates/index.html
deleted file mode 100644
index 02d6e9a6..00000000
--- a/ruoyi-extend/ruoyi-ai-copilot/src/main/resources/templates/index.html
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
- Copilot
-
-
-
-
-
-
-
-
-
-
-
-
Assistant
-
- 👋 Hello! I'm your AI file operations assistant. I can help you:
-
- 📁 Read files - View file contents with pagination support
-
✏️ Write files - Create new files or overwrite existing ones
-
🔧 Edit files - Make precise edits with diff preview
-
📋 List directories - Browse directory structure
-
- Try asking me to create a simple project, read a file, or explore the workspace!
-
- Workspace: /workspace
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
💡 Tips
-
- • Ask for step-by-step project creation
- • Request file content before editing
- • Use specific file paths
- • Ask for directory structure first
- • Try continuous operations
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ruoyi-modules/ruoyi-chat/docs/frontend-guide.md b/ruoyi-modules/ruoyi-chat/docs/frontend-guide.md
deleted file mode 100644
index 05cb5b9f..00000000
--- a/ruoyi-modules/ruoyi-chat/docs/frontend-guide.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# MCP工具管理前端开发指南
-
-## 前置条件
-
-- Node.js >= 16.0
-- Vue 3
-- Element Plus
-- ECharts (用于图表展示)
-- Axios (用于HTTP请求)
-
-## 安装依赖
-
-```bash
-npm install element-plus echarts axios
-```
-
-## 项目结构
-
-```
-ruoyi-ui/
-├── src/
-│ ├── api/
-│ │ └── mcp/
-│ │ └── tool.js # API接口
-│ ├── views/
-│ │ └── mcp/
-│ │ ├── tool/
-│ │ │ └── index.vue # 工具管理页面
-│ │ ├── market/
-│ │ │ └── index.vue # 市场管理页面
-│ │ └── log/
-│ │ └── index.vue # 调用日志页面
-│ └── utils/
-│ └── request.js # Axios封装
-```
-
-## 菜单配置
-
-在系统菜单管理中添加以下菜单:
-
-### 1. MCP工具管理
-
-| 字段 | 值 |
-|------|-----|
-| 菜单名称 | MCP工具管理 |
-| 菜单类型 | 目录 |
-| 显示顺序 | 1 |
-| 路由地址 | mcp |
-| 组件路径 | |
-
-#### 子菜单:工具列表
-
-| 字段 | 值 |
-|------|-----|
-| 菜单名称 | 工具列表 |
-| 菜单类型 | 菜单 |
-| 显示顺序 | 1 |
-| 路由地址 | tool |
-| 组件路径 | mcp/tool/index |
-| 权限标识 | mcp:tool:list |
-
-#### 子菜单:市场管理
-
-| 字段 | 值 |
-|------|-----|
-| 菜单名称 | 市场管理 |
-| 菜单类型 | 菜单 |
-| 显示顺序 | 2 |
-| 路由地址 | market |
-| 组件路径 | mcp/market/index |
-| 权限标识 | mcp:market:list |
-
-#### 子菜单:调用日志
-
-| 字段 | 值 |
-|------|-----|
-| 菜单名称 | 调用日志 |
-| 菜单类型 | 菜单 |
-| 显示顺序 | 3 |
-| 路由地址 | log |
-| 组件路径 | mcp/log/index |
-| 权限标识 | mcp:tool:query |
-
-## 权限配置
-
-| 权限标识 | 权限名称 | 说明 |
-|----------|----------|------|
-| mcp:tool:list | 工具列表 | 查看工具列表 |
-| mcp:tool:query | 工具查询 | 查看工具详情 |
-| mcp:tool:add | 工具新增 | 新增工具 |
-| mcp:tool:edit | 工具修改 | 修改工具 |
-| mcp:tool:remove | 工具删除 | 删除工具 |
-| mcp:tool:export | 工具导出 | 导出工具数据 |
-| mcp:market:list | 市场列表 | 查看市场列表 |
-| mcp:market:query | 市场查询 | 查看市场详情 |
-| mcp:market:add | 市场新增 | 新增市场 |
-| mcp:market:edit | 市场修改 | 修改市场 |
-| mcp:market:remove | 市场删除 | 删除市场 |
-
-## 路由配置
-
-在路由配置文件中添加:
-
-```javascript
-{
- path: '/mcp',
- component: Layout,
- redirect: '/mcp/tool',
- name: 'Mcp',
- meta: { title: 'MCP工具管理', icon: 'tools' },
- children: [
- {
- path: 'tool',
- name: 'McpTool',
- component: () => import('@/views/mcp/tool/index'),
- meta: { title: '工具列表', icon: 'tool' }
- },
- {
- path: 'market',
- name: 'McpMarket',
- component: () => import('@/views/mcp/market/index'),
- meta: { title: '市场管理', icon: 'shop' }
- },
- {
- path: 'log',
- name: 'McpLog',
- component: () => import('@/views/mcp/log/index'),
- meta: { title: '调用日志', icon: 'document' }
- }
- ]
-}
-```
-
-## API请求配置
-
-确保Axios请求拦截器正确配置:
-
-```javascript
-// src/utils/request.js
-import axios from 'axios'
-import { ElMessage } from 'element-plus'
-
-const service = axios.create({
- baseURL: process.env.VUE_APP_BASE_API,
- timeout: 30000
-})
-
-// 请求拦截器
-service.interceptors.request.use(
- config => {
- // 添加token
- const token = localStorage.getItem('Admin-Token')
- if (token) {
- config.headers['Authorization'] = 'Bearer ' + token
- }
- return config
- },
- error => {
- return Promise.reject(error)
- }
-)
-
-// 响应拦截器
-service.interceptors.response.use(
- response => {
- const res = response.data
- if (res.code !== 200) {
- ElMessage.error(res.msg || '请求失败')
- return Promise.reject(new Error(res.msg || '请求失败'))
- }
- return res
- },
- error => {
- ElMessage.error(error.message)
- return Promise.reject(error)
- }
-)
-
-export default service
-```
-
-## 开发步骤
-
-1. **复制代码文件**
- - 将 `tool.js` 复制到 `src/api/mcp/` 目录
- - 将 `*.vue` 文件复制到对应的视图目录
-
-2. **安装依赖**
- ```bash
- npm install element-plus echarts
- ```
-
-3. **配置路由**
- - 在路由配置中添加MCP相关路由
-
-4. **配置菜单**
- - 在系统管理中添加菜单
-
-5. **配置权限**
- - 在系统管理中添加权限标识
-
-6. **测试功能**
- - 启动开发服务器
- - 测试各项功能
-
-## 注意事项
-
-1. **工具类型说明**
- - BUILTIN: 内置工具(系统自带,不可编辑)
- - LOCAL: 本地STDIO工具(通过命令行启动)
- - REMOTE: 远程HTTP工具(通过网络连接)
-
-2. **配置JSON格式**
- - LOCAL类型: `{"command": "npx", "args": ["-y", "@example/tool"], "env": {}}`
- - REMOTE类型: `{"baseUrl": "http://localhost:8080/mcp"}`
-
-3. **错误处理**
- - 工具连接测试可能超时,请合理设置超时时间
- - 删除工具前请确认没有正在运行的Agent使用该工具
-
-4. **性能优化**
- - 调用日志数据量大时,建议使用分页加载
- - 图表数据建议缓存处理,避免频繁请求
-
-## 常见问题
-
-### 1. 跨域问题
-在 `vue.config.js` 中配置代理:
-```javascript
-devServer: {
- proxy: {
- '/api': {
- target: 'http://localhost:8080',
- changeOrigin: true
- }
- }
-}
-```
-
-### 2. 图表不显示
-确保ECharts容器有固定高度,并在数据加载后初始化图表。
-
-### 3. 权限不生效
-检查菜单权限配置和后端接口权限注解是否一致。
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java
new file mode 100644
index 00000000..73e72096
--- /dev/null
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java
@@ -0,0 +1,159 @@
+package org.ruoyi.service.chat.handler;
+
+import dev.langchain4j.agentic.AgenticServices;
+import dev.langchain4j.community.model.dashscope.QwenChatModel;
+import dev.langchain4j.service.tool.ToolProvider;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.ruoyi.agent.McpAgent;
+import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
+import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
+import org.ruoyi.common.chat.entity.chat.ChatContext;
+import org.ruoyi.common.chat.enums.RoleType;
+import org.ruoyi.common.chat.service.chatMessage.IChatMessageService;
+import org.ruoyi.common.sse.utils.SseMessageUtils;
+import org.ruoyi.mcp.service.core.ToolProviderFactory;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Agent 深度思考处理器
+ *
+ * 处理 enableThinking=true 的场景,使用 Agent 进行深度思考和工具调用
+ *
+ * @author ageerle@163.com
+ * @date 2025/12/13
+ */
+@Slf4j
+@Component
+@Order(3)
+@RequiredArgsConstructor
+public class AgentChatHandler implements ChatHandler {
+
+ private final ToolProviderFactory toolProviderFactory;
+ private final IChatMessageService chatMessageService;
+
+ @Override
+ public boolean supports(ChatContext context) {
+ Boolean enableThinking = context.getChatRequest().getEnableThinking();
+ return enableThinking != null && enableThinking;
+ }
+
+ @Override
+ public SseEmitter handle(ChatContext context) {
+ log.info("处理 Agent 深度思考,用户: {}", context.getUserId());
+
+ Long userId = context.getUserId();
+ String tokenValue = context.getTokenValue();
+ ChatModelVo chatModelVo = context.getChatModelVo();
+
+ try {
+ // 1. 保存用户消息
+ String content = extractUserContent(context);
+ saveChatMessage(context.getChatRequest(), userId, content,
+ RoleType.USER.getName(), chatModelVo);
+
+ // 2. 执行 Agent 任务
+ String result = doAgent(content, chatModelVo);
+
+ // 3. 发送结果并保存
+ SseMessageUtils.sendMessage(userId, result);
+ SseMessageUtils.completeConnection(userId, tokenValue);
+ saveChatMessage(context.getChatRequest(), userId, result,
+ RoleType.ASSISTANT.getName(), chatModelVo);
+
+ } catch (Exception e) {
+ log.error("Agent 执行失败: {}", e.getMessage(), e);
+ SseMessageUtils.sendMessage(userId, "Agent 执行失败:" + e.getMessage());
+ SseMessageUtils.completeConnection(userId, tokenValue);
+ }
+
+ return context.getEmitter();
+ }
+
+ /**
+ * 执行 Agent 任务
+ */
+ private String doAgent(String userMessage, ChatModelVo chatModelVo) {
+ log.info("执行 Agent 任务,消息: {}", userMessage);
+
+ try {
+ // 1. 加载 LLM 模型
+ QwenChatModel qwenChatModel = QwenChatModel.builder()
+ .apiKey(chatModelVo.getApiKey())
+ .modelName(chatModelVo.getModelName())
+ .build();
+
+ // 2. 获取内置工具
+ List