myagent-ai 1.23.19 → 1.23.21

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 (79) hide show
  1. package/chatbot/whatsapp_bot.py +0 -0
  2. package/chatbot/whatsapp_bridge/bridge.mjs +0 -0
  3. package/chatbot/whatsapp_bridge/package.json +0 -0
  4. package/core/stt.py +0 -0
  5. package/core/tool_dispatcher.py +0 -0
  6. package/core/web_control.py +0 -0
  7. package/data/novnc/lib/base64.js +0 -0
  8. package/data/novnc/lib/crypto/aes.js +0 -0
  9. package/data/novnc/lib/crypto/bigint.js +0 -0
  10. package/data/novnc/lib/crypto/crypto.js +0 -0
  11. package/data/novnc/lib/crypto/des.js +0 -0
  12. package/data/novnc/lib/crypto/dh.js +0 -0
  13. package/data/novnc/lib/crypto/md5.js +0 -0
  14. package/data/novnc/lib/crypto/rsa.js +0 -0
  15. package/data/novnc/lib/decoders/copyrect.js +0 -0
  16. package/data/novnc/lib/decoders/hextile.js +0 -0
  17. package/data/novnc/lib/decoders/jpeg.js +0 -0
  18. package/data/novnc/lib/decoders/raw.js +0 -0
  19. package/data/novnc/lib/decoders/rre.js +0 -0
  20. package/data/novnc/lib/decoders/tight.js +0 -0
  21. package/data/novnc/lib/decoders/tightpng.js +0 -0
  22. package/data/novnc/lib/decoders/zrle.js +0 -0
  23. package/data/novnc/lib/deflator.js +0 -0
  24. package/data/novnc/lib/display.js +0 -0
  25. package/data/novnc/lib/encodings.js +0 -0
  26. package/data/novnc/lib/inflator.js +0 -0
  27. package/data/novnc/lib/input/domkeytable.js +0 -0
  28. package/data/novnc/lib/input/fixedkeys.js +0 -0
  29. package/data/novnc/lib/input/gesturehandler.js +0 -0
  30. package/data/novnc/lib/input/keyboard.js +0 -0
  31. package/data/novnc/lib/input/keysym.js +0 -0
  32. package/data/novnc/lib/input/keysymdef.js +0 -0
  33. package/data/novnc/lib/input/util.js +0 -0
  34. package/data/novnc/lib/input/vkeys.js +0 -0
  35. package/data/novnc/lib/input/xtscancodes.js +0 -0
  36. package/data/novnc/lib/ra2.js +0 -0
  37. package/data/novnc/lib/rfb.js +0 -0
  38. package/data/novnc/lib/util/browser.js +0 -0
  39. package/data/novnc/lib/util/cursor.js +0 -0
  40. package/data/novnc/lib/util/element.js +0 -0
  41. package/data/novnc/lib/util/events.js +0 -0
  42. package/data/novnc/lib/util/eventtarget.js +0 -0
  43. package/data/novnc/lib/util/int.js +0 -0
  44. package/data/novnc/lib/util/logging.js +0 -0
  45. package/data/novnc/lib/util/strings.js +0 -0
  46. package/data/novnc/lib/vendor/pako/lib/utils/common.js +0 -0
  47. package/data/novnc/lib/vendor/pako/lib/zlib/adler32.js +0 -0
  48. package/data/novnc/lib/vendor/pako/lib/zlib/constants.js +0 -0
  49. package/data/novnc/lib/vendor/pako/lib/zlib/crc32.js +0 -0
  50. package/data/novnc/lib/vendor/pako/lib/zlib/deflate.js +0 -0
  51. package/data/novnc/lib/vendor/pako/lib/zlib/gzheader.js +0 -0
  52. package/data/novnc/lib/vendor/pako/lib/zlib/inffast.js +0 -0
  53. package/data/novnc/lib/vendor/pako/lib/zlib/inflate.js +0 -0
  54. package/data/novnc/lib/vendor/pako/lib/zlib/inftrees.js +0 -0
  55. package/data/novnc/lib/vendor/pako/lib/zlib/messages.js +0 -0
  56. package/data/novnc/lib/vendor/pako/lib/zlib/trees.js +0 -0
  57. package/data/novnc/lib/vendor/pako/lib/zlib/zstream.js +0 -0
  58. package/data/novnc/lib/websock.js +0 -0
  59. package/package.json +1 -1
  60. package/scripts/cli.py +0 -0
  61. package/skills/agent_tool_skill.py +0 -0
  62. package/web/ui/admin/admin-agents.js +570 -0
  63. package/web/ui/admin/admin-core.js +322 -0
  64. package/web/ui/admin/admin-dashboard.js +153 -0
  65. package/web/ui/admin/admin-executor.js +67 -0
  66. package/web/ui/admin/admin-files.js +81 -0
  67. package/web/ui/admin/admin-llm.js +190 -0
  68. package/web/ui/admin/admin-logs.js +69 -0
  69. package/web/ui/admin/admin-memory.js +91 -0
  70. package/web/ui/admin/admin-org.js +283 -0
  71. package/web/ui/admin/admin-permissions.js +147 -0
  72. package/web/ui/admin/admin-platforms.js +221 -0
  73. package/web/ui/admin/admin-sessions.js +182 -0
  74. package/web/ui/admin/admin-skills.js +217 -0
  75. package/web/ui/admin/admin-system.js +154 -0
  76. package/web/ui/admin/admin-tasks.js +131 -0
  77. package/web/ui/chat/chat_main.js +292 -304
  78. package/web/ui/chat/flow_engine.js +96 -41
  79. package/web/ui/index.html +15 -2776
@@ -0,0 +1,283 @@
1
+ // ========== Organization + Departments ==========
2
+ (function(){
3
+ try {
4
+ // ========== Organization ==========
5
+ async function renderOrganization(){
6
+ const [org,info]=await Promise.all([api('/api/organization'),api('/api/organization/info')]);
7
+ if(org.error){$('content').innerHTML='<div class="empty" style="color:var(--danger)">加载失败: '+escHtml(org.error)+'</div>';return}
8
+ const cfg=org.organization||org;
9
+ const inf=info.info||info||{};
10
+ let html=`<div class="card"><h3>组织配置</h3>
11
+ <div class="form-row">
12
+ <div class="form-group"><label>启用组织管理</label><label class="toggle"><input type="checkbox" id="orgEnabled" ${cfg.enabled?'checked':''}><span class="slider"></span></label></div>
13
+ <div class="form-group"><label>知识库管理员</label><input id="orgAdmin" value="${escHtml(cfg.knowledge_admin||'')}" placeholder="管理员用户名"></div>
14
+ </div>
15
+ <div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveOrgConfig()">保存配置</button></div></div>`;
16
+ html+=`<div class="card"><h3>组织信息</h3>
17
+ <div class="form-row">
18
+ <div class="form-group"><label>组织名称</label><input id="orgName" value="${escHtml(inf.name||'')}" placeholder="我的组织"></div>
19
+ <div class="form-group"><label>组织描述</label><input id="orgDesc" value="${escHtml(inf.description||'')}" placeholder="组织简介"></div>
20
+ <div class="form-group"><label>联系方式</label><input id="orgContact" value="${escHtml(inf.contact||'')}" placeholder="联系邮箱或电话"></div>
21
+ <div class="form-group"><label>网站</label><input id="orgWebsite" value="${escHtml(inf.website||'')}" placeholder="https://..."></div>
22
+ </div>
23
+ <div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveOrgInfo()">保存信息</button></div></div>`;
24
+ // 知识库 — 未启用组织时隐藏
25
+ const orgEnabled=cfg.enabled;
26
+ html+=`<div class="card" id="orgKBCard"><div class="flex justify-between items-center mb-16">
27
+ <h3 style="margin:0">组织知识库</h3>
28
+ <div id="orgKBActions">${orgEnabled?'<button class="btn btn-sm btn-primary" onclick="uploadOrgKnowledge(false)">上传文件</button> <button class="btn btn-sm btn-secondary" onclick="uploadOrgKnowledge(true)">📁 上传文件夹</button>':''}</div></div>
29
+ <div id="orgKBList"><div class="empty">${orgEnabled?'加载中...':'组织管理未启用,请先启用后再使用知识库'}</div></div></div>`;
30
+ $('content').innerHTML=html;
31
+ if(orgEnabled)loadOrgKnowledge();
32
+ }
33
+ async function saveOrgConfig(){
34
+ const r=await api('/api/organization',{method:'PUT',body:JSON.stringify({enabled:$('orgEnabled').checked,knowledge_admin:$('orgAdmin').value})});
35
+ if(r.error){showToast(r.error,'danger');return}showToast('已保存','success');renderOrganization();
36
+ }
37
+ async function saveOrgInfo(){
38
+ const r=await api('/api/organization/info',{method:'PUT',body:JSON.stringify({name:$('orgName').value,description:$('orgDesc').value,contact:$('orgContact').value,website:$('orgWebsite').value})});
39
+ if(r.error){showToast(r.error,'danger');return}showToast('已保存','success');
40
+ }
41
+ async function loadOrgKnowledge(){
42
+ const files=await api('/api/organization/knowledge');
43
+ const el=document.getElementById('orgKBList');if(!el)return;
44
+ if(!files||!files.length){el.innerHTML='<div class="empty">暂无知识库文件</div>';return}
45
+ let html='<div class="table-wrap"><table><tr><th>文件名</th><th>大小</th><th>操作</th></tr>';
46
+ for(const f of files){
47
+ html+=`<tr><td>${escHtml(f.name||f.path)}</td><td>${f.size?f.size>1024?(f.size/1024).toFixed(1)+' KB':f.size+' B':'-'}</td>
48
+ <td><button class="btn btn-sm btn-ghost" onclick="viewOrgKBFile('${escHtml(f.path||f.name)}')">查看</button>
49
+ <button class="btn btn-sm btn-ghost" onclick="downloadOrgKBFile('${escHtml(f.path||f.name)}')">下载</button>
50
+ <button class="btn btn-sm btn-danger" onclick="deleteOrgKBFile('${escHtml(f.path||f.name)}')">删除</button></td></tr>`;
51
+ }
52
+ html+='</table></div>';el.innerHTML=html;
53
+ }
54
+ function uploadOrgKnowledge(folderMode){
55
+ const input=document.createElement('input');input.type='file';input.multiple=true;
56
+ if(folderMode){input.webkitdirectory=true;}
57
+ input.onchange=async()=>{
58
+ const fd=new FormData();
59
+ for(const f of input.files){
60
+ // 文件夹上传时保留相对路径
61
+ if(f.webkitRelativePath){fd.append('files',f,{headers:{'X-File-Path':f.webkitRelativePath}});}
62
+ else{fd.append('files',f);}
63
+ }
64
+ showToast('正在上传...','info');
65
+ const r=await fetch(API+'/api/organization/knowledge/upload',{method:'POST',body:fd});
66
+ const data=await r.json();
67
+ if(data.error){showToast(data.error,'danger');return}
68
+ const total=data.results?data.results.length:0;
69
+ const okCount=data.results?data.results.filter(x=>x.ok).length:0;
70
+ showToast(`上传完成: ${okCount}/${total} 文件成功`,okCount===total?'success':'warning');
71
+ loadOrgKnowledge();
72
+ };input.click();
73
+ }
74
+ async function viewOrgKBFile(path){
75
+ const r=await fetch(API+'/api/organization/knowledge/file?path='+encodeURIComponent(path));
76
+ const data=await r.json();
77
+ if(data.error){showToast(data.error,'danger');return}
78
+ const content=typeof data==='string'?data:(data.content||JSON.stringify(data,null,2));
79
+ $('modalContainer').innerHTML=`<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()" style="max-width:800px">
80
+ <h3>${escHtml(path)}</h3>
81
+ <div class="log-viewer" style="max-height:60vh;white-space:pre-wrap">${escHtml(content.slice(0,5000))}${content.length>5000?'\\n...(已截断)':''}</div>
82
+ <div class="flex gap-8 mt-16"><button class="btn btn-ghost" onclick="closeModal()">关闭</button></div>
83
+ </div></div>`;
84
+ }
85
+ async function downloadOrgKBFile(path){
86
+ const a=document.createElement('a');
87
+ a.href=API+'/api/organization/knowledge/file?path='+encodeURIComponent(path)+'&download=1';
88
+ a.download=path.split('/').pop();
89
+ a.click();
90
+ }
91
+ async function deleteOrgKBFile(path){
92
+ showConfirm('删除文件','确认删除 "'+escHtml(path)+'" 吗?',async()=>{
93
+ const r=await api('/api/organization/knowledge?path='+encodeURIComponent(path),{method:'DELETE'});
94
+ if(r.error){showToast(r.error,'danger');closeModal();return}closeModal();showToast('已删除','success');loadOrgKnowledge();
95
+ });
96
+ }
97
+
98
+ // ========== Departments ==========
99
+ async function renderDepartments(){
100
+ const [tree,agents]=await Promise.all([api('/api/departments'),api('/api/agents')]);
101
+ if(tree.error){$('content').innerHTML='<div class="empty" style="color:var(--danger)">加载失败: '+escHtml(tree.error)+'</div>';return}
102
+ allAgentsCache=Array.isArray(agents)?agents:allAgentsCache;
103
+ const depts=Array.isArray(tree)?tree:(tree.children||[]);
104
+ let html=`<div class="flex justify-between items-center mb-16">
105
+ <div style="color:var(--text2);font-size:13px">共 ${countDepts(depts)} 个部门</div>
106
+ <button class="btn btn-primary" onclick="showCreateDeptModal()">+ 创建部门</button></div>`;
107
+ if(!depts.length){html+='<div class="empty">暂无部门,点击上方按钮创建</div>';$('content').innerHTML=html;return}
108
+ html+='<div id="deptTree">';
109
+ html+=renderDeptTree(depts,0);
110
+ html+='</div>';
111
+ $('content').innerHTML=html;
112
+ }
113
+ function countDepts(list){let c=0;for(const d of list||[]){c++;c+=countDepts(d.children||[])}return c}
114
+ function renderDeptTree(list,depth){
115
+ let html='';
116
+ for(const d of list||[]){
117
+ const hasChildren=(d.children||[]).length>0;
118
+ const indent=depth*24;
119
+ html+=`<div style="margin-left:${indent}px;border:1px solid var(--border);border-radius:var(--radius);margin-bottom:8px;background:var(--surface);padding:12px 16px">
120
+ <div class="flex justify-between items-center">
121
+ <div class="flex items-center gap-8">
122
+ <span style="font-size:20px">${escHtml(d.emoji||'📁')}</span>
123
+ <div><div style="font-weight:600">${escHtml(d.name)} <span class="tag">${d.agent_count||0} 成员</span></div>
124
+ <div style="font-size:12px;color:var(--text2)">${escHtml(d.description||'无描述')} ${hasChildren?'· '+d.children.length+' 个子部门':''}</div></div>
125
+ </div>
126
+ <div class="flex gap-8">
127
+ <button class="btn btn-sm btn-ghost" onclick="showDeptDetail('${escHtml(d.path||d.name)}')">详情/管理成员</button>
128
+ <button class="btn btn-sm btn-primary" onclick="showCreateDeptModal('${escHtml(d.path||d.name)}')">子部门</button>
129
+ <button class="btn btn-sm btn-danger" onclick="deleteDept('${escHtml(d.path||d.name)}','${escHtml(d.name)}')">删除</button>
130
+ </div>
131
+ </div>
132
+ ${hasChildren?renderDeptTree(d.children,depth+1):''}
133
+ </div>`;
134
+ }
135
+ return html;
136
+ }
137
+ function showCreateDeptModal(parent){
138
+ $('modalContainer').innerHTML=`<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()">
139
+ <h3>${parent?'创建子部门':'创建部门'}</h3>
140
+ ${parent?'<div class="form-group"><label>父部门</label><input value="'+escHtml(parent)+'" disabled></div>':''}
141
+ <div class="form-row">
142
+ <div class="form-group"><label>部门名称 *</label><input id="deptName" placeholder="部门名称"></div>
143
+ <div class="form-group"><label>Emoji</label><input id="deptEmoji" placeholder="📁" maxlength="4"></div>
144
+ </div>
145
+ <div class="form-group"><label>描述</label><textarea id="deptDesc" rows="3" placeholder="部门描述..."></textarea></div>
146
+ <div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="doCreateDept('${escHtml(parent||'')}')">创建</button><button class="btn btn-ghost" onclick="closeModal()">取消</button></div>
147
+ </div></div>`;
148
+ }
149
+ async function doCreateDept(parent){
150
+ const r=await api('/api/departments',{method:'POST',body:JSON.stringify({name:$('deptName').value,emoji:$('deptEmoji').value,description:$('deptDesc').value,parent})});
151
+ if(r.error){showToast(r.error,'danger');return}closeModal();showToast('已创建','success');renderDepartments();
152
+ }
153
+ async function deleteDept(path,name){
154
+ showConfirm('删除部门','确认删除部门 "'+escHtml(name)+'" 及其所有子部门吗?',async()=>{
155
+ const r=await api(`/api/departments/${encodeURIComponent(path)}`,{method:'DELETE'});
156
+ if(r.error){showToast(r.error,'danger');closeModal();return}closeModal();showToast('已删除','success');renderDepartments();
157
+ });
158
+ }
159
+ async function showDeptDetail(path){
160
+ try {
161
+ const [dept,info,agents]=await Promise.all([api(`/api/departments/${encodeURIComponent(path)}`),api(`/api/departments/${encodeURIComponent(path)}/info`),api('/api/agents')]);
162
+ if(dept.error){showToast(dept.error,'danger');return}
163
+ // 刷新 allAgentsCache 确保最新
164
+ allAgentsCache=Array.isArray(agents)?agents:allAgentsCache;
165
+ // 描述和负责人从 dept 元数据读取(非 info API 的 dept.md 内容)
166
+ const deptDesc=dept.description||'';
167
+ const deptHead=dept.head||'';
168
+ // 获取所有 agent 用于添加
169
+ const allAgents=Array.isArray(allAgentsCache)?allAgentsCache:[];
170
+ const currentAgents=Array.isArray(dept.agents)?dept.agents:[];
171
+ // 构建可选 agent 列表(排除已添加的)
172
+ const availAgents=allAgents.filter(a=>!currentAgents.includes(a.path||a.name));
173
+ const agentOptions=availAgents.map(a=>`<option value="${escHtml(a.path||a.name)}">${escHtml((a.avatar_emoji||'🤖')+' '+(a.name||a.path))}</option>`).join('');
174
+ // 已有 agent 列表 — 改为行列表形式,每行带删除按钮
175
+ const agentListHtml=currentAgents.length?`<div style="display:flex;flex-direction:column;gap:6px">${currentAgents.map(aName=>{
176
+ const aInfo=allAgents.find(a=>(a.path||a.name)===aName);
177
+ const label=aInfo?((aInfo.avatar_emoji||'🤖')+' '+(aInfo.name||aName)):aName;
178
+ const isHead=aName===deptHead;
179
+ return `<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--surface2);border-radius:8px;border:1px solid var(--border)">
180
+ <div style="display:flex;align-items:center;gap:8px">
181
+ <span>${isHead?'👑':'🤖'}</span>
182
+ <span style="font-weight:500">${escHtml(label)}</span>
183
+ ${isHead?'<span class="tag" style="font-size:11px">部长</span>':''}
184
+ </div>
185
+ <button class="btn btn-sm btn-danger" onclick="removeDeptAgent('${escHtml(path)}','${escHtml(aName)}')" style="padding:2px 10px;font-size:12px">删除</button>
186
+ </div>`;
187
+ }).join('')}</div>`:'<div style="padding:16px;text-align:center;color:var(--text3);font-size:13px;background:var(--surface2);border-radius:8px;border:1px dashed var(--border)">暂无成员</div>';
188
+
189
+ $('modalContainer').innerHTML=`<div class="modal-overlay" onclick="closeModal()"><div class="modal modal-wide" onclick="event.stopPropagation()">
190
+ <h3>${escHtml(dept.emoji||'📁')} ${escHtml(dept.name)}</h3>
191
+ <div class="form-row">
192
+ <div class="form-group"><label>部门名称</label><input id="ddName" value="${escHtml(dept.name||'')}"></div>
193
+ <div class="form-group"><label>Emoji</label><input id="ddEmoji" value="${escHtml(dept.emoji||'')}" maxlength="4"></div>
194
+ </div>
195
+ <div class="form-group"><label>描述</label><textarea id="ddDesc" rows="3">${escHtml(deptDesc)}</textarea></div>
196
+ <div class="form-group"><label>负责人</label><select id="ddHead"><option value="">请选择...</option>${currentAgents.map(aName=>{const aInfo=allAgents.find(a=>(a.path||a.name)===aName);const label=aInfo?((aInfo.avatar_emoji||'🤖')+' '+(aInfo.name||aName)):aName;return '<option value="'+escHtml(aName)+'"'+(aName===deptHead?' selected':'')+'>'+escHtml(label)+'</option>'}).join('')}${availAgents.map(a=>'<option value="'+escHtml(a.path||a.name)+'"'+((a.path||a.name)===deptHead?' selected':'')+'>'+escHtml((a.avatar_emoji||'🤖')+' '+(a.name||a.path))+'</option>').join('')}</select></div>
197
+ <hr style="border:none;border-top:1px solid var(--border);margin:20px 0">
198
+ <h4>🤖 部门成员 <span class="badge badge-blue">${currentAgents.length}</span></h4>
199
+ <div id="deptAgentList" style="margin:8px 0">${agentListHtml}</div>
200
+ ${agentOptions?`<div class="flex gap-8 mt-8"><select id="ddAddAgent" style="flex:1"><option value="">选择 Agent 添加...</option>${agentOptions}</select><button class="btn btn-sm btn-primary" onclick="addDeptAgent('${escHtml(path)}')">添加</button></div>`:'<div style="font-size:12px;color:var(--text3);margin-top:4px">所有 Agent 都已添加</div>'}
201
+ <hr style="border:none;border-top:1px solid var(--border);margin:20px 0">
202
+ <div class="flex gap-8">
203
+ <button class="btn btn-ghost" onclick="loadDeptKB('${escHtml(path)}')">查看知识库</button>
204
+ </div>
205
+ <div id="deptKBArea" style="margin-top:16px"></div>
206
+ <hr style="border:none;border-top:1px solid var(--border);margin:20px 0">
207
+ <div class="flex gap-8" style="justify-content:flex-end">
208
+ <button class="btn btn-primary" onclick="saveDeptInfo('${escHtml(path)}')">保存</button>
209
+ <button class="btn btn-ghost" onclick="closeModal()">关闭</button>
210
+ </div>
211
+ </div></div>`;
212
+ } catch(e) {
213
+ console.error('showDeptDetail error:', e);
214
+ showToast('加载部门详情失败: '+e.message, 'danger');
215
+ }
216
+ }
217
+ async function addDeptAgent(path){
218
+ const sel=document.getElementById('ddAddAgent');
219
+ const btn=sel?sel.nextElementSibling:null;
220
+ if(!sel||!sel.value){showToast('请选择要添加的 Agent','danger');return}
221
+ if(sel.disabled||btn?.disabled)return;
222
+ if(sel)sel.disabled=true;if(btn)btn.disabled=true;
223
+ try{
224
+ const r=await api(`/api/departments/${encodeURIComponent(path)}/agents`,{method:'PUT',body:JSON.stringify({agents:[sel.value],action:'add'})});
225
+ if(r.error||r.ok===false){showToast(r.error||r.message||'添加失败','danger');return}
226
+ showToast('已添加','success');_deptTreeNeedsRefresh=true;showDeptDetail(path);
227
+ }finally{if(sel)sel.disabled=false;if(btn)btn.disabled=false}
228
+ }
229
+ async function removeDeptAgent(path,agentName){
230
+ const r=await api(`/api/departments/${encodeURIComponent(path)}/agents`,{method:'PUT',body:JSON.stringify({agents:[agentName],action:'remove'})});
231
+ if(r.error||r.ok===false){showToast(r.error||r.message||'移除失败','danger');return}
232
+ showToast('已移除','success');_deptTreeNeedsRefresh=true;showDeptDetail(path);
233
+ }
234
+ async function saveDeptInfo(path){
235
+ // 同时保存名称、emoji 和元数据(描述、负责人)
236
+ const [metaRes,deptRes]=await Promise.all([
237
+ api(`/api/departments/${encodeURIComponent(path)}/info`,{method:'PUT',body:JSON.stringify({description:$('ddDesc').value,head_name:$('ddHead').value})}),
238
+ api(`/api/departments/${encodeURIComponent(path)}`,{method:'PUT',body:JSON.stringify({name:$('ddName').value,emoji:$('ddEmoji').value})})
239
+ ]);
240
+ if(metaRes.error){showToast(metaRes.error,'danger');return}
241
+ if(deptRes.error){showToast(deptRes.error,'danger');return}
242
+ showToast('已保存','success');
243
+ renderDepartments();
244
+ }
245
+ async function loadDeptKB(path){
246
+ const files=await api(`/api/departments/${encodeURIComponent(path)}/knowledge`);
247
+ const el=document.getElementById('deptKBArea');if(!el)return;
248
+ let html='<h4 style="margin-bottom:8px">部门知识库</h4>';
249
+ 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>';
250
+ if(!files||!files.length){html+='<div class="empty" style="margin-top:12px">暂无知识库文件</div>';el.innerHTML=html;return}
251
+ html+='<div class="table-wrap"><table><tr><th>文件</th><th>操作</th></tr>';
252
+ for(const f of files){
253
+ html+=`<tr><td>${escHtml(f.name||f.path)}</td>
254
+ <td><button class="btn btn-sm btn-ghost" onclick="viewDeptKBFile('${escHtml(path)}','${escHtml(f.path||f.name)}')">查看</button>
255
+ <button class="btn btn-sm btn-danger" onclick="deleteDeptKBFile('${escHtml(path)}','${escHtml(f.path||f.name)}')">删除</button></td></tr>`;
256
+ }
257
+ html+='</table></div>';el.innerHTML=html;
258
+ }
259
+ async function uploadDeptKB(path,folderMode){
260
+ const input=document.createElement('input');input.type='file';input.multiple=true;
261
+ if(folderMode){input.webkitdirectory=true;}
262
+ input.onchange=async()=>{
263
+ const fd=new FormData();
264
+ for(const f of input.files){
265
+ if(f.webkitRelativePath){fd.append('files',f,{headers:{'X-File-Path':f.webkitRelativePath}});}
266
+ else{fd.append('files',f);}
267
+ }
268
+ showToast('正在上传...','info');
269
+ const r=await fetch(API+`/api/departments/${encodeURIComponent(path)}/knowledge/upload`,{method:'POST',body:fd});
270
+ const data=await r.json();
271
+ if(data.error){showToast(data.error,'danger');return}
272
+ const total=data.results?data.results.length:0;
273
+ const okCount=data.results?data.results.filter(x=>x.ok).length:0;
274
+ showToast(`上传完成: ${okCount}/${total} 文件成功`,okCount===total?'success':'warning');
275
+ loadDeptKB(path);
276
+ };input.click();
277
+ }
278
+
279
+ if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
280
+ window._adminRenderers['organization'] = renderOrganization;
281
+ window._adminRenderers['departments'] = renderDepartments;
282
+ } catch(e) { console.error('[admin-org] load error:', e); }
283
+ })();
@@ -0,0 +1,147 @@
1
+ // ========== Permissions ==========
2
+ (function(){
3
+ try {
4
+ // ========== 权限管理 ==========
5
+ let _permsCache=null;
6
+ async function renderPermissions(){
7
+ const r=await api('/api/permissions');
8
+ if(r.error){$('content').innerHTML='<div class="empty" style="color:var(--danger)">加载失败: '+escHtml(r.error)+'</div>';return}
9
+ _permsCache=r;
10
+ const labels=r.labels||{};
11
+ const perms=r.all_permissions||[];
12
+ const defaults=r.defaults||{};
13
+ const agents=r.agents||{};
14
+
15
+ // 全局默认权限
16
+ let html='<div class="card"><h3>全局默认权限</h3><p style="color:var(--text2);font-size:13px;margin-bottom:12px">新 Agent 将继承这些默认权限设置</p>';
17
+ html+='<div class="grid grid-3" style="gap:12px">';
18
+ for(const p of perms){
19
+ const label=labels[p]||p;
20
+ const val=defaults[p]!==false?'checked':'';
21
+ html+=`<div class="form-group" style="display:flex;align-items:center;gap:10px"><label style="flex:1;font-weight:500">${label} <span style="color:var(--text3);font-size:11px">(${p})</span></label><input type="checkbox" id="perm_def_${p}" ${val} style="width:18px;height:18px;cursor:pointer"></div>`;
22
+ }
23
+ html+='</div><div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveDefaultPerms()">保存默认权限</button></div></div>';
24
+
25
+ // Agent 权限覆盖
26
+ const agentKeys=Object.keys(agents);
27
+ if(agentKeys.length>0){
28
+ 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>';
29
+ for(const p of perms){html+=`<th>${labels[p]||p}</th>`;}
30
+ html+='<th>操作</th></tr>';
31
+ for(const name of agentKeys){
32
+ const ap=agents[name]||{};
33
+ html+=`<tr><td>${escHtml(name)}</td>`;
34
+ for(const p of perms){
35
+ const v=ap[p]!==false;
36
+ html+=`<td style="text-align:center">${v?'<span style="color:var(--success)">✓</span>':'<span style="color:var(--danger)">✗</span>'}</td>`;
37
+ }
38
+ 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>`;
39
+ }
40
+ html+='</table></div></div>';
41
+ }
42
+
43
+ $('content').innerHTML=html;
44
+ }
45
+
46
+ async function saveDefaultPerms(){
47
+ if(!_permsCache)return;
48
+ const perms=_permsCache.all_permissions||[];
49
+ const data={};
50
+ for(const p of perms){
51
+ const el=document.getElementById('perm_def_'+p);
52
+ if(el)data[p]=el.checked;
53
+ }
54
+ const r=await api('/api/permissions/defaults',{method:'PUT',body:JSON.stringify(data)});
55
+ if(r.error){showToast(r.error,'danger');return}
56
+ showToast('默认权限已保存','success');
57
+ }
58
+
59
+ async function editAgentPerms(name){
60
+ if(!_permsCache)return;
61
+ const labels=_permsCache.labels||{};
62
+ const perms=_permsCache.all_permissions||[];
63
+ const defaults=_permsCache.defaults||{};
64
+ // 获取当前 agent 权限和 agent 信息(含执行模式)
65
+ let agentPerms={};
66
+ let agentExecMode='sandbox';
67
+ try{
68
+ const [r,ai]=await Promise.all([
69
+ api('/api/permissions/'+encodeURIComponent(name)),
70
+ api('/api/agents/'+encodeURIComponent(name))
71
+ ]);
72
+ agentPerms=r.permissions||{};
73
+ agentExecMode=ai.execution_mode||'sandbox';
74
+ }catch(e){agentPerms={...defaults}}
75
+
76
+ let html='<h3>编辑 Agent 权限: '+escHtml(name)+'</h3>';
77
+
78
+ // 执行模式选择
79
+ html+='<div style="margin-bottom:16px"><div style="font-size:13px;color:var(--text2);margin-bottom:8px;font-weight:600">⚡ 执行环境</div>';
80
+ html+='<div style="display:flex;gap:12px">';
81
+ html+=`<label id="modalExecSandboxLabel" style="flex:1;display:flex;align-items:center;gap:10px;padding:12px 14px;border-radius:var(--radius);border:2px solid ${agentExecMode==='sandbox'?'var(--primary)':'var(--border)'};cursor:pointer;background:${agentExecMode==='sandbox'?'var(--accent-light)':'transparent'};transition:all .15s" onclick="document.getElementById('modalExecSandbox').checked=true;updateModalExecUI()">`;
82
+ html+=`<input type="radio" name="modalExecMode" id="modalExecSandbox" value="sandbox" ${agentExecMode==='sandbox'?'checked':''} style="display:none">`;
83
+ html+=`<span style="font-size:24px">🐳</span><div><div style="font-weight:600;font-size:13px">沙盒模式</div><div style="font-size:11px;color:var(--text2)">Docker 容器隔离</div></div></label>`;
84
+ html+=`<label id="modalExecLocalLabel" style="flex:1;display:flex;align-items:center;gap:10px;padding:12px 14px;border-radius:var(--radius);border:2px solid ${agentExecMode==='local'?'var(--primary)':'var(--border)'};cursor:pointer;background:${agentExecMode==='local'?'var(--accent-light)':'transparent'};transition:all .15s" onclick="document.getElementById('modalExecLocal').checked=true;updateModalExecUI()">`;
85
+ html+=`<input type="radio" name="modalExecMode" id="modalExecLocal" value="local" ${agentExecMode==='local'?'checked':''} style="display:none">`;
86
+ html+=`<span style="font-size:24px">💻</span><div><div style="font-weight:600;font-size:13px">本机模式</div><div style="font-size:11px;color:var(--text2)">完整本地权限</div></div></label>`;
87
+ html+='</div></div>';
88
+
89
+ // 功能权限
90
+ html+='<div style="font-size:13px;color:var(--text2);margin-bottom:8px;font-weight:600">🔑 功能权限</div>';
91
+ html+='<div class="grid grid-3" style="gap:12px">';
92
+ for(const p of perms){
93
+ const label=labels[p]||p;
94
+ const defVal=defaults[p]!==false;
95
+ const curVal=agentPerms[p]!==undefined?agentPerms[p]:defVal;
96
+ const checked=curVal?'checked':'';
97
+ html+=`<div class="form-group" style="display:flex;align-items:center;gap:10px"><label style="flex:1;font-weight:500">${label}</label><input type="checkbox" id="perm_agent_${p}" ${checked} style="width:18px;height:18px;cursor:pointer"></div>`;
98
+ }
99
+ html+='</div><div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveAgentPerms(\''+escHtml(name)+'\')">💾 保存</button><button class="btn btn-ghost" onclick="renderPermissions()">取消</button></div>';
100
+ $('modalContainer').innerHTML='<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()">'+html+'</div></div>';
101
+ }
102
+
103
+ function updateModalExecUI(){
104
+ const sandbox=document.getElementById('modalExecSandbox');
105
+ const local=document.getElementById('modalExecLocal');
106
+ if(!sandbox||!local)return;
107
+ [sandbox.parentElement,local.parentElement].forEach(el=>{
108
+ const radio=el.querySelector('input[type=radio]');
109
+ if(radio.checked){el.style.borderColor='var(--primary)';el.style.background='var(--accent-light)'}
110
+ else{el.style.borderColor='var(--border)';el.style.background='transparent'}
111
+ });
112
+ }
113
+
114
+ async function saveAgentPerms(name){
115
+ if(!_permsCache)return;
116
+ // 1. 保存执行模式
117
+ const modalSandbox=document.getElementById('modalExecSandbox');
118
+ const modalLocal=document.getElementById('modalExecLocal');
119
+ if(modalSandbox||modalLocal){
120
+ const newMode=(modalSandbox&&modalSandbox.checked)?'sandbox':((modalLocal&&modalLocal.checked)?'local':null);
121
+ if(newMode){
122
+ await api('/api/agents/'+encodeURIComponent(name),{method:'PUT',body:JSON.stringify({execution_mode:newMode})});
123
+ }
124
+ }
125
+ // 2. 保存功能权限
126
+ const perms=_permsCache.all_permissions||[];
127
+ const data={};
128
+ for(const p of perms){
129
+ const el=document.getElementById('perm_agent_'+p);
130
+ if(el)data[p]=el.checked;
131
+ }
132
+ const r=await api('/api/permissions/'+encodeURIComponent(name),{method:'PUT',body:JSON.stringify(data)});
133
+ if(r.error){showToast(r.error,'danger');return}
134
+ closeModal();showToast('Agent 权限已更新','success');renderPermissions();
135
+ }
136
+
137
+ async function resetAgentPerms(name){
138
+ if(!confirm('确认重置 "'+name+'" 的权限为默认值?'))return;
139
+ const r=await api('/api/permissions/'+encodeURIComponent(name),{method:'DELETE'});
140
+ if(r.error){showToast(r.error,'danger');return}
141
+ showToast('已重置为默认权限','success');renderPermissions();
142
+ }
143
+
144
+ if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
145
+ window._adminRenderers['permissions'] = renderPermissions;
146
+ } catch(e) { console.error('[admin-permissions] load error:', e); }
147
+ })();