From f48facde942f938111ffdf2622f1c77f7a3fd512 Mon Sep 17 00:00:00 2001
From: Cx330 <1487537121@qq.com>
Date: Mon, 20 Apr 2026 14:19:56 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Eagent=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
main/CSS/agent.css | 175 ++++++++++++++
main/CSS/control.css | 3 +-
main/CSS/record.css | 6 +-
main/CSS/style.css | 10 -
main/Javascript/agent.js | 180 +++++++++++++++
main/Javascript/script.js | 57 -----
main/Path/轨迹.json | 21 ++
main/__pycache__/agent.cpython-38.pyc | Bin 0 -> 6704 bytes
main/__pycache__/motor.cpython-313.pyc | Bin 0 -> 5628 bytes
main/__pycache__/motor.cpython-38.pyc | Bin 3564 -> 3564 bytes
main/agent.py | 308 +++++++++++++++++++++++++
main/agent/skills.md | 108 +++++++++
main/index.html | 46 +++-
main/server.py | 33 +--
14 files changed, 856 insertions(+), 91 deletions(-)
create mode 100644 main/CSS/agent.css
create mode 100644 main/Javascript/agent.js
create mode 100644 main/Path/轨迹.json
create mode 100644 main/__pycache__/agent.cpython-38.pyc
create mode 100644 main/__pycache__/motor.cpython-313.pyc
create mode 100644 main/agent.py
create mode 100644 main/agent/skills.md
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 0000000000000000000000000000000000000000..543fd137568c39baf8cacc20db6b264489879056
GIT binary patch
literal 6704
zcmcgw>yuo?b-#UIbLXkmN*FB&yvV!;TM{V?tf=G%7)T|B7h%ak8H0z>^xfUrotOIF
zYfIxhl*CHWBC}}AmchV6%^Df8DI?+7g@kN@%O6tt1N1lnwcv
zzISFHNUBt&k~=khPxtLUefsq2bAElkl};xVJa3#@EKS{|C?8T~<)fqWHT<0h3a)Tg
zR&wE+<(RCiIaR*3oF?CTPM2>ZXW*@t%}Oj6V~QP{QgRm8@YA_5r{&_@x!#RCd4RHnv5xpO|HfbpS`NQrcR;$gz`7aPjznYg_@e|
z8MYtws=6OFEF(73SXUUctL}DZvQ#cR+bTR@qG5RM=+>QBoMl&wHD0QYc{f3{_Q_WF
z^5xFSBi*UXofl7Z=HFctrCK#G>Y`M2
zvueOb17P1Ga(UZixug5dcDrs>DGvTccDb7t?D=A!`Q}-&fYU9E1E9te#NL!OJvEOGc
zm7DX-38~mT6MKBdtuLWZ)dFP_#p66tVof#V+thf{*CtKz3d}?)YfatPTe`2cjCp0A
zwM_6gc-l8;H++?6b}Lnwk*_V+TJ@^d_DSSO2Bjib^1h>XlWR@HeUsnJH_xa&dvFsINuT*D+Hc{v&gjh;dVbcAO{Drg
zVtg3&bgxd|_-&KgEE58Kx2){j(<)hHBjv|h7FKM*Dj$MXS`(Sc1V;T1mKld-Zo%l!
z`SBj#-dHoTQQ`p`jXx9b+bHdfIjf4#@Y{V$QkJ{UxA^Tt$`s?NNgdy;u+CG^-W~Yg
zIixfb?tq_|Vv=LD??hK0>GRq1DL#At=X|ifuVH=bu{u?}9<6Q|tGnx(tApLGSsR_d
zyGMip!xbiqKzH0)eL)S(LQ$R?y&%S%MzT^UI9uULwiT<5_1lZ%1$TR~R;ib5>g0UH
zwl@aWHa1dUuT@#h3^*X_nYp
zrH1U&h&^Z*ge`_ejN~$%!;WiLyr)oEyxi*iRu!Rhv^ueZ;?*S>W6
z$}7j(C*NAUJlA>t#Nw~de0b#9teVqBt!xLeVkmx~dyXx}M&;A_YU$r0bXE_gvMRRY
zz-c(SB!sKmq5!{Z=Mt5|p}bq0w5v{5l|98RklR~_qWjJign9JQZwx=M=P!2+dl_nj
zn7??o^W%3Q>8{~N;PX-#3QqZgQ!JI1XdL;x{S5kAIhefLU+sNt
zqs@LlGF9|n3gE_bz=&pUp4c3v^56STl3sDn+Rz^TbIE7I=UoQykCb{x#GBE1`M(l>>
zKTI>x84y_%fmMJt<{@#Q^MYHTd7V0J9U_AA%C4YYk&Bm+BrHe`fhE^mD3^y&bE+s5
z1dN2Lt0{GmB|uras;HmnhQ%z^VuNfz%jlM7g0?UoVm5OQ~n^uNx!ngtSN})`t
zDoe4A2tZr3gt{42Ld|I5djPx;n08^tRe_1lH!V#D_{}ijS*7u@i=ghZDOJGDlVd`N
zSK&D6t#O9H^9m@PtMjbMTsXlg_K@<_y-%u5wW%Sv$Ue?aP#7h4aqLt*9ugt3y0>yvG9(pT9VUT*Plz?Fr>$jKyBzu(zkiZAW@Wc*ub?ARD3We#gHIrFnaj=^~{7g_Os;L>YZ)AgN3cUtUnoN!^5{BwM
zT08P?M0X-_urjnhiYAkZ-n$iQ!WO(VXc-Eb9L{|Q_-
zMO@7_T!*g7^``4^)h1Pd1cJRcM|@*z_-?)?-&?N3SC@RJ!S~jPueFBnXRpb3_&R(I
z$@d)i-WKw0#UtHqS)nUa_UcGJpTn6nd}Ee5zsFA9-s=Tz-w{H?UJG_ECdd5|-|k$|
zYN1sXJ63U1oh@s)-SsJMaXGHMW?XK~xVu-6!`EzYZa|$!0Cf@)>iiZ^#{`hO6KlW6
zH~T;?(W}v@2+oCo$9%@@gE^e??B_>vsY>m;c7C)bzE==DmzK2yh2o^F#u3D;`LaFg
z<`R+=rLl2W`hr}%UM?I)6hZV#luM2)Nhe6+N@cqgUhpCA6u?}PhD`y;qgc9lai)Fz
zX!qR3KIR~p6-F;{2Re8+pojoW56`rpo9TY=>ev_M^Wj(Ns^`>gn-|e_)kEH$oRxDc
z3Ky3(Xj`T8IuHL%r>~_+`@s4aw2{E#9vl&%*b;()vVcnyfeA1#+jc$B#_Vvp(!neM
z`l3?+ow+rU$Mw-G+bhD|27-+F!l-MDe2?vGBnsV=6?mP-J@z5H=s`X5LbYvoxmGNc
zov&=W%0R5pgy)0+LCo=r02+=*YYP=2z6DbJH42ZM(v`D6ysFY>UsyVF{^PiUK!kd`(sxqDW
z<^PLJ7e6i2sb7*z1fSYR=Q?lyw2$7sIJI5)`>;9CtA&a!W$#`%-+upCdv@l^*>`Y`
zfcO_LUb?by$s-`w`Qh80)9-gLzt#QVJ$cf)FI;G!dNFKhPruZie@pgjPq&s{J`?q$
zfr}r!)_(gZQ3Hw7d8yU;@pS)uN?_f2?reAd`7qzm%QR$@5|;*6&t(L0cbxJKNKypm
zK1mYV1p*K`@C{;-rwNjx2DoZV=Mto%%CdV2foV*TCy<9uJP9fnd*IPWAA92Ahos>J
zaq=vA(oG0Kmn~GH&-ALpHjD9&k3vxaIq3(y6#YIX018+NC}05=WV8XcnILGNPF#mZ
zFO`6WiQa>tR@b6-aFl?wg!zkiXt9mDOqsYdN)
z)DF-g+W|VzKhD*#{e&{g5HOO*EQ~>_4Ng8a(nyE-oZj_A-}Bhseef^1xZs}2omGdC
zd<+S-mjwDQ3i6$0r2-sVQ7NTGI2K402^3&rq{VvP{0q7|
z1gRBuK-yOdwnJ8DsT)}%Tf{lS0SQL{0=u@e957b$3ADd>c;Bi$q)L8Fe-k{{wrZkAt)#C|K?K0VsIjbo
z0aD~j;r|VtoD2#D*c(R@#|XbaHnv`O`D?i}l06GYLS~gJ$h{GCmyqCJK`g7{YUT`R
zh%1(}1ICDx$IUrpZIMg+j%!RQbd}<<8v*ifL>|#KeFJ9XOeSSAn`zxPhXK#fm?pg4{|NJ!IO4lErgBUl~25-Fvi4iIw@GKm6vE&;gV3q!;#x
zdVo^Y-J>VlXBPT#ARTUg|HOzo4I!5=lf&{NF7H)A!o|_bBXfZ?y;k>-2%%22P2q`3
ztvyk+gj_nxKu39s`>4CT52j@vgXg1E#B#jYNnPlSFEAN9R^YTmWPq@h|3nApb`;?m
zq!^GvT7i6d=>HK7K-0A`!y0#8*C>~{5;~P9HfsbL(tV)-2QiCqsW?o9JQRnBlCELH
zRe1k1zDC*h3`=D2k0Yr;K`gsY?8hrmYmU4)i*3Z{5h@N)yCD@UDAEXz3hA;4eJ^iC
usCnYH;*?6Mq(Ij05a51=yooc6Li#4D7m9hNM#okClWAI(m9uWKM*bTB_z_hA
literal 0
HcmV?d00001
diff --git a/main/__pycache__/motor.cpython-313.pyc b/main/__pycache__/motor.cpython-313.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d4f49b9b924ce79b311d5d611fe315427434d29f
GIT binary patch
literal 5628
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
literal 0
HcmV?d00001
diff --git a/main/__pycache__/motor.cpython-38.pyc b/main/__pycache__/motor.cpython-38.pyc
index 5f6e650dd6fd9b02780135fd3098d0acc5d5ccf0..86c66f9d84467d9e01cc42eac3469d0121d28f84 100644
GIT binary patch
delta 41
scmaDO{YIKEl$V!_0SJWJpJcA*-^iE1%3%x!kL@Q1a!75Sz#78=0PV92YybcN
delta 41
tcmaDO{YIKEl$V!_0SHV@?`OsdY~)K|<(M@S1YX)t4&;#9Jb^Wa0{{c+4O0LB
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 @@
垃圾桶控制界面
+
垃圾桶控制界面
-
+
-
+
-
+
@@ -46,7 +47,7 @@
-
+
-
+
+
+
+
+
智能控制助手
+
+
+
+
LLM模型配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+