mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-17 22:03:39 +00:00
ai编程助手
This commit is contained in:
@@ -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
@@ -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();
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user