@yunsoft/yuncms-api 0.1.9 → 0.1.11
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/package.json +2 -2
- package/src/ai/config.js +146 -0
- package/src/ai/secret-key.js +57 -0
- package/src/ai/service.js +268 -0
- package/src/ai/settings-store.js +135 -0
- package/src/ai/tools.js +304 -0
- package/src/app.js +4 -0
- package/src/error-response.js +16 -0
- package/src/mcp/config.js +154 -0
- package/src/mcp/settings-store.js +78 -0
- package/src/mcp.js +49 -4
- package/src/routes/ai.js +80 -0
- package/src/routes/studio-navigation.js +59 -0
- package/src/server.js +14 -1
- package/studio-dist/assets/index-B5vuZY2c.js +11 -0
- package/studio-dist/assets/{index-C1dF_B2k.css → index-sB3p3QRk.css} +1 -1
- package/studio-dist/index.html +2 -2
- package/studio-dist/assets/index-DqcKf_lG.js +0 -9
package/src/routes/ai.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
|
|
3
|
+
function authError() {
|
|
4
|
+
const error = new Error('Yapay Zeka requires an authenticated YunCMS account');
|
|
5
|
+
error.code = 'UNAUTHORIZED';
|
|
6
|
+
return error;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function requireAuthenticated(req) {
|
|
10
|
+
if (req?.authMethod === 'public' || !req?.accountability?.user) throw authError();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function requireAdministrator(req) {
|
|
14
|
+
requireAuthenticated(req);
|
|
15
|
+
if (req.accountability?.admin === true || req.accountability?.system === true) return;
|
|
16
|
+
const error = new Error('Yapay Zeka settings require administrator access');
|
|
17
|
+
error.code = 'FORBIDDEN';
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function aiRequestAccess(body = {}) {
|
|
22
|
+
const allowWrites = body?.allow_writes === true;
|
|
23
|
+
return {
|
|
24
|
+
allowWrites,
|
|
25
|
+
allowDeletes: allowWrites && body?.allow_deletes === true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createAiRouter({ assistant, settingsStore } = {}) {
|
|
30
|
+
if (!assistant) throw new Error('AI assistant service is required');
|
|
31
|
+
if (!settingsStore) throw new Error('AI settings store is required');
|
|
32
|
+
const router = express.Router();
|
|
33
|
+
|
|
34
|
+
router.get('/status', async (req, res, next) => {
|
|
35
|
+
try {
|
|
36
|
+
requireAuthenticated(req);
|
|
37
|
+
return res.json({ data: await assistant.status(), request_id: req.id });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return next(error);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
router.get('/settings', async (req, res, next) => {
|
|
44
|
+
try {
|
|
45
|
+
requireAdministrator(req);
|
|
46
|
+
return res.json({ data: await settingsStore.readAdmin(), request_id: req.id });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return next(error);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
router.patch('/settings', async (req, res, next) => {
|
|
53
|
+
try {
|
|
54
|
+
requireAdministrator(req);
|
|
55
|
+
const data = await settingsStore.update(req.body ?? {});
|
|
56
|
+
return res.json({ data, request_id: req.id });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return next(error);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
router.post('/chat', async (req, res, next) => {
|
|
63
|
+
try {
|
|
64
|
+
requireAuthenticated(req);
|
|
65
|
+
const access = aiRequestAccess(req.body);
|
|
66
|
+
const result = await assistant.chat(req, {
|
|
67
|
+
messages: req.body?.messages,
|
|
68
|
+
locale: req.body?.locale,
|
|
69
|
+
...access,
|
|
70
|
+
});
|
|
71
|
+
return res.json({ data: result, request_id: req.id });
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return next(error);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return router;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { requireAdministrator, requireAuthenticated };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
|
|
3
|
+
import { serviceOptionsFromRequest } from '../service-options.js';
|
|
4
|
+
|
|
5
|
+
function navigationService(req) {
|
|
6
|
+
const Service = req.context.services.NavigationGroupsService;
|
|
7
|
+
return new Service(serviceOptionsFromRequest(req));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function requireStudioUser(req) {
|
|
11
|
+
if (req?.authMethod !== 'public' && req?.accountability?.user) return;
|
|
12
|
+
const error = new Error('Studio navigation requires an authenticated YunCMS account');
|
|
13
|
+
error.code = 'UNAUTHORIZED';
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createStudioNavigationRouter() {
|
|
18
|
+
const router = express.Router();
|
|
19
|
+
|
|
20
|
+
router.get('/groups', async (req, res, next) => {
|
|
21
|
+
try {
|
|
22
|
+
requireStudioUser(req);
|
|
23
|
+
return res.json({ data: await navigationService(req).readMany(), request_id: req.id });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return next(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
router.post('/groups', async (req, res, next) => {
|
|
30
|
+
try {
|
|
31
|
+
requireStudioUser(req);
|
|
32
|
+
return res.status(201).json({ data: await navigationService(req).createOne(req.body ?? {}), request_id: req.id });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return next(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
router.patch('/groups/:id', async (req, res, next) => {
|
|
39
|
+
try {
|
|
40
|
+
requireStudioUser(req);
|
|
41
|
+
return res.json({ data: await navigationService(req).updateOne(req.params.id, req.body ?? {}), request_id: req.id });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return next(error);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
router.delete('/groups/:id', async (req, res, next) => {
|
|
48
|
+
try {
|
|
49
|
+
requireStudioUser(req);
|
|
50
|
+
return res.json({ data: await navigationService(req).deleteOne(req.params.id), request_id: req.id });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return next(error);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return router;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { requireStudioUser };
|
package/src/server.js
CHANGED
|
@@ -20,11 +20,16 @@ import {
|
|
|
20
20
|
SmtpMailer,
|
|
21
21
|
} from '@yunsoft/yuncms-core';
|
|
22
22
|
import { createApp } from './app.js';
|
|
23
|
+
import { AiAssistantService } from './ai/service.js';
|
|
24
|
+
import { loadOrCreateAiSettingsKey } from './ai/secret-key.js';
|
|
25
|
+
import { AiSettingsStore } from './ai/settings-store.js';
|
|
23
26
|
import { INTERNAL_AUDIT_EVENTS } from './audit-events.js';
|
|
24
27
|
import { loadExternalAuthConfig } from './external-auth/config.js';
|
|
25
28
|
import { ExternalAuthProviderRegistry } from './external-auth/providers.js';
|
|
26
29
|
import { loadExtensionRuntime } from './extensions/runtime.js';
|
|
27
30
|
import { createMcpRouter } from './mcp.js';
|
|
31
|
+
import { McpSettingsStore } from './mcp/settings-store.js';
|
|
32
|
+
import { createAiRouter } from './routes/ai.js';
|
|
28
33
|
|
|
29
34
|
loadEnvFileIfPresent();
|
|
30
35
|
await assertMaintenanceStartupAllowed({ cwd: process.cwd(), env: process.env });
|
|
@@ -103,6 +108,10 @@ function registerInternalAudit({ emitter, services }) {
|
|
|
103
108
|
|
|
104
109
|
async function start() {
|
|
105
110
|
await assertDatabaseCompatible(pool);
|
|
111
|
+
const aiKey = await loadOrCreateAiSettingsKey();
|
|
112
|
+
const aiSettingsStore = new AiSettingsStore({ database: pool, key: aiKey.key });
|
|
113
|
+
if (aiKey.created) logger.info('YunCMS AI settings encryption key created');
|
|
114
|
+
|
|
106
115
|
if (redisClient) {
|
|
107
116
|
try {
|
|
108
117
|
await redisClient.connect();
|
|
@@ -124,7 +133,10 @@ async function start() {
|
|
|
124
133
|
database: pool,
|
|
125
134
|
logger,
|
|
126
135
|
});
|
|
127
|
-
const
|
|
136
|
+
const aiAssistant = new AiAssistantService({ settingsStore: aiSettingsStore, logger });
|
|
137
|
+
const aiRouter = createAiRouter({ assistant: aiAssistant, settingsStore: aiSettingsStore });
|
|
138
|
+
const mcpSettingsStore = new McpSettingsStore({ database: pool });
|
|
139
|
+
const mcpRouter = createMcpRouter({ settingsStore: mcpSettingsStore, logger });
|
|
128
140
|
mailer?.setEmitter(emitter);
|
|
129
141
|
registerInternalAudit({ emitter, services });
|
|
130
142
|
|
|
@@ -152,6 +164,7 @@ async function start() {
|
|
|
152
164
|
redisClient,
|
|
153
165
|
externalAuthRegistry,
|
|
154
166
|
endpointExtensions: extensionRuntime.endpointExtensions,
|
|
167
|
+
aiRouter,
|
|
155
168
|
mcpRouter,
|
|
156
169
|
});
|
|
157
170
|
|