nodebb-plugin-openai 1.3.0 → 1.3.2
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/eslint.config.mjs +10 -0
- package/lib/summary.js +18 -8
- package/library.js +45 -6
- package/package.json +8 -6
- package/plugin.json +9 -1
- package/templates/admin/plugins/openai.tpl +2 -0
package/lib/summary.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const db = require.main.require('./src/database');
|
|
3
4
|
const batch = require.main.require('./src/batch');
|
|
4
5
|
const topics = require.main.require('./src/topics');
|
|
5
6
|
const posts = require.main.require('./src/posts');
|
|
@@ -7,7 +8,7 @@ const user = require.main.require('./src/user');
|
|
|
7
8
|
|
|
8
9
|
function formatPosts(posts) {
|
|
9
10
|
return posts
|
|
10
|
-
.map(
|
|
11
|
+
.map(post => `User ${post.username}:\n${post.content}`)
|
|
11
12
|
.join('\n---\n');
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -16,7 +17,6 @@ function chunkPosts(posts, maxTokensPerChunk = 3000) {
|
|
|
16
17
|
let currentChunk = [];
|
|
17
18
|
let currentTokens = 0;
|
|
18
19
|
|
|
19
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
20
20
|
for (const post of posts) {
|
|
21
21
|
const estimatedTokens = Math.ceil(post.content.length / 4) + 10;
|
|
22
22
|
if (currentTokens + estimatedTokens > maxTokensPerChunk) {
|
|
@@ -37,6 +37,9 @@ function chunkPosts(posts, maxTokensPerChunk = 3000) {
|
|
|
37
37
|
|
|
38
38
|
async function summarizeChunk(chunk, openai, settings) {
|
|
39
39
|
const threadText = formatPosts(chunk);
|
|
40
|
+
if (!threadText) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
40
43
|
const response = await openai.chat.completions.create({
|
|
41
44
|
model: settings.model || 'gpt-3.5-turbo',
|
|
42
45
|
messages: [
|
|
@@ -55,7 +58,7 @@ async function summarizeChunk(chunk, openai, settings) {
|
|
|
55
58
|
return response.choices[0].message.content.trim();
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
exports.summarizeTopic = async function(tid, openai, settings) {
|
|
61
|
+
exports.summarizeTopic = async function (tid, openai, settings) {
|
|
59
62
|
const allPids = await topics.getPids(tid);
|
|
60
63
|
|
|
61
64
|
const userMap = {};
|
|
@@ -63,12 +66,12 @@ exports.summarizeTopic = async function(tid, openai, settings) {
|
|
|
63
66
|
const chunkSummaries = [];
|
|
64
67
|
|
|
65
68
|
await batch.processArray(allPids, async function (pids) {
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
let postData = await posts.getPostsFields(pids, ['uid', 'content', 'deleted']);
|
|
70
|
+
postData = postData.filter(p => !p.deleted);
|
|
68
71
|
const missingUids = [];
|
|
69
72
|
postData.forEach((p) => {
|
|
70
73
|
if (!userMap.hasOwnProperty(p.uid)) {
|
|
71
|
-
missingUids.push(p.uid)
|
|
74
|
+
missingUids.push(p.uid);
|
|
72
75
|
}
|
|
73
76
|
});
|
|
74
77
|
const userData = await user.getUsersFields(missingUids, ['username']);
|
|
@@ -82,7 +85,7 @@ exports.summarizeTopic = async function(tid, openai, settings) {
|
|
|
82
85
|
const chunks = chunkPosts(postData);
|
|
83
86
|
|
|
84
87
|
chunkSummaries.push(...await Promise.all(chunks.map(async (chunk) => {
|
|
85
|
-
return summarizeChunk(chunk, openai, settings)
|
|
88
|
+
return summarizeChunk(chunk, openai, settings);
|
|
86
89
|
})));
|
|
87
90
|
}, {
|
|
88
91
|
batch: 500,
|
|
@@ -92,7 +95,10 @@ exports.summarizeTopic = async function(tid, openai, settings) {
|
|
|
92
95
|
return chunkSummaries[0];
|
|
93
96
|
}
|
|
94
97
|
// Final summary from all summaries
|
|
95
|
-
const finalInput = chunkSummaries.join(
|
|
98
|
+
const finalInput = chunkSummaries.join('\n\n');
|
|
99
|
+
if (!finalInput) {
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
96
102
|
const finalResponse = await openai.chat.completions.create({
|
|
97
103
|
model: settings.model || 'gpt-3.5-turbo',
|
|
98
104
|
messages: [
|
|
@@ -109,4 +115,8 @@ exports.summarizeTopic = async function(tid, openai, settings) {
|
|
|
109
115
|
});
|
|
110
116
|
|
|
111
117
|
return finalResponse.choices[0].message.content.trim();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
exports.clearTopicSummary = async function (tids) {
|
|
121
|
+
await db.deleteObjectFields(tids.map(tid => `topic:${tid}`), ['openai:summary']);
|
|
112
122
|
};
|
package/library.js
CHANGED
|
@@ -10,7 +10,6 @@ const controllers = require('./lib/controllers');
|
|
|
10
10
|
const routeHelpers = require.main.require('./src/routes/helpers');
|
|
11
11
|
const socketHelpers = require.main.require('./src/socket.io/helpers');
|
|
12
12
|
const topics = require.main.require('./src/topics');
|
|
13
|
-
const posts = require.main.require('./src/posts');
|
|
14
13
|
const user = require.main.require('./src/user');
|
|
15
14
|
const messaging = require.main.require('./src/messaging');
|
|
16
15
|
const api = require.main.require('./src/api');
|
|
@@ -35,18 +34,19 @@ const defaults = {
|
|
|
35
34
|
|
|
36
35
|
plugin.init = async (params) => {
|
|
37
36
|
const { router /* , middleware , controllers */ } = params;
|
|
38
|
-
const settings = await meta.settings.get('openai')
|
|
37
|
+
const settings = await meta.settings.get('openai');
|
|
39
38
|
if (settings && settings.apikey) {
|
|
40
39
|
openai = new OpenAI({
|
|
41
40
|
apiKey: settings.apikey,
|
|
42
41
|
});
|
|
42
|
+
plugin.openai = openai;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
routeHelpers.setupAdminPageRoute(router, '/admin/plugins/openai', controllers.renderAdminPage);
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
async function getSettings() {
|
|
49
|
-
const settings = await meta.settings.get('openai')
|
|
49
|
+
const settings = await meta.settings.get('openai');
|
|
50
50
|
return {...defaults, ...settings };
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -151,7 +151,7 @@ plugin.actionMessagingSave = async function (hookData) {
|
|
|
151
151
|
let messages = await messaging.getMessagesFields(mids, ['fromuid', 'content', 'system']);
|
|
152
152
|
messages = messages.filter(m => m && !m.system);
|
|
153
153
|
conversation = messages.map(
|
|
154
|
-
|
|
154
|
+
msg => ({ role: msg.fromuid === chatgptUid ? 'assistant' : 'user', content: msg.content })
|
|
155
155
|
);
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -181,7 +181,8 @@ async function canUseOpenAI(uid, settings, silent = false) {
|
|
|
181
181
|
|
|
182
182
|
async function checkReputation(uid, settings, silent) {
|
|
183
183
|
const reputation = await user.getUserField(uid, 'reputation');
|
|
184
|
-
const hasEnoughRep = parseInt(settings.minimumReputation, 10) === 0 ||
|
|
184
|
+
const hasEnoughRep = parseInt(settings.minimumReputation, 10) === 0 ||
|
|
185
|
+
parseInt(reputation, 10) >= parseInt(settings.minimumReputation, 10);
|
|
185
186
|
|
|
186
187
|
if (!hasEnoughRep && !silent) {
|
|
187
188
|
sockets.server.in(`uid_${uid}`).emit('event:alert', {
|
|
@@ -277,6 +278,38 @@ plugin.filterTopicThreadTools = async (hookData) => {
|
|
|
277
278
|
return hookData;
|
|
278
279
|
};
|
|
279
280
|
|
|
281
|
+
plugin.actionTopicReply = async (hookData) => {
|
|
282
|
+
await summary.clearTopicSummary([hookData.post.tid]);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
plugin.actionPostEdit = async (hookData) => {
|
|
286
|
+
await summary.clearTopicSummary([hookData.post.tid]);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
plugin.actionPostsPurge = async (hookData) => {
|
|
290
|
+
const { posts } = hookData;
|
|
291
|
+
const uniqTids = [...new Set(posts.map(p => p.tid))];
|
|
292
|
+
await summary.clearTopicSummary(uniqTids);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
plugin.actionPostRestore = async (hookData) => {
|
|
296
|
+
await summary.clearTopicSummary([hookData.post.tid]);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
plugin.actionPostDelete = async (hookData) => {
|
|
300
|
+
await summary.clearTopicSummary([hookData.post.tid]);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
plugin.actionPostMove = async (hookData) => {
|
|
304
|
+
await summary.clearTopicSummary([hookData.post.tid, hookData.tid]);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
plugin.actionPostChangeOwner = async (hookData) => {
|
|
308
|
+
const { posts } = hookData;
|
|
309
|
+
const uniqTids = [...new Set(posts.map(p => p.tid))];
|
|
310
|
+
await summary.clearTopicSummary(uniqTids);
|
|
311
|
+
};
|
|
312
|
+
|
|
280
313
|
socketPlugins.openai = {};
|
|
281
314
|
|
|
282
315
|
socketPlugins.openai.summarizeTopic = async function (socket, data) {
|
|
@@ -289,5 +322,11 @@ socketPlugins.openai.summarizeTopic = async function (socket, data) {
|
|
|
289
322
|
return;
|
|
290
323
|
}
|
|
291
324
|
|
|
292
|
-
|
|
325
|
+
let openaiSummary = await topics.getTopicField(tid, 'openai:summary');
|
|
326
|
+
if (openaiSummary) {
|
|
327
|
+
return openaiSummary;
|
|
328
|
+
}
|
|
329
|
+
openaiSummary = await summary.summarizeTopic(tid, openai, settings);
|
|
330
|
+
await topics.setTopicField(tid, 'openai:summary', openaiSummary);
|
|
331
|
+
return openaiSummary;
|
|
293
332
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-openai",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "A NodeBB OpenAI plugin",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/nodebb/nodebb-plugin-openai"
|
|
9
9
|
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"lint": "eslint ."
|
|
12
|
+
},
|
|
10
13
|
"keywords": [
|
|
11
14
|
"nodebb",
|
|
12
15
|
"plugin",
|
|
@@ -30,17 +33,16 @@
|
|
|
30
33
|
},
|
|
31
34
|
"readmeFilename": "README.md",
|
|
32
35
|
"nbbpm": {
|
|
33
|
-
"compatibility": "^3.2.0"
|
|
36
|
+
"compatibility": "^3.2.0 || ^4.0.0"
|
|
34
37
|
},
|
|
35
38
|
"dependencies": {
|
|
36
|
-
"openai": "4.
|
|
39
|
+
"openai": "4.98.0"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@commitlint/cli": "19.3.0",
|
|
40
43
|
"@commitlint/config-angular": "19.3.0",
|
|
41
|
-
"eslint": "
|
|
42
|
-
"eslint-config-nodebb": "
|
|
43
|
-
"eslint-plugin-import": "2.29.1",
|
|
44
|
+
"eslint": "9.26.0",
|
|
45
|
+
"eslint-config-nodebb": "^1.1.4",
|
|
44
46
|
"husky": "9.0.11",
|
|
45
47
|
"lint-staged": "15.2.7"
|
|
46
48
|
}
|
package/plugin.json
CHANGED
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
{ "hook": "filter:admin.header.build", "method": "addAdminNavigation" },
|
|
8
8
|
{ "hook": "action:mentions.notify", "method": "actionMentionsNotify" },
|
|
9
9
|
{ "hook": "action:messaging.save", "method": "actionMessagingSave" },
|
|
10
|
-
{ "hook": "filter:topic.thread_tools", "method": "filterTopicThreadTools" }
|
|
10
|
+
{ "hook": "filter:topic.thread_tools", "method": "filterTopicThreadTools" },
|
|
11
|
+
|
|
12
|
+
{ "hook": "action:topic.reply", "method": "actionTopicReply" },
|
|
13
|
+
{ "hook": "action:post.edit", "method": "actionPostEdit" },
|
|
14
|
+
{ "hook": "action:posts.purge", "method": "actionPostsPurge" },
|
|
15
|
+
{ "hook": "action:post.restore", "method": "actionPostRestore" },
|
|
16
|
+
{ "hook": "action:post.delete", "method": "actionPostDelete" },
|
|
17
|
+
{ "hook": "action:post.move", "method": "actionPostMove" },
|
|
18
|
+
{ "hook": "action:post.changeOwner", "method": "actionPostChangeOwner" }
|
|
11
19
|
],
|
|
12
20
|
"scripts": [
|
|
13
21
|
"./public/lib/main.js"
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
<option value="gpt-4o">gpt-4o</option>
|
|
39
39
|
<option value="gpt-4-turbo">gpt-4-turbo</option>
|
|
40
40
|
<option value="gpt-4">gpt-4</option>
|
|
41
|
+
<option value="gpt-4.1-mini">gpt-4.1-mini</option>
|
|
42
|
+
<option value="gpt-4.1">gpt-4.1</option>
|
|
41
43
|
</select>
|
|
42
44
|
</div>
|
|
43
45
|
<div class="">
|