collabdocchat 2.5.4 → 2.5.6

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.
@@ -793,7 +793,7 @@
793
793
 
794
794
  确定要清除吗?`))return;if(prompt(`请输入群组名称以确认删除:
795
795
 
796
- 群组名称:`+i.name)!==i.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const j=document.getElementById("clearChatBtn"),C=j.innerHTML;j.innerHTML="⏳ 清除中...",j.disabled=!0;const G=localStorage.getItem("token"),q=await fetch(`http://localhost:8765/api/messages/group/${i._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${G}`,"Content-Type":"application/json"}});if(!q.ok){const X=await q.json();throw new Error(X.message||"清除失败")}const Y=await q.json();b.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${Y.deletedCount||0} 条聊天记录!`),j.innerHTML=C,j.disabled=!1}catch(j){console.error("清除聊天记录失败:",j),alert("❌ 清除失败: "+j.message);const C=document.getElementById("clearChatBtn");C.innerHTML="🗑️ 清除记录",C.disabled=!1}}),document.getElementById("muteAllBtn").addEventListener("click",async()=>{try{const p=!d;d=!!(await a.setMuteAll(i._id,p)).mutedAll,s();const j=document.createElement("div");j.className="notification",j.textContent=d?"已开启全体禁言(成员无法发言)":"已取消全体禁言",b.appendChild(j),b.scrollTop=b.scrollHeight}catch(p){alert("设置失败: "+p.message)}}),document.getElementById("manageMuteBtn").addEventListener("click",async()=>{u=(await a.getGroup(i._id)).group,r=new Set((u.mutedUsers||[]).map(String));const L=document.getElementById("membersList");L.innerHTML=u.members.filter(j=>j._id.toString()!==o).map(j=>{const C=r.has(j._id.toString());return`
796
+ 群组名称:`+i.name)!==i.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const j=document.getElementById("clearChatBtn"),C=j.innerHTML;j.innerHTML="⏳ 清除中...",j.disabled=!0;const G=localStorage.getItem("token"),q=await fetch(`http://localhost:3000/api/messages/group/${i._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${G}`,"Content-Type":"application/json"}});if(!q.ok){const X=await q.json();throw new Error(X.message||"清除失败")}const Y=await q.json();b.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${Y.deletedCount||0} 条聊天记录!`),j.innerHTML=C,j.disabled=!1}catch(j){console.error("清除聊天记录失败:",j),alert("❌ 清除失败: "+j.message);const C=document.getElementById("clearChatBtn");C.innerHTML="🗑️ 清除记录",C.disabled=!1}}),document.getElementById("muteAllBtn").addEventListener("click",async()=>{try{const p=!d;d=!!(await a.setMuteAll(i._id,p)).mutedAll,s();const j=document.createElement("div");j.className="notification",j.textContent=d?"已开启全体禁言(成员无法发言)":"已取消全体禁言",b.appendChild(j),b.scrollTop=b.scrollHeight}catch(p){alert("设置失败: "+p.message)}}),document.getElementById("manageMuteBtn").addEventListener("click",async()=>{u=(await a.getGroup(i._id)).group,r=new Set((u.mutedUsers||[]).map(String));const L=document.getElementById("membersList");L.innerHTML=u.members.filter(j=>j._id.toString()!==o).map(j=>{const C=r.has(j._id.toString());return`
797
797
  <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; border-bottom: 1px solid var(--border);">
798
798
  <div style="display: flex; align-items: center; gap: 10px;">
799
799
  <div class="avatar" style="width: 35px; height: 35px;">${j.username[0].toUpperCase()}</div>
@@ -813,7 +813,7 @@
813
813
  <span class="message-time">${new Date(p.timestamp).toLocaleTimeString()}</span>
814
814
  </div>
815
815
  <div class="message-content" style="${C?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${j}</div>
816
- `,b.appendChild(L),b.scrollTop=b.scrollHeight}}),e.on("chat_blocked",p=>{if(p.groupId===i._id){const L=document.createElement("div");L.className="notification",L.textContent=p.message||"消息发送失败",b.appendChild(L),b.scrollTop=b.scrollHeight}});const c=()=>{const p=k.value.trim();p&&(e.sendChatMessage(i._id,t.username,p),k.value="")};B.addEventListener("click",c),k.addEventListener("keypress",p=>{p.key==="Enter"&&c()}),document.getElementById("openAIBtn").addEventListener("click",()=>{document.getElementById("aiModal").classList.remove("hidden")}),document.getElementById("closeAIModal").addEventListener("click",()=>{document.getElementById("aiModal").classList.add("hidden")}),document.querySelectorAll(".quick-question-btn").forEach(p=>{p.addEventListener("click",()=>{document.getElementById("aiInputText").value=p.dataset.question,document.getElementById("aiSendBtnModal").click()}),p.addEventListener("mouseenter",L=>{L.target.style.background="var(--primary)",L.target.style.color="white",L.target.style.transform="translateY(-2px)"}),p.addEventListener("mouseleave",L=>{L.target.style.background="var(--bg)",L.target.style.color="inherit",L.target.style.transform="translateY(0)"})});const v=document.getElementById("aiInputText");v.addEventListener("input",()=>{v.style.height="auto",v.style.height=v.scrollHeight+"px"}),v.addEventListener("keydown",p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),document.getElementById("aiSendBtnModal").click())}),v.addEventListener("focus",()=>{v.style.borderColor="var(--primary)"}),v.addEventListener("blur",()=>{v.style.borderColor="var(--border)"});const w=document.getElementById("aiSendBtnModal");w.addEventListener("mouseenter",()=>{w.style.transform="scale(1.05)"}),w.addEventListener("mouseleave",()=>{w.style.transform="scale(1)"}),document.getElementById("aiSendBtnModal").addEventListener("click",async()=>{const p=document.getElementById("aiInputText"),L=p.value.trim();if(!L)return;const j=document.getElementById("aiChatMessages"),C=document.createElement("div");C.className="ai-message user",C.style.cssText="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 18px; border-radius: 18px 18px 4px 18px; margin: 10px 0; max-width: 75%; margin-left: auto; text-align: right; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); animation: slideInRight 0.3s ease;",C.textContent=L,j.appendChild(C),p.value="";const G=document.createElement("div");G.className="ai-message ai loading",G.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",G.textContent="思考中...",j.appendChild(G),j.scrollTop=j.scrollHeight;try{const q=localStorage.getItem("token"),X=await(await fetch("http://localhost:8765/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${q}`},body:JSON.stringify({question:L,groupId:i==null?void 0:i._id})})).json();G.remove();const te=document.createElement("div");te.className="ai-message ai",te.style.cssText="background: var(--bg-secondary); padding: 15px 18px; border-radius: 18px 18px 18px 4px; margin: 10px 0; max-width: 75%; border: 1px solid var(--border); box-shadow: 0 2px 4px rgba(0,0,0,0.05); animation: slideInLeft 0.3s ease; line-height: 1.6;",te.textContent=X.answer||"抱歉,我无法回答这个问题。",j.appendChild(te),j.scrollTop=j.scrollHeight}catch(q){G.remove();const Y=document.createElement("div");Y.className="ai-message ai error",Y.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",Y.textContent="抱歉,发生了错误: "+q.message,j.appendChild(Y),j.scrollTop=j.scrollHeight}}),window.showImageModal=p=>{const L=document.getElementById("imagePreviewModal"),j=document.getElementById("previewImage"),C=document.getElementById("downloadImageBtn");j.src=p,L.classList.remove("hidden"),C.onclick=async()=>{try{const q=await(await fetch(p)).blob(),Y=window.URL.createObjectURL(q),X=document.createElement("a");X.href=Y,X.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(X),X.click(),document.body.removeChild(X),window.URL.revokeObjectURL(Y)}catch(G){console.error("下载失败:",G),alert("下载失败,请重试")}}},document.getElementById("closeImagePreview").addEventListener("click",()=>{document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("imagePreviewModal").addEventListener("click",p=>{p.target.id==="imagePreviewModal"&&document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("openWhiteboardBtn").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.remove("hidden"),P()}),document.getElementById("closeWhiteboardModal").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.add("hidden")});function P(){const p=document.getElementById("whiteboardCanvas");if(!p)return;const L=p.getContext("2d");let j=!1,C="pen",G="#000000",q=3,Y=0,X=0;document.querySelectorAll(".tool-btn").forEach(J=>{J.onclick=()=>{document.querySelectorAll(".tool-btn").forEach(ee=>{ee.style.background="transparent",ee.style.borderColor="var(--border)",ee.style.color="inherit",ee.classList.remove("active")}),J.style.background="var(--primary)",J.style.borderColor="var(--primary)",J.style.color="white",J.classList.add("active"),C=J.dataset.tool}});const te=document.getElementById("colorPickerCanvas");te&&(te.onchange=J=>{G=J.target.value});const ue=document.getElementById("brushSizeCanvas"),ie=document.getElementById("brushSizeLabel");ue&&ie&&(ue.oninput=J=>{q=J.target.value,ie.textContent=`大小: ${q}`}),p.onmousedown=J=>{j=!0;const ee=p.getBoundingClientRect();Y=J.clientX-ee.left,X=J.clientY-ee.top},p.onmousemove=J=>{if(!j)return;const ee=p.getBoundingClientRect(),de=J.clientX-ee.left,ve=J.clientY-ee.top;L.beginPath(),L.moveTo(Y,X),L.lineTo(de,ve),L.strokeStyle=C==="eraser"?"#ffffff":G,L.lineWidth=q,L.lineCap="round",L.stroke(),Y=de,X=ve},p.onmouseup=()=>{j=!1},p.onmouseleave=()=>{j=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&L.clearRect(0,0,p.width,p.height)},document.getElementById("saveCanvasBtn").onclick=()=>{const J=p.toDataURL("image/png"),ee=document.createElement("a");ee.download=`whiteboard-${Date.now()}.png`,ee.href=J,ee.click(),alert("白板已保存!")},document.getElementById("sendToGroupBtn").onclick=async()=>{try{const J=p.toDataURL("image/png"),ee=await fetch(J).then(Ze=>Ze.blob()),de=new FormData;de.append("file",ee,`whiteboard-${Date.now()}.png`),de.append("groupId",i._id),de.append("description","协作白板作品");const ve=localStorage.getItem("token"),Xe=await fetch("http://localhost:8765/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${ve}`},body:de});if(Xe.ok){const Mt=`http://localhost:8765/api/files/${(await Xe.json()).file._id}/download?token=${ve}`;e.sendChatMessage(i._id,t.username,`[白板作品]${Mt}`),alert("白板作品已发送到群聊!"),document.getElementById("whiteboardModal").classList.add("hidden")}else throw new Error("上传失败")}catch(J){console.error("发送白板作品错误:",J),alert("发送失败: "+J.message)}}}}async function V(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}h.innerHTML=`
816
+ `,b.appendChild(L),b.scrollTop=b.scrollHeight}}),e.on("chat_blocked",p=>{if(p.groupId===i._id){const L=document.createElement("div");L.className="notification",L.textContent=p.message||"消息发送失败",b.appendChild(L),b.scrollTop=b.scrollHeight}});const c=()=>{const p=k.value.trim();p&&(e.sendChatMessage(i._id,t.username,p),k.value="")};B.addEventListener("click",c),k.addEventListener("keypress",p=>{p.key==="Enter"&&c()}),document.getElementById("openAIBtn").addEventListener("click",()=>{document.getElementById("aiModal").classList.remove("hidden")}),document.getElementById("closeAIModal").addEventListener("click",()=>{document.getElementById("aiModal").classList.add("hidden")}),document.querySelectorAll(".quick-question-btn").forEach(p=>{p.addEventListener("click",()=>{document.getElementById("aiInputText").value=p.dataset.question,document.getElementById("aiSendBtnModal").click()}),p.addEventListener("mouseenter",L=>{L.target.style.background="var(--primary)",L.target.style.color="white",L.target.style.transform="translateY(-2px)"}),p.addEventListener("mouseleave",L=>{L.target.style.background="var(--bg)",L.target.style.color="inherit",L.target.style.transform="translateY(0)"})});const v=document.getElementById("aiInputText");v.addEventListener("input",()=>{v.style.height="auto",v.style.height=v.scrollHeight+"px"}),v.addEventListener("keydown",p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),document.getElementById("aiSendBtnModal").click())}),v.addEventListener("focus",()=>{v.style.borderColor="var(--primary)"}),v.addEventListener("blur",()=>{v.style.borderColor="var(--border)"});const w=document.getElementById("aiSendBtnModal");w.addEventListener("mouseenter",()=>{w.style.transform="scale(1.05)"}),w.addEventListener("mouseleave",()=>{w.style.transform="scale(1)"}),document.getElementById("aiSendBtnModal").addEventListener("click",async()=>{const p=document.getElementById("aiInputText"),L=p.value.trim();if(!L)return;const j=document.getElementById("aiChatMessages"),C=document.createElement("div");C.className="ai-message user",C.style.cssText="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 18px; border-radius: 18px 18px 4px 18px; margin: 10px 0; max-width: 75%; margin-left: auto; text-align: right; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); animation: slideInRight 0.3s ease;",C.textContent=L,j.appendChild(C),p.value="";const G=document.createElement("div");G.className="ai-message ai loading",G.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",G.textContent="思考中...",j.appendChild(G),j.scrollTop=j.scrollHeight;try{const q=localStorage.getItem("token"),X=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${q}`},body:JSON.stringify({question:L,groupId:i==null?void 0:i._id})})).json();G.remove();const te=document.createElement("div");te.className="ai-message ai",te.style.cssText="background: var(--bg-secondary); padding: 15px 18px; border-radius: 18px 18px 18px 4px; margin: 10px 0; max-width: 75%; border: 1px solid var(--border); box-shadow: 0 2px 4px rgba(0,0,0,0.05); animation: slideInLeft 0.3s ease; line-height: 1.6;",te.textContent=X.answer||"抱歉,我无法回答这个问题。",j.appendChild(te),j.scrollTop=j.scrollHeight}catch(q){G.remove();const Y=document.createElement("div");Y.className="ai-message ai error",Y.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",Y.textContent="抱歉,发生了错误: "+q.message,j.appendChild(Y),j.scrollTop=j.scrollHeight}}),window.showImageModal=p=>{const L=document.getElementById("imagePreviewModal"),j=document.getElementById("previewImage"),C=document.getElementById("downloadImageBtn");j.src=p,L.classList.remove("hidden"),C.onclick=async()=>{try{const q=await(await fetch(p)).blob(),Y=window.URL.createObjectURL(q),X=document.createElement("a");X.href=Y,X.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(X),X.click(),document.body.removeChild(X),window.URL.revokeObjectURL(Y)}catch(G){console.error("下载失败:",G),alert("下载失败,请重试")}}},document.getElementById("closeImagePreview").addEventListener("click",()=>{document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("imagePreviewModal").addEventListener("click",p=>{p.target.id==="imagePreviewModal"&&document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("openWhiteboardBtn").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.remove("hidden"),P()}),document.getElementById("closeWhiteboardModal").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.add("hidden")});function P(){const p=document.getElementById("whiteboardCanvas");if(!p)return;const L=p.getContext("2d");let j=!1,C="pen",G="#000000",q=3,Y=0,X=0;document.querySelectorAll(".tool-btn").forEach(J=>{J.onclick=()=>{document.querySelectorAll(".tool-btn").forEach(ee=>{ee.style.background="transparent",ee.style.borderColor="var(--border)",ee.style.color="inherit",ee.classList.remove("active")}),J.style.background="var(--primary)",J.style.borderColor="var(--primary)",J.style.color="white",J.classList.add("active"),C=J.dataset.tool}});const te=document.getElementById("colorPickerCanvas");te&&(te.onchange=J=>{G=J.target.value});const ue=document.getElementById("brushSizeCanvas"),ie=document.getElementById("brushSizeLabel");ue&&ie&&(ue.oninput=J=>{q=J.target.value,ie.textContent=`大小: ${q}`}),p.onmousedown=J=>{j=!0;const ee=p.getBoundingClientRect();Y=J.clientX-ee.left,X=J.clientY-ee.top},p.onmousemove=J=>{if(!j)return;const ee=p.getBoundingClientRect(),de=J.clientX-ee.left,ve=J.clientY-ee.top;L.beginPath(),L.moveTo(Y,X),L.lineTo(de,ve),L.strokeStyle=C==="eraser"?"#ffffff":G,L.lineWidth=q,L.lineCap="round",L.stroke(),Y=de,X=ve},p.onmouseup=()=>{j=!1},p.onmouseleave=()=>{j=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&L.clearRect(0,0,p.width,p.height)},document.getElementById("saveCanvasBtn").onclick=()=>{const J=p.toDataURL("image/png"),ee=document.createElement("a");ee.download=`whiteboard-${Date.now()}.png`,ee.href=J,ee.click(),alert("白板已保存!")},document.getElementById("sendToGroupBtn").onclick=async()=>{try{const J=p.toDataURL("image/png"),ee=await fetch(J).then(Ze=>Ze.blob()),de=new FormData;de.append("file",ee,`whiteboard-${Date.now()}.png`),de.append("groupId",i._id),de.append("description","协作白板作品");const ve=localStorage.getItem("token"),Xe=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${ve}`},body:de});if(Xe.ok){const Mt=`http://localhost:3000/api/files/${(await Xe.json()).file._id}/download?token=${ve}`;e.sendChatMessage(i._id,t.username,`[白板作品]${Mt}`),alert("白板作品已发送到群聊!"),document.getElementById("whiteboardModal").classList.add("hidden")}else throw new Error("上传失败")}catch(J){console.error("发送白板作品错误:",J),alert("发送失败: "+J.message)}}}}async function V(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}h.innerHTML=`
817
817
  <div class="view-header">
818
818
  <h2>随机点名 - ${i.name}</h2>
819
819
  </div>
@@ -923,7 +923,7 @@
923
923
  </div>
924
924
  `}).join("")}
925
925
  </div>
926
- `;const w=document.getElementById("auditPagination"),P=document.getElementById("pageInfo");P.textContent=`第 ${v.pagination.page} 页,共 ${v.pagination.pages} 页`,document.getElementById("prevPage").disabled=v.pagination.page<=1,document.getElementById("nextPage").disabled=v.pagination.page>=v.pagination.pages,w.style.display=v.pagination.pages>1?"flex":"none"}catch(s){console.error("加载审计日志失败:",s),document.getElementById("auditLogs").innerHTML='<div class="error-state">加载失败: '+s.message+"</div>"}}async function k(){try{const r=new Date,d=new Date(r.getTime()-7*24*60*60*1e3),s=await a.getAuditSummary({startDate:r.toISOString().split("T")[0],endDate:r.toISOString().split("T")[0]}),c=await a.getAuditSummary({startDate:d.toISOString().split("T")[0],endDate:r.toISOString().split("T")[0]});document.getElementById("todayCount").textContent=s.summary.totalLogs,document.getElementById("weekCount").textContent=c.summary.totalLogs,document.getElementById("activeUsers").textContent=c.summary.topUsers.length}catch(r){console.error("加载统计信息失败:",r)}}function B(r){var w,P,p,L,j,C;const d=((w=r.user)==null?void 0:w.username)||"未知用户",s=T(r.action),c=r.resourceTitle||r.resourceId;let v='<strong style="color: #6366f1;">'+d+"</strong> ";switch(r.action){case"document_create":v+='创建了文档 <strong>"'+c+'"</strong>';break;case"document_update":v+='更新了文档 <strong>"'+c+'"</strong>',(P=r.details)!=null&&P.field&&(v+=" 的 <strong>"+y(r.details.field)+"</strong>");break;case"document_delete":v+='删除了文档 <strong>"'+c+'"</strong>';break;case"content_edit":if(v+='编辑了文档 <strong>"'+c+'"</strong> 的内容',r.changes){const G=((p=r.changes.insertions)==null?void 0:p.reduce((Y,X)=>Y+X.length,0))||0,q=((L=r.changes.deletions)==null?void 0:L.reduce((Y,X)=>Y+X.length,0))||0;(G>0||q>0)&&(v+=' (<span style="color: #10b981;">+'+G+'</span> / <span style="color: #ef4444;">-'+q+"</span> 字符)")}break;case"title_edit":v+='修改了文档 <strong>"'+c+'"</strong> 的标题',(j=r.details)!=null&&j.oldValue&&((C=r.details)!=null&&C.newValue)&&(v+=' 从 <strong>"'+r.details.oldValue+'"</strong> 改为 <strong>"'+r.details.newValue+'"</strong>');break;case"document_permission_change":v+='修改了文档 <strong>"'+c+'"</strong> 的权限设置';break;default:v+="执行了 <strong>"+s+"</strong> 操作"}return v}function y(r){return{title:"标题",content:"内容",permissions:"权限",status:"状态",tags:"标签",category:"分类",description:"描述"}[r]||r}function g(r){if(r==null)return'<span style="color: var(--text-tertiary); font-style: italic;">空</span>';if(typeof r=="object")return JSON.stringify(r,null,2);const d=String(r);return d.length>500?d.substring(0,500)+'... <span style="color: var(--text-tertiary); font-style: italic;">(内容过长,已截断)</span>':d.replace(/</g,"&lt;").replace(/>/g,"&gt;")}window.showAuditDetail=async r=>{var d,s,c,v,w,P;try{const p=localStorage.getItem("token"),C=(await(await fetch(`http://localhost:8765/api/audit/${r}`,{headers:{Authorization:`Bearer ${p}`}})).json()).log,G=document.getElementById("auditDetailModal"),q=document.getElementById("auditDetailContent"),Y=ue=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[ue]||"#6366f1";q.innerHTML=`
926
+ `;const w=document.getElementById("auditPagination"),P=document.getElementById("pageInfo");P.textContent=`第 ${v.pagination.page} 页,共 ${v.pagination.pages} 页`,document.getElementById("prevPage").disabled=v.pagination.page<=1,document.getElementById("nextPage").disabled=v.pagination.page>=v.pagination.pages,w.style.display=v.pagination.pages>1?"flex":"none"}catch(s){console.error("加载审计日志失败:",s),document.getElementById("auditLogs").innerHTML='<div class="error-state">加载失败: '+s.message+"</div>"}}async function k(){try{const r=new Date,d=new Date(r.getTime()-7*24*60*60*1e3),s=await a.getAuditSummary({startDate:r.toISOString().split("T")[0],endDate:r.toISOString().split("T")[0]}),c=await a.getAuditSummary({startDate:d.toISOString().split("T")[0],endDate:r.toISOString().split("T")[0]});document.getElementById("todayCount").textContent=s.summary.totalLogs,document.getElementById("weekCount").textContent=c.summary.totalLogs,document.getElementById("activeUsers").textContent=c.summary.topUsers.length}catch(r){console.error("加载统计信息失败:",r)}}function B(r){var w,P,p,L,j,C;const d=((w=r.user)==null?void 0:w.username)||"未知用户",s=T(r.action),c=r.resourceTitle||r.resourceId;let v='<strong style="color: #6366f1;">'+d+"</strong> ";switch(r.action){case"document_create":v+='创建了文档 <strong>"'+c+'"</strong>';break;case"document_update":v+='更新了文档 <strong>"'+c+'"</strong>',(P=r.details)!=null&&P.field&&(v+=" 的 <strong>"+y(r.details.field)+"</strong>");break;case"document_delete":v+='删除了文档 <strong>"'+c+'"</strong>';break;case"content_edit":if(v+='编辑了文档 <strong>"'+c+'"</strong> 的内容',r.changes){const G=((p=r.changes.insertions)==null?void 0:p.reduce((Y,X)=>Y+X.length,0))||0,q=((L=r.changes.deletions)==null?void 0:L.reduce((Y,X)=>Y+X.length,0))||0;(G>0||q>0)&&(v+=' (<span style="color: #10b981;">+'+G+'</span> / <span style="color: #ef4444;">-'+q+"</span> 字符)")}break;case"title_edit":v+='修改了文档 <strong>"'+c+'"</strong> 的标题',(j=r.details)!=null&&j.oldValue&&((C=r.details)!=null&&C.newValue)&&(v+=' 从 <strong>"'+r.details.oldValue+'"</strong> 改为 <strong>"'+r.details.newValue+'"</strong>');break;case"document_permission_change":v+='修改了文档 <strong>"'+c+'"</strong> 的权限设置';break;default:v+="执行了 <strong>"+s+"</strong> 操作"}return v}function y(r){return{title:"标题",content:"内容",permissions:"权限",status:"状态",tags:"标签",category:"分类",description:"描述"}[r]||r}function g(r){if(r==null)return'<span style="color: var(--text-tertiary); font-style: italic;">空</span>';if(typeof r=="object")return JSON.stringify(r,null,2);const d=String(r);return d.length>500?d.substring(0,500)+'... <span style="color: var(--text-tertiary); font-style: italic;">(内容过长,已截断)</span>':d.replace(/</g,"&lt;").replace(/>/g,"&gt;")}window.showAuditDetail=async r=>{var d,s,c,v,w,P;try{const p=localStorage.getItem("token"),C=(await(await fetch(`http://localhost:3000/api/audit/${r}`,{headers:{Authorization:`Bearer ${p}`}})).json()).log,G=document.getElementById("auditDetailModal"),q=document.getElementById("auditDetailContent"),Y=ue=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[ue]||"#6366f1";q.innerHTML=`
927
927
  <div class="audit-detail" style="padding: 0; background: linear-gradient(135deg, rgba(99, 102, 241, 0.03) 0%, rgba(168, 85, 247, 0.03) 100%);">
928
928
 
929
929
  <!-- 操作信息 -->
@@ -1042,7 +1042,7 @@
1042
1042
  ${r.group?`<span class="result-group">群组: ${r.group}</span>`:""}
1043
1043
  ${r.status?`<span class="result-status">状态: ${E(r.status)}</span>`:""}
1044
1044
  </div>
1045
- `).join("")}catch(g){b.innerHTML=`<div class="empty-state">搜索失败: ${g.message}</div>`}};u.addEventListener("click",k),m.addEventListener("keypress",B=>{B.key==="Enter"&&k()})}function re(h,m){if(!m)return h;const u=new RegExp(`(${m})`,"gi");return h.replace(u,"<mark>$1</mark>")}function T(h){return{document_create:"创建文档",document_update:"更新文档",document_delete:"删除文档",content_edit:"编辑内容",title_edit:"修改标题",document_permission_change:"权限修改"}[h]||h}function E(h){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[h]||h}async function F(h){var m;if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=localStorage.getItem("token"),b=await fetch(`http://localhost:8765/api/knowledge/group/${i._id}`,{headers:{Authorization:`Bearer ${u}`}});if(!b.ok)throw new Error(`HTTP ${b.status}: ${b.statusText}`);const k=await b.json();console.log("知识库数据:",k);const B=((m=k.data)==null?void 0:m.knowledgeList)||[];console.log("知识库条目数量:",B.length),h.innerHTML=`
1045
+ `).join("")}catch(g){b.innerHTML=`<div class="empty-state">搜索失败: ${g.message}</div>`}};u.addEventListener("click",k),m.addEventListener("keypress",B=>{B.key==="Enter"&&k()})}function re(h,m){if(!m)return h;const u=new RegExp(`(${m})`,"gi");return h.replace(u,"<mark>$1</mark>")}function T(h){return{document_create:"创建文档",document_update:"更新文档",document_delete:"删除文档",content_edit:"编辑内容",title_edit:"修改标题",document_permission_change:"权限修改"}[h]||h}function E(h){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[h]||h}async function F(h){var m;if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=localStorage.getItem("token"),b=await fetch(`http://localhost:3000/api/knowledge/group/${i._id}`,{headers:{Authorization:`Bearer ${u}`}});if(!b.ok)throw new Error(`HTTP ${b.status}: ${b.statusText}`);const k=await b.json();console.log("知识库数据:",k);const B=((m=k.data)==null?void 0:m.knowledgeList)||[];console.log("知识库条目数量:",B.length),h.innerHTML=`
1046
1046
  <div class="view-header">
1047
1047
  <h2>📚 知识库管理 - ${i.name}</h2>
1048
1048
  <button class="btn-primary" id="createKnowledgeBtn">➕ 创建知识条目</button>
@@ -1098,7 +1098,7 @@
1098
1098
  <button class="btn-secondary btn-sm" data-id="${g._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
1099
1099
  <button class="btn-danger btn-sm" data-id="${g._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1100
1100
  </div>
1101
- `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="edit"]').forEach(g=>{g.addEventListener("click",async()=>{var d;const r=B.find(s=>s._id===g.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=r.title,document.querySelector('[name="content"]').value=r.content,document.querySelector('[name="tags"]').value=((d=r.tags)==null?void 0:d.join(", "))||"",document.getElementById("isSharedCheckbox").checked=r.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=r._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(g=>{g.addEventListener("click",async()=>{try{const r=await fetch(`http://localhost:8765/api/backup/download/${g.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${u}`}});if(!r.ok)throw new Error("下载失败");const d=await r.blob(),s=window.URL.createObjectURL(d),c=document.createElement("a");c.href=s,c.download=g.dataset.filename,document.body.appendChild(c),c.click(),window.URL.revokeObjectURL(s),document.body.removeChild(c)}catch(r){alert("下载失败: "+r.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:8765/api/knowledge/${g.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}}),alert("删除成功!"),await F(h)}catch(r){alert("删除失败: "+r.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async g=>{g.preventDefault();const r=new FormData(g.target),d={title:r.get("title"),content:r.get("content"),tags:r.get("tags").split(",").map(s=>s.trim()).filter(s=>s),groupId:i._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const s=g.target.dataset.editId,c=s?`http://localhost:8765/api/knowledge/${s}`:"http://localhost:8765/api/knowledge",w=await fetch(c,{method:s?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify(d)});if(!w.ok){const p=await w.json();throw new Error(p.message||"操作失败")}const P=await w.json();console.log("知识库操作结果:",P),alert(s?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await F(h)}catch(s){console.error("知识库操作错误:",s),alert("操作失败: "+s.message)}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}function x(h){if(!h)return"未设置";if(typeof h=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[h]||h;const m=[];if(h.event){const u={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};m.push(u[h.event]||h.event)}if(h.conditions&&Object.keys(h.conditions).length>0){const u=[];for(const[b,k]of Object.entries(h.conditions)){const y={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[b]||b;u.push(`${y}=${k}`)}u.length>0&&m.push(`(条件: ${u.join(", ")})`)}return h.schedule&&m.push(`⏱️ 定时: ${h.schedule}`),m.length>0?m.join(" "):"自定义触发条件"}async function f(h){var m;if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=localStorage.getItem("token"),B=((m=(await(await fetch(`http://localhost:8765/api/workflows/group/${i._id}`,{headers:{Authorization:`Bearer ${u}`}})).json()).data)==null?void 0:m.workflows)||[];h.innerHTML=`
1101
+ `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="edit"]').forEach(g=>{g.addEventListener("click",async()=>{var d;const r=B.find(s=>s._id===g.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=r.title,document.querySelector('[name="content"]').value=r.content,document.querySelector('[name="tags"]').value=((d=r.tags)==null?void 0:d.join(", "))||"",document.getElementById("isSharedCheckbox").checked=r.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=r._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(g=>{g.addEventListener("click",async()=>{try{const r=await fetch(`http://localhost:3000/api/backup/download/${g.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${u}`}});if(!r.ok)throw new Error("下载失败");const d=await r.blob(),s=window.URL.createObjectURL(d),c=document.createElement("a");c.href=s,c.download=g.dataset.filename,document.body.appendChild(c),c.click(),window.URL.revokeObjectURL(s),document.body.removeChild(c)}catch(r){alert("下载失败: "+r.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${g.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}}),alert("删除成功!"),await F(h)}catch(r){alert("删除失败: "+r.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async g=>{g.preventDefault();const r=new FormData(g.target),d={title:r.get("title"),content:r.get("content"),tags:r.get("tags").split(",").map(s=>s.trim()).filter(s=>s),groupId:i._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const s=g.target.dataset.editId,c=s?`http://localhost:3000/api/knowledge/${s}`:"http://localhost:3000/api/knowledge",w=await fetch(c,{method:s?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify(d)});if(!w.ok){const p=await w.json();throw new Error(p.message||"操作失败")}const P=await w.json();console.log("知识库操作结果:",P),alert(s?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await F(h)}catch(s){console.error("知识库操作错误:",s),alert("操作失败: "+s.message)}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}function x(h){if(!h)return"未设置";if(typeof h=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[h]||h;const m=[];if(h.event){const u={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};m.push(u[h.event]||h.event)}if(h.conditions&&Object.keys(h.conditions).length>0){const u=[];for(const[b,k]of Object.entries(h.conditions)){const y={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[b]||b;u.push(`${y}=${k}`)}u.length>0&&m.push(`(条件: ${u.join(", ")})`)}return h.schedule&&m.push(`⏱️ 定时: ${h.schedule}`),m.length>0?m.join(" "):"自定义触发条件"}async function f(h){var m;if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=localStorage.getItem("token"),B=((m=(await(await fetch(`http://localhost:3000/api/workflows/group/${i._id}`,{headers:{Authorization:`Bearer ${u}`}})).json()).data)==null?void 0:m.workflows)||[];h.innerHTML=`
1102
1102
  <div class="view-header">
1103
1103
  <h2>⚙️ 工作流管理 - ${i.name}</h2>
1104
1104
  <button class="btn-primary" id="createWorkflowBtn">➕ 创建工作流</button>
@@ -1178,7 +1178,7 @@
1178
1178
  </button>
1179
1179
  <button class="btn-danger btn-sm" data-id="${g._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1180
1180
  </div>
1181
- `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="toggle"]').forEach(g=>{g.addEventListener("click",async()=>{try{await fetch(`http://localhost:8765/api/workflows/${g.dataset.id}/toggle`,{method:"POST",headers:{Authorization:`Bearer ${u}`}}),await f(h)}catch(r){alert("操作失败: "+r.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个工作流吗?"))try{await fetch(`http://localhost:8765/api/workflows/${g.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}}),alert("删除成功!"),await f(h)}catch(r){alert("删除失败: "+r.message)}})})),document.getElementById("createWorkflowBtn").addEventListener("click",()=>{document.getElementById("workflowModal").classList.remove("hidden")}),document.getElementById("closeWorkflowModal").addEventListener("click",()=>{document.getElementById("workflowModal").classList.add("hidden")}),document.getElementById("workflowForm").addEventListener("submit",async g=>{g.preventDefault();const r=new FormData(g.target),d={name:r.get("name"),description:r.get("description"),trigger:r.get("trigger"),groupId:i._id,actions:[]};try{await fetch("http://localhost:8765/api/workflows",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify(d)}),alert("创建成功!"),document.getElementById("workflowModal").classList.add("hidden"),await f(h)}catch(s){alert("创建失败: "+s.message)}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}async function M(h){var m;try{const u=localStorage.getItem("token"),B=((m=(await(await fetch("http://localhost:8765/api/backup/list",{headers:{Authorization:`Bearer ${u}`}})).json()).data)==null?void 0:m.backups)||[];h.innerHTML=`
1181
+ `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="toggle"]').forEach(g=>{g.addEventListener("click",async()=>{try{await fetch(`http://localhost:3000/api/workflows/${g.dataset.id}/toggle`,{method:"POST",headers:{Authorization:`Bearer ${u}`}}),await f(h)}catch(r){alert("操作失败: "+r.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个工作流吗?"))try{await fetch(`http://localhost:3000/api/workflows/${g.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}}),alert("删除成功!"),await f(h)}catch(r){alert("删除失败: "+r.message)}})})),document.getElementById("createWorkflowBtn").addEventListener("click",()=>{document.getElementById("workflowModal").classList.remove("hidden")}),document.getElementById("closeWorkflowModal").addEventListener("click",()=>{document.getElementById("workflowModal").classList.add("hidden")}),document.getElementById("workflowForm").addEventListener("submit",async g=>{g.preventDefault();const r=new FormData(g.target),d={name:r.get("name"),description:r.get("description"),trigger:r.get("trigger"),groupId:i._id,actions:[]};try{await fetch("http://localhost:3000/api/workflows",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify(d)}),alert("创建成功!"),document.getElementById("workflowModal").classList.add("hidden"),await f(h)}catch(s){alert("创建失败: "+s.message)}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}async function M(h){var m;try{const u=localStorage.getItem("token"),B=((m=(await(await fetch("http://localhost:3000/api/backup/list",{headers:{Authorization:`Bearer ${u}`}})).json()).data)==null?void 0:m.backups)||[];h.innerHTML=`
1182
1182
  <div class="view-header">
1183
1183
  <h2>💾 备份管理</h2>
1184
1184
  <button class="btn-primary" id="createBackupBtn">➕ 创建备份</button>
@@ -1199,7 +1199,7 @@
1199
1199
  <button class="btn-primary btn-sm" data-backup-name="${g.name}" data-filename="${g.filename}" data-action="download" style="flex: 1;">⬇️ 下载</button>
1200
1200
  <button class="btn-danger btn-sm" data-backup-name="${g.name}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1201
1201
  </div>
1202
- `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="download"]').forEach(g=>{g.addEventListener("click",async()=>{try{const r=g.dataset.backupName,d=g.dataset.filename,s=localStorage.getItem("token");console.log("开始下载备份:",{backupName:r,filename:d});const c=await fetch(`http://localhost:8765/api/backup/download/${r}`,{method:"GET",headers:{Authorization:`Bearer ${s}`}});if(!c.ok)throw new Error(`下载失败: ${c.status} ${c.statusText}`);const v=await c.blob();console.log("文件下载成功,大小:",v.size,"bytes");const w=window.URL.createObjectURL(v),P=document.createElement("a");P.href=w,P.download=d,P.style.display="none",document.body.appendChild(P),P.click(),setTimeout(()=>{document.body.removeChild(P),window.URL.revokeObjectURL(w)},100);const p=g.textContent;g.textContent="✅ 下载成功",g.disabled=!0,setTimeout(()=>{g.textContent=p,g.disabled=!1},2e3)}catch(r){console.error("下载失败:",r),alert("下载失败: "+r.message),g.textContent="⬇️ 下载",g.disabled=!1}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个备份吗?"))try{const r=g.dataset.backupName;console.log("删除备份:",r);const d=await fetch(`http://localhost:8765/api/backup/${r}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}});if(!d.ok)throw new Error(`删除失败: ${d.status}`);alert("删除成功!"),await M(h)}catch(r){console.error("删除失败:",r),alert("删除失败: "+r.message)}})})),document.getElementById("createBackupBtn").addEventListener("click",async()=>{if(confirm("确定要创建新备份吗?这可能需要一些时间。")){const g=document.getElementById("createBackupBtn");g.disabled=!0,g.textContent="⏳ 创建中...";try{await fetch("http://localhost:8765/api/backup/create",{method:"POST",headers:{Authorization:`Bearer ${u}`}}),alert("备份创建成功!"),await M(h)}catch(r){alert("创建失败: "+r.message)}finally{g.disabled=!1,g.textContent="➕ 创建备份"}}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}async function A(h){h.innerHTML='<div class="empty-state">AI助手功能开发中...</div>'}async function I(h){h.innerHTML=`
1202
+ `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="download"]').forEach(g=>{g.addEventListener("click",async()=>{try{const r=g.dataset.backupName,d=g.dataset.filename,s=localStorage.getItem("token");console.log("开始下载备份:",{backupName:r,filename:d});const c=await fetch(`http://localhost:3000/api/backup/download/${r}`,{method:"GET",headers:{Authorization:`Bearer ${s}`}});if(!c.ok)throw new Error(`下载失败: ${c.status} ${c.statusText}`);const v=await c.blob();console.log("文件下载成功,大小:",v.size,"bytes");const w=window.URL.createObjectURL(v),P=document.createElement("a");P.href=w,P.download=d,P.style.display="none",document.body.appendChild(P),P.click(),setTimeout(()=>{document.body.removeChild(P),window.URL.revokeObjectURL(w)},100);const p=g.textContent;g.textContent="✅ 下载成功",g.disabled=!0,setTimeout(()=>{g.textContent=p,g.disabled=!1},2e3)}catch(r){console.error("下载失败:",r),alert("下载失败: "+r.message),g.textContent="⬇️ 下载",g.disabled=!1}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个备份吗?"))try{const r=g.dataset.backupName;console.log("删除备份:",r);const d=await fetch(`http://localhost:3000/api/backup/${r}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}});if(!d.ok)throw new Error(`删除失败: ${d.status}`);alert("删除成功!"),await M(h)}catch(r){console.error("删除失败:",r),alert("删除失败: "+r.message)}})})),document.getElementById("createBackupBtn").addEventListener("click",async()=>{if(confirm("确定要创建新备份吗?这可能需要一些时间。")){const g=document.getElementById("createBackupBtn");g.disabled=!0,g.textContent="⏳ 创建中...";try{await fetch("http://localhost:3000/api/backup/create",{method:"POST",headers:{Authorization:`Bearer ${u}`}}),alert("备份创建成功!"),await M(h)}catch(r){alert("创建失败: "+r.message)}finally{g.disabled=!1,g.textContent="➕ 创建备份"}}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}async function A(h){h.innerHTML='<div class="empty-state">AI助手功能开发中...</div>'}async function I(h){h.innerHTML=`
1203
1203
  <div class="view-header">
1204
1204
  <h2>📤 数据导出</h2>
1205
1205
  </div>
@@ -1244,15 +1244,15 @@
1244
1244
  <div id="historyList">加载中...</div>
1245
1245
  </div>
1246
1246
  </div>
1247
- `;try{const m=localStorage.getItem("token"),b=await(await fetch("http://localhost:8765/api/export/history",{headers:{Authorization:`Bearer ${m}`}})).json(),k=document.getElementById("historyList");b.exports&&b.exports.length>0?k.innerHTML=b.exports.map(B=>`
1247
+ `;try{const m=localStorage.getItem("token"),b=await(await fetch("http://localhost:3000/api/export/history",{headers:{Authorization:`Bearer ${m}`}})).json(),k=document.getElementById("historyList");b.exports&&b.exports.length>0?k.innerHTML=b.exports.map(B=>`
1248
1248
  <div class="export-item" style="display: flex; justify-content: space-between; align-items: center; padding: 15px; background: var(--bg); border-radius: 8px; margin-bottom: 10px;">
1249
1249
  <div>
1250
1250
  <div style="font-weight: 600; margin-bottom: 5px;">📦 ${B.format.toUpperCase()} 导出</div>
1251
1251
  <div style="font-size: 12px; color: var(--text-secondary);">📅 ${new Date(B.createdAt).toLocaleString()}</div>
1252
1252
  </div>
1253
- <a href="http://localhost:8765/api/export/download/${B.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
1253
+ <a href="http://localhost:3000/api/export/download/${B.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
1254
1254
  </div>
1255
- `).join(""):k.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const m={groups:document.getElementById("exportGroups").checked,documents:document.getElementById("exportDocuments").checked,tasks:document.getElementById("exportTasks").checked,messages:document.getElementById("exportMessages").checked,files:document.getElementById("exportFiles").checked,format:document.getElementById("exportFormat").value},u=document.getElementById("exportBtn");u.disabled=!0,u.textContent="⏳ 导出中...";try{const b=localStorage.getItem("token"),k=await fetch("http://localhost:8765/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${b}`},body:JSON.stringify(m)});if(k.ok){const B=await k.blob(),y=window.URL.createObjectURL(B),g=document.createElement("a");g.href=y,g.download=`export-${Date.now()}.${m.format}`,g.click(),alert("导出成功!"),await I(h)}else throw new Error("导出失败")}catch(b){alert("导出失败: "+b.message)}finally{u.disabled=!1,u.textContent="🚀 开始导出"}})}async function z(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}h.innerHTML=`
1255
+ `).join(""):k.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const m={groups:document.getElementById("exportGroups").checked,documents:document.getElementById("exportDocuments").checked,tasks:document.getElementById("exportTasks").checked,messages:document.getElementById("exportMessages").checked,files:document.getElementById("exportFiles").checked,format:document.getElementById("exportFormat").value},u=document.getElementById("exportBtn");u.disabled=!0,u.textContent="⏳ 导出中...";try{const b=localStorage.getItem("token"),k=await fetch("http://localhost:3000/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${b}`},body:JSON.stringify(m)});if(k.ok){const B=await k.blob(),y=window.URL.createObjectURL(B),g=document.createElement("a");g.href=y,g.download=`export-${Date.now()}.${m.format}`,g.click(),alert("导出成功!"),await I(h)}else throw new Error("导出失败")}catch(b){alert("导出失败: "+b.message)}finally{u.disabled=!1,u.textContent="🚀 开始导出"}})}async function z(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}h.innerHTML=`
1256
1256
  <div class="view-header">
1257
1257
  <h2>🎨 协作白板 - ${i.name}</h2>
1258
1258
  <div style="display: flex; gap: 10px;">
@@ -1891,7 +1891,7 @@
1891
1891
  前往我的群组
1892
1892
  </button>
1893
1893
  </div>
1894
- `;return}try{let h=function(d,s,c="💬"){"Notification"in window&&Notification.permission==="granted"&&new Notification(d,{body:s,icon:"/icon.png",badge:"/icon.png",tag:"chat-message"})},k=function(){const d=document.getElementById("whiteboard");if(!d||d.dataset.initialized)return;d.dataset.initialized="true";const s=d.getContext("2d");d.width=d.offsetWidth,d.height=d.offsetHeight;let c=!1,v="pen",w="#667eea",P=3;document.querySelectorAll(".tool-btn").forEach(p=>{p.addEventListener("click",()=>{v=p.dataset.tool,document.querySelectorAll(".tool-btn").forEach(L=>{L.style.background="var(--bg-secondary)",L.style.color="var(--text-primary)",L.style.border="1px solid var(--border)"}),p.style.background="var(--primary)",p.style.color="white",p.style.border="none"})}),document.getElementById("colorPicker").addEventListener("change",p=>{w=p.target.value}),document.getElementById("brushSize").addEventListener("input",p=>{P=p.target.value}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&(s.clearRect(0,0,d.width,d.height),e.sendWhiteboardClear(i._id))}),document.getElementById("sendWhiteboardBtn").addEventListener("click",async()=>{try{const p=d.toDataURL("image/png"),L=document.createElement("canvas");L.width=d.width,L.height=d.height;const j=L.toDataURL("image/png");if(p===j){alert("画布是空的,请先绘制内容!");return}if(p.length*.75/1024/1024<1)e.sendChatMessage(i._id,t.username,`[白板作品]${p}`),alert("白板作品已发送到群聊!");else{const G=await fetch(p).then(te=>te.blob()),q=new FormData;q.append("file",G,`whiteboard-${Date.now()}.png`),q.append("groupId",i._id),q.append("description","协作白板作品");const Y=localStorage.getItem("token"),X=await fetch("http://localhost:8765/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${Y}`},body:q});if(X.ok){const ie=`http://localhost:8765/api/files/${(await X.json()).file._id}/download?token=${Y}`;e.sendChatMessage(i._id,t.username,`[白板作品]${ie}`),alert("白板作品已发送到群聊!")}else throw new Error("上传失败")}}catch(p){console.error("发送白板失败:",p),alert("发送失败,请重试!")}}),d.addEventListener("mousedown",p=>{c=!0;const L=d.getBoundingClientRect(),j=p.clientX-L.left,C=p.clientY-L.top;s.beginPath(),s.moveTo(j,C)}),d.addEventListener("mousemove",p=>{if(!c)return;const L=d.getBoundingClientRect(),j=p.clientX-L.left,C=p.clientY-L.top;s.lineWidth=P,s.lineCap="round",v==="pen"?(s.strokeStyle=w,s.globalCompositeOperation="source-over"):v==="eraser"&&(s.globalCompositeOperation="destination-out"),s.lineTo(j,C),s.stroke(),e.sendWhiteboardDraw(i._id,{tool:v,color:w,size:P,x:j,y:C})}),d.addEventListener("mouseup",()=>{c=!1}),d.addEventListener("mouseleave",()=>{c=!1}),e.on("whiteboard_draw",p=>{p.groupId===i._id&&(s.lineWidth=p.size,s.lineCap="round",p.tool==="pen"?(s.strokeStyle=p.color,s.globalCompositeOperation="source-over"):p.tool==="eraser"&&(s.globalCompositeOperation="destination-out"),s.lineTo(p.x,p.y),s.stroke())}),e.on("whiteboard_clear",p=>{p.groupId===i._id&&s.clearRect(0,0,d.width,d.height)})};var E=h,F=k;const f=(await a.getGroup(i._id)).group,M=!!f.mutedAll,A=(f.mutedUsers||[]).map(String).includes(String(o)),I=!M&&!A;T.innerHTML=`
1894
+ `;return}try{let h=function(d,s,c="💬"){"Notification"in window&&Notification.permission==="granted"&&new Notification(d,{body:s,icon:"/icon.png",badge:"/icon.png",tag:"chat-message"})},k=function(){const d=document.getElementById("whiteboard");if(!d||d.dataset.initialized)return;d.dataset.initialized="true";const s=d.getContext("2d");d.width=d.offsetWidth,d.height=d.offsetHeight;let c=!1,v="pen",w="#667eea",P=3;document.querySelectorAll(".tool-btn").forEach(p=>{p.addEventListener("click",()=>{v=p.dataset.tool,document.querySelectorAll(".tool-btn").forEach(L=>{L.style.background="var(--bg-secondary)",L.style.color="var(--text-primary)",L.style.border="1px solid var(--border)"}),p.style.background="var(--primary)",p.style.color="white",p.style.border="none"})}),document.getElementById("colorPicker").addEventListener("change",p=>{w=p.target.value}),document.getElementById("brushSize").addEventListener("input",p=>{P=p.target.value}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&(s.clearRect(0,0,d.width,d.height),e.sendWhiteboardClear(i._id))}),document.getElementById("sendWhiteboardBtn").addEventListener("click",async()=>{try{const p=d.toDataURL("image/png"),L=document.createElement("canvas");L.width=d.width,L.height=d.height;const j=L.toDataURL("image/png");if(p===j){alert("画布是空的,请先绘制内容!");return}if(p.length*.75/1024/1024<1)e.sendChatMessage(i._id,t.username,`[白板作品]${p}`),alert("白板作品已发送到群聊!");else{const G=await fetch(p).then(te=>te.blob()),q=new FormData;q.append("file",G,`whiteboard-${Date.now()}.png`),q.append("groupId",i._id),q.append("description","协作白板作品");const Y=localStorage.getItem("token"),X=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${Y}`},body:q});if(X.ok){const ie=`http://localhost:3000/api/files/${(await X.json()).file._id}/download?token=${Y}`;e.sendChatMessage(i._id,t.username,`[白板作品]${ie}`),alert("白板作品已发送到群聊!")}else throw new Error("上传失败")}}catch(p){console.error("发送白板失败:",p),alert("发送失败,请重试!")}}),d.addEventListener("mousedown",p=>{c=!0;const L=d.getBoundingClientRect(),j=p.clientX-L.left,C=p.clientY-L.top;s.beginPath(),s.moveTo(j,C)}),d.addEventListener("mousemove",p=>{if(!c)return;const L=d.getBoundingClientRect(),j=p.clientX-L.left,C=p.clientY-L.top;s.lineWidth=P,s.lineCap="round",v==="pen"?(s.strokeStyle=w,s.globalCompositeOperation="source-over"):v==="eraser"&&(s.globalCompositeOperation="destination-out"),s.lineTo(j,C),s.stroke(),e.sendWhiteboardDraw(i._id,{tool:v,color:w,size:P,x:j,y:C})}),d.addEventListener("mouseup",()=>{c=!1}),d.addEventListener("mouseleave",()=>{c=!1}),e.on("whiteboard_draw",p=>{p.groupId===i._id&&(s.lineWidth=p.size,s.lineCap="round",p.tool==="pen"?(s.strokeStyle=p.color,s.globalCompositeOperation="source-over"):p.tool==="eraser"&&(s.globalCompositeOperation="destination-out"),s.lineTo(p.x,p.y),s.stroke())}),e.on("whiteboard_clear",p=>{p.groupId===i._id&&s.clearRect(0,0,d.width,d.height)})};var E=h,F=k;const f=(await a.getGroup(i._id)).group,M=!!f.mutedAll,A=(f.mutedUsers||[]).map(String).includes(String(o)),I=!M&&!A;T.innerHTML=`
1895
1895
  <div class="view-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; margin-bottom: 20px;">
1896
1896
  <h2 style="margin: 0; display: flex; align-items: center; gap: 12px;">
1897
1897
  <span style="font-size: 32px;">💬</span>
@@ -1956,7 +1956,7 @@
1956
1956
  </div>
1957
1957
  </div>
1958
1958
  <div class="ai-input" style="display: flex; gap: 10px; padding: 16px; background: var(--bg-tertiary); border-top: 1px solid var(--border);">
1959
- <textarea id="aiInput" placeholder="向AI助手提问..." rows="2" style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); resize: none;"></textarea>
1959
+ <textarea id="aiInput" placeholder="向AI助手提问..." rows="2" style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); resize: none; color: white;"></textarea>
1960
1960
  <button class="btn-primary" id="aiSendBtn" style="padding: 10px 24px; border-radius: 8px; align-self: flex-end;">发送</button>
1961
1961
  </div>
1962
1962
  </div>
@@ -2027,7 +2027,7 @@
2027
2027
  margin-bottom: 16px;
2028
2028
  max-width: 80%;
2029
2029
  font-style: italic;
2030
- `,c.textContent="🤔 思考中...",B.appendChild(c),B.scrollTop=B.scrollHeight;try{const v=localStorage.getItem("token"),P=await(await fetch("http://localhost:8765/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${v}`},body:JSON.stringify({question:d,groupId:i==null?void 0:i._id})})).json();c.remove();const p=document.createElement("div");p.style.cssText=`
2030
+ `,c.textContent="🤔 思考中...",B.appendChild(c),B.scrollTop=B.scrollHeight;try{const v=localStorage.getItem("token"),P=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${v}`},body:JSON.stringify({question:d,groupId:i==null?void 0:i._id})})).json();c.remove();const p=document.createElement("div");p.style.cssText=`
2031
2031
  background: var(--bg-tertiary);
2032
2032
  color: var(--text-primary);
2033
2033
  padding: 12px 16px;
@@ -2092,7 +2092,7 @@
2092
2092
  前往我的群组
2093
2093
  </button>
2094
2094
  </div>
2095
- `;return}try{const E=localStorage.getItem("token"),F=await fetch(`http://localhost:8765/api/knowledge/group/${i._id}`,{headers:{Authorization:`Bearer ${E}`}});if(!F.ok)throw new Error(`HTTP ${F.status}: ${F.statusText}`);const x=await F.json();let f=[];Array.isArray(x)?f=x:x.data&&Array.isArray(x.data)?f=x.data:x.data&&x.data.knowledgeList&&Array.isArray(x.data.knowledgeList)?f=x.data.knowledgeList:x.items&&Array.isArray(x.items)?f=x.items:x.knowledge&&Array.isArray(x.knowledge)&&(f=x.knowledge),T.innerHTML=`
2095
+ `;return}try{const E=localStorage.getItem("token"),F=await fetch(`http://localhost:3000/api/knowledge/group/${i._id}`,{headers:{Authorization:`Bearer ${E}`}});if(!F.ok)throw new Error(`HTTP ${F.status}: ${F.statusText}`);const x=await F.json();let f=[];Array.isArray(x)?f=x:x.data&&Array.isArray(x.data)?f=x.data:x.data&&x.data.knowledgeList&&Array.isArray(x.data.knowledgeList)?f=x.data.knowledgeList:x.items&&Array.isArray(x.items)?f=x.items:x.knowledge&&Array.isArray(x.knowledge)&&(f=x.knowledge),T.innerHTML=`
2096
2096
  <div class="view-header">
2097
2097
  <h2>📚 知识库 - ${i.name}</h2>
2098
2098
  <button class="btn-primary" id="createKnowledgeBtn">📝 创建知识条目</button>
@@ -2153,7 +2153,7 @@
2153
2153
  <button class="btn-secondary btn-sm" data-id="${A._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
2154
2154
  <button class="btn-danger btn-sm" data-id="${A._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
2155
2155
  </div>
2156
- `,I.onmouseenter=()=>{I.style.transform="translateY(-4px)",I.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},I.onmouseleave=()=>{I.style.transform="translateY(0)",I.style.boxShadow="none"},M.appendChild(I)}),document.querySelectorAll('[data-action="edit"]').forEach(A=>{A.addEventListener("click",async()=>{var z;const I=f.find(W=>W._id===A.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=I.title,document.querySelector('[name="content"]').value=I.content,document.querySelector('[name="tags"]').value=((z=I.tags)==null?void 0:z.join(", "))||"",document.getElementById("isSharedCheckbox").checked=I.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=I._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="delete"]').forEach(A=>{A.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:8765/api/knowledge/${A.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${E}`}}),alert("删除成功!"),await re(T)}catch(I){alert("删除失败: "+I.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("cancelKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async A=>{A.preventDefault();const I=new FormData(A.target),z={title:I.get("title"),content:I.get("content"),tags:I.get("tags").split(",").map(W=>W.trim()).filter(W=>W),groupId:i._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const W=A.target.dataset.editId,O=W?`http://localhost:8765/api/knowledge/${W}`:"http://localhost:8765/api/knowledge";if(!(await fetch(O,{method:W?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${E}`},body:JSON.stringify(z)})).ok)throw new Error("操作失败");alert(W?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await re(T)}catch(W){alert("操作失败: "+W.message)}})}catch(E){console.error("加载知识库失败:",E),T.innerHTML=`
2156
+ `,I.onmouseenter=()=>{I.style.transform="translateY(-4px)",I.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},I.onmouseleave=()=>{I.style.transform="translateY(0)",I.style.boxShadow="none"},M.appendChild(I)}),document.querySelectorAll('[data-action="edit"]').forEach(A=>{A.addEventListener("click",async()=>{var z;const I=f.find(W=>W._id===A.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=I.title,document.querySelector('[name="content"]').value=I.content,document.querySelector('[name="tags"]').value=((z=I.tags)==null?void 0:z.join(", "))||"",document.getElementById("isSharedCheckbox").checked=I.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=I._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="delete"]').forEach(A=>{A.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${A.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${E}`}}),alert("删除成功!"),await re(T)}catch(I){alert("删除失败: "+I.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("cancelKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async A=>{A.preventDefault();const I=new FormData(A.target),z={title:I.get("title"),content:I.get("content"),tags:I.get("tags").split(",").map(W=>W.trim()).filter(W=>W),groupId:i._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const W=A.target.dataset.editId,O=W?`http://localhost:3000/api/knowledge/${W}`:"http://localhost:3000/api/knowledge";if(!(await fetch(O,{method:W?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${E}`},body:JSON.stringify(z)})).ok)throw new Error("操作失败");alert(W?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await re(T)}catch(W){alert("操作失败: "+W.message)}})}catch(E){console.error("加载知识库失败:",E),T.innerHTML=`
2157
2157
  <div class="view-header">
2158
2158
  <h2>📚 知识库 - ${i.name}</h2>
2159
2159
  </div>
package/dist/index.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <title>CollabDocChat - 协作文档聊天平台</title>
7
7
  <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
8
8
  <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
9
- <script type="module" crossorigin src="/assets/index-CZgXE9nz.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-CNzBYLJL.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-D8ZqeoaM.css">
11
11
  </head>
12
12
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "collabdocchat",
3
- "version": "2.5.4",
3
+ "version": "2.5.6",
4
4
  "description": "开源的实时协作文档聊天平台 - 集成任务管理、多人文档编辑、智能点名功能",
5
5
  "main": "./server/index.js",
6
6
  "type": "module",
@@ -1481,7 +1481,7 @@ export function renderAdminDashboard(user, wsService) {
1481
1481
  btn.disabled = true;
1482
1482
 
1483
1483
  const token = localStorage.getItem('token');
1484
- const response = await fetch(`http://localhost:8765/api/messages/group/${currentGroup._id}/clear`, {
1484
+ const response = await fetch(`http://localhost:3000/api/messages/group/${currentGroup._id}/clear`, {
1485
1485
  method: 'DELETE',
1486
1486
  headers: {
1487
1487
  'Authorization': `Bearer ${token}`,
@@ -1725,7 +1725,7 @@ export function renderAdminDashboard(user, wsService) {
1725
1725
 
1726
1726
  try {
1727
1727
  const token = localStorage.getItem('token');
1728
- const response = await fetch('http://localhost:8765/api/ai/ask', {
1728
+ const response = await fetch('http://localhost:3000/api/ai/ask', {
1729
1729
  method: 'POST',
1730
1730
  headers: {
1731
1731
  'Content-Type': 'application/json',
@@ -1914,7 +1914,7 @@ export function renderAdminDashboard(user, wsService) {
1914
1914
  formData.append('description', '协作白板作品');
1915
1915
 
1916
1916
  const token = localStorage.getItem('token');
1917
- const response = await fetch('http://localhost:8765/api/files/upload', {
1917
+ const response = await fetch('http://localhost:3000/api/files/upload', {
1918
1918
  method: 'POST',
1919
1919
  headers: {
1920
1920
  'Authorization': `Bearer ${token}`
@@ -1925,7 +1925,7 @@ export function renderAdminDashboard(user, wsService) {
1925
1925
  if (response.ok) {
1926
1926
  const result = await response.json();
1927
1927
  const fileId = result.file._id;
1928
- const fileUrl = `http://localhost:8765/api/files/${fileId}/download?token=${token}`;
1928
+ const fileUrl = `http://localhost:3000/api/files/${fileId}/download?token=${token}`;
1929
1929
 
1930
1930
  // 发送包含图片的消息到群聊
1931
1931
  wsService.sendChatMessage(currentGroup._id, user.username, `[白板作品]${fileUrl}`);
@@ -2249,7 +2249,7 @@ export function renderAdminDashboard(user, wsService) {
2249
2249
  window.showAuditDetail = async (logId) => {
2250
2250
  try {
2251
2251
  const token = localStorage.getItem('token');
2252
- const response = await fetch(`http://localhost:8765/api/audit/${logId}`, {
2252
+ const response = await fetch(`http://localhost:3000/api/audit/${logId}`, {
2253
2253
  headers: { 'Authorization': `Bearer ${token}` }
2254
2254
  });
2255
2255
  const result = await response.json();
@@ -2702,7 +2702,7 @@ export function renderAdminDashboard(user, wsService) {
2702
2702
 
2703
2703
  try {
2704
2704
  const token = localStorage.getItem('token');
2705
- const response = await fetch(`http://localhost:8765/api/knowledge/group/${currentGroup._id}`, {
2705
+ const response = await fetch(`http://localhost:3000/api/knowledge/group/${currentGroup._id}`, {
2706
2706
  headers: { 'Authorization': `Bearer ${token}` }
2707
2707
  });
2708
2708
 
@@ -2811,7 +2811,7 @@ export function renderAdminDashboard(user, wsService) {
2811
2811
  document.querySelectorAll('[data-action="download"]').forEach(btn => {
2812
2812
  btn.addEventListener('click', async () => {
2813
2813
  try {
2814
- const response = await fetch(`http://localhost:8765/api/backup/download/${btn.dataset.filename}`, {
2814
+ const response = await fetch(`http://localhost:3000/api/backup/download/${btn.dataset.filename}`, {
2815
2815
  method: 'GET',
2816
2816
  headers: { 'Authorization': `Bearer ${token}` }
2817
2817
  });
@@ -2839,7 +2839,7 @@ export function renderAdminDashboard(user, wsService) {
2839
2839
  btn.addEventListener('click', async () => {
2840
2840
  if (confirm('确定要删除这个知识条目吗?')) {
2841
2841
  try {
2842
- await fetch(`http://localhost:8765/api/knowledge/${btn.dataset.id}`, {
2842
+ await fetch(`http://localhost:3000/api/knowledge/${btn.dataset.id}`, {
2843
2843
  method: 'DELETE',
2844
2844
  headers: { 'Authorization': `Bearer ${token}` }
2845
2845
  });
@@ -2878,8 +2878,8 @@ export function renderAdminDashboard(user, wsService) {
2878
2878
  try {
2879
2879
  const editId = e.target.dataset.editId;
2880
2880
  const url = editId
2881
- ? `http://localhost:8765/api/knowledge/${editId}`
2882
- : 'http://localhost:8765/api/knowledge';
2881
+ ? `http://localhost:3000/api/knowledge/${editId}`
2882
+ : 'http://localhost:3000/api/knowledge';
2883
2883
  const method = editId ? 'PUT' : 'POST';
2884
2884
 
2885
2885
  const response = await fetch(url, {
@@ -2989,7 +2989,7 @@ async function renderWorkflowView(container) {
2989
2989
 
2990
2990
  try {
2991
2991
  const token = localStorage.getItem('token');
2992
- const response = await fetch(`http://localhost:8765/api/workflows/group/${currentGroup._id}`, {
2992
+ const response = await fetch(`http://localhost:3000/api/workflows/group/${currentGroup._id}`, {
2993
2993
  headers: { 'Authorization': `Bearer ${token}` }
2994
2994
  });
2995
2995
  const result = await response.json();
@@ -3101,7 +3101,7 @@ async function renderWorkflowView(container) {
3101
3101
  document.querySelectorAll('[data-action="toggle"]').forEach(btn => {
3102
3102
  btn.addEventListener('click', async () => {
3103
3103
  try {
3104
- await fetch(`http://localhost:8765/api/workflows/${btn.dataset.id}/toggle`, {
3104
+ await fetch(`http://localhost:3000/api/workflows/${btn.dataset.id}/toggle`, {
3105
3105
  method: 'POST',
3106
3106
  headers: { 'Authorization': `Bearer ${token}` }
3107
3107
  });
@@ -3116,7 +3116,7 @@ async function renderWorkflowView(container) {
3116
3116
  btn.addEventListener('click', async () => {
3117
3117
  if (confirm('确定要删除这个工作流吗?')) {
3118
3118
  try {
3119
- await fetch(`http://localhost:8765/api/workflows/${btn.dataset.id}`, {
3119
+ await fetch(`http://localhost:3000/api/workflows/${btn.dataset.id}`, {
3120
3120
  method: 'DELETE',
3121
3121
  headers: { 'Authorization': `Bearer ${token}` }
3122
3122
  });
@@ -3150,7 +3150,7 @@ async function renderWorkflowView(container) {
3150
3150
  };
3151
3151
 
3152
3152
  try {
3153
- await fetch('http://localhost:8765/api/workflows', {
3153
+ await fetch('http://localhost:3000/api/workflows', {
3154
3154
  method: 'POST',
3155
3155
  headers: {
3156
3156
  'Content-Type': 'application/json',
@@ -3174,7 +3174,7 @@ async function renderWorkflowView(container) {
3174
3174
  async function renderBackupView(container) {
3175
3175
  try {
3176
3176
  const token = localStorage.getItem('token');
3177
- const response = await fetch('http://localhost:8765/api/backup/list', {
3177
+ const response = await fetch('http://localhost:3000/api/backup/list', {
3178
3178
  headers: { 'Authorization': `Bearer ${token}` }
3179
3179
  });
3180
3180
  const result = await response.json();
@@ -3236,7 +3236,7 @@ async function renderWorkflowView(container) {
3236
3236
  console.log('开始下载备份:', { backupName, filename });
3237
3237
 
3238
3238
  // 使用 fetch 下载,带 Authorization header
3239
- const response = await fetch(`http://localhost:8765/api/backup/download/${backupName}`, {
3239
+ const response = await fetch(`http://localhost:3000/api/backup/download/${backupName}`, {
3240
3240
  method: 'GET',
3241
3241
  headers: {
3242
3242
  'Authorization': `Bearer ${token}`
@@ -3293,7 +3293,7 @@ async function renderWorkflowView(container) {
3293
3293
  const backupName = btn.dataset.backupName; // 使用 name 而不是 filename
3294
3294
  console.log('删除备份:', backupName);
3295
3295
 
3296
- const response = await fetch(`http://localhost:8765/api/backup/${backupName}`, {
3296
+ const response = await fetch(`http://localhost:3000/api/backup/${backupName}`, {
3297
3297
  method: 'DELETE',
3298
3298
  headers: { 'Authorization': `Bearer ${token}` }
3299
3299
  });
@@ -3319,7 +3319,7 @@ async function renderWorkflowView(container) {
3319
3319
  btn.disabled = true;
3320
3320
  btn.textContent = '⏳ 创建中...';
3321
3321
  try {
3322
- await fetch('http://localhost:8765/api/backup/create', {
3322
+ await fetch('http://localhost:3000/api/backup/create', {
3323
3323
  method: 'POST',
3324
3324
  headers: { 'Authorization': `Bearer ${token}` }
3325
3325
  });
@@ -3395,7 +3395,7 @@ async function renderWorkflowView(container) {
3395
3395
  // 加载导出历史
3396
3396
  try {
3397
3397
  const token = localStorage.getItem('token');
3398
- const response = await fetch('http://localhost:8765/api/export/history', {
3398
+ const response = await fetch('http://localhost:3000/api/export/history', {
3399
3399
  headers: { 'Authorization': `Bearer ${token}` }
3400
3400
  });
3401
3401
  const result = await response.json();
@@ -3408,7 +3408,7 @@ async function renderWorkflowView(container) {
3408
3408
  <div style="font-weight: 600; margin-bottom: 5px;">📦 ${exp.format.toUpperCase()} 导出</div>
3409
3409
  <div style="font-size: 12px; color: var(--text-secondary);">📅 ${new Date(exp.createdAt).toLocaleString()}</div>
3410
3410
  </div>
3411
- <a href="http://localhost:8765/api/export/download/${exp.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
3411
+ <a href="http://localhost:3000/api/export/download/${exp.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
3412
3412
  </div>
3413
3413
  `).join('');
3414
3414
  } else {
@@ -3434,7 +3434,7 @@ async function renderWorkflowView(container) {
3434
3434
 
3435
3435
  try {
3436
3436
  const token = localStorage.getItem('token');
3437
- const response = await fetch('http://localhost:8765/api/export', {
3437
+ const response = await fetch('http://localhost:3000/api/export', {
3438
3438
  method: 'POST',
3439
3439
  headers: {
3440
3440
  'Content-Type': 'application/json',
@@ -968,7 +968,7 @@ export function renderUserDashboard(user, wsService) {
968
968
  </div>
969
969
  </div>
970
970
  <div class="ai-input" style="display: flex; gap: 10px; padding: 16px; background: var(--bg-tertiary); border-top: 1px solid var(--border);">
971
- <textarea id="aiInput" placeholder="向AI助手提问..." rows="2" style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); resize: none;"></textarea>
971
+ <textarea id="aiInput" placeholder="向AI助手提问..." rows="2" style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); resize: none; color: white;"></textarea>
972
972
  <button class="btn-primary" id="aiSendBtn" style="padding: 10px 24px; border-radius: 8px; align-self: flex-end;">发送</button>
973
973
  </div>
974
974
  </div>
@@ -1294,7 +1294,7 @@ export function renderUserDashboard(user, wsService) {
1294
1294
  formData.append('description', '协作白板作品');
1295
1295
 
1296
1296
  const token = localStorage.getItem('token');
1297
- const response = await fetch('http://localhost:8765/api/files/upload', {
1297
+ const response = await fetch('http://localhost:3000/api/files/upload', {
1298
1298
  method: 'POST',
1299
1299
  headers: {
1300
1300
  'Authorization': `Bearer ${token}`
@@ -1305,7 +1305,7 @@ export function renderUserDashboard(user, wsService) {
1305
1305
  if (response.ok) {
1306
1306
  const result = await response.json();
1307
1307
  const fileId = result.file._id;
1308
- const fileUrl = `http://localhost:8765/api/files/${fileId}/download?token=${token}`;
1308
+ const fileUrl = `http://localhost:3000/api/files/${fileId}/download?token=${token}`;
1309
1309
 
1310
1310
  // 发送包含图片链接的消息到群聊
1311
1311
  wsService.sendChatMessage(currentGroup._id, user.username, `[白板作品]${fileUrl}`);
@@ -1437,7 +1437,7 @@ export function renderUserDashboard(user, wsService) {
1437
1437
 
1438
1438
  try {
1439
1439
  const token = localStorage.getItem('token');
1440
- const response = await fetch('http://localhost:8765/api/ai/ask', {
1440
+ const response = await fetch('http://localhost:3000/api/ai/ask', {
1441
1441
  method: 'POST',
1442
1442
  headers: {
1443
1443
  'Content-Type': 'application/json',
@@ -1692,7 +1692,7 @@ export function renderUserDashboard(user, wsService) {
1692
1692
 
1693
1693
  try {
1694
1694
  const token = localStorage.getItem('token');
1695
- const response = await fetch(`http://localhost:8765/api/knowledge/group/${currentGroup._id}`, {
1695
+ const response = await fetch(`http://localhost:3000/api/knowledge/group/${currentGroup._id}`, {
1696
1696
  headers: { 'Authorization': `Bearer ${token}` }
1697
1697
  });
1698
1698
 
@@ -1819,7 +1819,7 @@ export function renderUserDashboard(user, wsService) {
1819
1819
  btn.addEventListener('click', async () => {
1820
1820
  if (confirm('确定要删除这个知识条目吗?')) {
1821
1821
  try {
1822
- await fetch(`http://localhost:8765/api/knowledge/${btn.dataset.id}`, {
1822
+ await fetch(`http://localhost:3000/api/knowledge/${btn.dataset.id}`, {
1823
1823
  method: 'DELETE',
1824
1824
  headers: { 'Authorization': `Bearer ${token}` }
1825
1825
  });
@@ -1865,8 +1865,8 @@ export function renderUserDashboard(user, wsService) {
1865
1865
  try {
1866
1866
  const editId = e.target.dataset.editId;
1867
1867
  const url = editId
1868
- ? `http://localhost:8765/api/knowledge/${editId}`
1869
- : 'http://localhost:8765/api/knowledge';
1868
+ ? `http://localhost:3000/api/knowledge/${editId}`
1869
+ : 'http://localhost:3000/api/knowledge';
1870
1870
  const method = editId ? 'PUT' : 'POST';
1871
1871
 
1872
1872
  const response = await fetch(url, {