myagent-ai 1.15.21 → 1.15.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/web/ui/index.html +60 -35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.15.21",
3
+ "version": "1.15.23",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/ui/index.html CHANGED
@@ -52,6 +52,9 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
52
52
  .card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:16px}
53
53
  .card h3{font-size:15px;margin-bottom:12px;color:var(--text2);text-transform:uppercase;letter-spacing:.5px}
54
54
  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
55
+ .grid-2{grid-template-columns:repeat(2,1fr)}
56
+ .grid-3{grid-template-columns:repeat(3,1fr)}
57
+ .grid-4{grid-template-columns:repeat(4,1fr)}
55
58
  .stat{background:var(--surface2);padding:16px;border-radius:var(--radius)}
56
59
  .stat .label{font-size:12px;color:var(--text2);margin-bottom:4px}
57
60
  .stat .value{font-size:28px;font-weight:700}
@@ -184,7 +187,17 @@ tr:hover{background:var(--surface2)}
184
187
  .content{padding:16px}
185
188
  .grid{grid-template-columns:1fr}
186
189
  .form-row{grid-template-columns:1fr}
187
- .table-wrap{overflow-x:auto}
190
+ .table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
191
+ .card{padding:12px;margin-bottom:12px}
192
+ .card h3{font-size:14px;margin-bottom:8px}
193
+ .stat{padding:12px}
194
+ .stat .label{font-size:11px}
195
+ .stat .value{font-size:20px}
196
+ .grid,.grid-2,.grid-3,.grid-4{grid-template-columns:repeat(2,1fr)!important}
197
+ .btn-sm{padding:6px 12px;font-size:12px;min-height:32px}
198
+ th,td{padding:6px 8px;font-size:12px;white-space:nowrap}
199
+ th{font-size:11px}
200
+ .badge{padding:2px 6px;font-size:10px}
188
201
  .modal{width:95%;max-height:90vh;padding:16px}
189
202
  .modal-wide{max-width:95%}
190
203
  .tabs{gap:0;overflow-x:auto}
@@ -192,6 +205,18 @@ tr:hover{background:var(--surface2)}
192
205
  .agent-card{flex-direction:column;align-items:flex-start}
193
206
  .agent-card .flex.flex-col{flex-direction:row;gap:4px}
194
207
  }
208
+ @media(max-width:480px){
209
+ .content{padding:12px}
210
+ .card{padding:10px}
211
+ .grid,.grid-2,.grid-3,.grid-4{grid-template-columns:1fr!important}
212
+ .header h2{font-size:15px}
213
+ .btn-sm{padding:8px 12px;font-size:12px;min-height:36px}
214
+ .stat .value{font-size:18px}
215
+ .stat .label{font-size:10px}
216
+ .modal{width:98%;padding:12px}
217
+ .form-group{margin-bottom:10px}
218
+ .form-group label{font-size:12px}
219
+ }
195
220
  </style>
196
221
  </head>
197
222
  <body>
@@ -517,7 +542,7 @@ async function openEditAgentModal(path){
517
542
  <label class="btn btn-sm" style="cursor:pointer" ${isSys?'style=\"pointer-events:none;opacity:0.5\"':''}><input type="file" accept="image/*" hidden onchange="handleAvatarUpload(this,'ea')" ${isSys?'disabled':''}>
518
543
  📷 上传图片
519
544
  </label>
520
- ${a.avatar_image?'<button class="btn btn-sm btn-ghost" onclick="removeAvatarImage(\\''+escHtml(path)+'\\')">🗑️ 移除图片</button>':''}
545
+ ${a.avatar_image?'<button class="btn btn-sm btn-ghost" onclick="removeAvatarImage(&quot;'+escHtml(path)+'&quot;)">🗑️ 移除图片</button>':''}
521
546
  </div>
522
547
  <div id="eaCropArea" style="display:none;margin-top:8px">
523
548
  <div style="position:relative;display:inline-block">
@@ -635,9 +660,9 @@ async function loadAgentKB(){
635
660
  <h4 style="font-size:14px;color:var(--text2)">知识库文件 (${files.length})</h4>
636
661
  <button class="btn btn-sm btn-primary" onclick="uploadAgentKB('${escHtml(path)}',false)">上传文件</button> <button class="btn btn-sm btn-secondary" onclick="uploadAgentKB('${escHtml(path)}',true)">📁 上传文件夹</button></div>`;
637
662
  if(files.length===0){html+='<div class="empty">暂无知识库文件</div>';}
638
- else{html+='<table><tr><th>文件名</th><th>大小</th><th></th></tr>';
663
+ else{html+='<div class="table-wrap"><table><tr><th>文件名</th><th>大小</th><th></th></tr>';
639
664
  for(const f of files){html+=`<tr><td>${escHtml(f.name||f.filename||'')}</td><td>${f.size||'-'}</td><td><button class="btn btn-sm btn-danger" onclick="deleteAgentKB('${escHtml(path)}','${escHtml(f.name||f.filename||'')}')">删除</button></td></tr>`}
640
- html+='</table>';}
665
+ html+='</table></div>';}
641
666
  $('kbContent').innerHTML=html;
642
667
  }
643
668
 
@@ -672,9 +697,9 @@ async function loadAgentSessions(){
672
697
  const sessions=Array.isArray(data)?data:(data?.sessions||[]);
673
698
  let html=`<div class="flex justify-between items-center mb-16"><h4 style="font-size:14px;color:var(--text2)">会话 (${sessions.length})</h4></div>`;
674
699
  if(sessions.length===0){html+='<div class="empty">暂无会话</div>';}
675
- else{html+='<table><tr><th>会话</th><th>消息数</th><th>最后活动</th><th></th></tr>';
700
+ else{html+='<div class="table-wrap"><table><tr><th>会话</th><th>消息数</th><th>最后活动</th><th></th></tr>';
676
701
  for(const s of sessions){const dn=s.display_name||s.id;html+=`<tr><td style="max-width:200px;overflow:hidden;text-overflow:ellipsis" title="${escHtml(s.id)}">${escHtml(dn.length>25?dn.slice(0,25)+'...':dn)}</td><td>${s.messages||0}</td><td>${fmtTimeAgo(s.last)}</td><td><button class="btn btn-sm" style="background:var(--success);color:#fff" onclick="enterSession('${escHtml(s.id)}','${escHtml(path)}')">切入</button> <button class="btn btn-sm btn-ghost" onclick="viewSessionMsgs('${escHtml(s.id)}')">查看</button></td></tr>`}
677
- html+='</table>';}
702
+ html+='</table></div>';}
678
703
  $('sessionsContent').innerHTML=html;
679
704
  }
680
705
 
@@ -745,7 +770,7 @@ async function loadAgentPerms(){
745
770
  html+='<div class="card" style="margin-bottom:16px">';
746
771
  html+='<h3 style="font-size:14px;color:var(--text2);margin-bottom:8px">🔑 功能权限</h3>';
747
772
  html+='<p style="color:var(--text2);font-size:12px;margin-bottom:12px">精细控制 Agent 的各项能力。未设置的项目将使用全局默认值。</p>';
748
- html+='<div class="grid" style="grid-template-columns:repeat(3,1fr);gap:12px">';
773
+ html+='<div class="grid grid-3" style="gap:12px">';
749
774
  for(const p of perms){
750
775
  const label=labels[p]||p;
751
776
  const defVal=defaults[p]!==false;
@@ -891,7 +916,7 @@ async function doDeletePlatform(name){
891
916
  // ========== Sessions ==========
892
917
  async function renderSessions(){
893
918
  const ss=await api('/api/sessions');
894
- let html='<table><tr><th>会话</th><th>Agent</th><th>消息数</th><th>最后活动</th><th>操作</th></tr>';
919
+ let html='<div class="table-wrap"><table><tr><th>会话</th><th>Agent</th><th>消息数</th><th>最后活动</th><th>操作</th></tr>';
895
920
  for(const s of (ss||[])){
896
921
  // 从 session_id 提取 agent 名 (格式: agent_web_timestamp)
897
922
  const parts=(s.id||'').split('_web_');
@@ -901,7 +926,7 @@ async function renderSessions(){
901
926
  <td><button class="btn btn-sm" style="background:var(--success);color:#fff" onclick="enterSession('${escHtml(s.id)}','${escHtml(agentName)}')">切入</button>
902
927
  <button class="btn btn-sm btn-ghost" onclick="viewSession('${s.id}')">查看</button>
903
928
  <button class="btn btn-sm btn-danger" onclick="clearSession('${s.id}')">清除</button></td></tr>`;}
904
- html+='</table>';if(!ss||!ss.length)html='<div class="empty">暂无会话</div>';
929
+ html+='</table></div>';if(!ss||!ss.length)html='<div class="empty">暂无会话</div>';
905
930
  $('content').innerHTML=html;
906
931
  }
907
932
  async function viewSession(sid){
@@ -975,7 +1000,7 @@ async function renderMemory(){
975
1000
  tabHtml+=`<button class="btn ${active}" onclick="_memCategory='${c.k}';renderMemory()">${c.l} (${count})</button>`;
976
1001
  }
977
1002
  tabHtml+='</div>';
978
- let html=`<div class="grid" style="grid-template-columns:repeat(3,1fr);margin-bottom:16px">
1003
+ let html=`<div class="grid grid-3" style="margin-bottom:16px">
979
1004
  <div class="stat"><div class="label">总计</div><div class="value">${stats.total_count||0}</div></div>
980
1005
  <div class="stat"><div class="label">全局记忆</div><div class="value">${stats.global_count||0}</div></div>
981
1006
  <div class="stat"><div class="label">会话记忆</div><div class="value">${stats.session_count||0}</div></div></div>`;
@@ -989,7 +1014,7 @@ async function renderMemory(){
989
1014
  thHtml+='<th>会话</th>';
990
1015
  if(!isSession)thHtml+='<th>重要性</th>';
991
1016
  thHtml+='<th></th></tr>';
992
- html+='<table>'+thHtml;
1017
+ html+='<div class="table-wrap"><table>'+thHtml;
993
1018
  for(const e of lt){
994
1019
  const content=(e.content||e.summary||'')||(e.role==='user'?'[用户消息]':e.role==='assistant'?'[助手回复]':'[系统]');
995
1020
 
@@ -1002,7 +1027,7 @@ async function renderMemory(){
1002
1027
  if(!isSession)html+='<td>'+(e.importance!=null?e.importance.toFixed(2):'')+'</td>';
1003
1028
  html+='<td><button class="btn btn-sm btn-danger" onclick="deleteMemory(\''+e.id+'\')">删除</button></td></tr>';
1004
1029
  }
1005
- html+='</table>';
1030
+ html+='</table></div>';
1006
1031
  }else{
1007
1032
  html+='<div class="empty">暂无'+(_memCategory==='session'?'会话':'全局')+'记忆</div>';
1008
1033
  }
@@ -1013,12 +1038,12 @@ async function searchMemory(){
1013
1038
  const r=await api('/api/memory/search?q='+encodeURIComponent(q));
1014
1039
  let html='<h3>搜索结果: '+(r.length||0)+' 条</h3>';
1015
1040
  if(r&&r.length){
1016
- html+='<table><tr><th>Key</th><th>内容</th><th>分类</th><th>角色</th><th>会话</th></tr>';
1041
+ html+='<div class="table-wrap"><table><tr><th>Key</th><th>内容</th><th>分类</th><th>角色</th><th>会话</th></tr>';
1017
1042
  for(const e of r){
1018
1043
  const content=(e.content||'').slice(0,300);
1019
1044
  html+=`<tr><td style="white-space:nowrap">${escHtml(e.key||'')}</td><td style="max-width:600px;word-break:break-word;font-size:13px">${escHtml(content)}</td><td>${e.category||''}</td><td>${escHtml(e.role||'')}</td><td style="font-size:12px;color:var(--text3)">${escHtml((e.session_id||'').split('_web_')[0])}</td></tr>`;
1020
1045
  }
1021
- html+='</table>';
1046
+ html+='</table></div>';
1022
1047
  }else{
1023
1048
  html+='<div class="empty">未找到匹配的记忆</div>';
1024
1049
  }
@@ -1040,7 +1065,7 @@ async function renderPermissions(){
1040
1065
 
1041
1066
  // 全局默认权限
1042
1067
  let html='<div class="card"><h3>全局默认权限</h3><p style="color:var(--text2);font-size:13px;margin-bottom:12px">新 Agent 将继承这些默认权限设置</p>';
1043
- html+='<div class="grid" style="grid-template-columns:repeat(3,1fr);gap:12px">';
1068
+ html+='<div class="grid grid-3" style="gap:12px">';
1044
1069
  for(const p of perms){
1045
1070
  const label=labels[p]||p;
1046
1071
  const val=defaults[p]!==false?'checked':'';
@@ -1051,7 +1076,7 @@ async function renderPermissions(){
1051
1076
  // Agent 权限覆盖
1052
1077
  const agentKeys=Object.keys(agents);
1053
1078
  if(agentKeys.length>0){
1054
- html+='<div class="card"><h3>Agent 权限覆盖</h3><p style="color:var(--text2);font-size:13px;margin-bottom:12px">以下 Agent 使用自定义权限(覆盖默认值)</p><table><tr><th>Agent</th>';
1079
+ html+='<div class="card"><h3>Agent 权限覆盖</h3><p style="color:var(--text2);font-size:13px;margin-bottom:12px">以下 Agent 使用自定义权限(覆盖默认值)</p><div class="table-wrap"><table><tr><th>Agent</th>';
1055
1080
  for(const p of perms){html+=`<th>${labels[p]||p}</th>`;}
1056
1081
  html+='<th>操作</th></tr>';
1057
1082
  for(const name of agentKeys){
@@ -1063,7 +1088,7 @@ async function renderPermissions(){
1063
1088
  }
1064
1089
  html+=`<td><button class="btn btn-sm btn-ghost" onclick="editAgentPerms('${escHtml(name)}')">编辑</button> <button class="btn btn-sm btn-danger" onclick="resetAgentPerms('${escHtml(name)}')">重置</button></td></tr>`;
1065
1090
  }
1066
- html+='</table></div>';
1091
+ html+='</table></div></div>';
1067
1092
  }
1068
1093
 
1069
1094
  $('content').innerHTML=html;
@@ -1114,7 +1139,7 @@ async function editAgentPerms(name){
1114
1139
 
1115
1140
  // 功能权限
1116
1141
  html+='<div style="font-size:13px;color:var(--text2);margin-bottom:8px;font-weight:600">🔑 功能权限</div>';
1117
- html+='<div class="grid" style="grid-template-columns:repeat(3,1fr);gap:12px">';
1142
+ html+='<div class="grid grid-3" style="gap:12px">';
1118
1143
  for(const p of perms){
1119
1144
  const label=labels[p]||p;
1120
1145
  const defVal=defaults[p]!==false;
@@ -1174,7 +1199,7 @@ async function renderLLM(){
1174
1199
  allModelsCache=Array.isArray(models)?models:[];
1175
1200
  let html='';
1176
1201
  // 用量统计
1177
- html+=`<div class="card"><h3>用量统计</h3><div class="grid" style="grid-template-columns:repeat(4,1fr)">
1202
+ html+=`<div class="card"><h3>用量统计</h3><div class="grid grid-4">
1178
1203
  <div class="stat"><div class="label">调用次数</div><div class="value">${u.call_count||0}</div></div>
1179
1204
  <div class="stat"><div class="label">Prompt Tokens</div><div class="value">${u.total_prompt_tokens||0}</div></div>
1180
1205
  <div class="stat"><div class="label">Completion Tokens</div><div class="value">${u.total_completion_tokens||0}</div></div>
@@ -1191,7 +1216,7 @@ async function renderLLM(){
1191
1216
  if(!modelList.length){
1192
1217
  html+='<div class="empty">暂无自定义模型,点击上方按钮添加。</div>';
1193
1218
  }else{
1194
- html+='<table style="font-size:12px"><tr><th>ID</th><th>名称</th><th>Provider</th><th>模型</th><th>上下文</th><th>输入</th><th>推理</th><th>兜底</th><th>状态</th><th>操作</th></tr>';
1219
+ html+='<div class="table-wrap"><table style="font-size:12px"><tr><th>ID</th><th>名称</th><th>Provider</th><th>模型</th><th>上下文</th><th>输入</th><th>推理</th><th>兜底</th><th>状态</th><th>操作</th></tr>';
1195
1220
  const providerColors={openai:'badge-green',anthropic:'badge-yellow',ollama:'badge-purple',zhipu:'badge-blue',custom:'badge-red',deepseek:'badge-blue',moonshot:'badge-purple',qwen:'badge-yellow',modelscope:'badge-purple'};
1196
1221
  for(const m of modelList){
1197
1222
  const badgeClass=providerColors[m.provider]||'badge-green';
@@ -1211,7 +1236,7 @@ async function renderLLM(){
1211
1236
  <button class="btn btn-sm btn-success" onclick="testModel(encodeURIComponent('${escHtml(m.id)}'))">测试</button>
1212
1237
  <button class="btn btn-sm btn-danger" onclick="deleteModel('${escHtml(m.id)}','${escHtml(String(m.name||'').replace(/'/g,"\\'"))}')">删除</button></td></tr>`;
1213
1238
  }
1214
- html+='</table>';
1239
+ html+='</table></div>';
1215
1240
  }
1216
1241
  html+='</div>';
1217
1242
  $('content').innerHTML=html;
@@ -1343,12 +1368,12 @@ async function renderExecutor(){
1343
1368
  <input type="radio" name="execMode" value="sandbox" ${isSandbox?'checked':''} onchange="switchMode('sandbox')" ${!dockerOk?'disabled':''}>
1344
1369
  <div><strong>📦 沙盒执行 (Docker)</strong><br><span style="font-size:12px;color:var(--text2)">在隔离容器中运行,更安全${!dockerOk?' (Docker 不可用)':''}</span></div></label></div>
1345
1370
  <div style="font-size:13px;color:var(--text2)">当前模式: <span class="badge ${isSandbox?'badge-yellow':'badge-green'}">${isSandbox?'沙盒 (Docker)':'本机'}</span> Docker 状态: <span class="badge ${dockerOk?'badge-green':'badge-red'}">${dockerOk?'可用':'不可用'}</span> 累计执行: <span class="tag">${e.execution_count||0} 次</span></div></div>`;
1346
- html+=`<div class="card"><h3>沙盒设置</h3><div class="grid" style="grid-template-columns:1fr 1fr">
1371
+ html+=`<div class="card"><h3>沙盒设置</h3><div class="form-row">
1347
1372
  <div class="form-group"><label>Docker 镜像</label><input id="sbImage" value="${e.sandbox_image||'python:3.12-slim'}"></div>
1348
1373
  <div class="form-group"><label>内存限制</label><input id="sbMemory" value="${e.sandbox_memory||'512m'}" placeholder="512m"></div>
1349
1374
  <div class="form-group"><label>网络访问</label><select id="sbNetwork"><option ${!e.sandbox_network?'selected':''} value="false">禁止 (更安全)</option><option ${e.sandbox_network?'selected':''} value="true">允许</option></select></div></div>
1350
1375
  <div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveExecutor()">保存设置</button></div></div>`;
1351
- html+=`<div class="card"><h3>执行参数</h3><div class="grid" style="grid-template-columns:1fr 1fr 1fr">
1376
+ html+=`<div class="card"><h3>执行参数</h3><div class="form-row">
1352
1377
  <div class="form-group"><label>超时时间 (秒)</label><input id="exTimeout" type="number" value="${e.timeout||300}"></div>
1353
1378
  <div class="form-group"><label>自动重试</label><input id="exRetries" type="number" value="2"></div>
1354
1379
  <div class="form-group"><label>自动修复</label><select id="exAutoFix"><option ${e.auto_fix?'selected':''} value="true">开启</option><option ${!e.auto_fix?'selected':''} value="false">关闭</option></select></div></div></div>`;
@@ -1420,9 +1445,9 @@ async function viewSkillDetail(name){
1420
1445
  </div>
1421
1446
  <div class="form-group"><label>危险操作</label><div>${s.dangerous?'<span class="badge badge-red">是</span>':'<span class="badge badge-green">否</span>'}</div></div>
1422
1447
  <div class="form-group"><label>参数 (${(s.parameters||[]).length})</label>
1423
- <table><tr><th>名称</th><th>类型</th><th>必需</th><th>描述</th></tr>
1448
+ <div class="table-wrap"><table><tr><th>名称</th><th>类型</th><th>必需</th><th>描述</th></tr>
1424
1449
  ${(s.parameters||[]).map(p=>`<tr><td><code>${escHtml(p.name)}</code></td><td>${p.type||'string'}</td><td>${p.required?'✅':'❌'}</td><td>${escHtml(p.description||'')}</td></tr>`).join('')}
1425
- </table></div>`;
1450
+ </table></div></div>`;
1426
1451
  $('modalContainer').innerHTML=`<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()">${html}<div class="flex gap-8 mt-16"><button class="btn btn-ghost" onclick="closeModal()">关闭</button></div></div></div>`;
1427
1452
  }
1428
1453
 
@@ -1433,12 +1458,12 @@ async function renderFiles(){
1433
1458
  <span style="font-size:14px;color:var(--text2)">工作目录: ${wd.path}</span>
1434
1459
  <button class="btn btn-sm btn-ghost" onclick="changeWorkdir()">更改</button>
1435
1460
  <button class="btn btn-sm btn-ghost" onclick="renderFiles()">刷新</button></div>`;
1436
- html+='<table><tr><th>名称</th><th>类型</th><th>大小</th></tr>';
1461
+ html+='<div class="table-wrap"><table><tr><th>名称</th><th>类型</th><th>大小</th></tr>';
1437
1462
  for(const f of (files||[])){
1438
1463
  const icon=f.type==='dir'?'📁':'📄';const size=f.type==='file'?(f.size>1024?(f.size/1024).toFixed(1)+'KB':f.size+'B'):'-';
1439
1464
  html+=`<tr><td>${icon} ${escHtml(f.name)}</td><td>${f.type}</td><td>${size}</td></tr>`;}
1440
1465
  if(!files||!files.length)html+='<tr><td colspan="3" class="empty">目录为空</td></tr>';
1441
- html+='</table>';$('content').innerHTML=html;
1466
+ html+='</table></div>';$('content').innerHTML=html;
1442
1467
  }
1443
1468
  async function changeWorkdir(){const p=prompt('新路径:');if(!p)return;await api('/api/workdir',{method:'PUT',body:JSON.stringify({path:p})});renderFiles();}
1444
1469
 
@@ -1471,7 +1496,7 @@ async function renderTasks(){
1471
1496
  const tasks=r.tasks||[];
1472
1497
  const statusCounts={pending:0,running:0,completed:0,failed:0};
1473
1498
  for(const t of tasks){statusCounts[t.status]=(statusCounts[t.status]||0)+1;}
1474
- let html=`<div class="grid" style="grid-template-columns:repeat(4,1fr);margin-bottom:16px">
1499
+ let html=`<div class="grid grid-4" style="margin-bottom:16px">
1475
1500
  <div class="stat"><div class="label">待处理</div><div class="value">${statusCounts.pending}</div></div>
1476
1501
  <div class="stat"><div class="label">运行中</div><div class="value">${statusCounts.running}</div></div>
1477
1502
  <div class="stat"><div class="label">已完成</div><div class="value" style="color:var(--success)">${statusCounts.completed}</div></div>
@@ -1489,7 +1514,7 @@ async function renderTasks(){
1489
1514
  if(tasks.length===0){html+='<div class="empty">暂无任务记录</div>';}
1490
1515
  else{
1491
1516
  html+='<div style="max-height:calc(100vh - 340px);overflow-y:auto" id="taskList">';
1492
- html+='<table><tr><th>任务ID</th><th>描述</th><th>群聊</th><th>状态</th><th>时间</th><th>操作</th></tr>';
1517
+ html+='<div class="table-wrap"><table><tr><th>任务ID</th><th>描述</th><th>群聊</th><th>状态</th><th>时间</th><th>操作</th></tr>';
1493
1518
  for(const t of tasks){
1494
1519
  const statusBadge=t.status==='completed'?'<span class="badge badge-green">已完成</span>':
1495
1520
  t.status==='running'?'<span class="badge badge-blue">运行中</span>':
@@ -1511,7 +1536,7 @@ async function renderTasks(){
1511
1536
  <button class="btn btn-sm btn-danger" onclick="deleteTask('${escHtml(t.task_id)}')">删除</button>
1512
1537
  </td></tr>`;
1513
1538
  }
1514
- html+='</table></div>';
1539
+ html+='</table></div></div>';
1515
1540
  }
1516
1541
  $('content').innerHTML=html;
1517
1542
  }
@@ -1554,7 +1579,7 @@ async function renderOrganization(){
1554
1579
  </div>
1555
1580
  <div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveOrgConfig()">保存配置</button></div></div>`;
1556
1581
  html+=`<div class="card"><h3>组织信息</h3>
1557
- <div class="grid" style="grid-template-columns:1fr 1fr">
1582
+ <div class="form-row">
1558
1583
  <div class="form-group"><label>组织名称</label><input id="orgName" value="${escHtml(inf.name||'')}" placeholder="我的组织"></div>
1559
1584
  <div class="form-group"><label>组织描述</label><input id="orgDesc" value="${escHtml(inf.description||'')}" placeholder="组织简介"></div>
1560
1585
  <div class="form-group"><label>联系方式</label><input id="orgContact" value="${escHtml(inf.contact||'')}" placeholder="联系邮箱或电话"></div>
@@ -1581,13 +1606,13 @@ async function loadOrgKnowledge(){
1581
1606
  const files=await api('/api/organization/knowledge');
1582
1607
  const el=document.getElementById('orgKBList');if(!el)return;
1583
1608
  if(!files||!files.length){el.innerHTML='<div class="empty">暂无知识库文件</div>';return}
1584
- let html='<table><tr><th>文件名</th><th>大小</th><th>操作</th></tr>';
1609
+ let html='<div class="table-wrap"><table><tr><th>文件名</th><th>大小</th><th>操作</th></tr>';
1585
1610
  for(const f of files){
1586
1611
  html+=`<tr><td>${escHtml(f.name||f.path)}</td><td>${f.size||'-'}</td>
1587
1612
  <td><button class="btn btn-sm btn-ghost" onclick="viewOrgKBFile('${escHtml(f.path||f.name)}')">查看</button>
1588
1613
  <button class="btn btn-sm btn-danger" onclick="deleteOrgKBFile('${escHtml(f.path||f.name)}')">删除</button></td></tr>`;
1589
1614
  }
1590
- html+='</table>';el.innerHTML=html;
1615
+ html+='</table></div>';el.innerHTML=html;
1591
1616
  }
1592
1617
  function uploadOrgKnowledge(folderMode){
1593
1618
  const input=document.createElement('input');input.type='file';input.multiple=true;
@@ -1773,13 +1798,13 @@ async function loadDeptKB(path){
1773
1798
  let html='<h4 style="margin-bottom:8px">部门知识库</h4>';
1774
1799
  html+='<div style="margin-bottom:8px"><button class="btn btn-sm btn-primary" onclick="uploadDeptKB(\''+escHtml(path)+'\',false)">上传文件</button> <button class="btn btn-sm btn-secondary" onclick="uploadDeptKB(\''+escHtml(path)+'\',true)">📁 上传文件夹</button></div>';
1775
1800
  if(!files||!files.length){html+='<div class="empty" style="margin-top:12px">暂无知识库文件</div>';el.innerHTML=html;return}
1776
- html+='<table><tr><th>文件</th><th>操作</th></tr>';
1801
+ html+='<div class="table-wrap"><table><tr><th>文件</th><th>操作</th></tr>';
1777
1802
  for(const f of files){
1778
1803
  html+=`<tr><td>${escHtml(f.name||f.path)}</td>
1779
1804
  <td><button class="btn btn-sm btn-ghost" onclick="viewDeptKBFile('${escHtml(path)}','${escHtml(f.path||f.name)}')">查看</button>
1780
1805
  <button class="btn btn-sm btn-danger" onclick="deleteDeptKBFile('${escHtml(path)}','${escHtml(f.path||f.name)}')">删除</button></td></tr>`;
1781
1806
  }
1782
- html+='</table>';el.innerHTML=html;
1807
+ html+='</table></div>';el.innerHTML=html;
1783
1808
  }
1784
1809
  async function uploadDeptKB(path,folderMode){
1785
1810
  const input=document.createElement('input');input.type='file';input.multiple=true;