新增agent功能

This commit is contained in:
张梦南 2026-04-20 14:19:56 +08:00
parent d9ec92220e
commit f48facde94
14 changed files with 856 additions and 91 deletions

175
main/CSS/agent.css Normal file
View File

@ -0,0 +1,175 @@
/* 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;
}
}

View File

@ -268,4 +268,5 @@
.title {
font-size: 20px;
}
}
}

View File

@ -120,17 +120,17 @@
flex-direction: column;
align-items: center;
}
.path-input input {
width: 100%;
max-width: 300px;
}
.btn-save {
width: 100%;
max-width: 300px;
}
.path-management {
padding: 15px;
}

View File

@ -56,16 +56,6 @@ h1 {
color: white;
}
.btn-record {
background-color: #ff9800;
color: white;
}
.btn-playback {
background-color: #2196f3;
color: white;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);

180
main/Javascript/agent.js Normal file
View File

@ -0,0 +1,180 @@
// 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();
});

View File

@ -1,25 +1,12 @@
// 获取按钮和状态元素
const forwardBtn = document.getElementById('forwardBtn');
const backwardBtn = document.getElementById('backwardBtn');
const recordBtn = document.getElementById('recordBtn');
const playbackBtn = document.getElementById('playbackBtn');
const status = document.getElementById('status');
// 录制相关变量
let isRecording = false;
let recordedActions = [];
let recordingStartTime = 0;
// 实际控制函数
function controlMotor(direction) {
status.innerHTML = `<p>正在${direction === 'forward' ? '前进' : '后退'}...</p>`;
// 记录操作(如果正在录制)
if (isRecording) {
const timestamp = Date.now() - recordingStartTime;
recordedActions.push({ direction, timestamp });
}
// 发送AJAX请求到后端服务器
fetch('/control', {
method: 'POST',
@ -41,50 +28,6 @@ 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', () => {
controlMotor('forward');

21
main/Path/轨迹.json Normal file
View File

@ -0,0 +1,21 @@
{
"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.

308
main/agent.py Normal file
View File

@ -0,0 +1,308 @@
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}'})

108
main/agent/skills.md Normal file
View File

@ -0,0 +1,108 @@
# 垃圾桶控制技能文档
你是一个智能垃圾桶控制助手可以调用以下技能控制垃圾桶运动。你必须而且只能返回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_forwardduration为X
4. 用户说"后退X秒" → move_backwardduration为X
5. 用户说"左转X秒" → turn_leftduration为X
6. 用户说"右转X秒" → turn_rightduration为X
7. 用户说"执行路径xxx" → play_pathname为xxx
8. 用户说"列出路径" → list_paths
9. 用户说"删除路径xxx" → delete_pathname为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代码块。**

View File

@ -6,11 +6,12 @@
<title>垃圾桶控制界面</title>
<link rel="stylesheet" href="CSS/control.css">
<link rel="stylesheet" href="CSS/record.css">
<link rel="stylesheet" href="CSS/agent.css">
</head>
<body>
<div class="container">
<h1 class="title">垃圾桶控制界面</h1>
<!-- 手柄风格控制器 -->
<div class="controller">
<div class="controller-layout">
@ -32,13 +33,13 @@
<button class="btn btn-backward-right" id="backwardRightBtn"><span></span></button>
</div>
</div>
<!-- 中间:录制回放控制 -->
<div class="center-controls">
<button class="btn btn-record" id="recordBtn"><span>录制</span></button>
<button class="btn btn-playback" id="playbackBtn"><span>回放</span></button>
</div>
<!-- 右侧:旋转控制 -->
<div class="rotate-controls">
<button class="btn btn-rotate-left" id="rotateLeftBtn"><span></span></button>
@ -46,7 +47,7 @@
</div>
</div>
</div>
<!-- 轨迹管理区域 -->
<div class="path-management">
<h3>轨迹管理</h3>
@ -59,15 +60,48 @@
<ul id="pathList"></ul>
</div>
</div>
<!-- 状态显示区域 -->
<div class="status" id="status">
<p>就绪</p>
</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>
<script src="Javascript/control.js"></script>
<script src="Javascript/record.js"></script>
<script src="Javascript/joycon.js"></script>
<script src="Javascript/agent.js"></script>
</body>
</html>

View File

@ -3,6 +3,9 @@ import motor
import time
import os
import json
import sys
sys.path.insert(0, os.path.dirname(__file__))
import agent
app = Flask(__name__)
@ -94,19 +97,19 @@ def save_path():
data = request.get_json()
path_name = data.get('name')
recorded_actions = data.get('actions')
if not path_name or not recorded_actions:
return jsonify({'status': 'error', 'message': '路径名称和轨迹数据不能为空'})
try:
# 确保Path目录存在
os.makedirs('Path', exist_ok=True)
# 保存为JSON文件
file_path = os.path.join('Path', f'{path_name}.json')
with open(file_path, 'w', encoding='utf-8') as f:
json.dump({'name': path_name, 'actions': recorded_actions}, f, ensure_ascii=False, indent=2)
return jsonify({'status': 'success', 'message': '轨迹保存成功'})
except Exception as e:
print(f"保存轨迹出错: {e}")
@ -118,10 +121,10 @@ def list_paths():
try:
# 确保Path目录存在
os.makedirs('Path', exist_ok=True)
# 列出所有轨迹文件
path_files = [f.replace('.json', '') for f in os.listdir('Path') if f.endswith('.json')]
return jsonify({'status': 'success', 'paths': path_files})
except Exception as e:
print(f"列出轨迹出错: {e}")
@ -131,18 +134,18 @@ def list_paths():
@app.route('/load_path', methods=['GET'])
def load_path():
path_name = request.args.get('name')
if not path_name:
return jsonify({'status': 'error', 'message': '路径名称不能为空'})
try:
file_path = os.path.join('Path', f'{path_name}.json')
if not os.path.exists(file_path):
return jsonify({'status': 'error', 'message': '轨迹文件不存在'})
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return jsonify({'status': 'success', 'data': data})
except Exception as e:
print(f"加载轨迹出错: {e}")
@ -153,20 +156,22 @@ def load_path():
def delete_path():
data = request.get_json()
path_name = data.get('name')
if not path_name:
return jsonify({'status': 'error', 'message': '路径名称不能为空'})
try:
file_path = os.path.join('Path', f'{path_name}.json')
if not os.path.exists(file_path):
return jsonify({'status': 'error', 'message': '轨迹文件不存在'})
os.remove(file_path)
return jsonify({'status': 'success', 'message': '轨迹删除成功'})
except Exception as e:
print(f"删除轨迹出错: {e}")
return jsonify({'status': 'error', 'message': f'删除轨迹出错: {e}'})
agent.create_agent_routes(app, motor)
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)