diff --git a/main/CSS/agent.css b/main/CSS/agent.css new file mode 100644 index 0000000..3e6d5e9 --- /dev/null +++ b/main/CSS/agent.css @@ -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; + } +} \ No newline at end of file diff --git a/main/CSS/control.css b/main/CSS/control.css index 5830af6..36561a9 100644 --- a/main/CSS/control.css +++ b/main/CSS/control.css @@ -268,4 +268,5 @@ .title { font-size: 20px; } -} \ No newline at end of file +} + diff --git a/main/CSS/record.css b/main/CSS/record.css index 4d46d72..53df625 100644 --- a/main/CSS/record.css +++ b/main/CSS/record.css @@ -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; } diff --git a/main/CSS/style.css b/main/CSS/style.css index 533da69..1e43f28 100644 --- a/main/CSS/style.css +++ b/main/CSS/style.css @@ -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); diff --git a/main/Javascript/agent.js b/main/Javascript/agent.js new file mode 100644 index 0000000..8903416 --- /dev/null +++ b/main/Javascript/agent.js @@ -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 = '

缓存已清除

'; + } + + // 加载配置 + 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 = `

加载配置失败: ${error.message}

`; + }); + } + } + + // 保存配置 + 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 = '

配置已保存

'; + } else { + status.innerHTML = `

配置保存失败: ${data.message}

`; + } + }) + .catch(error => { + console.error('配置保存失败:', error); + status.innerHTML = `

配置保存失败: ${error.message}

`; + }); + } + + // 发送消息 + function sendMessage() { + const text = agentInput.value.trim(); + if (!text) return; + + addMessage('user', text); + agentInput.value = ''; + status.innerHTML = '

处理中...

'; + + 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 = `

${data.message || '处理完成'}

`; + }) + .catch(error => { + console.error('通信错误:', error); + addMessage('agent', '通信错误,请重试'); + status.innerHTML = `

通信错误: ${error.message}

`; + }); + } + + // 添加消息到聊天历史 + function addMessage(type, content) { + const messageDiv = document.createElement('div'); + messageDiv.className = `message ${type}`; + messageDiv.innerHTML = `

${content}

`; + 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(); +}); \ No newline at end of file diff --git a/main/Javascript/script.js b/main/Javascript/script.js index fadfe1e..502ec25 100644 --- a/main/Javascript/script.js +++ b/main/Javascript/script.js @@ -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 = `

正在${direction === 'forward' ? '前进' : '后退'}...

`; - // 记录操作(如果正在录制) - 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 = `

录制完成,共记录 ${recordedActions.length} 个操作

`; - } else { - // 开始录制 - isRecording = true; - recordedActions = []; - recordingStartTime = Date.now(); - recordBtn.textContent = '停止录制'; - status.innerHTML = `

开始录制...

`; - } -}); - -// 回放按钮点击事件 -playbackBtn.addEventListener('click', () => { - if (recordedActions.length === 0) { - status.innerHTML = `

没有可回放的操作

`; - return; - } - - status.innerHTML = `

开始回放...

`; - - // 按时间顺序回放操作 - let lastTimestamp = 0; - recordedActions.forEach((action, index) => { - setTimeout(() => { - controlMotor(action.direction); - - // 最后一个操作完成后显示回放完成 - if (index === recordedActions.length - 1) { - setTimeout(() => { - status.innerHTML = `

回放完成

`; - }, 2000); - } - }, action.timestamp - lastTimestamp); - - lastTimestamp = action.timestamp; - }); -}); - // 按钮点击事件 forwardBtn.addEventListener('click', () => { controlMotor('forward'); diff --git a/main/Path/轨迹.json b/main/Path/轨迹.json new file mode 100644 index 0000000..0ab9e04 --- /dev/null +++ b/main/Path/轨迹.json @@ -0,0 +1,21 @@ +{ + "name": "轨迹", + "actions": [ + { + "direction": "forward", + "timestamp": 858 + }, + { + "direction": "stop", + "timestamp": 1166 + }, + { + "direction": "backward", + "timestamp": 2327 + }, + { + "direction": "stop", + "timestamp": 2642 + } + ] +} \ No newline at end of file diff --git a/main/__pycache__/agent.cpython-38.pyc b/main/__pycache__/agent.cpython-38.pyc new file mode 100644 index 0000000..543fd13 Binary files /dev/null and b/main/__pycache__/agent.cpython-38.pyc differ diff --git a/main/__pycache__/motor.cpython-313.pyc b/main/__pycache__/motor.cpython-313.pyc new file mode 100644 index 0000000..d4f49b9 Binary files /dev/null and b/main/__pycache__/motor.cpython-313.pyc differ diff --git a/main/__pycache__/motor.cpython-38.pyc b/main/__pycache__/motor.cpython-38.pyc index 5f6e650..86c66f9 100644 Binary files a/main/__pycache__/motor.cpython-38.pyc and b/main/__pycache__/motor.cpython-38.pyc differ diff --git a/main/agent.py b/main/agent.py new file mode 100644 index 0000000..0bc9f73 --- /dev/null +++ b/main/agent.py @@ -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}'}) \ No newline at end of file diff --git a/main/agent/skills.md b/main/agent/skills.md new file mode 100644 index 0000000..8659724 --- /dev/null +++ b/main/agent/skills.md @@ -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_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代码块)。** \ No newline at end of file diff --git a/main/index.html b/main/index.html index f2a6732..810c15e 100644 --- a/main/index.html +++ b/main/index.html @@ -6,11 +6,12 @@ 垃圾桶控制界面 +

垃圾桶控制界面

- +
@@ -32,13 +33,13 @@
- +
- +
@@ -46,7 +47,7 @@
- +

轨迹管理

@@ -59,15 +60,48 @@
- +

就绪

+ + +
+

智能控制助手

+ + +
+

LLM模型配置

+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+ + +
+
+ +
+
- + + \ No newline at end of file diff --git a/main/server.py b/main/server.py index 8c5bcca..c2b0d88 100644 --- a/main/server.py +++ b/main/server.py @@ -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) \ No newline at end of file