collabdocchat 1.2.12 → 2.0.0
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/README.md +219 -218
- package/index.html +2 -0
- package/install-and-start.bat +5 -0
- package/install-and-start.sh +5 -0
- package/package.json +9 -2
- package/scripts/pre-publish-check.js +213 -0
- package/scripts/start-app.js +15 -15
- package/server/index.js +38 -6
- package/server/middleware/cache.js +115 -0
- package/server/middleware/errorHandler.js +209 -0
- package/server/models/Document.js +66 -59
- package/server/models/File.js +49 -43
- package/server/models/Group.js +6 -0
- package/server/models/KnowledgeBase.js +254 -0
- package/server/models/Message.js +43 -0
- package/server/models/Task.js +87 -55
- package/server/models/User.js +67 -60
- package/server/models/Workflow.js +249 -0
- package/server/routes/ai.js +327 -0
- package/server/routes/audit.js +245 -210
- package/server/routes/backup.js +108 -0
- package/server/routes/chunked-upload.js +343 -0
- package/server/routes/export.js +440 -0
- package/server/routes/files.js +294 -218
- package/server/routes/groups.js +182 -0
- package/server/routes/knowledge.js +509 -0
- package/server/routes/tasks.js +257 -110
- package/server/routes/workflows.js +380 -0
- package/server/utils/backup.js +439 -0
- package/server/utils/cache.js +223 -0
- package/server/utils/workflow-engine.js +479 -0
- package/server/websocket/enhanced.js +509 -0
- package/server/websocket/index.js +233 -1
- package/src/components/knowledge-modal.js +485 -0
- package/src/components/optimized-poll-detail.js +724 -0
- package/src/main.js +5 -0
- package/src/pages/admin-dashboard.js +2248 -44
- package/src/pages/optimized-backup-view.js +616 -0
- package/src/pages/optimized-knowledge-view.js +803 -0
- package/src/pages/optimized-task-detail.js +843 -0
- package/src/pages/optimized-workflow-view.js +806 -0
- package/src/pages/simplified-workflows.js +651 -0
- package/src/pages/user-dashboard.js +677 -58
- package/src/services/api.js +65 -1
- package/src/services/websocket.js +124 -16
- package/src/styles/collaboration-modern.js +708 -0
- package/src/styles/enhancements.css +392 -0
- package/src/styles/main.css +620 -1420
- package/src/styles/responsive.css +1000 -0
- package/src/styles/sidebar-fix.css +60 -0
- package/src/utils/ai-assistant.js +1398 -0
- package/src/utils/chat-enhancements.js +509 -0
- package/src/utils/collaboration-enhancer.js +1151 -0
- package/src/utils/feature-integrator.js +1724 -0
- package/src/utils/onboarding-guide.js +734 -0
- package/src/utils/performance.js +394 -0
- package/src/utils/permission-manager.js +890 -0
- package/src/utils/responsive-handler.js +491 -0
- package/src/utils/theme-manager.js +811 -0
- package/src/utils/ui-enhancements-loader.js +329 -0
- package/USAGE.md +0 -298
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { Workflow, WorkflowExecution, Approval } from '../models/Workflow.js';
|
|
3
|
+
import Group from '../models/Group.js';
|
|
4
|
+
import workflowEngine from '../utils/workflow-engine.js';
|
|
5
|
+
import { authenticate, isAdmin } from '../middleware/auth.js';
|
|
6
|
+
import { asyncHandler, ValidationError, NotFoundError, AuthorizationError } from '../middleware/errorHandler.js';
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
// 创建工作流
|
|
11
|
+
router.post('/', authenticate, asyncHandler(async (req, res) => {
|
|
12
|
+
const { name, description, groupId, trigger, steps } = req.body;
|
|
13
|
+
|
|
14
|
+
if (!name || !groupId) {
|
|
15
|
+
throw new ValidationError('工作流名称和群组ID不能为空');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 验证群组
|
|
19
|
+
const group = await Group.findById(groupId);
|
|
20
|
+
if (!group) {
|
|
21
|
+
throw new NotFoundError('群组');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 检查权限(只有管理员可以创建工作流)
|
|
25
|
+
const isGroupAdmin = group.admin.toString() === req.user.userId;
|
|
26
|
+
if (!isGroupAdmin && req.user.role !== 'admin') {
|
|
27
|
+
throw new AuthorizationError('只有管理员可以创建工作流');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const workflow = new Workflow({
|
|
31
|
+
name,
|
|
32
|
+
description: description || '',
|
|
33
|
+
group: groupId,
|
|
34
|
+
creator: req.user.userId,
|
|
35
|
+
trigger: trigger || { type: 'manual' },
|
|
36
|
+
steps: steps || [],
|
|
37
|
+
status: 'draft'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await workflow.save();
|
|
41
|
+
|
|
42
|
+
res.status(201).json({
|
|
43
|
+
success: true,
|
|
44
|
+
message: '工作流创建成功',
|
|
45
|
+
data: { workflow }
|
|
46
|
+
});
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// 获取群组的工作流列表
|
|
50
|
+
router.get('/group/:groupId', authenticate, asyncHandler(async (req, res) => {
|
|
51
|
+
const { status } = req.query;
|
|
52
|
+
|
|
53
|
+
// 验证群组
|
|
54
|
+
const group = await Group.findById(req.params.groupId);
|
|
55
|
+
if (!group) {
|
|
56
|
+
throw new NotFoundError('群组');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 检查用户是否在群组中
|
|
60
|
+
const isMember = group.members.some(m => m.toString() === req.user.userId);
|
|
61
|
+
if (!isMember) {
|
|
62
|
+
throw new AuthorizationError('您不在该群组中');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const query = { group: req.params.groupId };
|
|
66
|
+
if (status) query.status = status;
|
|
67
|
+
|
|
68
|
+
const workflows = await Workflow.find(query)
|
|
69
|
+
.populate('creator', 'username avatar')
|
|
70
|
+
.sort({ createdAt: -1 });
|
|
71
|
+
|
|
72
|
+
res.json({
|
|
73
|
+
success: true,
|
|
74
|
+
data: { workflows }
|
|
75
|
+
});
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// 获取工作流详情
|
|
79
|
+
router.get('/:id', authenticate, asyncHandler(async (req, res) => {
|
|
80
|
+
const workflow = await Workflow.findById(req.params.id)
|
|
81
|
+
.populate('creator', 'username avatar')
|
|
82
|
+
.populate('group', 'name');
|
|
83
|
+
|
|
84
|
+
if (!workflow) {
|
|
85
|
+
throw new NotFoundError('工作流');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
res.json({
|
|
89
|
+
success: true,
|
|
90
|
+
data: { workflow }
|
|
91
|
+
});
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
// 更新工作流
|
|
95
|
+
router.put('/:id', authenticate, asyncHandler(async (req, res) => {
|
|
96
|
+
const { name, description, trigger, steps, status } = req.body;
|
|
97
|
+
|
|
98
|
+
const workflow = await Workflow.findById(req.params.id);
|
|
99
|
+
if (!workflow) {
|
|
100
|
+
throw new NotFoundError('工作流');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 检查权限
|
|
104
|
+
const group = await Group.findById(workflow.group);
|
|
105
|
+
const isCreator = workflow.creator.toString() === req.user.userId;
|
|
106
|
+
const isGroupAdmin = group.admin.toString() === req.user.userId;
|
|
107
|
+
|
|
108
|
+
if (!isCreator && !isGroupAdmin && req.user.role !== 'admin') {
|
|
109
|
+
throw new AuthorizationError('无权编辑此工作流');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (name) workflow.name = name;
|
|
113
|
+
if (description !== undefined) workflow.description = description;
|
|
114
|
+
if (trigger) workflow.trigger = trigger;
|
|
115
|
+
if (steps) workflow.steps = steps;
|
|
116
|
+
if (status) workflow.status = status;
|
|
117
|
+
|
|
118
|
+
await workflow.save();
|
|
119
|
+
|
|
120
|
+
res.json({
|
|
121
|
+
success: true,
|
|
122
|
+
message: '工作流更新成功',
|
|
123
|
+
data: { workflow }
|
|
124
|
+
});
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
// 删除工作流
|
|
128
|
+
router.delete('/:id', authenticate, asyncHandler(async (req, res) => {
|
|
129
|
+
const workflow = await Workflow.findById(req.params.id);
|
|
130
|
+
if (!workflow) {
|
|
131
|
+
throw new NotFoundError('工作流');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 检查权限
|
|
135
|
+
const group = await Group.findById(workflow.group);
|
|
136
|
+
const isCreator = workflow.creator.toString() === req.user.userId;
|
|
137
|
+
const isGroupAdmin = group.admin.toString() === req.user.userId;
|
|
138
|
+
|
|
139
|
+
if (!isCreator && !isGroupAdmin && req.user.role !== 'admin') {
|
|
140
|
+
throw new AuthorizationError('无权删除此工作流');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await Workflow.findByIdAndDelete(req.params.id);
|
|
144
|
+
|
|
145
|
+
res.json({
|
|
146
|
+
success: true,
|
|
147
|
+
message: '工作流删除成功'
|
|
148
|
+
});
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
// 激活工作流
|
|
152
|
+
router.post('/:id/activate', authenticate, asyncHandler(async (req, res) => {
|
|
153
|
+
const workflow = await Workflow.findById(req.params.id);
|
|
154
|
+
if (!workflow) {
|
|
155
|
+
throw new NotFoundError('工作流');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 检查权限
|
|
159
|
+
const group = await Group.findById(workflow.group);
|
|
160
|
+
const isCreator = workflow.creator.toString() === req.user.userId;
|
|
161
|
+
const isGroupAdmin = group.admin.toString() === req.user.userId;
|
|
162
|
+
|
|
163
|
+
if (!isCreator && !isGroupAdmin && req.user.role !== 'admin') {
|
|
164
|
+
throw new AuthorizationError('无权激活此工作流');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
workflow.status = 'active';
|
|
168
|
+
await workflow.save();
|
|
169
|
+
|
|
170
|
+
res.json({
|
|
171
|
+
success: true,
|
|
172
|
+
message: '工作流已激活',
|
|
173
|
+
data: { workflow }
|
|
174
|
+
});
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
// 停用工作流
|
|
178
|
+
router.post('/:id/deactivate', authenticate, asyncHandler(async (req, res) => {
|
|
179
|
+
const workflow = await Workflow.findById(req.params.id);
|
|
180
|
+
if (!workflow) {
|
|
181
|
+
throw new NotFoundError('工作流');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 检查权限
|
|
185
|
+
const group = await Group.findById(workflow.group);
|
|
186
|
+
const isCreator = workflow.creator.toString() === req.user.userId;
|
|
187
|
+
const isGroupAdmin = group.admin.toString() === req.user.userId;
|
|
188
|
+
|
|
189
|
+
if (!isCreator && !isGroupAdmin && req.user.role !== 'admin') {
|
|
190
|
+
throw new AuthorizationError('无权停用此工作流');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
workflow.status = 'inactive';
|
|
194
|
+
await workflow.save();
|
|
195
|
+
|
|
196
|
+
res.json({
|
|
197
|
+
success: true,
|
|
198
|
+
message: '工作流已停用',
|
|
199
|
+
data: { workflow }
|
|
200
|
+
});
|
|
201
|
+
}));
|
|
202
|
+
|
|
203
|
+
// 手动触发工作流
|
|
204
|
+
router.post('/:id/trigger', authenticate, asyncHandler(async (req, res) => {
|
|
205
|
+
const { triggerData } = req.body;
|
|
206
|
+
|
|
207
|
+
const workflow = await Workflow.findById(req.params.id);
|
|
208
|
+
if (!workflow) {
|
|
209
|
+
throw new NotFoundError('工作流');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (workflow.status !== 'active') {
|
|
213
|
+
throw new ValidationError('工作流未激活');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 检查权限
|
|
217
|
+
const group = await Group.findById(workflow.group);
|
|
218
|
+
const isMember = group.members.some(m => m.toString() === req.user.userId);
|
|
219
|
+
if (!isMember) {
|
|
220
|
+
throw new AuthorizationError('您不在该群组中');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const execution = await workflowEngine.triggerWorkflow(
|
|
224
|
+
workflow._id,
|
|
225
|
+
triggerData || {},
|
|
226
|
+
req.user.userId
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
res.json({
|
|
230
|
+
success: true,
|
|
231
|
+
message: '工作流已触发',
|
|
232
|
+
data: { execution }
|
|
233
|
+
});
|
|
234
|
+
}));
|
|
235
|
+
|
|
236
|
+
// 获取工作流执行历史
|
|
237
|
+
router.get('/:id/executions', authenticate, asyncHandler(async (req, res) => {
|
|
238
|
+
const { page = 1, limit = 20, status } = req.query;
|
|
239
|
+
|
|
240
|
+
const workflow = await Workflow.findById(req.params.id);
|
|
241
|
+
if (!workflow) {
|
|
242
|
+
throw new NotFoundError('工作流');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const query = { workflow: req.params.id };
|
|
246
|
+
if (status) query.status = status;
|
|
247
|
+
|
|
248
|
+
const skip = (parseInt(page) - 1) * parseInt(limit);
|
|
249
|
+
|
|
250
|
+
const [executions, total] = await Promise.all([
|
|
251
|
+
WorkflowExecution.find(query)
|
|
252
|
+
.sort({ createdAt: -1 })
|
|
253
|
+
.skip(skip)
|
|
254
|
+
.limit(parseInt(limit))
|
|
255
|
+
.populate('triggeredBy', 'username avatar'),
|
|
256
|
+
WorkflowExecution.countDocuments(query)
|
|
257
|
+
]);
|
|
258
|
+
|
|
259
|
+
res.json({
|
|
260
|
+
success: true,
|
|
261
|
+
data: {
|
|
262
|
+
executions,
|
|
263
|
+
pagination: {
|
|
264
|
+
total,
|
|
265
|
+
page: parseInt(page),
|
|
266
|
+
limit: parseInt(limit),
|
|
267
|
+
pages: Math.ceil(total / parseInt(limit))
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
// 获取执行详情
|
|
274
|
+
router.get('/executions/:executionId', authenticate, asyncHandler(async (req, res) => {
|
|
275
|
+
const execution = await WorkflowExecution.findById(req.params.executionId)
|
|
276
|
+
.populate('workflow')
|
|
277
|
+
.populate('triggeredBy', 'username avatar');
|
|
278
|
+
|
|
279
|
+
if (!execution) {
|
|
280
|
+
throw new NotFoundError('执行记录');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
res.json({
|
|
284
|
+
success: true,
|
|
285
|
+
data: { execution }
|
|
286
|
+
});
|
|
287
|
+
}));
|
|
288
|
+
|
|
289
|
+
// 取消执行
|
|
290
|
+
router.post('/executions/:executionId/cancel', authenticate, asyncHandler(async (req, res) => {
|
|
291
|
+
const execution = await WorkflowExecution.findById(req.params.executionId);
|
|
292
|
+
if (!execution) {
|
|
293
|
+
throw new NotFoundError('执行记录');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (execution.status !== 'running' && execution.status !== 'pending') {
|
|
297
|
+
throw new ValidationError('只能取消运行中或待执行的工作流');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 检查权限
|
|
301
|
+
const workflow = await Workflow.findById(execution.workflow);
|
|
302
|
+
const group = await Group.findById(workflow.group);
|
|
303
|
+
const isTriggeredBy = execution.triggeredBy.toString() === req.user.userId;
|
|
304
|
+
const isGroupAdmin = group.admin.toString() === req.user.userId;
|
|
305
|
+
|
|
306
|
+
if (!isTriggeredBy && !isGroupAdmin && req.user.role !== 'admin') {
|
|
307
|
+
throw new AuthorizationError('无权取消此执行');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
execution.status = 'cancelled';
|
|
311
|
+
execution.completedAt = new Date();
|
|
312
|
+
await execution.save();
|
|
313
|
+
|
|
314
|
+
res.json({
|
|
315
|
+
success: true,
|
|
316
|
+
message: '执行已取消',
|
|
317
|
+
data: { execution }
|
|
318
|
+
});
|
|
319
|
+
}));
|
|
320
|
+
|
|
321
|
+
// 获取待审批列表
|
|
322
|
+
router.get('/approvals/pending', authenticate, asyncHandler(async (req, res) => {
|
|
323
|
+
const approvals = await Approval.find({
|
|
324
|
+
approver: req.user.userId,
|
|
325
|
+
status: 'pending'
|
|
326
|
+
})
|
|
327
|
+
.populate('workflow', 'name')
|
|
328
|
+
.populate('execution')
|
|
329
|
+
.sort({ createdAt: -1 });
|
|
330
|
+
|
|
331
|
+
res.json({
|
|
332
|
+
success: true,
|
|
333
|
+
data: { approvals }
|
|
334
|
+
});
|
|
335
|
+
}));
|
|
336
|
+
|
|
337
|
+
// 处理审批
|
|
338
|
+
router.post('/approvals/:approvalId/decide', authenticate, asyncHandler(async (req, res) => {
|
|
339
|
+
const { decision, comment } = req.body;
|
|
340
|
+
|
|
341
|
+
if (!decision || !['approved', 'rejected'].includes(decision)) {
|
|
342
|
+
throw new ValidationError('审批决策必须是 approved 或 rejected');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const approval = await workflowEngine.handleApprovalDecision(
|
|
346
|
+
req.params.approvalId,
|
|
347
|
+
decision,
|
|
348
|
+
comment || '',
|
|
349
|
+
req.user.userId
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
res.json({
|
|
353
|
+
success: true,
|
|
354
|
+
message: decision === 'approved' ? '审批通过' : '审批拒绝',
|
|
355
|
+
data: { approval }
|
|
356
|
+
});
|
|
357
|
+
}));
|
|
358
|
+
|
|
359
|
+
// 获取工作流统计
|
|
360
|
+
router.get('/:id/stats', authenticate, asyncHandler(async (req, res) => {
|
|
361
|
+
const workflow = await Workflow.findById(req.params.id);
|
|
362
|
+
if (!workflow) {
|
|
363
|
+
throw new NotFoundError('工作流');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const stats = {
|
|
367
|
+
...workflow.stats.toObject(),
|
|
368
|
+
successRate: workflow.stats.totalExecutions > 0
|
|
369
|
+
? (workflow.stats.successfulExecutions / workflow.stats.totalExecutions * 100).toFixed(2)
|
|
370
|
+
: 0
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
res.json({
|
|
374
|
+
success: true,
|
|
375
|
+
data: { stats }
|
|
376
|
+
});
|
|
377
|
+
}));
|
|
378
|
+
|
|
379
|
+
export default router;
|
|
380
|
+
|