feat(admin): add News Engine console and image-make config API

Expose memind-news-engine health, collect, rank, and scheduler controls in md.tkmind.cn admin, proxied via MEMIND_NEWS_ENGINE_URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-22 14:36:38 +08:00
parent eba37640e4
commit 9ec4dda6af
11 changed files with 1097 additions and 0 deletions
+125
View File
@@ -7,6 +7,7 @@ import express from 'express';
import { listUsagePaged, listLedgerPaged, getUsageStats, getUsageSummary } from './pagination.mjs';
import { fetchMemindDiscoveryPages } from './umami-analytics.mjs';
import { importMemind } from './lib-path.mjs';
import { fetchNewsEngine } from './news-engine-admin.mjs';
const projectRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -95,6 +96,7 @@ export function createAdminApp(services) {
userAuth,
llmProviderService,
assetGatewayConfigService,
imageMakeAdminConfigService,
pool,
ready,
wechatAdmin,
@@ -624,6 +626,64 @@ export function createAdminApp(services) {
res.json(result);
});
adminApi.get('/image-make/config', requireAdmin, async (_req, res) => {
if (!imageMakeAdminConfigService?.getAdminConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
return res.json(await imageMakeAdminConfigService.getAdminConfig());
});
const updateImageMakeConfig = async (req, res) => {
if (!imageMakeAdminConfigService?.updateAdminConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
const result = await imageMakeAdminConfigService.updateAdminConfig(req.body ?? {}, {
updatedBy: req.currentUser.id,
});
if (result.ok === false) return res.status(400).json({ message: result.message });
return res.json(result);
};
adminApi.put('/image-make/config', requireAdmin, updateImageMakeConfig);
adminApi.patch('/image-make/config', requireAdmin, updateImageMakeConfig);
adminApi.get('/image-make/runtime', requireAdmin, async (_req, res) => {
if (!imageMakeAdminConfigService?.getRuntimeConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
const runtime = await imageMakeAdminConfigService.getRuntimeConfig();
if (!runtime.ok) {
return res.status(503).json({ message: runtime.message ?? 'image_make 运行时配置无效' });
}
const { providers, ...rest } = runtime;
return res.json({
...rest,
providers: {
mock: { enabled: providers.mock.enabled },
aliyun_bailian: providers.aliyun_bailian.enabled
? {
enabled: true,
model: providers.aliyun_bailian.model,
apiBase: providers.aliyun_bailian.apiBase,
apiKeyConfigured: Boolean(providers.aliyun_bailian.apiKey),
}
: { enabled: false },
comfyui: providers.comfyui.enabled
? { enabled: true, ...providers.comfyui }
: { enabled: false },
fal_ai: providers.fal_ai.enabled
? {
enabled: true,
model: providers.fal_ai.model,
apiBase: providers.fal_ai.apiBase,
numInferenceSteps: providers.fal_ai.numInferenceSteps,
apiKeyConfigured: Boolean(providers.fal_ai.apiKey),
}
: { enabled: false },
},
});
});
adminApi.get('/memory-v2/config', requireAdmin, async (_req, res) => {
if (!memoryV2ConfigService) return res.status(503).json({ message: 'Memory V2 配置未启用' });
res.json(await memoryV2ConfigService.getAdminConfig());
@@ -674,6 +734,71 @@ export function createAdminApp(services) {
}
});
adminApi.get('/news-engine/health', requireAdmin, async (_req, res) => {
const result = await fetchNewsEngine('/health');
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 不可用' });
});
adminApi.get('/news-engine/config', requireAdmin, async (_req, res) => {
const result = await fetchNewsEngine('/v1/config');
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 不可用' });
});
adminApi.get('/news-engine/collections', requireAdmin, async (req, res) => {
const limit = Math.min(50, Math.max(1, Number(req.query.limit) || 20));
const result = await fetchNewsEngine(`/v1/collections?limit=${limit}`);
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 不可用' });
});
adminApi.get('/news-engine/collections/latest', requireAdmin, async (_req, res) => {
const result = await fetchNewsEngine('/v1/collections/latest');
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 不可用' });
});
adminApi.get('/news-engine/collections/:id', requireAdmin, async (req, res) => {
const result = await fetchNewsEngine(`/v1/collections/${encodeURIComponent(req.params.id)}`);
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 不可用' });
});
adminApi.post('/news-engine/collect', requireAdmin, async (req, res) => {
const result = await fetchNewsEngine('/v1/collect', { method: 'POST', body: req.body ?? {} });
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 采集失败' });
});
adminApi.post('/news-engine/collections/:id/score', requireAdmin, async (req, res) => {
const result = await fetchNewsEngine(`/v1/collections/${encodeURIComponent(req.params.id)}/score`, {
method: 'POST',
body: req.body ?? {},
});
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 评分失败' });
});
adminApi.post('/news-engine/rank', requireAdmin, async (req, res) => {
const result = await fetchNewsEngine('/v1/rank', { method: 'POST', body: req.body ?? {} });
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 排序失败' });
});
adminApi.get('/news-engine/runs', requireAdmin, async (req, res) => {
const limit = Math.min(50, Math.max(1, Number(req.query.limit) || 30));
const result = await fetchNewsEngine(`/v1/runs?limit=${limit}`);
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 不可用' });
});
adminApi.get('/news-engine/runs/:id', requireAdmin, async (req, res) => {
const result = await fetchNewsEngine(`/v1/runs/${encodeURIComponent(req.params.id)}`);
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 不可用' });
});
adminApi.get('/news-engine/scheduler/status', requireAdmin, async (_req, res) => {
const result = await fetchNewsEngine('/v1/scheduler/status');
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 不可用' });
});
adminApi.post('/news-engine/scheduler/trigger', requireAdmin, async (req, res) => {
const result = await fetchNewsEngine('/v1/scheduler/trigger', { method: 'POST', body: req.body ?? {} });
return res.status(result.ok ? 200 : result.status || 502).json(result.data ?? { message: 'News Engine 采集触发失败' });
});
adminApi.get('/orchestrator/config', requireAdmin, async (_req, res) => {
if (!orchestratorConfigService?.getAdminConfig) {
return res.status(503).json({ message: 'Orchestrator 配置服务未启用' });