ai编程助手

This commit is contained in:
ageerle
2025-07-07 12:36:53 +08:00
parent c1a178c0be
commit 0eff37fa51
50 changed files with 11899 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
spring:
application:
name: springAI-alibaba-copilot
ai:
openai:
base-url: https://dashscope.aliyuncs.com/compatible-mode
api-key: xx
chat:
options:
model: qwen-plus
server:
port: 8080
app:
# 工作目录配置
workspace:
# 使用 ${file.separator} 或让 Java 代码处理路径拼接
root-directory: ${user.dir}/workspace # 改为使用正斜杠Java会自动转换
max-file-size: 10485760 # 10MB
allowed-extensions:
- .txt
- .md
- .java
- .js
- .ts
- .json
- .xml
- .yml
- .yaml
- .properties
- .html
- .css
- .sql
# 浏览器自动打开配置
browser:
# 是否启用项目启动后自动打开浏览器
auto-open: true
# 要打开的URL默认为项目首页
url: http://localhost:${server.port:8080}
# 启动后延迟打开时间(秒)
delay-seconds: 2
# 安全配置
security:
approval-mode: DEFAULT # DEFAULT, AUTO_EDIT, YOLO
dangerous-commands:
- rm
- del
- format
- fdisk
- mkfs
# 工具配置
tools:
read-file:
enabled: true
max-lines-per-read: 1000
write-file:
enabled: true
backup-enabled: true
edit-file:
enabled: true
diff-context-lines: 3
list-directory:
enabled: true
max-depth: 5
shell:
enabled: true
timeout-seconds: 30
logging:
level:
com.example.demo: DEBUG
com.example.demo.tools: INFO
com.example.demo.controller: INFO
com.example.demo.service: INFO
com.example.demo.config: DEBUG
# 禁用 Spring AI 默认工具调用日志,使用我们的自定义日志
org.springframework.ai.model.tool.DefaultToolCallingManager: WARN
org.springframework.ai.tool.method.MethodToolCallback: WARN
org.springframework.ai: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
file:
name: logs/copilot-file-ops.log

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,332 @@
/**
* SpringAI Alibaba Copilot - 主JavaScript文件
* 处理聊天界面交互、SSE连接和工具日志显示
*/
// 全局变量
const messagesContainer = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const clearBtn = document.getElementById('clearBtn');
const loading = document.getElementById('loading');
const status = document.getElementById('status');
// 全局错误处理
window.addEventListener('error', function(event) {
console.error('Global JavaScript error:', event.error);
if (event.error && event.error.message && event.error.message.includes('userMessage')) {
console.error('Detected userMessage error, this might be a variable scope issue');
}
});
// 函数声明会被提升,但为了安全起见,我们在页面加载后再设置全局引用
// 发送消息
async function sendMessage() {
const message = messageInput.value.trim();
if (!message) return;
// 添加用户消息
addMessage('user', message);
messageInput.value = '';
// 显示加载状态
showLoading(true);
setButtonsEnabled(false);
try {
const response = await fetch('/api/chat/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: message })
});
const data = await response.json();
if (data.success) {
// 如果是异步任务工具调用建立SSE连接
if (data.taskId && data.asyncTask) {
// 先显示等待状态的工具卡片
showWaitingToolCard();
logStreamManager.startLogStream(data.taskId);
showStatus('任务已启动,正在建立实时连接...', 'success');
} else if (data.streamResponse) {
// 流式对话响应
handleStreamResponse(message);
showStatus('开始流式对话...', 'success');
} else {
// 同步任务,直接显示结果
addMessage('assistant', data.message);
// 显示连续对话统计信息
let statusMessage = 'Message sent successfully';
if (data.totalTurns && data.totalTurns > 1) {
statusMessage += ` (${data.totalTurns} turns`;
if (data.totalDurationMs) {
statusMessage += `, ${(data.totalDurationMs / 1000).toFixed(1)}s`;
}
statusMessage += ')';
if (data.reachedMaxTurns) {
statusMessage += ' - Reached max turns limit';
}
if (data.stopReason) {
statusMessage += ` - ${data.stopReason}`;
}
}
showStatus(statusMessage, 'success');
}
} else {
addMessage('assistant', data.message);
showStatus('Error: ' + data.message, 'error');
}
} catch (error) {
console.error('Error:', error);
// 更安全的错误处理
const errorMessage = error && error.message ? error.message : 'Unknown error occurred';
addMessage('assistant', 'Sorry, there was an error processing your request: ' + errorMessage);
showStatus('Network error: ' + errorMessage, 'error');
} finally {
showLoading(false);
setButtonsEnabled(true);
messageInput.focus();
}
}
// 快速操作
function quickAction(message) {
messageInput.value = message;
sendMessage();
}
// 清除历史
async function clearHistory() {
try {
await fetch('/api/chat/clear', { method: 'POST' });
messagesContainer.innerHTML = '';
showStatus('History cleared', 'success');
} catch (error) {
showStatus('Error clearing history', 'error');
}
}
// 添加消息到界面
function addMessage(role, content) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
// 处理代码块和格式化
const formattedContent = formatMessage(content);
messageDiv.innerHTML = `
<div>
<div class="message-role">${role === 'user' ? 'You' : 'Assistant'}</div>
<div class="message-content">${formattedContent}</div>
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 格式化消息内容
function formatMessage(content) {
// 简单的代码块处理
content = content.replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
// 处理行内代码
content = content.replace(/`([^`]+)`/g, '<code style="background: #f0f0f0; padding: 2px 4px; border-radius: 3px;">$1</code>');
// 处理换行
content = content.replace(/\n/g, '<br>');
return content;
}
// 显示/隐藏加载状态
function showLoading(show) {
loading.classList.toggle('show', show);
}
// 启用/禁用按钮
function setButtonsEnabled(enabled) {
sendBtn.disabled = !enabled;
clearBtn.disabled = !enabled;
}
// 显示状态消息
function showStatus(message, type) {
status.textContent = message;
status.className = `status ${type}`;
status.style.display = 'block';
setTimeout(() => {
status.style.display = 'none';
}, 3000);
}
// 显示等待状态的工具卡片
function showWaitingToolCard() {
const waitingCard = document.createElement('div');
waitingCard.className = 'tool-log-container waiting';
waitingCard.id = 'waiting-tool-card';
waitingCard.innerHTML = `
<div class="tool-log-header">
<span class="tool-log-title">🔧 工具执行准备中</span>
<span class="connection-status connecting">连接中...</span>
</div>
<div class="tool-log-content">
<div class="waiting-message">
<div class="loading-spinner"></div>
<div class="waiting-text">正在等待工具执行推送...</div>
<div class="waiting-hint">AI正在分析您的请求并准备执行相应的工具操作</div>
</div>
</div>
`;
messagesContainer.appendChild(waitingCard);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 处理流式响应
function handleStreamResponse(userMessage) {
console.log('🌊 开始处理流式响应,消息:', userMessage);
// 参数验证
if (!userMessage) {
console.error('handleStreamResponse: userMessage is undefined or empty');
showStatus('流式响应参数错误', 'error');
return;
}
// 创建流式消息容器
const streamMessageId = 'stream-message-' + Date.now();
const streamContainer = document.createElement('div');
streamContainer.className = 'message assistant streaming';
streamContainer.id = streamMessageId;
streamContainer.innerHTML = `
<div class="message-content">
<div class="stream-content"></div>
<div class="stream-indicator">
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
`;
messagesContainer.appendChild(streamContainer);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// 使用fetch API处理流式响应
const streamContent = streamContainer.querySelector('.stream-content');
const streamIndicator = streamContainer.querySelector('.stream-indicator');
let fullContent = '';
fetch('/api/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: userMessage })
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
function readStream() {
return reader.read().then(({ done, value }) => {
if (done) {
console.log('✅ 流式响应完成');
streamIndicator.style.display = 'none';
streamContainer.classList.remove('streaming');
showStatus('流式对话完成', 'success');
return;
}
const chunk = decoder.decode(value, { stream: true });
console.log('📨 收到流式数据块:', chunk);
// 处理SSE格式的数据
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.substring(6);
if (data === '[DONE]') {
console.log('✅ 流式响应完成');
streamIndicator.style.display = 'none';
streamContainer.classList.remove('streaming');
showStatus('流式对话完成', 'success');
return;
}
// 追加内容
fullContent += data;
streamContent.textContent = fullContent;
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
}
return readStream();
});
}
return readStream();
})
.catch(error => {
console.error('❌ 流式响应错误:', error);
const errorMessage = error && error.message ? error.message : 'Unknown stream error';
streamIndicator.innerHTML = '<span class="error">连接错误: ' + errorMessage + '</span>';
showStatus('流式对话连接错误: ' + errorMessage, 'error');
});
}
// 移除等待状态卡片
function removeWaitingToolCard() {
const waitingCard = document.getElementById('waiting-tool-card');
if (waitingCard) {
waitingCard.remove();
}
}
// 事件监听器
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// 调试函数
function debugVariables() {
console.log('=== Debug Variables ===');
console.log('messagesContainer:', messagesContainer);
console.log('messageInput:', messageInput);
console.log('sendBtn:', sendBtn);
console.log('clearBtn:', clearBtn);
console.log('loading:', loading);
console.log('status:', status);
console.log('addMessage function:', typeof addMessage);
console.log('showStatus function:', typeof showStatus);
console.log('logStreamManager:', typeof logStreamManager);
}
// 页面加载完成后聚焦输入框
window.addEventListener('load', function() {
messageInput.focus();
// 确保函数在全局作用域中可用
window.addMessage = addMessage;
window.showStatus = showStatus;
// 调试信息
debugVariables();
});

View File

@@ -0,0 +1,190 @@
/**
* SSE日志流管理器
* 负责管理Server-Sent Events连接和工具日志显示
*/
// SSE实时日志管理器
class LogStreamManager {
constructor() {
this.activeConnections = new Map(); // taskId -> EventSource
this.toolLogDisplays = new Map(); // taskId -> ToolLogDisplay
}
// 建立SSE连接
startLogStream(taskId) {
if (this.activeConnections.has(taskId)) {
console.log('SSE连接已存在:', taskId);
return;
}
console.log('🔗 建立SSE连接:', taskId);
// 创建工具日志显示组件
const toolLogDisplay = new ToolLogDisplay(taskId);
this.toolLogDisplays.set(taskId, toolLogDisplay);
// 建立EventSource连接
const eventSource = new EventSource(`/api/logs/stream/${taskId}`);
eventSource.onopen = () => {
console.log('✅ SSE连接建立成功:', taskId);
toolLogDisplay.showConnectionStatus('已连接');
};
eventSource.onmessage = (event) => {
try {
const logEvent = JSON.parse(event.data);
console.log('📨 收到日志事件:', logEvent);
this.handleLogEvent(taskId, logEvent);
} catch (error) {
console.error('解析日志事件失败:', error);
}
};
// 监听特定的 "log" 事件
eventSource.addEventListener('log', (event) => {
try {
const logEvent = JSON.parse(event.data);
console.log('📨 收到log事件:', logEvent);
this.handleLogEvent(taskId, logEvent);
} catch (error) {
console.error('解析log事件失败:', error);
}
});
eventSource.onerror = (error) => {
console.error('❌ SSE连接错误:', error);
toolLogDisplay.showConnectionStatus('连接错误');
this.handleConnectionError(taskId);
};
this.activeConnections.set(taskId, eventSource);
}
// 处理日志事件
handleLogEvent(taskId, logEvent) {
const toolLogDisplay = this.toolLogDisplays.get(taskId);
if (!toolLogDisplay) {
console.warn('找不到工具日志显示组件:', taskId);
return;
}
switch (logEvent.type) {
case 'CONNECTION_ESTABLISHED':
toolLogDisplay.showConnectionStatus('已连接');
// 连接建立后如果5秒内没有工具事件显示提示
setTimeout(() => {
const waitingCard = document.getElementById('waiting-tool-card');
if (waitingCard) {
const waitingText = waitingCard.querySelector('.waiting-text');
if (waitingText) {
waitingText.textContent = '连接已建立等待AI开始执行工具...';
}
}
}, 5000);
break;
case 'TOOL_START':
toolLogDisplay.addToolStart(logEvent);
break;
case 'TOOL_SUCCESS':
toolLogDisplay.updateToolSuccess(logEvent);
break;
case 'TOOL_ERROR':
toolLogDisplay.updateToolError(logEvent);
break;
case 'TASK_COMPLETE':
toolLogDisplay.showTaskComplete();
this.handleTaskComplete(taskId);
this.closeConnection(taskId);
break;
default:
console.log('未知日志事件类型:', logEvent.type);
}
}
// 关闭SSE连接
closeConnection(taskId) {
const eventSource = this.activeConnections.get(taskId);
if (eventSource) {
eventSource.close();
this.activeConnections.delete(taskId);
console.log('🔚 关闭SSE连接:', taskId);
}
// 延迟移除显示组件
setTimeout(() => {
const toolLogDisplay = this.toolLogDisplays.get(taskId);
if (toolLogDisplay) {
toolLogDisplay.fadeOut();
this.toolLogDisplays.delete(taskId);
}
}, 5000);
}
// 处理任务完成
async handleTaskComplete(taskId) {
try {
// 获取对话结果
const response = await fetch(`/api/task/result/${taskId}`);
const resultData = await response.json();
// 安全地显示最终结果
if (typeof addMessage === 'function' && resultData && resultData.fullResponse) {
addMessage('assistant', resultData.fullResponse);
} else {
console.error('addMessage function not available or invalid result data');
}
// 显示统计信息
let statusMessage = '对话完成';
if (resultData.totalTurns > 1) {
statusMessage += ` (${resultData.totalTurns}`;
if (resultData.totalDurationMs) {
statusMessage += `, ${(resultData.totalDurationMs / 1000).toFixed(1)}`;
}
statusMessage += ')';
if (resultData.reachedMaxTurns) {
statusMessage += ' - 达到最大轮次限制';
}
if (resultData.stopReason) {
statusMessage += ` - ${resultData.stopReason}`;
}
}
// 安全地调用showStatus函数
if (typeof showStatus === 'function') {
showStatus(statusMessage, 'success');
} else {
console.log(statusMessage);
}
} catch (error) {
console.error('获取对话结果失败:', error);
// 安全地调用showStatus函数
if (typeof showStatus === 'function') {
showStatus('获取对话结果失败', 'error');
} else {
console.error('获取对话结果失败');
}
}
}
// 处理连接错误
handleConnectionError(taskId) {
// 可以实现重连逻辑
console.log('处理连接错误:', taskId);
setTimeout(() => {
if (!this.activeConnections.has(taskId)) {
console.log('尝试重连:', taskId);
this.startLogStream(taskId);
}
}, 3000);
}
}
// 创建SSE日志流管理器实例
const logStreamManager = new LogStreamManager();
// 确保在全局作用域中可用
window.logStreamManager = logStreamManager;

View File

@@ -0,0 +1,149 @@
/**
* 工具日志显示组件
* 负责显示工具执行的实时状态和结果
*/
class ToolLogDisplay {
constructor(taskId) {
this.taskId = taskId;
this.toolCards = new Map(); // toolName -> DOM element
this.container = this.createContainer();
this.appendToPage();
}
// 创建容器
createContainer() {
const container = document.createElement('div');
container.className = 'tool-log-container';
container.id = `tool-log-${this.taskId}`;
container.innerHTML = `
<div class="tool-log-header">
<span class="tool-log-title">🔧 工具执行日志</span>
<span class="connection-status">连接中...</span>
</div>
<div class="tool-log-content">
<!-- 工具卡片将在这里动态添加 -->
</div>
`;
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 = `
<div class="tool-header">
<span class="tool-icon">${logEvent.icon}</span>
<span class="tool-name">${logEvent.toolName}</span>
<span class="tool-status">⏳ 执行中</span>
</div>
<div class="tool-file">📁 ${this.getFileName(logEvent.filePath)}</div>
<div class="tool-message">${logEvent.message}</div>
<div class="tool-time">开始时间: ${logEvent.timestamp}</div>
`;
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 = `
<span class="tool-log-title">🎉 任务执行完成</span>
<span class="connection-status completed">已完成</span>
`;
}
// 淡出效果
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;
}
}

View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SpringAI Alibaba Copilot</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<div class="container">
<div class="header">
<h1>🤖 SpringAI Alibaba 编码助手</h1>
<p>AI助手将分析您的需求制定执行计划并逐步完成任务</p>
</div>
<div class="main-content">
<div class="chat-area">
<div class="messages" id="messages">
<div class="message assistant">
<div>
<div class="message-role">Assistant</div>
<div class="message-content">
👋 Hello! I'm your AI file operations assistant. I can help you:
<br><br>
📁 <strong>Read files</strong> - View file contents with pagination support
<br>✏️ <strong>Write files</strong> - Create new files or overwrite existing ones
<br>🔧 <strong>Edit files</strong> - Make precise edits with diff preview
<br>📋 <strong>List directories</strong> - Browse directory structure
<br><br>
Try asking me to create a simple project, read a file, or explore the workspace!
<br><br>
<em>Workspace: /workspace</em>
</div>
</div>
</div>
</div>
<div class="loading" id="loading">
<div>🤔 AI is thinking...</div>
</div>
<div class="input-area">
<input type="text" id="messageInput" placeholder="Ask me to create files, read content, or manage your project..." />
<button onclick="sendMessage()" id="sendBtn">Send</button>
<button onclick="clearHistory()" class="clear-btn" id="clearBtn">Clear</button>
</div>
</div>
<div class="sidebar">
<h3>🚀 Quick Actions</h3>
<div class="quick-actions">
<div class="quick-action" onclick="quickAction('List the workspace directory')">
📁 List workspace directory
</div>
<div class="quick-action" onclick="quickAction('Create a simple Java Hello World project')">
☕ Create Java Hello World
</div>
<div class="quick-action" onclick="quickAction('Create a simple web project with HTML, CSS and JS')">
🌐 Create web project
</div>
<div class="quick-action" onclick="quickAction('Create a README.md file for this project')">
📝 Create README.md
</div>
<div class="quick-action" onclick="quickAction('Show me the structure of the current directory recursively')">
🌳 Show directory tree
</div>
<div class="quick-action" onclick="quickAction('Create a simple Python script that prints hello world')">
🐍 Create Python script
</div>
</div>
<h3>🔄 Continuous Task Tests</h3>
<div class="quick-actions">
<div class="quick-action" onclick="quickAction('Create a complete React project with components, styles, and package.json')">
⚛️ Create React project
</div>
<div class="quick-action" onclick="quickAction('Create a full-stack todo app with HTML, CSS, JavaScript frontend and Node.js backend')">
📋 Create Todo App
</div>
<div class="quick-action" onclick="quickAction('Create a Spring Boot REST API project with controller, service, and model classes')">
🍃 Create Spring Boot API
</div>
<div class="quick-action" onclick="quickAction('Create a complete blog website with multiple HTML pages, CSS styles, and JavaScript functionality')">
📰 Create Blog Website
</div>
</div>
<div id="status" class="status" style="display: none;"></div>
<div style="margin-top: 30px;">
<h3>💡 Tips</h3>
<div style="font-size: 12px; color: #666; line-height: 1.4;">
• Ask for step-by-step project creation<br>
• Request file content before editing<br>
• Use specific file paths<br>
• Ask for directory structure first<br>
• Try continuous operations
</div>
</div>
</div>
</div>
</div>
<!-- JavaScript文件引用 -->
<script src="/js/tool-log-display.js"></script>
<script src="/js/sse-manager.js"></script>
<script src="/js/main.js"></script>
</body>
</html>