nodebb-plugin-integration 0.3.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/LICENSE +22 -0
- package/README.md +320 -0
- package/README.ru.md +347 -0
- package/library.js +171 -0
- package/package.json +53 -0
- package/plugin.json +23 -0
- package/src/ai/embeddings.js +254 -0
- package/src/ai/indexer.js +383 -0
- package/src/ai/openai.js +331 -0
- package/src/ai/qdrant.js +564 -0
- package/src/ai/search.js +283 -0
- package/src/config/index.js +322 -0
- package/src/events/index.js +324 -0
- package/src/mcp/server.js +232 -0
- package/src/mcp/tools.js +654 -0
- package/src/nodebb/index.js +306 -0
- package/src/nodebb/meta.js +273 -0
- package/src/nodebb/posts.js +348 -0
- package/src/nodebb/topics.js +356 -0
- package/src/nodebb/users.js +346 -0
- package/src/routes/api.js +240 -0
- package/src/routes/index.js +110 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
|
|
5
|
+
NodeBB Integration Layer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Knowledge Indexer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const Embeddings =
|
|
18
|
+
require("./embeddings");
|
|
19
|
+
|
|
20
|
+
const Qdrant =
|
|
21
|
+
require("./qdrant");
|
|
22
|
+
|
|
23
|
+
const crypto =
|
|
24
|
+
require("crypto");
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function createId(prefix, id) {
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* Qdrant supports UUID or integer.
|
|
42
|
+
* Create deterministic UUID from source id.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
return crypto
|
|
47
|
+
|
|
48
|
+
.createHash("sha256")
|
|
49
|
+
|
|
50
|
+
.update(
|
|
51
|
+
|
|
52
|
+
`${prefix}:${id}`
|
|
53
|
+
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
.digest("hex")
|
|
57
|
+
|
|
58
|
+
.substring(0, 32);
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async function indexDocument(
|
|
75
|
+
|
|
76
|
+
type,
|
|
77
|
+
|
|
78
|
+
id,
|
|
79
|
+
|
|
80
|
+
text,
|
|
81
|
+
|
|
82
|
+
payload = {}
|
|
83
|
+
|
|
84
|
+
) {
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if(!text || !text.trim()) {
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
return false;
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
const vector =
|
|
104
|
+
|
|
105
|
+
await Embeddings.create(
|
|
106
|
+
|
|
107
|
+
text
|
|
108
|
+
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if(!vector) {
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
console.log(
|
|
121
|
+
|
|
122
|
+
"[Indexer] embedding unavailable"
|
|
123
|
+
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
return false;
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
await Qdrant.ensureCollection();
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
const pointId =
|
|
148
|
+
|
|
149
|
+
createId(
|
|
150
|
+
|
|
151
|
+
type,
|
|
152
|
+
|
|
153
|
+
id
|
|
154
|
+
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
const result =
|
|
164
|
+
|
|
165
|
+
await Qdrant.upsert(
|
|
166
|
+
|
|
167
|
+
pointId,
|
|
168
|
+
|
|
169
|
+
vector,
|
|
170
|
+
|
|
171
|
+
{
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
type,
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
sourceId: id,
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
text,
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
...payload
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if(result) {
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
console.log(
|
|
203
|
+
|
|
204
|
+
`[Indexer] indexed ${type}:${id}`
|
|
205
|
+
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
/*
|
|
237
|
+
|
|
238
|
+
Index post
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
async function indexPost(post) {
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if(!post) {
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
return false;
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
return indexDocument(
|
|
261
|
+
|
|
262
|
+
"post",
|
|
263
|
+
|
|
264
|
+
post.pid,
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
post.content || post.text || "",
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
{
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
pid:
|
|
274
|
+
post.pid,
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
uid:
|
|
278
|
+
post.uid,
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
tid:
|
|
282
|
+
post.tid
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
/*
|
|
306
|
+
|
|
307
|
+
Index topic
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
async function indexTopic(topic) {
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
if(!topic) {
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
return false;
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
return indexDocument(
|
|
330
|
+
|
|
331
|
+
"topic",
|
|
332
|
+
|
|
333
|
+
topic.tid,
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
topic.title || "",
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
{
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
tid:
|
|
343
|
+
topic.tid,
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
uid:
|
|
347
|
+
topic.uid
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
module.exports = {
|
|
371
|
+
|
|
372
|
+
indexPost,
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
indexTopic,
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
indexDocument
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
};
|
package/src/ai/openai.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* NodeBB Integration Layer
|
|
6
|
+
*
|
|
7
|
+
* OpenAI Adapter
|
|
8
|
+
*
|
|
9
|
+
* Provides:
|
|
10
|
+
*
|
|
11
|
+
* - Chat completion
|
|
12
|
+
* - Embeddings
|
|
13
|
+
*
|
|
14
|
+
* Safe modes:
|
|
15
|
+
*
|
|
16
|
+
* - Development
|
|
17
|
+
* - Test
|
|
18
|
+
* - Production
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const Config =
|
|
24
|
+
require("../config");
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
let client = null;
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* Check API availability
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
function isAvailable() {
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
return Boolean(
|
|
46
|
+
|
|
47
|
+
Config.openai.apiKey
|
|
48
|
+
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* Lazy OpenAI client creation
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async function getClient() {
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if(client) {
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
return client;
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if(!isAvailable()) {
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
console.log(
|
|
89
|
+
|
|
90
|
+
"[OpenAI] API key not configured"
|
|
91
|
+
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
const OpenAI =
|
|
112
|
+
require("openai");
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
client =
|
|
119
|
+
new OpenAI({
|
|
120
|
+
|
|
121
|
+
apiKey:
|
|
122
|
+
Config.openai.apiKey
|
|
123
|
+
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
return client;
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
catch(error) {
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
console.error(
|
|
142
|
+
|
|
143
|
+
"[OpenAI] initialization failed",
|
|
144
|
+
|
|
145
|
+
error
|
|
146
|
+
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
return null;
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
/*
|
|
168
|
+
* Create embedding vector
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async function embeddings(text) {
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
const openai =
|
|
177
|
+
await getClient();
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if(!openai) {
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
return null;
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if(!text || !text.trim()) {
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
return null;
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
const response =
|
|
216
|
+
await openai.embeddings.create({
|
|
217
|
+
|
|
218
|
+
model:
|
|
219
|
+
Config.openai.embeddingModel,
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
input:
|
|
223
|
+
text
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
return response
|
|
234
|
+
.data[0]
|
|
235
|
+
.embedding;
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
* Chat completion
|
|
250
|
+
*/
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
async function chat(messages) {
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
const openai =
|
|
258
|
+
await getClient();
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if(!openai) {
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
return null;
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
const response =
|
|
279
|
+
await openai.chat.completions.create({
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
model:
|
|
283
|
+
Config.openai.model,
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
messages
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
return response
|
|
300
|
+
.choices[0]
|
|
301
|
+
.message
|
|
302
|
+
.content;
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
module.exports = {
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
isAvailable,
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
getClient,
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
embeddings,
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
chat
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
};
|