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.
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ import serverConfig from 'eslint-config-nodebb';
4
+ import publicConfig from 'eslint-config-nodebb/public';
5
+
6
+ export default [
7
+ ...publicConfig,
8
+ ...serverConfig,
9
+ ];
10
+
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((post) => `User ${post.username}:\n${post.content}`)
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
- const postData = await posts.getPostsFields(pids, ['uid', 'content']);
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("\n\n");
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
- (msg) => ({ role: msg.fromuid === chatgptUid ? 'assistant' : 'user', content: msg.content })
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 || parseInt(reputation, 10) >= parseInt(settings.minimumReputation, 10);
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
- return summary.summarizeTopic(tid, openai, settings);
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.0",
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.53.2"
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": "8.57.0",
42
- "eslint-config-nodebb": "0.2.1",
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="">