Compare commits
No commits in common. "f48facde942f938111ffdf2622f1c77f7a3fd512" and "dae78fffc324b3ca47cbf45b87bac404813efc1d" have entirely different histories.
f48facde94
...
dae78fffc3
@ -1,175 +0,0 @@
|
|||||||
/* Agent聊天区域样式 */
|
|
||||||
.agent-chat {
|
|
||||||
margin-top: 30px;
|
|
||||||
padding: 20px;
|
|
||||||
background: linear-gradient(145deg, #e6e6e6, #ffffff);
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 10px 10px 20px rgba(0, 0, 0, 0.1), -10px -10px 20px rgba(255, 255, 255, 0.7);
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-chat h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LLM配置区域 */
|
|
||||||
.llm-config {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: inset 3px 3px 6px rgba(0, 0, 0, 0.1), inset -3px -3px 6px rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.llm-config h4 {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
color: #555;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row label {
|
|
||||||
width: 70px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 8px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #ffffff;
|
|
||||||
box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.1), inset -2px -2px 4px rgba(255, 255, 255, 0.7);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row input:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: inset 3px 3px 6px rgba(0, 0, 0, 0.15), inset -3px -3px 6px rgba(255, 255, 255, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-config {
|
|
||||||
background: linear-gradient(145deg, #2196F3, #1976D2);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.1), -3px -3px 6px rgba(255, 255, 255, 0.7);
|
|
||||||
margin-left: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-config:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.15), -5px -5px 10px rgba(255, 255, 255, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 聊天输入区域 */
|
|
||||||
.chat-input {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-input input {
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
box-shadow: inset 5px 5px 10px rgba(0, 0, 0, 0.1), inset -5px -5px 10px rgba(255, 255, 255, 0.7);
|
|
||||||
width: 250px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-history {
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 15px;
|
|
||||||
box-shadow: inset 5px 5px 10px rgba(0, 0, 0, 0.1), inset -5px -5px 10px rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
max-width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.user {
|
|
||||||
background: linear-gradient(145deg, #E3F2FD, #BBDEFB);
|
|
||||||
margin-left: auto;
|
|
||||||
box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.1), -3px -3px 6px rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.agent {
|
|
||||||
background: linear-gradient(145deg, #E8F5E8, #C8E6C9);
|
|
||||||
margin-right: auto;
|
|
||||||
box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.1), -3px -3px 6px rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message p {
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式设计 - Agent聊天 */
|
|
||||||
@media (max-width: 650px) {
|
|
||||||
.chat-input {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-input input {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-chat {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
max-width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row label {
|
|
||||||
width: auto;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-config {
|
|
||||||
margin-left: 0;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -268,5 +268,4 @@
|
|||||||
.title {
|
.title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,17 +120,17 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.path-input input {
|
.path-input input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-save {
|
.btn-save {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.path-management {
|
.path-management {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,16 @@ h1 {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-record {
|
||||||
|
background-color: #ff9800;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-playback {
|
||||||
|
background-color: #2196f3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
|||||||
@ -1,180 +0,0 @@
|
|||||||
// Agent聊天功能
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const agentInput = document.getElementById('agentInput');
|
|
||||||
const sendAgentBtn = document.getElementById('sendAgentBtn');
|
|
||||||
const chatHistory = document.getElementById('chatHistory');
|
|
||||||
const status = document.getElementById('status');
|
|
||||||
|
|
||||||
// LLM配置相关元素
|
|
||||||
const apiUrlInput = document.getElementById('apiUrlInput');
|
|
||||||
const apiKeyInput = document.getElementById('apiKeyInput');
|
|
||||||
const modelInput = document.getElementById('modelInput');
|
|
||||||
const saveConfigBtn = document.getElementById('saveConfigBtn');
|
|
||||||
|
|
||||||
// 添加清除缓存按钮
|
|
||||||
const clearCacheBtn = document.createElement('button');
|
|
||||||
clearCacheBtn.className = 'btn btn-config';
|
|
||||||
clearCacheBtn.id = 'clearCacheBtn';
|
|
||||||
clearCacheBtn.textContent = '清除缓存';
|
|
||||||
saveConfigBtn.parentNode.appendChild(clearCacheBtn);
|
|
||||||
|
|
||||||
// 从本地存储加载配置
|
|
||||||
function loadConfigFromLocalStorage() {
|
|
||||||
const savedConfig = localStorage.getItem('llmConfig');
|
|
||||||
if (savedConfig) {
|
|
||||||
try {
|
|
||||||
const config = JSON.parse(savedConfig);
|
|
||||||
apiUrlInput.value = config.api_url || '';
|
|
||||||
apiKeyInput.value = config.api_key || '';
|
|
||||||
modelInput.value = config.model || '';
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.log('解析本地存储配置失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存配置到本地存储
|
|
||||||
function saveConfigToLocalStorage(config) {
|
|
||||||
localStorage.setItem('llmConfig', JSON.stringify(config));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除本地存储
|
|
||||||
function clearLocalStorage() {
|
|
||||||
localStorage.removeItem('llmConfig');
|
|
||||||
apiUrlInput.value = '';
|
|
||||||
apiKeyInput.value = '';
|
|
||||||
modelInput.value = '';
|
|
||||||
status.innerHTML = '<p>缓存已清除</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载配置
|
|
||||||
function loadConfig() {
|
|
||||||
// 优先从本地存储加载
|
|
||||||
if (!loadConfigFromLocalStorage()) {
|
|
||||||
// 如果本地存储没有,从后端加载
|
|
||||||
fetch('/agent_config')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status === 'success') {
|
|
||||||
apiUrlInput.value = data.api_url || '';
|
|
||||||
apiKeyInput.value = data.api_key || '';
|
|
||||||
modelInput.value = data.model || '';
|
|
||||||
// 保存到本地存储
|
|
||||||
saveConfigToLocalStorage({
|
|
||||||
api_url: data.api_url || '',
|
|
||||||
api_key: data.api_key || '',
|
|
||||||
model: data.model || ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log('加载配置失败:', error);
|
|
||||||
status.innerHTML = `<p>加载配置失败: ${error.message}</p>`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
function saveConfig() {
|
|
||||||
const config = {
|
|
||||||
api_url: apiUrlInput.value.trim(),
|
|
||||||
api_key: apiKeyInput.value.trim(),
|
|
||||||
model: modelInput.value.trim()
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('保存配置:', config);
|
|
||||||
|
|
||||||
// 保存到本地存储
|
|
||||||
saveConfigToLocalStorage(config);
|
|
||||||
|
|
||||||
fetch('/agent_config', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(config)
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
console.log('保存配置响应:', data);
|
|
||||||
if (data.status === 'success') {
|
|
||||||
status.innerHTML = '<p>配置已保存</p>';
|
|
||||||
} else {
|
|
||||||
status.innerHTML = `<p>配置保存失败: ${data.message}</p>`;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('配置保存失败:', error);
|
|
||||||
status.innerHTML = `<p>配置保存失败: ${error.message}</p>`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送消息
|
|
||||||
function sendMessage() {
|
|
||||||
const text = agentInput.value.trim();
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
addMessage('user', text);
|
|
||||||
agentInput.value = '';
|
|
||||||
status.innerHTML = '<p>处理中...</p>';
|
|
||||||
|
|
||||||
fetch('/agent_chat', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ text: text })
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
console.log('聊天响应:', data);
|
|
||||||
addMessage('agent', data.message || '处理完成');
|
|
||||||
|
|
||||||
// 更新状态
|
|
||||||
status.innerHTML = `<p>${data.message || '处理完成'}</p>`;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('通信错误:', error);
|
|
||||||
addMessage('agent', '通信错误,请重试');
|
|
||||||
status.innerHTML = `<p>通信错误: ${error.message}</p>`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加消息到聊天历史
|
|
||||||
function addMessage(type, content) {
|
|
||||||
const messageDiv = document.createElement('div');
|
|
||||||
messageDiv.className = `message ${type}`;
|
|
||||||
messageDiv.innerHTML = `<p>${content}</p>`;
|
|
||||||
chatHistory.appendChild(messageDiv);
|
|
||||||
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定发送按钮点击事件
|
|
||||||
if (sendAgentBtn) {
|
|
||||||
sendAgentBtn.addEventListener('click', sendMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定回车键发送
|
|
||||||
if (agentInput) {
|
|
||||||
agentInput.addEventListener('keypress', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定保存配置按钮
|
|
||||||
if (saveConfigBtn) {
|
|
||||||
saveConfigBtn.addEventListener('click', saveConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定清除缓存按钮
|
|
||||||
if (clearCacheBtn) {
|
|
||||||
clearCacheBtn.addEventListener('click', clearLocalStorage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载时获取配置
|
|
||||||
loadConfig();
|
|
||||||
});
|
|
||||||
@ -1,12 +1,25 @@
|
|||||||
// 获取按钮和状态元素
|
// 获取按钮和状态元素
|
||||||
const forwardBtn = document.getElementById('forwardBtn');
|
const forwardBtn = document.getElementById('forwardBtn');
|
||||||
const backwardBtn = document.getElementById('backwardBtn');
|
const backwardBtn = document.getElementById('backwardBtn');
|
||||||
|
const recordBtn = document.getElementById('recordBtn');
|
||||||
|
const playbackBtn = document.getElementById('playbackBtn');
|
||||||
const status = document.getElementById('status');
|
const status = document.getElementById('status');
|
||||||
|
|
||||||
|
// 录制相关变量
|
||||||
|
let isRecording = false;
|
||||||
|
let recordedActions = [];
|
||||||
|
let recordingStartTime = 0;
|
||||||
|
|
||||||
// 实际控制函数
|
// 实际控制函数
|
||||||
function controlMotor(direction) {
|
function controlMotor(direction) {
|
||||||
status.innerHTML = `<p>正在${direction === 'forward' ? '前进' : '后退'}...</p>`;
|
status.innerHTML = `<p>正在${direction === 'forward' ? '前进' : '后退'}...</p>`;
|
||||||
|
|
||||||
|
// 记录操作(如果正在录制)
|
||||||
|
if (isRecording) {
|
||||||
|
const timestamp = Date.now() - recordingStartTime;
|
||||||
|
recordedActions.push({ direction, timestamp });
|
||||||
|
}
|
||||||
|
|
||||||
// 发送AJAX请求到后端服务器
|
// 发送AJAX请求到后端服务器
|
||||||
fetch('/control', {
|
fetch('/control', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -28,6 +41,50 @@ function controlMotor(direction) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 录制按钮点击事件
|
||||||
|
recordBtn.addEventListener('click', () => {
|
||||||
|
if (isRecording) {
|
||||||
|
// 停止录制
|
||||||
|
isRecording = false;
|
||||||
|
recordBtn.textContent = '开始录制';
|
||||||
|
status.innerHTML = `<p>录制完成,共记录 ${recordedActions.length} 个操作</p>`;
|
||||||
|
} else {
|
||||||
|
// 开始录制
|
||||||
|
isRecording = true;
|
||||||
|
recordedActions = [];
|
||||||
|
recordingStartTime = Date.now();
|
||||||
|
recordBtn.textContent = '停止录制';
|
||||||
|
status.innerHTML = `<p>开始录制...</p>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 回放按钮点击事件
|
||||||
|
playbackBtn.addEventListener('click', () => {
|
||||||
|
if (recordedActions.length === 0) {
|
||||||
|
status.innerHTML = `<p>没有可回放的操作</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.innerHTML = `<p>开始回放...</p>`;
|
||||||
|
|
||||||
|
// 按时间顺序回放操作
|
||||||
|
let lastTimestamp = 0;
|
||||||
|
recordedActions.forEach((action, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
controlMotor(action.direction);
|
||||||
|
|
||||||
|
// 最后一个操作完成后显示回放完成
|
||||||
|
if (index === recordedActions.length - 1) {
|
||||||
|
setTimeout(() => {
|
||||||
|
status.innerHTML = `<p>回放完成</p>`;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}, action.timestamp - lastTimestamp);
|
||||||
|
|
||||||
|
lastTimestamp = action.timestamp;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 按钮点击事件
|
// 按钮点击事件
|
||||||
forwardBtn.addEventListener('click', () => {
|
forwardBtn.addEventListener('click', () => {
|
||||||
controlMotor('forward');
|
controlMotor('forward');
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "轨迹",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"direction": "forward",
|
|
||||||
"timestamp": 858
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"direction": "stop",
|
|
||||||
"timestamp": 1166
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"direction": "backward",
|
|
||||||
"timestamp": 2327
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"direction": "stop",
|
|
||||||
"timestamp": 2642
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
308
main/agent.py
308
main/agent.py
@ -1,308 +0,0 @@
|
|||||||
import time
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
import re
|
|
||||||
import threading
|
|
||||||
from queue import Queue
|
|
||||||
|
|
||||||
task_queue = Queue()
|
|
||||||
|
|
||||||
llm_config = {
|
|
||||||
"api_url": "",
|
|
||||||
"api_key": "",
|
|
||||||
"model": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
def load_skills_md():
|
|
||||||
try:
|
|
||||||
with open('agent/skills.md', 'r', encoding='utf-8') as f:
|
|
||||||
return f.read()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"加载技能文档出错: {e}")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def llm_call(prompt):
|
|
||||||
if not llm_config["api_url"] or not llm_config["api_key"] or not llm_config["model"]:
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 确保API URL以/chat/completions结尾(除了讯飞MaaS API)
|
|
||||||
api_url = llm_config["api_url"].strip()
|
|
||||||
# 对于非讯飞MaaS API,自动添加/chat/completions路径
|
|
||||||
if "maas-api.cn" not in api_url and not api_url.endswith("/chat/completions"):
|
|
||||||
if api_url.endswith("/"):
|
|
||||||
api_url += "chat/completions"
|
|
||||||
else:
|
|
||||||
api_url += "/chat/completions"
|
|
||||||
|
|
||||||
# 准备请求头
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查API Key格式,如果是client_id:client_secret格式,使用Basic认证
|
|
||||||
api_key = llm_config["api_key"]
|
|
||||||
if ":" in api_key:
|
|
||||||
# 讯飞MaaS API格式:client_id:client_secret
|
|
||||||
import base64
|
|
||||||
auth_str = base64.b64encode(api_key.encode()).decode()
|
|
||||||
headers["Authorization"] = f"Basic {auth_str}"
|
|
||||||
else:
|
|
||||||
# OpenAI API格式:Bearer token
|
|
||||||
headers["Authorization"] = f"Bearer {api_key}"
|
|
||||||
|
|
||||||
# 构建请求数据
|
|
||||||
# 优先使用讯飞MaaS API格式
|
|
||||||
if "maas-api.cn" in api_url:
|
|
||||||
# 讯飞MaaS API格式
|
|
||||||
data = {
|
|
||||||
"model": llm_config["model"],
|
|
||||||
"messages": [
|
|
||||||
{"role": "system", "content": "你是一个智能垃圾桶控制助手,请根据用户输入返回对应的动作指令。"},
|
|
||||||
{"role": "user", "content": prompt}
|
|
||||||
],
|
|
||||||
"temperature": 0.7,
|
|
||||||
"max_tokens": 500
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# OpenAI API格式
|
|
||||||
data = {
|
|
||||||
"model": llm_config["model"],
|
|
||||||
"messages": [
|
|
||||||
{"role": "system", "content": "你是一个智能垃圾桶控制助手,请根据用户输入返回对应的动作指令。"},
|
|
||||||
{"role": "user", "content": prompt}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"调用LLM API: {api_url}")
|
|
||||||
print(f"请求数据: {json.dumps(data, ensure_ascii=False)}")
|
|
||||||
|
|
||||||
response = requests.post(api_url, headers=headers, json=data, timeout=15)
|
|
||||||
print(f"API响应状态码: {response.status_code}")
|
|
||||||
print(f"API响应内容: {response.text}")
|
|
||||||
|
|
||||||
# 检查响应状态码
|
|
||||||
if response.status_code != 200:
|
|
||||||
print(f"API调用失败,状态码: {response.status_code}")
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = response.json()
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
print(f"JSON解析错误: {e}")
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
|
|
||||||
# 处理不同API的响应格式
|
|
||||||
if "maas-api.cn" in api_url:
|
|
||||||
# 讯飞MaaS API响应格式
|
|
||||||
if "choices" in result and len(result["choices"]) > 0:
|
|
||||||
content = result["choices"][0]["message"]["content"]
|
|
||||||
return safe_parse(content)
|
|
||||||
else:
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
else:
|
|
||||||
# OpenAI API响应格式
|
|
||||||
if "choices" in result and len(result["choices"]) > 0:
|
|
||||||
content = result["choices"][0]["message"]["content"]
|
|
||||||
return safe_parse(content)
|
|
||||||
else:
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
except Exception as e:
|
|
||||||
print(f"LLM调用出错: {e}")
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
|
|
||||||
def safe_parse(result):
|
|
||||||
try:
|
|
||||||
if isinstance(result, dict):
|
|
||||||
return result
|
|
||||||
if isinstance(result, list):
|
|
||||||
return result
|
|
||||||
result = result.strip()
|
|
||||||
if result.startswith("```"):
|
|
||||||
lines = result.split("\n")
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if not line.startswith("```") and line.strip():
|
|
||||||
result = "\n".join(lines[i:])
|
|
||||||
break
|
|
||||||
if result.startswith("```"):
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
if result.endswith("```"):
|
|
||||||
result = result[:-3].strip()
|
|
||||||
|
|
||||||
# 尝试解析为JSON数组(多个动作)
|
|
||||||
try:
|
|
||||||
parsed = json.loads(result)
|
|
||||||
if isinstance(parsed, list):
|
|
||||||
return parsed
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 尝试解析为JSON对象(单个动作)
|
|
||||||
for line in result.split("\n"):
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith("{") and line.endswith("}"):
|
|
||||||
return json.loads(line)
|
|
||||||
|
|
||||||
match = re.search(r'\{[^}]+\}', result)
|
|
||||||
if match:
|
|
||||||
return json.loads(match.group())
|
|
||||||
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
except:
|
|
||||||
return {"action": "stop", "args": {}}
|
|
||||||
|
|
||||||
def execute_skill(action, args, motor_module):
|
|
||||||
ALLOWED = [
|
|
||||||
"move_forward", "move_backward", "turn_left", "turn_right",
|
|
||||||
"stop", "play_path", "list_paths", "delete_path", "save_path"
|
|
||||||
]
|
|
||||||
|
|
||||||
if action not in ALLOWED:
|
|
||||||
return {"status": "error", "message": "不允许的动作"}
|
|
||||||
|
|
||||||
try:
|
|
||||||
if action == "move_forward":
|
|
||||||
print("控制垃圾桶前进")
|
|
||||||
motor_module.backward(speed=0.6)
|
|
||||||
def stop_after_duration():
|
|
||||||
time.sleep(args.get("duration", 1))
|
|
||||||
motor_module.stop()
|
|
||||||
threading.Thread(target=stop_after_duration).start()
|
|
||||||
return {"status": "success", "message": f"前进{args.get('duration', 1)}秒"}
|
|
||||||
|
|
||||||
elif action == "move_backward":
|
|
||||||
print("控制垃圾桶后退")
|
|
||||||
motor_module.forward(speed=0.6)
|
|
||||||
def stop_after_duration():
|
|
||||||
time.sleep(args.get("duration", 1))
|
|
||||||
motor_module.stop()
|
|
||||||
threading.Thread(target=stop_after_duration).start()
|
|
||||||
return {"status": "success", "message": f"后退{args.get('duration', 1)}秒"}
|
|
||||||
|
|
||||||
elif action == "turn_left":
|
|
||||||
print("控制垃圾桶左旋转")
|
|
||||||
motor_module.rotate_left(speed=0.6)
|
|
||||||
def stop_after_duration():
|
|
||||||
time.sleep(args.get("duration", 1))
|
|
||||||
motor_module.stop()
|
|
||||||
threading.Thread(target=stop_after_duration).start()
|
|
||||||
return {"status": "success", "message": f"左转{args.get('duration', 1)}秒"}
|
|
||||||
|
|
||||||
elif action == "turn_right":
|
|
||||||
print("控制垃圾桶右旋转")
|
|
||||||
motor_module.rotate_right(speed=0.6)
|
|
||||||
def stop_after_duration():
|
|
||||||
time.sleep(args.get("duration", 1))
|
|
||||||
motor_module.stop()
|
|
||||||
threading.Thread(target=stop_after_duration).start()
|
|
||||||
return {"status": "success", "message": f"右转{args.get('duration', 1)}秒"}
|
|
||||||
|
|
||||||
elif action == "stop":
|
|
||||||
print("停止垃圾桶")
|
|
||||||
motor_module.stop()
|
|
||||||
return {"status": "success", "message": "已停止"}
|
|
||||||
|
|
||||||
elif action == "play_path":
|
|
||||||
path_name = args.get("name")
|
|
||||||
if not path_name:
|
|
||||||
return {"status": "error", "message": "路径名称不能为空"}
|
|
||||||
return {"status": "success", "message": f"播放轨迹{path_name}"}
|
|
||||||
|
|
||||||
elif action == "list_paths":
|
|
||||||
return {"status": "success", "message": "获取轨迹列表"}
|
|
||||||
|
|
||||||
elif action == "delete_path":
|
|
||||||
path_name = args.get("name")
|
|
||||||
if not path_name:
|
|
||||||
return {"status": "error", "message": "路径名称不能为空"}
|
|
||||||
return {"status": "success", "message": f"删除轨迹{path_name}"}
|
|
||||||
|
|
||||||
elif action == "save_path":
|
|
||||||
path_name = args.get("name")
|
|
||||||
if not path_name:
|
|
||||||
return {"status": "error", "message": "路径名称不能为空"}
|
|
||||||
return {"status": "success", "message": f"保存轨迹{path_name}"}
|
|
||||||
|
|
||||||
else:
|
|
||||||
return {"status": "error", "message": "无效的动作"}
|
|
||||||
except Exception as e:
|
|
||||||
print(f"执行技能出错: {e}")
|
|
||||||
return {"status": "error", "message": f"执行技能出错: {e}"}
|
|
||||||
|
|
||||||
def create_agent_routes(app, motor_module):
|
|
||||||
@app.route('/agent_config', methods=['POST'])
|
|
||||||
def agent_config():
|
|
||||||
from flask import request, jsonify
|
|
||||||
data = request.get_json()
|
|
||||||
api_url = data.get('api_url', '')
|
|
||||||
api_key = data.get('api_key', '')
|
|
||||||
model = data.get('model', '')
|
|
||||||
|
|
||||||
llm_config['api_url'] = api_url
|
|
||||||
llm_config['api_key'] = api_key
|
|
||||||
llm_config['model'] = model
|
|
||||||
|
|
||||||
print(f"LLM配置已更新: API={api_url}, Model={model}")
|
|
||||||
return jsonify({'status': 'success', 'message': '配置已保存'})
|
|
||||||
|
|
||||||
@app.route('/agent_config', methods=['GET'])
|
|
||||||
def get_agent_config():
|
|
||||||
from flask import jsonify
|
|
||||||
return jsonify({
|
|
||||||
'status': 'success',
|
|
||||||
'api_url': llm_config['api_url'],
|
|
||||||
'api_key': llm_config['api_key'],
|
|
||||||
'model': llm_config['model']
|
|
||||||
})
|
|
||||||
|
|
||||||
@app.route('/agent_chat', methods=['POST'])
|
|
||||||
def agent_chat():
|
|
||||||
from flask import request, jsonify
|
|
||||||
data = request.get_json()
|
|
||||||
text = data.get('text')
|
|
||||||
|
|
||||||
if not text:
|
|
||||||
return jsonify({'status': 'error', 'message': '输入文本不能为空'})
|
|
||||||
|
|
||||||
try:
|
|
||||||
skills_prompt = load_skills_md()
|
|
||||||
prompt = skills_prompt + "\n用户输入:" + text
|
|
||||||
result = llm_call(prompt)
|
|
||||||
parsed_result = safe_parse(result)
|
|
||||||
|
|
||||||
# 检查是否是多个动作(列表)
|
|
||||||
if isinstance(parsed_result, list) and len(parsed_result) > 0:
|
|
||||||
# 执行第一个动作
|
|
||||||
first_action = parsed_result[0]
|
|
||||||
action = first_action.get("action", "stop")
|
|
||||||
args = first_action.get("args", {})
|
|
||||||
task_queue.put((action, args))
|
|
||||||
response = execute_skill(action, args, motor_module)
|
|
||||||
|
|
||||||
# 如果有后续动作,在第一个动作完成后执行
|
|
||||||
if len(parsed_result) > 1:
|
|
||||||
def execute_next_actions():
|
|
||||||
for i, next_action_data in enumerate(parsed_result[1:]):
|
|
||||||
# 等待前一个动作完成
|
|
||||||
time.sleep(next_action_data.get("args", {}).get("duration", 1))
|
|
||||||
# 添加0.5秒缓冲时间
|
|
||||||
time.sleep(0.5)
|
|
||||||
next_action = next_action_data.get("action", "stop")
|
|
||||||
next_args = next_action_data.get("args", {})
|
|
||||||
task_queue.put((next_action, next_args))
|
|
||||||
execute_skill(next_action, next_args, motor_module)
|
|
||||||
|
|
||||||
threading.Thread(target=execute_next_actions).start()
|
|
||||||
|
|
||||||
return jsonify({"status": "success", "message": f"开始执行复合指令,共{len(parsed_result)}个动作"})
|
|
||||||
else:
|
|
||||||
# 单个动作的处理
|
|
||||||
action = parsed_result.get("action", "stop")
|
|
||||||
args = parsed_result.get("args", {})
|
|
||||||
task_queue.put((action, args))
|
|
||||||
response = execute_skill(action, args, motor_module)
|
|
||||||
return jsonify(response)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Agent聊天出错: {e}")
|
|
||||||
return jsonify({'status': 'error', 'message': f'Agent聊天出错: {e}'})
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
# 垃圾桶控制技能文档
|
|
||||||
|
|
||||||
你是一个智能垃圾桶控制助手,可以调用以下技能控制垃圾桶运动。你必须而且只能返回JSON格式的指令,不要返回任何其他内容。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、基础运动技能
|
|
||||||
|
|
||||||
### 1. move_forward
|
|
||||||
向前移动垃圾桶
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- duration: 持续时间(秒)
|
|
||||||
|
|
||||||
### 2. move_backward
|
|
||||||
向后移动垃圾桶
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- duration: 秒
|
|
||||||
|
|
||||||
### 3. turn_left
|
|
||||||
左转
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- duration: 秒
|
|
||||||
|
|
||||||
### 4. turn_right
|
|
||||||
右转
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- duration: 秒
|
|
||||||
|
|
||||||
### 5. stop
|
|
||||||
停止所有运动
|
|
||||||
|
|
||||||
参数:无
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、轨迹技能(重要)
|
|
||||||
|
|
||||||
### 6. play_path
|
|
||||||
播放已保存轨迹
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- name: 轨迹名称
|
|
||||||
|
|
||||||
说明:
|
|
||||||
调用系统中已经录制好的路径,让垃圾桶自动执行。
|
|
||||||
|
|
||||||
### 7. save_path
|
|
||||||
保存当前录制轨迹
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- name: 轨迹名称
|
|
||||||
|
|
||||||
### 8. list_paths
|
|
||||||
获取所有轨迹列表
|
|
||||||
|
|
||||||
参数:无
|
|
||||||
|
|
||||||
### 9. delete_path
|
|
||||||
删除轨迹
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- name: 轨迹名称
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、规则(必须严格遵守)
|
|
||||||
|
|
||||||
1. **只能返回JSON格式,不要返回任何解释或额外文字**
|
|
||||||
2. 用户说"停"、"停止" → stop
|
|
||||||
3. 用户说"前进X秒" → move_forward,duration为X
|
|
||||||
4. 用户说"后退X秒" → move_backward,duration为X
|
|
||||||
5. 用户说"左转X秒" → turn_left,duration为X
|
|
||||||
6. 用户说"右转X秒" → turn_right,duration为X
|
|
||||||
7. 用户说"执行路径xxx" → play_path,name为xxx
|
|
||||||
8. 用户说"列出路径" → list_paths
|
|
||||||
9. 用户说"删除路径xxx" → delete_path,name为xxx
|
|
||||||
10. **用户说"动作1之后动作2"或"动作1然后动作2"等复合指令时,返回包含多个动作的JSON数组**
|
|
||||||
例如:"前进1秒之后回退1秒" → [{"action": "move_forward", "args": {"duration": 1}}, {"action": "move_backward", "args": {"duration": 1}}]
|
|
||||||
11. 如果不确定运动时间,duration默认1秒
|
|
||||||
12. 如果无法理解用户意图,返回:{"action": "stop", "args": {}}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、输出格式示例
|
|
||||||
|
|
||||||
用户说:前进3秒
|
|
||||||
输出:
|
|
||||||
{"action": "move_forward", "args": {"duration": 3}}
|
|
||||||
|
|
||||||
用户说:停止
|
|
||||||
输出:
|
|
||||||
{"action": "stop", "args": {}}
|
|
||||||
|
|
||||||
用户说:执行路径test1
|
|
||||||
输出:
|
|
||||||
{"action": "play_path", "args": {"name": "test1"}}
|
|
||||||
|
|
||||||
用户说:前进1秒之后回退1秒
|
|
||||||
输出:
|
|
||||||
[{"action": "move_forward", "args": {"duration": 1}}, {"action": "move_backward", "args": {"duration": 1}}]
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**重要:你只能返回JSON格式的指令,不要包含任何其他文字、解释或格式符号(如markdown代码块)。**
|
|
||||||
@ -99,7 +99,7 @@ while True:
|
|||||||
if area < 75: # 过滤噪声,增大阈值
|
if area < 75: # 过滤噪声,增大阈值
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if area > 5000: # 过滤大面积假目标(如整个画面),减小阈值
|
if area > 8000: # 过滤大面积假目标(如整个画面),减小阈值
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 计算轮廓的宽高比,过滤不符合垃圾特征的目标
|
# 计算轮廓的宽高比,过滤不符合垃圾特征的目标
|
||||||
@ -117,7 +117,7 @@ while True:
|
|||||||
best_match = None
|
best_match = None
|
||||||
min_distance = float('inf')
|
min_distance = float('inf')
|
||||||
|
|
||||||
search_radius = 80 # 目标锁定区域半径
|
search_radius = 100 # 目标锁定区域半径
|
||||||
|
|
||||||
for area, cnt, x, y, w, h in valid_contours:
|
for area, cnt, x, y, w, h in valid_contours:
|
||||||
cx = x + w // 2
|
cx = x + w // 2
|
||||||
@ -170,7 +170,7 @@ while True:
|
|||||||
cy = int(alpha * prev_cy + (1 - alpha) * cy)
|
cy = int(alpha * prev_cy + (1 - alpha) * cy)
|
||||||
|
|
||||||
# 绘制画面中心
|
# 绘制画面中心
|
||||||
center_x = 90 # 画面中心x坐标
|
center_x = 50 # 画面中心x坐标
|
||||||
center_y = 120 # 画面中心y坐标
|
center_y = 120 # 画面中心y坐标
|
||||||
cv2.circle(frame, (center_x, center_y), 5, (255, 0, 0), -1)
|
cv2.circle(frame, (center_x, center_y), 5, (255, 0, 0), -1)
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ while True:
|
|||||||
tracking_target = None
|
tracking_target = None
|
||||||
|
|
||||||
# 控制逻辑:根据x轴和y轴坐标控制垃圾桶移动
|
# 控制逻辑:根据x轴和y轴坐标控制垃圾桶移动
|
||||||
center_x = 90 # 画面中心x坐标
|
center_x = 160 # 画面中心x坐标
|
||||||
center_y = 120 # 画面中心y坐标
|
center_y = 120 # 画面中心y坐标
|
||||||
threshold = 15 # 阈值范围
|
threshold = 15 # 阈值范围
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,11 @@
|
|||||||
<title>垃圾桶控制界面</title>
|
<title>垃圾桶控制界面</title>
|
||||||
<link rel="stylesheet" href="CSS/control.css">
|
<link rel="stylesheet" href="CSS/control.css">
|
||||||
<link rel="stylesheet" href="CSS/record.css">
|
<link rel="stylesheet" href="CSS/record.css">
|
||||||
<link rel="stylesheet" href="CSS/agent.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="title">垃圾桶控制界面</h1>
|
<h1 class="title">垃圾桶控制界面</h1>
|
||||||
|
|
||||||
<!-- 手柄风格控制器 -->
|
<!-- 手柄风格控制器 -->
|
||||||
<div class="controller">
|
<div class="controller">
|
||||||
<div class="controller-layout">
|
<div class="controller-layout">
|
||||||
@ -33,13 +32,13 @@
|
|||||||
<button class="btn btn-backward-right" id="backwardRightBtn"><span>↘</span></button>
|
<button class="btn btn-backward-right" id="backwardRightBtn"><span>↘</span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 中间:录制回放控制 -->
|
<!-- 中间:录制回放控制 -->
|
||||||
<div class="center-controls">
|
<div class="center-controls">
|
||||||
<button class="btn btn-record" id="recordBtn"><span>录制</span></button>
|
<button class="btn btn-record" id="recordBtn"><span>录制</span></button>
|
||||||
<button class="btn btn-playback" id="playbackBtn"><span>回放</span></button>
|
<button class="btn btn-playback" id="playbackBtn"><span>回放</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧:旋转控制 -->
|
<!-- 右侧:旋转控制 -->
|
||||||
<div class="rotate-controls">
|
<div class="rotate-controls">
|
||||||
<button class="btn btn-rotate-left" id="rotateLeftBtn"><span>↶</span></button>
|
<button class="btn btn-rotate-left" id="rotateLeftBtn"><span>↶</span></button>
|
||||||
@ -47,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 轨迹管理区域 -->
|
<!-- 轨迹管理区域 -->
|
||||||
<div class="path-management">
|
<div class="path-management">
|
||||||
<h3>轨迹管理</h3>
|
<h3>轨迹管理</h3>
|
||||||
@ -60,48 +59,15 @@
|
|||||||
<ul id="pathList"></ul>
|
<ul id="pathList"></ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 状态显示区域 -->
|
<!-- 状态显示区域 -->
|
||||||
<div class="status" id="status">
|
<div class="status" id="status">
|
||||||
<p>就绪</p>
|
<p>就绪</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Agent聊天区域 -->
|
|
||||||
<div class="agent-chat">
|
|
||||||
<h3>智能控制助手</h3>
|
|
||||||
|
|
||||||
<!-- LLM配置区域 -->
|
|
||||||
<div class="llm-config">
|
|
||||||
<h4>LLM模型配置</h4>
|
|
||||||
<div class="config-row">
|
|
||||||
<label for="apiUrlInput">API URL:</label>
|
|
||||||
<input type="text" id="apiUrlInput" placeholder="例如: https://api.openai.com/v1">
|
|
||||||
</div>
|
|
||||||
<div class="config-row">
|
|
||||||
<label for="apiKeyInput">API Key:</label>
|
|
||||||
<input type="password" id="apiKeyInput" placeholder="输入API密钥">
|
|
||||||
</div>
|
|
||||||
<div class="config-row">
|
|
||||||
<label for="modelInput">模型:</label>
|
|
||||||
<input type="text" id="modelInput" placeholder="例如: gpt-3.5-turbo">
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-config" id="saveConfigBtn">保存配置</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 聊天输入区域 -->
|
|
||||||
<div class="chat-input">
|
|
||||||
<input type="text" id="agentInput" placeholder="输入命令,例如:前进3秒、左转、执行路径test1">
|
|
||||||
<button class="btn btn-save" id="sendAgentBtn">发送</button>
|
|
||||||
</div>
|
|
||||||
<div class="chat-history" id="chatHistory">
|
|
||||||
<!-- 聊天历史记录 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="Javascript/control.js"></script>
|
<script src="Javascript/control.js"></script>
|
||||||
<script src="Javascript/record.js"></script>
|
<script src="Javascript/record.js"></script>
|
||||||
<script src="Javascript/joycon.js"></script>
|
<script src="Javascript/joycon.js"></script>
|
||||||
<script src="Javascript/agent.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -72,25 +72,25 @@ def motor_drive(name, speed):
|
|||||||
# ======================
|
# ======================
|
||||||
# 基础移动
|
# 基础移动
|
||||||
# ======================
|
# ======================
|
||||||
def forward(speed=0.6):
|
def forward(speed=0.8):
|
||||||
motor_drive("M1", -speed)
|
motor_drive("M1", -speed)
|
||||||
motor_drive("M2", -speed)
|
motor_drive("M2", -speed)
|
||||||
motor_drive("M3", speed)
|
motor_drive("M3", speed)
|
||||||
motor_drive("M4", speed)
|
motor_drive("M4", speed)
|
||||||
|
|
||||||
def backward(speed=0.6):
|
def backward(speed=0.8):
|
||||||
motor_drive("M1", speed)
|
motor_drive("M1", speed)
|
||||||
motor_drive("M2", speed)
|
motor_drive("M2", speed)
|
||||||
motor_drive("M3", -speed)
|
motor_drive("M3", -speed)
|
||||||
motor_drive("M4", -speed)
|
motor_drive("M4", -speed)
|
||||||
|
|
||||||
def move_left(speed=0.6):
|
def move_left(speed=0.8):
|
||||||
motor_drive("M1", -speed)
|
motor_drive("M1", -speed)
|
||||||
motor_drive("M2", speed)
|
motor_drive("M2", speed)
|
||||||
motor_drive("M3", speed)
|
motor_drive("M3", speed)
|
||||||
motor_drive("M4", -speed)
|
motor_drive("M4", -speed)
|
||||||
|
|
||||||
def move_right(speed=0.6):
|
def move_right(speed=0.8):
|
||||||
motor_drive("M1", speed)
|
motor_drive("M1", speed)
|
||||||
motor_drive("M2", -speed)
|
motor_drive("M2", -speed)
|
||||||
motor_drive("M3", -speed)
|
motor_drive("M3", -speed)
|
||||||
@ -99,13 +99,13 @@ def move_right(speed=0.6):
|
|||||||
# ======================
|
# ======================
|
||||||
# 旋转
|
# 旋转
|
||||||
# ======================
|
# ======================
|
||||||
def rotate_left(speed=0.6):
|
def rotate_left(speed=0.8):
|
||||||
motor_drive("M1", -speed)
|
motor_drive("M1", -speed)
|
||||||
motor_drive("M2", -speed)
|
motor_drive("M2", -speed)
|
||||||
motor_drive("M3", -speed)
|
motor_drive("M3", -speed)
|
||||||
motor_drive("M4", -speed)
|
motor_drive("M4", -speed)
|
||||||
|
|
||||||
def rotate_right(speed=0.6):
|
def rotate_right(speed=0.8):
|
||||||
motor_drive("M1", speed)
|
motor_drive("M1", speed)
|
||||||
motor_drive("M2", speed)
|
motor_drive("M2", speed)
|
||||||
motor_drive("M3", speed)
|
motor_drive("M3", speed)
|
||||||
@ -114,25 +114,25 @@ def rotate_right(speed=0.6):
|
|||||||
# ======================
|
# ======================
|
||||||
# 斜向移动
|
# 斜向移动
|
||||||
# ======================
|
# ======================
|
||||||
def move_left_forward(speed=0.6):
|
def move_left_forward(speed=0.8):
|
||||||
motor_drive("M1", 0)
|
motor_drive("M1", 0)
|
||||||
motor_drive("M2", speed)
|
motor_drive("M2", speed)
|
||||||
motor_drive("M3", 0)
|
motor_drive("M3", 0)
|
||||||
motor_drive("M4", -speed)
|
motor_drive("M4", -speed)
|
||||||
|
|
||||||
def move_right_forward(speed=0.6):
|
def move_right_forward(speed=0.8):
|
||||||
motor_drive("M1", speed)
|
motor_drive("M1", speed)
|
||||||
motor_drive("M2", 0)
|
motor_drive("M2", 0)
|
||||||
motor_drive("M3", -speed)
|
motor_drive("M3", -speed)
|
||||||
motor_drive("M4", 0)
|
motor_drive("M4", 0)
|
||||||
|
|
||||||
def move_left_backward(speed=0.6):
|
def move_left_backward(speed=0.8):
|
||||||
motor_drive("M1", -speed)
|
motor_drive("M1", -speed)
|
||||||
motor_drive("M2", 0)
|
motor_drive("M2", 0)
|
||||||
motor_drive("M3", speed)
|
motor_drive("M3", speed)
|
||||||
motor_drive("M4", 0)
|
motor_drive("M4", 0)
|
||||||
|
|
||||||
def move_right_backward(speed=0.6):
|
def move_right_backward(speed=0.8):
|
||||||
motor_drive("M1", 0)
|
motor_drive("M1", 0)
|
||||||
motor_drive("M2", -speed)
|
motor_drive("M2", -speed)
|
||||||
motor_drive("M3", 0)
|
motor_drive("M3", 0)
|
||||||
|
|||||||
@ -3,9 +3,6 @@ import motor
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
|
||||||
import agent
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@ -97,19 +94,19 @@ def save_path():
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
path_name = data.get('name')
|
path_name = data.get('name')
|
||||||
recorded_actions = data.get('actions')
|
recorded_actions = data.get('actions')
|
||||||
|
|
||||||
if not path_name or not recorded_actions:
|
if not path_name or not recorded_actions:
|
||||||
return jsonify({'status': 'error', 'message': '路径名称和轨迹数据不能为空'})
|
return jsonify({'status': 'error', 'message': '路径名称和轨迹数据不能为空'})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 确保Path目录存在
|
# 确保Path目录存在
|
||||||
os.makedirs('Path', exist_ok=True)
|
os.makedirs('Path', exist_ok=True)
|
||||||
|
|
||||||
# 保存为JSON文件
|
# 保存为JSON文件
|
||||||
file_path = os.path.join('Path', f'{path_name}.json')
|
file_path = os.path.join('Path', f'{path_name}.json')
|
||||||
with open(file_path, 'w', encoding='utf-8') as f:
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump({'name': path_name, 'actions': recorded_actions}, f, ensure_ascii=False, indent=2)
|
json.dump({'name': path_name, 'actions': recorded_actions}, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
return jsonify({'status': 'success', 'message': '轨迹保存成功'})
|
return jsonify({'status': 'success', 'message': '轨迹保存成功'})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"保存轨迹出错: {e}")
|
print(f"保存轨迹出错: {e}")
|
||||||
@ -121,10 +118,10 @@ def list_paths():
|
|||||||
try:
|
try:
|
||||||
# 确保Path目录存在
|
# 确保Path目录存在
|
||||||
os.makedirs('Path', exist_ok=True)
|
os.makedirs('Path', exist_ok=True)
|
||||||
|
|
||||||
# 列出所有轨迹文件
|
# 列出所有轨迹文件
|
||||||
path_files = [f.replace('.json', '') for f in os.listdir('Path') if f.endswith('.json')]
|
path_files = [f.replace('.json', '') for f in os.listdir('Path') if f.endswith('.json')]
|
||||||
|
|
||||||
return jsonify({'status': 'success', 'paths': path_files})
|
return jsonify({'status': 'success', 'paths': path_files})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"列出轨迹出错: {e}")
|
print(f"列出轨迹出错: {e}")
|
||||||
@ -134,18 +131,18 @@ def list_paths():
|
|||||||
@app.route('/load_path', methods=['GET'])
|
@app.route('/load_path', methods=['GET'])
|
||||||
def load_path():
|
def load_path():
|
||||||
path_name = request.args.get('name')
|
path_name = request.args.get('name')
|
||||||
|
|
||||||
if not path_name:
|
if not path_name:
|
||||||
return jsonify({'status': 'error', 'message': '路径名称不能为空'})
|
return jsonify({'status': 'error', 'message': '路径名称不能为空'})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file_path = os.path.join('Path', f'{path_name}.json')
|
file_path = os.path.join('Path', f'{path_name}.json')
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
return jsonify({'status': 'error', 'message': '轨迹文件不存在'})
|
return jsonify({'status': 'error', 'message': '轨迹文件不存在'})
|
||||||
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
return jsonify({'status': 'success', 'data': data})
|
return jsonify({'status': 'success', 'data': data})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"加载轨迹出错: {e}")
|
print(f"加载轨迹出错: {e}")
|
||||||
@ -156,22 +153,20 @@ def load_path():
|
|||||||
def delete_path():
|
def delete_path():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
path_name = data.get('name')
|
path_name = data.get('name')
|
||||||
|
|
||||||
if not path_name:
|
if not path_name:
|
||||||
return jsonify({'status': 'error', 'message': '路径名称不能为空'})
|
return jsonify({'status': 'error', 'message': '路径名称不能为空'})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file_path = os.path.join('Path', f'{path_name}.json')
|
file_path = os.path.join('Path', f'{path_name}.json')
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
return jsonify({'status': 'error', 'message': '轨迹文件不存在'})
|
return jsonify({'status': 'error', 'message': '轨迹文件不存在'})
|
||||||
|
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
return jsonify({'status': 'success', 'message': '轨迹删除成功'})
|
return jsonify({'status': 'success', 'message': '轨迹删除成功'})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"删除轨迹出错: {e}")
|
print(f"删除轨迹出错: {e}")
|
||||||
return jsonify({'status': 'error', 'message': f'删除轨迹出错: {e}'})
|
return jsonify({'status': 'error', 'message': f'删除轨迹出错: {e}'})
|
||||||
|
|
||||||
agent.create_agent_routes(app, motor)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user