collabdocchat 2.1.4 → 2.1.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "collabdocchat",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
4
4
  "description": "开源的实时协作文档聊天平台 - 集成任务管理、多人文档编辑、智能点名功能",
5
5
  "main": "./server/index.js",
6
6
  "type": "module",
@@ -0,0 +1,66 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ const filePath = path.join(__dirname, '../src/pages/admin-dashboard.js');
9
+
10
+ console.log('读取文件...');
11
+ let content = fs.readFileSync(filePath, 'utf8');
12
+
13
+ // 找到最后一个 renderView('groups'); 之前插入新函数
14
+ const insertPoint = content.lastIndexOf(" renderView('groups');");
15
+
16
+ if (insertPoint === -1) {
17
+ console.error('❌ 找不到插入点');
18
+ process.exit(1);
19
+ }
20
+
21
+ const newFunctions = `
22
+ // 知识库管理
23
+ async function renderKnowledgeView(container) {
24
+ if (!currentGroup) {
25
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
26
+ return;
27
+ }
28
+ container.innerHTML = '<div class="empty-state">知识库功能开发中...</div>';
29
+ }
30
+
31
+ // 工作流管理
32
+ async function renderWorkflowView(container) {
33
+ if (!currentGroup) {
34
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
35
+ return;
36
+ }
37
+ container.innerHTML = '<div class="empty-state">工作流功能开发中...</div>';
38
+ }
39
+
40
+ // 备份管理
41
+ async function renderBackupView(container) {
42
+ container.innerHTML = '<div class="empty-state">备份功能开发中...</div>';
43
+ }
44
+
45
+ // AI助手
46
+ async function renderAIView(container) {
47
+ container.innerHTML = '<div class="empty-state">AI助手功能开发中...</div>';
48
+ }
49
+
50
+ // 数据导出
51
+ async function renderExportView(container) {
52
+ container.innerHTML = '<div class="empty-state">数据导出功能开发中...</div>';
53
+ }
54
+
55
+ `;
56
+
57
+ const before = content.substring(0, insertPoint);
58
+ const after = content.substring(insertPoint);
59
+
60
+ content = before + newFunctions + after;
61
+
62
+ console.log('写入文件...');
63
+ fs.writeFileSync(filePath, content, 'utf8');
64
+
65
+ console.log('✅ 完成!已添加缺失的函数');
66
+
@@ -0,0 +1,201 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ const filePath = path.join(__dirname, '../src/pages/user-dashboard.js');
9
+
10
+ console.log('读取 user-dashboard.js...');
11
+ let content = fs.readFileSync(filePath, 'utf8');
12
+
13
+ // 1. 在导航菜单中添加知识库和AI助手
14
+ console.log('添加导航菜单项...');
15
+ const navMenuPattern = /(<button class="nav-item" data-view="search">[\s\S]*?<\/button>)/;
16
+ const navMenuReplacement = `$1
17
+ <button class="nav-item" data-view="knowledge">
18
+ <span class="icon">📚</span> 知识库
19
+ </button>
20
+ <button class="nav-item" data-view="ai">
21
+ <span class="icon">🤖</span> AI助手
22
+ </button>`;
23
+
24
+ content = content.replace(navMenuPattern, navMenuReplacement);
25
+
26
+ // 2. 在 switch 语句中添加 case
27
+ console.log('添加 switch case...');
28
+ const switchPattern = /(case 'search':[\s\S]*?await renderSearchView\(contentArea\);[\s\S]*?break;)/;
29
+ const switchReplacement = `$1
30
+ case 'knowledge':
31
+ await renderKnowledgeView(contentArea);
32
+ break;
33
+ case 'ai':
34
+ await renderAIView(contentArea);
35
+ break;`;
36
+
37
+ content = content.replace(switchPattern, switchReplacement);
38
+
39
+ // 3. 在文件末尾添加函数实现
40
+ console.log('添加函数实现...');
41
+ const insertPoint = content.lastIndexOf(" renderView('groups');");
42
+
43
+ if (insertPoint === -1) {
44
+ console.error('❌ 找不到插入点');
45
+ process.exit(1);
46
+ }
47
+
48
+ const newFunctions = `
49
+ // 知识库(用户版)
50
+ async function renderKnowledgeView(container) {
51
+ if (!currentGroup) {
52
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
53
+ return;
54
+ }
55
+
56
+ try {
57
+ const token = localStorage.getItem('token');
58
+ const response = await fetch(\`http://localhost:3000/api/knowledge/group/\${currentGroup._id}\`, {
59
+ headers: { 'Authorization': \`Bearer \${token}\` }
60
+ });
61
+ const result = await response.json();
62
+ const knowledgeItems = result.data || [];
63
+
64
+ container.innerHTML = \`
65
+ <div class="view-header">
66
+ <h2>知识库 - \${currentGroup.name}</h2>
67
+ <input type="text" id="knowledgeSearch" placeholder="搜索知识..." style="padding: 8px; border: 1px solid var(--border); border-radius: 4px;">
68
+ </div>
69
+ <div class="knowledge-list" id="knowledgeList"></div>
70
+ \`;
71
+
72
+ const knowledgeList = document.getElementById('knowledgeList');
73
+ if (knowledgeItems.length === 0) {
74
+ knowledgeList.innerHTML = '<div class="empty-state">暂无知识条目</div>';
75
+ } else {
76
+ const renderItems = (items) => {
77
+ knowledgeList.innerHTML = items.map(item => \`
78
+ <div class="knowledge-card">
79
+ <h3>\${item.title}</h3>
80
+ <p>\${item.content}</p>
81
+ <div class="knowledge-meta">
82
+ <span>创建者: \${item.creator?.username || '未知'}</span>
83
+ <span>创建时间: \${new Date(item.createdAt).toLocaleDateString()}</span>
84
+ </div>
85
+ \${item.tags && item.tags.length > 0 ? \`
86
+ <div class="tags">
87
+ \${item.tags.map(tag => \`<span class="tag">\${tag}</span>\`).join('')}
88
+ </div>
89
+ \` : ''}
90
+ </div>
91
+ \`).join('');
92
+ };
93
+
94
+ renderItems(knowledgeItems);
95
+
96
+ // 搜索功能
97
+ document.getElementById('knowledgeSearch').addEventListener('input', (e) => {
98
+ const query = e.target.value.toLowerCase();
99
+ const filtered = knowledgeItems.filter(item =>
100
+ item.title.toLowerCase().includes(query) ||
101
+ item.content.toLowerCase().includes(query) ||
102
+ (item.tags && item.tags.some(tag => tag.toLowerCase().includes(query)))
103
+ );
104
+ renderItems(filtered);
105
+ });
106
+ }
107
+ } catch (error) {
108
+ container.innerHTML = \`<div class="empty-state">加载失败: \${error.message}</div>\`;
109
+ }
110
+ }
111
+
112
+ // AI助手(用户版)
113
+ async function renderAIView(container) {
114
+ container.innerHTML = \`
115
+ <div class="view-header">
116
+ <h2>🤖 AI助手</h2>
117
+ </div>
118
+ <div class="ai-container">
119
+ <div class="ai-chat" id="aiChat">
120
+ <div class="ai-message ai">
121
+ <p>你好!我是AI助手,有什么可以帮助你的吗?</p>
122
+ <p>你可以问我关于文档、任务、群组的问题。</p>
123
+ </div>
124
+ </div>
125
+ <div class="ai-input">
126
+ <textarea id="aiInput" placeholder="向AI助手提问..." rows="3"></textarea>
127
+ <button class="btn-primary" id="aiSendBtn">发送</button>
128
+ </div>
129
+ </div>
130
+ \`;
131
+
132
+ const aiChat = document.getElementById('aiChat');
133
+ const aiInput = document.getElementById('aiInput');
134
+ const aiSendBtn = document.getElementById('aiSendBtn');
135
+
136
+ const sendMessage = async () => {
137
+ const question = aiInput.value.trim();
138
+ if (!question) return;
139
+
140
+ // 显示用户消息
141
+ const userMsg = document.createElement('div');
142
+ userMsg.className = 'ai-message user';
143
+ userMsg.textContent = question;
144
+ aiChat.appendChild(userMsg);
145
+ aiInput.value = '';
146
+
147
+ // 显示加载中
148
+ const loadingMsg = document.createElement('div');
149
+ loadingMsg.className = 'ai-message ai loading';
150
+ loadingMsg.textContent = '思考中...';
151
+ aiChat.appendChild(loadingMsg);
152
+ aiChat.scrollTop = aiChat.scrollHeight;
153
+
154
+ try {
155
+ const token = localStorage.getItem('token');
156
+ const response = await fetch('http://localhost:3000/api/ai/ask', {
157
+ method: 'POST',
158
+ headers: {
159
+ 'Content-Type': 'application/json',
160
+ 'Authorization': \`Bearer \${token}\`
161
+ },
162
+ body: JSON.stringify({ question, groupId: currentGroup?._id })
163
+ });
164
+ const result = await response.json();
165
+
166
+ loadingMsg.remove();
167
+ const aiMsg = document.createElement('div');
168
+ aiMsg.className = 'ai-message ai';
169
+ aiMsg.textContent = result.answer || '抱歉,我无法回答这个问题。';
170
+ aiChat.appendChild(aiMsg);
171
+ aiChat.scrollTop = aiChat.scrollHeight;
172
+ } catch (error) {
173
+ loadingMsg.remove();
174
+ const errorMsg = document.createElement('div');
175
+ errorMsg.className = 'ai-message ai error';
176
+ errorMsg.textContent = '抱歉,发生了错误: ' + error.message;
177
+ aiChat.appendChild(errorMsg);
178
+ }
179
+ };
180
+
181
+ aiSendBtn.addEventListener('click', sendMessage);
182
+ aiInput.addEventListener('keypress', (e) => {
183
+ if (e.key === 'Enter' && !e.shiftKey) {
184
+ e.preventDefault();
185
+ sendMessage();
186
+ }
187
+ });
188
+ }
189
+
190
+ `;
191
+
192
+ const before = content.substring(0, insertPoint);
193
+ const after = content.substring(insertPoint);
194
+
195
+ content = before + newFunctions + after;
196
+
197
+ console.log('写入文件...');
198
+ fs.writeFileSync(filePath, content, 'utf8');
199
+
200
+ console.log('✅ 完成!已添加用户面板的缺失功能');
201
+
@@ -0,0 +1,41 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ const files = [
9
+ '../src/pages/optimized-knowledge-view.js',
10
+ '../src/pages/optimized-backup-view.js',
11
+ '../src/pages/optimized-workflow-view.js'
12
+ ];
13
+
14
+ files.forEach(file => {
15
+ const filePath = path.join(__dirname, file);
16
+ console.log(`\n检查文件: ${file}`);
17
+
18
+ // 读取文件为 Buffer
19
+ const buffer = fs.readFileSync(filePath);
20
+
21
+ // 检查 BOM
22
+ if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
23
+ console.log(' ⚠️ 发现 UTF-8 BOM,正在移除...');
24
+ const content = buffer.slice(3).toString('utf8');
25
+ fs.writeFileSync(filePath, content, 'utf8');
26
+ console.log(' ✅ BOM 已移除');
27
+ } else {
28
+ console.log(' ✅ 无 BOM');
29
+ }
30
+
31
+ // 读取内容检查乱码
32
+ const content = fs.readFileSync(filePath, 'utf8');
33
+ if (content.includes('�')) {
34
+ console.log(' ❌ 发现乱码字符!');
35
+ } else {
36
+ console.log(' ✅ 无乱码');
37
+ }
38
+ });
39
+
40
+ console.log('\n✅ 检查完成!');
41
+
@@ -1,9 +1,6 @@
1
1
  import { ApiService } from '../services/api.js';
2
2
  import { AuthService } from '../services/auth.js';
3
3
  import 'emoji-picker-element';
4
- import { renderOptimizedKnowledgeView } from './optimized-knowledge-view.js';
5
- import { renderOptimizedWorkflowView } from './optimized-workflow-view.js';
6
- import { renderOptimizedBackupView } from './optimized-backup-view.js';
7
4
 
8
5
  export function renderAdminDashboard(user, wsService) {
9
6
  const app = document.getElementById('app');
@@ -64,6 +61,12 @@ export function renderAdminDashboard(user, wsService) {
64
61
  <button class="nav-item" data-view="backup">
65
62
  <span class="icon">💾</span> 备份管理
66
63
  </button>
64
+ <button class="nav-item" data-view="ai">
65
+ <span class="icon">🤖</span> AI助手
66
+ </button>
67
+ <button class="nav-item" data-view="export">
68
+ <span class="icon">📤</span> 数据导出
69
+ </button>
67
70
  </nav>
68
71
 
69
72
  <button class="btn-logout" id="logoutBtn">退出登录</button>
@@ -119,13 +122,19 @@ export function renderAdminDashboard(user, wsService) {
119
122
  await renderAuditView(contentArea);
120
123
  break;
121
124
  case 'knowledge':
122
- await renderOptimizedKnowledgeView(contentArea, currentGroup, apiService, currentUserId);
125
+ await renderKnowledgeView(contentArea);
123
126
  break;
124
127
  case 'workflow':
125
- await renderOptimizedWorkflowView(contentArea, currentGroup, apiService);
128
+ await renderWorkflowView(contentArea);
126
129
  break;
127
130
  case 'backup':
128
- await renderOptimizedBackupView(contentArea);
131
+ await renderBackupView(contentArea);
132
+ break;
133
+ case 'ai':
134
+ await renderAIView(contentArea);
135
+ break;
136
+ case 'export':
137
+ await renderExportView(contentArea);
129
138
  break;
130
139
  }
131
140
  }
@@ -1489,6 +1498,40 @@ export function renderAdminDashboard(user, wsService) {
1489
1498
  return statusMap[status] || status;
1490
1499
  }
1491
1500
 
1501
+
1502
+ // 知识库管理
1503
+ async function renderKnowledgeView(container) {
1504
+ if (!currentGroup) {
1505
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
1506
+ return;
1507
+ }
1508
+ container.innerHTML = '<div class="empty-state">知识库功能开发中...</div>';
1509
+ }
1510
+
1511
+ // 工作流管理
1512
+ async function renderWorkflowView(container) {
1513
+ if (!currentGroup) {
1514
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
1515
+ return;
1516
+ }
1517
+ container.innerHTML = '<div class="empty-state">工作流功能开发中...</div>';
1518
+ }
1519
+
1520
+ // 备份管理
1521
+ async function renderBackupView(container) {
1522
+ container.innerHTML = '<div class="empty-state">备份功能开发中...</div>';
1523
+ }
1524
+
1525
+ // AI助手
1526
+ async function renderAIView(container) {
1527
+ container.innerHTML = '<div class="empty-state">AI助手功能开发中...</div>';
1528
+ }
1529
+
1530
+ // 数据导出
1531
+ async function renderExportView(container) {
1532
+ container.innerHTML = '<div class="empty-state">数据导出功能开发中...</div>';
1533
+ }
1534
+
1492
1535
  renderView('groups');
1493
1536
  }
1494
1537
 
@@ -48,6 +48,12 @@ export function renderUserDashboard(user, wsService) {
48
48
  </button>
49
49
  <button class="nav-item" data-view="search">
50
50
  <span class="icon">🔍</span> 搜索
51
+ </button>
52
+ <button class="nav-item" data-view="knowledge">
53
+ <span class="icon">📚</span> 知识库
54
+ </button>
55
+ <button class="nav-item" data-view="ai">
56
+ <span class="icon">🤖</span> AI助手
51
57
  </button>
52
58
  </nav>
53
59
 
@@ -99,6 +105,12 @@ export function renderUserDashboard(user, wsService) {
99
105
  break;
100
106
  case 'search':
101
107
  await renderSearchView(contentArea);
108
+ break;
109
+ case 'knowledge':
110
+ await renderKnowledgeView(contentArea);
111
+ break;
112
+ case 'ai':
113
+ await renderAIView(contentArea);
102
114
  break;
103
115
  }
104
116
  }
@@ -899,6 +911,148 @@ export function renderUserDashboard(user, wsService) {
899
911
  return statusMap[status] || status;
900
912
  }
901
913
 
914
+
915
+ // 知识库(用户版)
916
+ async function renderKnowledgeView(container) {
917
+ if (!currentGroup) {
918
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
919
+ return;
920
+ }
921
+
922
+ try {
923
+ const token = localStorage.getItem('token');
924
+ const response = await fetch(`http://localhost:3000/api/knowledge/group/${currentGroup._id}`, {
925
+ headers: { 'Authorization': `Bearer ${token}` }
926
+ });
927
+ const result = await response.json();
928
+ const knowledgeItems = result.data || [];
929
+
930
+ container.innerHTML = `
931
+ <div class="view-header">
932
+ <h2>知识库 - ${currentGroup.name}</h2>
933
+ <input type="text" id="knowledgeSearch" placeholder="搜索知识..." style="padding: 8px; border: 1px solid var(--border); border-radius: 4px;">
934
+ </div>
935
+ <div class="knowledge-list" id="knowledgeList"></div>
936
+ `;
937
+
938
+ const knowledgeList = document.getElementById('knowledgeList');
939
+ if (knowledgeItems.length === 0) {
940
+ knowledgeList.innerHTML = '<div class="empty-state">暂无知识条目</div>';
941
+ } else {
942
+ const renderItems = (items) => {
943
+ knowledgeList.innerHTML = items.map(item => `
944
+ <div class="knowledge-card">
945
+ <h3>${item.title}</h3>
946
+ <p>${item.content}</p>
947
+ <div class="knowledge-meta">
948
+ <span>创建者: ${item.creator?.username || '未知'}</span>
949
+ <span>创建时间: ${new Date(item.createdAt).toLocaleDateString()}</span>
950
+ </div>
951
+ ${item.tags && item.tags.length > 0 ? `
952
+ <div class="tags">
953
+ ${item.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
954
+ </div>
955
+ ` : ''}
956
+ </div>
957
+ `).join('');
958
+ };
959
+
960
+ renderItems(knowledgeItems);
961
+
962
+ // 搜索功能
963
+ document.getElementById('knowledgeSearch').addEventListener('input', (e) => {
964
+ const query = e.target.value.toLowerCase();
965
+ const filtered = knowledgeItems.filter(item =>
966
+ item.title.toLowerCase().includes(query) ||
967
+ item.content.toLowerCase().includes(query) ||
968
+ (item.tags && item.tags.some(tag => tag.toLowerCase().includes(query)))
969
+ );
970
+ renderItems(filtered);
971
+ });
972
+ }
973
+ } catch (error) {
974
+ container.innerHTML = `<div class="empty-state">加载失败: ${error.message}</div>`;
975
+ }
976
+ }
977
+
978
+ // AI助手(用户版)
979
+ async function renderAIView(container) {
980
+ container.innerHTML = `
981
+ <div class="view-header">
982
+ <h2>🤖 AI助手</h2>
983
+ </div>
984
+ <div class="ai-container">
985
+ <div class="ai-chat" id="aiChat">
986
+ <div class="ai-message ai">
987
+ <p>你好!我是AI助手,有什么可以帮助你的吗?</p>
988
+ <p>你可以问我关于文档、任务、群组的问题。</p>
989
+ </div>
990
+ </div>
991
+ <div class="ai-input">
992
+ <textarea id="aiInput" placeholder="向AI助手提问..." rows="3"></textarea>
993
+ <button class="btn-primary" id="aiSendBtn">发送</button>
994
+ </div>
995
+ </div>
996
+ `;
997
+
998
+ const aiChat = document.getElementById('aiChat');
999
+ const aiInput = document.getElementById('aiInput');
1000
+ const aiSendBtn = document.getElementById('aiSendBtn');
1001
+
1002
+ const sendMessage = async () => {
1003
+ const question = aiInput.value.trim();
1004
+ if (!question) return;
1005
+
1006
+ // 显示用户消息
1007
+ const userMsg = document.createElement('div');
1008
+ userMsg.className = 'ai-message user';
1009
+ userMsg.textContent = question;
1010
+ aiChat.appendChild(userMsg);
1011
+ aiInput.value = '';
1012
+
1013
+ // 显示加载中
1014
+ const loadingMsg = document.createElement('div');
1015
+ loadingMsg.className = 'ai-message ai loading';
1016
+ loadingMsg.textContent = '思考中...';
1017
+ aiChat.appendChild(loadingMsg);
1018
+ aiChat.scrollTop = aiChat.scrollHeight;
1019
+
1020
+ try {
1021
+ const token = localStorage.getItem('token');
1022
+ const response = await fetch('http://localhost:3000/api/ai/ask', {
1023
+ method: 'POST',
1024
+ headers: {
1025
+ 'Content-Type': 'application/json',
1026
+ 'Authorization': `Bearer ${token}`
1027
+ },
1028
+ body: JSON.stringify({ question, groupId: currentGroup?._id })
1029
+ });
1030
+ const result = await response.json();
1031
+
1032
+ loadingMsg.remove();
1033
+ const aiMsg = document.createElement('div');
1034
+ aiMsg.className = 'ai-message ai';
1035
+ aiMsg.textContent = result.answer || '抱歉,我无法回答这个问题。';
1036
+ aiChat.appendChild(aiMsg);
1037
+ aiChat.scrollTop = aiChat.scrollHeight;
1038
+ } catch (error) {
1039
+ loadingMsg.remove();
1040
+ const errorMsg = document.createElement('div');
1041
+ errorMsg.className = 'ai-message ai error';
1042
+ errorMsg.textContent = '抱歉,发生了错误: ' + error.message;
1043
+ aiChat.appendChild(errorMsg);
1044
+ }
1045
+ };
1046
+
1047
+ aiSendBtn.addEventListener('click', sendMessage);
1048
+ aiInput.addEventListener('keypress', (e) => {
1049
+ if (e.key === 'Enter' && !e.shiftKey) {
1050
+ e.preventDefault();
1051
+ sendMessage();
1052
+ }
1053
+ });
1054
+ }
1055
+
902
1056
  renderView('groups');
903
1057
  }
904
1058