nodebb-plugin-integration 0.4.2 → 0.4.4

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,213 @@
1
+ <div class="acp-page-container">
2
+
3
+ <h1>
4
+ <i class="fa fa-robot"></i>
5
+ {{title}}
6
+ </h1>
7
+
8
+ <hr />
9
+
10
+ <form role="form">
11
+
12
+ <h3>AI Provider</h3>
13
+
14
+ <div class="mb-3">
15
+ <label class="form-label">Provider</label>
16
+ <select class="form-select" name="aiProvider">
17
+ <option value="openai" <!-- IF settings.aiProviderOpenai -->selected<!-- ENDIF settings.aiProviderOpenai -->>
18
+ OpenAI
19
+ </option>
20
+ <option value="fake" <!-- IF settings.aiProviderFake -->selected<!-- ENDIF settings.aiProviderFake -->>
21
+ Fake (AI disabled)
22
+ </option>
23
+ </select>
24
+ <div class="form-text">
25
+ Fake disables AI embeddings, indexing, semantic search and Qdrant access.
26
+ NodeBB and MCP tools remain available.
27
+ </div>
28
+ </div>
29
+
30
+ <h3>OpenAI</h3>
31
+
32
+ <div class="mb-3">
33
+ <label class="form-label">OpenAI API Key</label>
34
+ <input
35
+ type="password"
36
+ class="form-control"
37
+ name="openaiApiKey"
38
+ autocomplete="new-password"
39
+ placeholder="Leave empty to keep the current key"
40
+ />
41
+ <!-- IF settings.openaiConfigured -->
42
+ <div class="form-text text-success">API key is configured</div>
43
+ <!-- ELSE -->
44
+ <div class="form-text">API key is not configured</div>
45
+ <!-- ENDIF settings.openaiConfigured -->
46
+ </div>
47
+
48
+ <div class="mb-3">
49
+ <label class="form-label">Embedding Model</label>
50
+ <input
51
+ class="form-control"
52
+ name="openaiModel"
53
+ value="{settings.openaiModel}"
54
+ />
55
+ </div>
56
+
57
+ <div class="mb-3">
58
+ <label class="form-label">Embedding Dimensions</label>
59
+ <input
60
+ class="form-control"
61
+ type="number"
62
+ name="embeddingSize"
63
+ value="{settings.embeddingSize}"
64
+ min="1"
65
+ />
66
+ <div class="form-text">
67
+ The default dimension for text-embedding-3-small is 1536.
68
+ Changing this value requires a matching Qdrant collection.
69
+ </div>
70
+ </div>
71
+
72
+ <hr />
73
+
74
+ <h3>Qdrant</h3>
75
+
76
+ <div class="mb-3">
77
+ <label class="form-label">Qdrant URL</label>
78
+ <input
79
+ class="form-control"
80
+ name="qdrantUrl"
81
+ value="{settings.qdrantUrl}"
82
+ />
83
+ </div>
84
+
85
+ <div class="mb-3">
86
+ <label class="form-label">Qdrant Collection</label>
87
+ <input
88
+ class="form-control"
89
+ name="collection"
90
+ value="{settings.collection}"
91
+ />
92
+ </div>
93
+
94
+ <hr />
95
+
96
+ <h3>Indexing</h3>
97
+
98
+ <div class="mb-3">
99
+ <label class="form-label">Excluded Categories</label>
100
+ <select
101
+ class="form-select"
102
+ name="excludedCategories"
103
+ multiple
104
+ size="10"
105
+ >
106
+ <!-- BEGIN categories -->
107
+ <option value="{categories.cid}" <!-- IF categories.selected -->selected<!-- ENDIF categories.selected -->>
108
+ {categories.name}
109
+ </option>
110
+ <!-- END categories -->
111
+ </select>
112
+ <div class="form-text">
113
+ Posts in selected categories are excluded from indexing and search.
114
+ </div>
115
+ </div>
116
+
117
+ <hr />
118
+
119
+ <h3>MCP Server</h3>
120
+
121
+ <div class="form-check mb-3">
122
+ <input
123
+ class="form-check-input"
124
+ type="checkbox"
125
+ name="mcpEnabled"
126
+ id="mcpEnabled"
127
+ <!-- IF settings.mcpEnabled -->checked<!-- ENDIF settings.mcpEnabled -->
128
+ />
129
+ <label class="form-check-label" for="mcpEnabled">
130
+ Enable MCP Server
131
+ </label>
132
+ </div>
133
+
134
+ <div class="mb-3">
135
+ <label class="form-label">Bind Address</label>
136
+ <input
137
+ class="form-control"
138
+ name="mcpHost"
139
+ value="{settings.mcpHost}"
140
+ placeholder="127.0.0.1"
141
+ />
142
+ <div class="form-text">
143
+ 127.0.0.1 allows local connections only.
144
+ 0.0.0.0 listens on all network interfaces.
145
+ </div>
146
+ </div>
147
+
148
+ <div class="mb-3">
149
+ <label class="form-label">HTTP Port</label>
150
+ <input
151
+ class="form-control"
152
+ type="number"
153
+ name="mcpPort"
154
+ value="{settings.mcpPort}"
155
+ min="1"
156
+ max="65535"
157
+ />
158
+ </div>
159
+
160
+ <div class="mb-3">
161
+ <label class="form-label">NodeBB API URL</label>
162
+ <input
163
+ class="form-control"
164
+ name="mcpNodebbUrl"
165
+ value="{settings.mcpNodebbUrl}"
166
+ placeholder="http://127.0.0.1:4567"
167
+ />
168
+ <div class="form-text">
169
+ Base URL used by the MCP process to access the internal NodeBB API.
170
+ Use an address reachable from the MCP process environment.
171
+ </div>
172
+ </div>
173
+
174
+ <div class="mb-3">
175
+ <label class="form-label">MCP Bearer Token</label>
176
+ <input
177
+ class="form-control"
178
+ type="password"
179
+ name="mcpBearerToken"
180
+ autocomplete="new-password"
181
+ placeholder="Leave empty to keep the current token"
182
+ />
183
+ <!-- IF settings.mcpBearerTokenConfigured -->
184
+ <div class="form-text text-success">Bearer token is configured</div>
185
+ <!-- ELSE -->
186
+ <div class="form-text">Bearer token is not configured</div>
187
+ <!-- ENDIF settings.mcpBearerTokenConfigured -->
188
+ </div>
189
+
190
+ <div class="mb-3">
191
+ <label class="form-label">NodeBB Service UID</label>
192
+ <input
193
+ class="form-control"
194
+ type="number"
195
+ name="mcpServiceUid"
196
+ value="{settings.mcpServiceUid}"
197
+ min="1"
198
+ />
199
+ <div class="form-text">
200
+ NodeBB user ID used by MCP for search and access-control checks.
201
+ </div>
202
+ </div>
203
+
204
+ <hr />
205
+
206
+ <button id="save" class="btn btn-primary" type="button">
207
+ <i class="fa fa-save"></i>
208
+ Save Settings
209
+ </button>
210
+
211
+ </form>
212
+
213
+ </div>
@@ -1,254 +1,47 @@
1
- "use strict";
2
-
3
-
4
- /*
5
- * NodeBB Integration Layer
6
- *
7
- * Embeddings Service
8
- *
9
- * Unified interface for:
10
- *
11
- * - OpenAI embeddings
12
- * - Fake test embeddings
13
- *
14
- */
15
-
16
-
17
-
18
- const Config =
19
- require("../config");
20
-
21
-
22
- const OpenAI =
23
- require("./openai");
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
- /*
32
- * Embedding size
33
- *
34
- * text-embedding-3-small = 1536
35
- */
36
-
37
-
38
- const SIZE =
39
-
40
- Number(
41
-
42
- process.env.EMBEDDING_SIZE ||
43
-
44
- 1536
45
-
46
- );
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
- /*
57
- * Generate fake vector
58
- *
59
- * Used for:
60
- *
61
- * - tests
62
- * - development
63
- *
64
- */
65
-
66
-
67
- function fakeEmbedding() {
68
-
69
-
70
- const vector = [];
71
-
72
-
73
-
74
- for(let i = 0; i < SIZE; i++) {
75
-
76
-
77
- vector.push(
78
-
79
- Math.random()
80
-
81
- );
82
-
83
-
84
- }
85
-
86
-
87
-
88
- return vector;
89
-
90
-
91
- }
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
- /*
102
- * Select provider
103
- */
104
-
105
-
106
- function provider() {
107
-
108
-
109
-
110
- return (
111
-
112
- process.env.AI_PROVIDER ||
113
-
114
- "fake"
115
-
116
- );
117
-
118
-
119
- }
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
- /*
130
- * Create embedding
131
- */
132
-
133
-
134
- async function create(text) {
135
-
136
-
137
-
138
- if(!text || !text.trim()) {
139
-
140
-
141
- return null;
142
-
143
-
144
- }
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
- /*
154
- * OpenAI mode
155
- */
156
-
157
-
158
- if(
159
-
160
- provider() === "openai"
161
-
162
- ) {
163
-
164
-
165
-
166
- const vector =
167
-
168
- await OpenAI.embeddings(
169
-
170
- text
171
-
172
- );
173
-
174
-
175
-
176
-
177
-
178
- return vector;
179
-
180
-
181
- }
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
- /*
191
- * Development/Test mode
192
- */
193
-
194
-
195
- return fakeEmbedding();
196
-
197
-
198
-
199
- }
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
- /*
210
- * Service information
211
- */
212
-
213
-
214
- function info() {
215
-
216
-
217
- return {
218
-
219
-
220
- provider:
221
- provider(),
222
-
223
-
224
- size:
225
- SIZE
226
-
227
-
228
- };
229
-
230
-
231
- }
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
- module.exports = {
242
-
243
-
244
- create,
245
-
246
-
247
- info,
248
-
249
-
250
- SIZE
251
-
252
-
253
-
1
+ "use strict";
2
+
3
+ const OpenAI = require("./openai");
4
+
5
+ const MAX_TEXT_LENGTH = 30000;
6
+
7
+ function normalizeText(value) {
8
+ if (typeof value !== "string") {
9
+ return "";
10
+ }
11
+
12
+ return value
13
+ .replace(/<[^>]*>/g, " ")
14
+ .replace(/&nbsp;/gi, " ")
15
+ .replace(/&amp;/gi, "&")
16
+ .replace(/&lt;/gi, "<")
17
+ .replace(/&gt;/gi, ">")
18
+ .replace(/&quot;/gi, '"')
19
+ .replace(/&#39;/gi, "'")
20
+ .replace(/\s+/g, " ")
21
+ .trim();
22
+ }
23
+
24
+ function preparePost(post) {
25
+ if (!post || typeof post !== "object") {
26
+ throw new TypeError("Post is required");
27
+ }
28
+
29
+ const content = normalizeText(post.content);
30
+
31
+ if (!content) {
32
+ return "";
33
+ }
34
+
35
+ return content.slice(0, MAX_TEXT_LENGTH);
36
+ }
37
+
38
+ async function create(text) {
39
+ return OpenAI.createEmbedding(text);
40
+ }
41
+
42
+ module.exports = {
43
+ normalizeText,
44
+ preparePost,
45
+ create,
46
+ MAX_TEXT_LENGTH
254
47
  };