nodebb-plugin-integration 0.3.0 → 0.4.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,15 @@
1
+ {
2
+ "title": "NodeBB Integration",
3
+
4
+ "settings": {
5
+ "ai-provider": "AI Provider",
6
+ "openai-key": "OpenAI API Key",
7
+ "openai-model": "OpenAI Embedding Model",
8
+ "qdrant-url": "Qdrant URL",
9
+ "collection": "Collection",
10
+ "mcp-enabled": "Enable MCP Server",
11
+ "mcp-port": "MCP Port",
12
+ "save": "Save Settings",
13
+ "saved": "Settings saved successfully"
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "title": "���������� NodeBB",
3
+
4
+ "settings": {
5
+ "ai-provider": "AI ���������",
6
+ "openai-key": "OpenAI API ����",
7
+ "openai-model": "������ ����������� OpenAI",
8
+ "qdrant-url": "URL Qdrant",
9
+ "collection": "���������",
10
+ "mcp-enabled": "�������� MCP ������",
11
+ "mcp-port": "���� MCP",
12
+ "save": "��������� ���������",
13
+ "saved": "��������� ������� ���������"
14
+ }
15
+ }
package/library.js CHANGED
@@ -6,166 +6,110 @@
6
6
  * Main plugin entry point
7
7
  */
8
8
 
9
- require("dotenv").config();
10
-
11
9
  const Config = require("./src/config");
12
10
  const Routes = require("./src/routes");
13
11
  const Events = require("./src/events");
14
12
  const Indexer = require("./src/ai/indexer");
15
13
  const MCP = require("./src/mcp/server");
16
14
 
17
-
18
15
  const Plugin = {};
19
16
 
20
-
21
17
  /**
22
- * NodeBB hook:
23
- *
24
18
  * static:app.load
25
19
  */
26
- Plugin.init = async function ({ router } = {}) {
20
+ Plugin.init = async function (params = {}) {
27
21
 
28
- console.log(
29
- "[NodeBB Integration] initializing"
30
- );
22
+ console.log("[NodeBB Integration] initializing");
31
23
 
24
+ const { router, middleware } = params;
32
25
 
33
26
  if (router) {
34
-
35
- Routes.init(
36
- router
37
- );
38
-
27
+ Routes.init(router, middleware);
39
28
  } else {
40
-
41
- console.log(
42
- "[NodeBB Integration] router unavailable (standalone mode)"
43
- );
44
-
29
+ console.log("[NodeBB Integration] router unavailable (standalone mode)");
45
30
  }
46
31
 
47
-
48
- if (
49
- Events &&
50
- typeof Events.init === "function"
51
- ) {
52
-
32
+ if (Events && typeof Events.init === "function") {
53
33
  await Events.init();
54
-
55
34
  }
56
35
 
36
+ const settings = await Config.get();
57
37
 
58
38
  if (
59
- Config?.mcp?.enabled &&
60
- process.env.MCP_AUTOSTART === "true" &&
39
+ settings.mcpEnabled &&
61
40
  MCP &&
62
41
  typeof MCP.start === "function"
63
42
  ) {
64
-
65
- await MCP.start();
66
-
67
- console.log(
68
- "[NodeBB Integration] MCP started"
69
- );
70
-
43
+ try {
44
+ await MCP.start();
45
+ console.log("[NodeBB Integration] MCP server started");
46
+ } catch (err) {
47
+ console.error("[NodeBB Integration] MCP start failed:", err.message);
48
+ }
71
49
  }
72
50
 
73
-
74
- console.log(
75
- "[NodeBB Integration] ready"
76
- );
77
-
51
+ console.log("[NodeBB Integration] ready");
78
52
  };
79
53
 
80
-
81
54
  /**
82
- * NodeBB hook:
83
- *
84
55
  * action:post.save
85
56
  */
86
57
  Plugin.onPostSave = async function (post) {
87
58
 
88
59
  try {
89
60
 
90
- if (
91
- Events &&
92
- typeof Events.postSave === "function"
93
- ) {
94
-
95
- await Events.postSave(
96
- post
97
- );
98
-
61
+ if (Events && typeof Events.postSave === "function") {
62
+ await Events.postSave(post);
99
63
  }
100
64
 
101
-
102
- if (
103
- Indexer &&
104
- typeof Indexer.indexPost === "function"
105
- ) {
106
-
107
- await Indexer.indexPost(
108
- post
109
- );
110
-
65
+ if (Indexer && typeof Indexer.indexPost === "function") {
66
+ await Indexer.indexPost(post);
111
67
  }
112
68
 
69
+ } catch (err) {
113
70
 
114
- } catch (error) {
115
-
116
- console.error(
117
- "[Plugin] post save error",
118
- error.message
119
- );
71
+ console.error("[Plugin] post save error:", err.message);
120
72
 
121
73
  }
122
74
 
123
75
  };
124
76
 
125
-
126
77
  /**
127
- * NodeBB hook:
128
- *
129
78
  * action:topic.save
130
79
  */
131
80
  Plugin.onTopicSave = async function (topic) {
132
81
 
133
82
  try {
134
83
 
135
- if (
136
- Events &&
137
- typeof Events.topicCreate === "function"
138
- ) {
139
-
140
- await Events.topicCreate(
141
- topic
142
- );
143
-
84
+ if (Events && typeof Events.topicCreate === "function") {
85
+ await Events.topicCreate(topic);
144
86
  }
145
87
 
88
+ if (Indexer && typeof Indexer.indexTopic === "function") {
89
+ await Indexer.indexTopic(topic);
90
+ }
146
91
 
147
- if (
148
- Indexer &&
149
- typeof Indexer.indexTopic === "function"
150
- ) {
92
+ } catch (err) {
151
93
 
152
- await Indexer.indexTopic(
153
- topic
154
- );
94
+ console.error("[Plugin] topic save error:", err.message);
155
95
 
156
- }
96
+ }
157
97
 
98
+ };
158
99
 
159
- } catch (error) {
100
+ /**
101
+ * ACP menu item
102
+ */
103
+ Plugin.addAdminNavigation = async function (header) {
160
104
 
161
- console.error(
162
- "[Plugin] topic save error",
163
- error.message
164
- );
105
+ header.plugins.push({
106
+ route: "/plugins/integration",
107
+ icon: "fa-robot",
108
+ name: "NodeBB Integration"
109
+ });
165
110
 
166
- }
111
+ return header;
167
112
 
168
113
  };
169
114
 
170
-
171
115
  module.exports = Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-integration",
3
- "version": "0.3.0",
3
+ "version": "0.4.2",
4
4
  "description": "AI integration layer for NodeBB forums with MCP, semantic search, RAG and Qdrant knowledge pipeline",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
@@ -8,6 +8,17 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/Sergey-ek/nodebb-plugin-integration.git"
10
10
  },
11
+ "files": [
12
+ "library.js",
13
+ "plugin.json",
14
+ "src",
15
+ "public",
16
+ "templates",
17
+ "languages",
18
+ "README.md",
19
+ "README.ru.md",
20
+ "LICENSE"
21
+ ],
11
22
  "keywords": [
12
23
  "nodebb",
13
24
  "nodebb-plugin",
@@ -23,13 +34,6 @@
23
34
  "engines": {
24
35
  "node": ">=20"
25
36
  },
26
- "files": [
27
- "library.js",
28
- "plugin.json",
29
- "src",
30
- "README.md",
31
- "LICENSE"
32
- ],
33
37
  "scripts": {
34
38
  "test": "cross-env NODE_ENV=test mocha test/*.test.js",
35
39
  "check": "node --check library.js && node --check src/routes/index.js && node --check src/routes/api.js && node --check src/mcp/server.js && node --check src/mcp/tools.js",
package/plugin.json CHANGED
@@ -3,9 +3,15 @@
3
3
  "name": "NodeBB Integration Layer",
4
4
  "description": "AI integration layer for NodeBB with MCP, semantic search, RAG and Qdrant knowledge pipeline",
5
5
  "library": "./library.js",
6
+
6
7
  "staticDirs": {
7
8
  "public": "public"
8
9
  },
10
+
11
+ "modules": {
12
+ "admin/plugins/integration.js": "public/js/admin.js"
13
+ },
14
+
9
15
  "hooks": [
10
16
  {
11
17
  "hook": "static:app.load",
@@ -18,6 +24,10 @@
18
24
  {
19
25
  "hook": "action:topic.save",
20
26
  "method": "onTopicSave"
27
+ },
28
+ {
29
+ "hook": "filter:admin.header.build",
30
+ "method": "addAdminNavigation"
21
31
  }
22
32
  ]
23
33
  }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ define("admin/plugins/integration", [
4
+ "settings",
5
+ "alerts"
6
+ ], function (Settings, Alerts) {
7
+
8
+ const ACP = {};
9
+
10
+ ACP.init = function () {
11
+
12
+ Settings.load(
13
+ "nodebb-plugin-integration",
14
+ $(".acp-page-container"),
15
+ function () {
16
+
17
+ console.log(
18
+ "[NodeBB Integration] settings loaded"
19
+ );
20
+
21
+ }
22
+ );
23
+
24
+ $("#save").on("click", function () {
25
+
26
+ Settings.save(
27
+ "nodebb-plugin-integration",
28
+ $(".acp-page-container"),
29
+ function () {
30
+
31
+ Alerts.success({
32
+ title: "NodeBB Integration",
33
+ message: "Settings saved"
34
+ });
35
+
36
+ }
37
+ );
38
+
39
+ });
40
+
41
+ };
42
+
43
+ return ACP;
44
+
45
+ });
@@ -1,322 +1,133 @@
1
- "use strict";
2
-
3
- /*
4
-
5
- NodeBB Integration Layer
6
-
7
-
8
- Global configuration
9
-
10
-
11
-
12
- */
13
-
14
- require("dotenv").config();
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
- const Config = {
29
-
30
-
31
-
32
-
33
- environment:
34
-
35
- process.env.NODE_ENV ||
36
-
37
- "development",
38
-
39
-
40
-
41
-
42
-
43
- nodebb: {
44
-
45
-
46
- url:
47
-
48
- process.env.NODEBB_URL ||
49
-
50
- "http://localhost:4567",
51
-
52
-
53
-
54
- apiKey:
55
-
56
- process.env.NODEBB_API_KEY ||
57
-
58
- ""
59
-
60
-
61
- },
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
- redis: {
71
-
72
-
73
- host:
74
-
75
- process.env.REDIS_HOST ||
76
-
77
- "localhost",
78
-
79
-
80
-
81
- port:
82
-
83
- Number(
84
-
85
- process.env.REDIS_PORT ||
86
-
87
- 6379
88
-
89
- ),
90
-
91
-
92
-
93
- password:
94
-
95
- process.env.REDIS_PASSWORD ||
96
-
97
- ""
98
-
99
-
100
-
101
- },
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
- qdrant: {
111
-
112
-
113
- url:
114
-
115
- process.env.QDRANT_URL ||
116
-
117
- "http://localhost:6333",
118
-
119
-
120
-
121
- collection:
122
-
123
- process.env.QDRANT_COLLECTION ||
124
-
125
- "nodebb_knowledge"
126
-
127
-
128
-
129
- },
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
- openai: {
139
-
140
-
141
- apiKey:
142
-
143
- process.env.OPENAI_API_KEY ||
144
-
145
- "",
146
-
147
-
148
-
149
- model:
150
-
151
- process.env.OPENAI_MODEL ||
152
-
153
- "gpt-4.1-mini",
154
-
155
-
156
-
157
- embeddingModel:
158
-
159
- process.env.OPENAI_EMBEDDING_MODEL ||
160
-
161
- "text-embedding-3-small"
162
-
163
-
164
-
165
- },
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
- embeddings: {
175
-
176
-
177
- provider:
178
-
179
- process.env.AI_PROVIDER ||
180
-
181
- "fake",
182
-
183
-
184
-
185
- size:
186
-
187
- Number(
188
-
189
- process.env.EMBEDDING_SIZE ||
190
-
191
- 1536
192
-
193
- )
194
-
195
-
196
-
197
- },
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
- mcp: {
207
-
208
-
209
- enabled:
210
-
211
- process.env.MCP_ENABLED !== "false"
212
-
213
-
214
-
215
- }
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
- };
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
- /*
244
-
245
- Safe debug output
246
-
247
-
248
- Does not expose API keys
249
- */
250
-
251
- Config.info = function() {
252
-
253
-
254
-
255
-
256
- return {
257
-
258
-
259
- environment:
260
-
261
- Config.environment,
262
-
263
-
264
-
265
- nodebb:
266
-
267
- {
268
-
269
- url:
270
-
271
- Config.nodebb.url
272
-
273
- },
274
-
275
-
276
-
277
- qdrant:
278
-
279
- {
280
-
281
- url:
282
-
283
- Config.qdrant.url,
284
-
285
-
286
- collection:
287
-
288
- Config.qdrant.collection
289
-
290
- },
291
-
292
-
293
-
294
- embeddings:
295
-
296
- Config.embeddings,
297
-
298
-
299
-
300
- mcp:
301
-
302
- Config.mcp
303
-
304
-
305
-
306
- };
307
-
308
- };
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
- module.exports = Config;
1
+ "use strict";
2
+
3
+ /**
4
+ * NodeBB Integration Layer
5
+ *
6
+ * Configuration provider
7
+ *
8
+ * Settings are stored in the NodeBB database
9
+ * using meta.settings.
10
+ */
11
+
12
+ let meta = null;
13
+
14
+ try {
15
+
16
+ meta = require.main.require("./src/meta");
17
+
18
+ } catch (err) {
19
+
20
+ // Standalone mode (tests, npm, CLI)
21
+
22
+ }
23
+
24
+ const DEFAULTS = {
25
+
26
+ aiProvider: "fake",
27
+
28
+ openaiApiKey: "",
29
+
30
+ openaiModel: "text-embedding-3-small",
31
+
32
+ qdrantUrl: "http://localhost:6333",
33
+
34
+ collection: "nodebb_knowledge",
35
+
36
+ mcpEnabled: false,
37
+
38
+ mcpPort: 3001
39
+
40
+ };
41
+
42
+ async function get() {
43
+
44
+ if (!meta) {
45
+ return { ...DEFAULTS };
46
+ }
47
+
48
+ try {
49
+
50
+ return await meta.settings.get(
51
+ "nodebb-plugin-integration",
52
+ DEFAULTS
53
+ );
54
+
55
+ } catch (err) {
56
+
57
+ console.error(
58
+ "[Config] unable to load settings:",
59
+ err.message
60
+ );
61
+
62
+ return { ...DEFAULTS };
63
+
64
+ }
65
+
66
+ }
67
+
68
+ async function set(values) {
69
+
70
+ if (!meta) {
71
+ return false;
72
+ }
73
+
74
+ try {
75
+
76
+ await meta.settings.set(
77
+ "nodebb-plugin-integration",
78
+ values
79
+ );
80
+
81
+ return true;
82
+
83
+ } catch (err) {
84
+
85
+ console.error(
86
+ "[Config] unable to save settings:",
87
+ err.message
88
+ );
89
+
90
+ return false;
91
+
92
+ }
93
+
94
+ }
95
+
96
+ async function info() {
97
+
98
+ const cfg = await get();
99
+
100
+ return {
101
+
102
+ aiProvider: cfg.aiProvider,
103
+
104
+ openaiModel: cfg.openaiModel,
105
+
106
+ qdrantUrl: cfg.qdrantUrl,
107
+
108
+ collection: cfg.collection,
109
+
110
+ mcpEnabled: cfg.mcpEnabled,
111
+
112
+ mcpPort: cfg.mcpPort,
113
+
114
+ openaiApiKey:
115
+ cfg.openaiApiKey
116
+ ? "***configured***"
117
+ : ""
118
+
119
+ };
120
+
121
+ }
122
+
123
+ module.exports = {
124
+
125
+ get,
126
+
127
+ set,
128
+
129
+ info,
130
+
131
+ defaults: DEFAULTS
132
+
133
+ };
package/src/routes/api.js CHANGED
@@ -1,240 +1,97 @@
1
1
  "use strict";
2
2
 
3
- /*
3
+ /**
4
+ * NodeBB Integration Layer
5
+ *
6
+ * API controllers
7
+ */
4
8
 
5
- NodeBB Integration Layer
9
+ const Config = require("../config");
10
+ const Search = require("../ai/search");
11
+ const Meta = require("../nodebb/meta");
6
12
 
13
+ /**
14
+ * GET /api/v3/integration/info
15
+ */
16
+ exports.apiInfo = async function (req, res) {
7
17
 
8
- HTTP API routes
18
+ try {
9
19
 
20
+ const forum = await Meta.getInfo();
21
+ const settings = await Config.info();
10
22
 
23
+ res.json({
24
+ success: true,
25
+ forum,
26
+ settings
27
+ });
11
28
 
12
- */
13
-
14
-
15
-
16
-
17
- const Meta =
18
- require("../nodebb/meta");
19
-
20
- const Search =
21
- require("../ai/search");
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
- function init(router) {
36
-
37
-
38
-
39
-
40
- if(!router) {
41
-
42
-
43
- return false;
44
-
45
-
46
- }
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
- router.get(
56
-
57
- "/api/v3/integration/info",
58
-
59
- async function(
60
-
61
- req,
62
-
63
- res
64
-
65
- ) {
66
-
67
-
68
-
69
- try {
70
-
71
-
72
-
73
- const info =
74
-
75
- await Meta.getInfo();
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
- res.json(info);
84
-
85
-
86
-
87
- }
88
-
89
-
90
- catch(error) {
91
-
92
-
93
-
94
- res.status(500)
95
-
96
- .json({
97
-
98
- error:
99
-
100
- error.message
101
-
102
- });
103
-
104
-
105
-
106
- }
107
-
29
+ } catch (err) {
108
30
 
31
+ res.status(500).json({
32
+ success: false,
33
+ error: err.message
34
+ });
109
35
 
110
36
  }
111
37
 
112
- );
113
-
114
-
115
-
116
-
117
-
38
+ };
118
39
 
40
+ /**
41
+ * POST /api/v3/integration/search
42
+ */
43
+ exports.apiSearch = async function (req, res) {
119
44
 
45
+ try {
120
46
 
47
+ const query = req.body?.query || "";
48
+ const limit = Number(req.body?.limit || 5);
121
49
 
122
- router.post(
50
+ const result = await Search.searchText(
51
+ query,
52
+ limit
53
+ );
123
54
 
124
- "/api/v3/integration/search",
125
-
126
- async function(
127
-
128
- req,
129
-
130
- res
131
-
132
- ) {
133
-
134
-
135
-
136
- try {
137
-
138
-
139
-
140
- const query =
141
-
142
- req.body.query;
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
- const limit =
151
-
152
- req.body.limit || 5;
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
- const result =
161
-
162
- await Search.searchText(
163
-
164
- query,
165
-
166
- limit
167
-
168
- );
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
- res.json(result);
177
-
178
-
179
-
180
- }
181
-
182
-
183
- catch(error) {
184
-
185
-
186
-
187
- res.status(500)
188
-
189
- .json({
190
-
191
- error:
192
-
193
- error.message
194
-
195
- });
196
-
197
-
198
-
199
- }
55
+ res.json({
56
+ success: true,
57
+ result
58
+ });
200
59
 
60
+ } catch (err) {
201
61
 
62
+ res.status(500).json({
63
+ success: false,
64
+ error: err.message
65
+ });
202
66
 
203
67
  }
204
68
 
205
- );
206
-
207
-
208
-
209
-
69
+ };
210
70
 
71
+ /**
72
+ * ACP page
73
+ */
74
+ exports.renderAdmin = async function (req, res) {
211
75
 
76
+ try {
212
77
 
78
+ const settings = await Config.get();
213
79
 
214
- return true;
80
+ res.render(
81
+ "admin/plugins/integration",
82
+ {
83
+ title: "NodeBB Integration",
215
84
 
85
+ settings
86
+ }
87
+ );
216
88
 
89
+ } catch (err) {
217
90
 
91
+ res.status(500).render("500", {
92
+ error: err.message
93
+ });
218
94
 
219
- }
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
- module.exports = {
234
-
235
- init
236
-
237
-
238
-
95
+ }
239
96
 
240
97
  };
@@ -1,110 +1,49 @@
1
- "use strict";
2
-
3
- /*
4
-
5
- NodeBB Integration Layer
6
-
7
-
8
- Routes loader
9
-
10
-
11
-
12
- */
13
-
14
-
15
-
16
-
17
- const API =
18
- require("./api");
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
- function init(router) {
33
-
34
-
35
-
36
-
37
- if(!router) {
38
-
39
-
40
- console.log(
41
-
42
- "[Routes] router unavailable"
43
-
44
- );
45
-
46
-
47
- return false;
48
-
49
-
50
- }
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
- API.init(
60
-
61
- router
62
-
63
- );
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
- console.log(
73
-
74
- "[Routes] initialized"
75
-
76
- );
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
- return true;
85
-
86
-
87
-
88
-
89
- }
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
- module.exports = {
104
-
105
- init
106
-
107
-
108
-
109
-
1
+ "use strict";
2
+
3
+ /**
4
+ * NodeBB Integration Layer
5
+ *
6
+ * Routes
7
+ */
8
+
9
+ const API = require("./api");
10
+
11
+ exports.init = function (router, middleware) {
12
+
13
+ if (!router) {
14
+ return;
15
+ }
16
+
17
+ /*
18
+ * REST API
19
+ */
20
+
21
+ router.get(
22
+ "/api/v3/integration/info",
23
+ API.apiInfo
24
+ );
25
+
26
+ router.post(
27
+ "/api/v3/integration/search",
28
+ API.apiSearch
29
+ );
30
+
31
+ /*
32
+ * ACP page
33
+ */
34
+
35
+ if (
36
+ middleware &&
37
+ middleware.admin &&
38
+ typeof middleware.admin.buildHeader === "function"
39
+ ) {
40
+
41
+ router.get(
42
+ "/admin/plugins/integration",
43
+ middleware.admin.buildHeader,
44
+ API.renderAdmin
45
+ );
46
+
47
+ }
48
+
110
49
  };
@@ -0,0 +1,130 @@
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
+ <div class="mb-3">
13
+
14
+ <label class="form-label">
15
+ {{settings.ai-provider}}
16
+ </label>
17
+
18
+ <select
19
+ class="form-select"
20
+ name="aiProvider"
21
+ >
22
+ <option value="fake">Fake</option>
23
+ <option value="openai">OpenAI</option>
24
+ </select>
25
+
26
+ </div>
27
+
28
+ <div class="mb-3">
29
+
30
+ <label class="form-label">
31
+ OpenAI API Key
32
+ </label>
33
+
34
+ <input
35
+ type="password"
36
+ class="form-control"
37
+ name="openaiApiKey"
38
+ autocomplete="off"
39
+ />
40
+
41
+ </div>
42
+
43
+ <div class="mb-3">
44
+
45
+ <label class="form-label">
46
+ OpenAI Embedding Model
47
+ </label>
48
+
49
+ <input
50
+ class="form-control"
51
+ name="openaiModel"
52
+ />
53
+
54
+ </div>
55
+
56
+ <div class="mb-3">
57
+
58
+ <label class="form-label">
59
+ Qdrant URL
60
+ </label>
61
+
62
+ <input
63
+ class="form-control"
64
+ name="qdrantUrl"
65
+ />
66
+
67
+ </div>
68
+
69
+ <div class="mb-3">
70
+
71
+ <label class="form-label">
72
+ Collection
73
+ </label>
74
+
75
+ <input
76
+ class="form-control"
77
+ name="collection"
78
+ />
79
+
80
+ </div>
81
+
82
+ <div class="form-check mb-3">
83
+
84
+ <input
85
+ id="mcpEnabled"
86
+ class="form-check-input"
87
+ type="checkbox"
88
+ name="mcpEnabled"
89
+ />
90
+
91
+ <label
92
+ class="form-check-label"
93
+ for="mcpEnabled"
94
+ >
95
+ Enable MCP Server
96
+ </label>
97
+
98
+ </div>
99
+
100
+ <div class="mb-3">
101
+
102
+ <label class="form-label">
103
+ MCP Port
104
+ </label>
105
+
106
+ <input
107
+ class="form-control"
108
+ name="mcpPort"
109
+ type="number"
110
+ />
111
+
112
+ </div>
113
+
114
+ <hr />
115
+
116
+ <button
117
+ id="save"
118
+ class="btn btn-primary"
119
+ type="button"
120
+ >
121
+
122
+ <i class="fa fa-save"></i>
123
+
124
+ Save Settings
125
+
126
+ </button>
127
+
128
+ </form>
129
+
130
+ </div>