diff --git a/server/app.mjs b/server/app.mjs index a1bf2a6..f52e986 100644 --- a/server/app.mjs +++ b/server/app.mjs @@ -98,6 +98,7 @@ export function createAdminApp(services) { loadMindSpaceConfig, updateMindSpaceConfig, memoryV2ConfigService, + mindSearchConfigService, personalMemoryCandidateStore, skillRuntimeConfigService, wechatScheduleLlmConfigService, @@ -368,6 +369,26 @@ export function createAdminApp(services) { res.json(result); }); + adminApi.get('/mindsearch/config', requireAdmin, async (_req, res) => { + if (!mindSearchConfigService?.getAdminConfig) return res.status(503).json({ message: 'MindSearch 配置未启用' }); + res.json(await mindSearchConfigService.getAdminConfig()); + }); + + adminApi.patch('/mindsearch/config', requireAdmin, async (req, res) => { + if (!mindSearchConfigService?.updateAdminConfig) return res.status(503).json({ message: 'MindSearch 配置未启用' }); + res.json(await mindSearchConfigService.updateAdminConfig(req.body ?? {}, { updatedBy: req.currentUser.id })); + }); + + adminApi.put('/mindsearch/config', requireAdmin, async (req, res) => { + if (!mindSearchConfigService?.updateAdminConfig) return res.status(503).json({ message: 'MindSearch 配置未启用' }); + res.json(await mindSearchConfigService.updateAdminConfig(req.body ?? {}, { updatedBy: req.currentUser.id })); + }); + + adminApi.get('/mindsearch/runtime', requireAdmin, async (_req, res) => { + if (!mindSearchConfigService?.getRuntimeState) return res.status(503).json({ message: 'MindSearch 配置未启用' }); + res.json(await mindSearchConfigService.getRuntimeState()); + }); + adminApi.get('/memory-v2/status', requireAdmin, async (_req, res) => { const portalBaseUrl = `http://127.0.0.1:${process.env.H5_PORT ?? 8081}`; try { diff --git a/server/bootstrap.mjs b/server/bootstrap.mjs index 9c4f88c..cb603c2 100644 --- a/server/bootstrap.mjs +++ b/server/bootstrap.mjs @@ -29,6 +29,7 @@ export async function bootstrapAdminServices() { updateMindSpaceConfig, } = await importMemind('mindspace-config.mjs'); const { createMemoryV2AdminConfigService } = await importMemind('memory-v2-admin-config.mjs'); + const { createMindSearchConfigService } = await importMemind('mindsearch-config.mjs'); const { createPersonalMemoryCandidateStore } = await importMemind('memory-v2-personal-store.mjs'); const { createSkillRuntimeAdminConfigService } = await importMemind('skill-runtime-admin-config.mjs'); const { createAssetGatewayConfigService } = await importMemind('asset-gateway.mjs'); @@ -95,6 +96,8 @@ export async function bootstrapAdminServices() { const memoryV2ConfigService = createMemoryV2AdminConfigService(pool, { env: process.env, }); + const mindSearchConfigService = createMindSearchConfigService(pool, { env: process.env }); + await mindSearchConfigService.ensureSchema(); const personalMemoryCandidateStore = createPersonalMemoryCandidateStore(pool); const skillRuntimeConfigService = createSkillRuntimeAdminConfigService(pool, { env: process.env, @@ -152,6 +155,7 @@ export async function bootstrapAdminServices() { loadMindSpaceConfig, updateMindSpaceConfig, memoryV2ConfigService, + mindSearchConfigService, personalMemoryCandidateStore, skillRuntimeConfigService, wechatScheduleLlmConfigService, diff --git a/server/index.mjs b/server/index.mjs index 18bc618..9f314d2 100644 --- a/server/index.mjs +++ b/server/index.mjs @@ -105,6 +105,7 @@ ready loadMindSpaceConfig, updateMindSpaceConfig, memoryV2ConfigService, + mindSearchConfigService, personalMemoryCandidateStore, skillRuntimeConfigService, wechatScheduleLlmConfigService, @@ -130,6 +131,7 @@ ready loadMindSpaceConfig, updateMindSpaceConfig, memoryV2ConfigService, + mindSearchConfigService, personalMemoryCandidateStore, skillRuntimeConfigService, wechatScheduleLlmConfigService, diff --git a/src/App.tsx b/src/App.tsx index 50ff2db..bbb91c2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { DashboardPage } from './admin/pages/DashboardPage'; import { PoliciesPage } from './admin/pages/PoliciesPage'; import { MindSpacePage } from './admin/pages/MindSpacePage'; import { MemoryV2Page } from './admin/pages/MemoryV2Page'; +import { MindSearchPage } from './admin/pages/MindSearchPage'; import { ProvidersPage } from './admin/pages/ProvidersPage'; import { SkillsPage } from './admin/pages/SkillsPage'; import { SystemTestsPage } from './admin/pages/SystemTestsPage'; @@ -122,6 +123,7 @@ function AdminApp({ user, onLogout }: { user: PortalUser; onLogout: () => void } } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/admin/AdminNav.tsx b/src/admin/AdminNav.tsx index f3e3c02..a411b20 100644 --- a/src/admin/AdminNav.tsx +++ b/src/admin/AdminNav.tsx @@ -29,6 +29,7 @@ const NAV_SECTIONS: NavSection[] = [ { to: '/wechat', label: '服务号' }, { to: '/mindspace', label: 'MindSpace 配置' }, { to: '/memory-v2', label: 'Memory V2' }, + { to: '/mindsearch', label: 'MindSearch' }, { to: '/skill-runtime', label: 'Skill Runtime' }, { to: '/system-tests', label: '系统测试验证' }, { to: '/capabilities', label: '能力' }, diff --git a/src/admin/pages/MindSearchPage.tsx b/src/admin/pages/MindSearchPage.tsx new file mode 100644 index 0000000..71c5d28 --- /dev/null +++ b/src/admin/pages/MindSearchPage.tsx @@ -0,0 +1,68 @@ +import { useEffect, useState } from 'react'; +import { getMindSearchConfig, updateMindSearchConfig } from '../../api/client'; +import type { MindSearchConfig } from '../../types'; + +const initial: MindSearchConfig = { + enabled: false, + mode: 'off', + providers: { searxng: false, github: false, reader: false }, + settings: { searxngEndpoint: '', maxResults: 10, timeoutMs: 8000, readerMaxChars: 12000 }, +}; + +export function MindSearchPage() { + const [config, setConfig] = useState(initial); + const [meta, setMeta] = useState<{ source?: string; updatedAt?: number | null; updatedBy?: string | null }>({}); + const [message, setMessage] = useState('加载配置中…'); + + useEffect(() => { + getMindSearchConfig() + .then((result) => { + setConfig({ ...initial, ...result.config, settings: { ...initial.settings, ...result.config.settings } }); + setMeta(result); + setMessage(''); + }) + .catch((error) => setMessage(error instanceof Error ? error.message : '配置加载失败')); + }, []); + + const save = async () => { + setMessage('保存中…'); + try { + const result = await updateMindSearchConfig(config); + setConfig({ ...initial, ...result.config, settings: { ...initial.settings, ...result.config.settings } }); + setMeta(result); + setMessage('已保存到数据库;新会话生效,旧会话不受影响'); + } catch (error) { + setMessage(error instanceof Error ? error.message : '保存失败'); + } + }; + + const toggleProvider = (key: keyof MindSearchConfig['providers']) => + setConfig((current) => ({ ...current, providers: { ...current.providers, [key]: !current.providers[key] } })); + const updateSetting = (key: keyof MindSearchConfig['settings'], value: string | number) => + setConfig((current) => ({ ...current, settings: { ...current.settings, [key]: value } })); + + return ( +
+

MindSearch

可插拔外部搜索增强。关闭时完全保持原有搜索、记忆和上下文链路。

+
+

总开关与模式

+ + +

Provider

+
可用 Provider + + + +
+

运行参数

+ + + + + + {message &&

{message}

} +

配置来源:{meta.source === 'admin' ? '数据库' : '环境变量默认值'};最后更新:{meta.updatedAt ? new Date(meta.updatedAt).toLocaleString() : '尚未保存'}{meta.updatedBy ? `;操作人:${meta.updatedBy}` : ''}

+
+
+ ); +} diff --git a/src/api/client.ts b/src/api/client.ts index 0b5dcd1..a81fddb 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -47,6 +47,7 @@ import type { WechatScheduleLlmConfig, WechatMessage, WechatWebNotification, + MindSearchConfig, } from '../types'; export class ApiError extends Error { @@ -653,6 +654,14 @@ export async function clearUserCapabilityOverrides(userId: string): Promise { + return portalFetch('/admin-api/mindsearch/config'); +} + +export async function updateMindSearchConfig(config: Partial): Promise<{ config: MindSearchConfig; source: string }> { + return portalFetch('/admin-api/mindsearch/config', { method: 'PATCH', body: JSON.stringify(config) }); +} + // ── Policies ────────────────────────────────────────── export async function listPolicyCatalog(): Promise { diff --git a/src/types.ts b/src/types.ts index 6b57b2b..dc9341e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -597,3 +597,10 @@ export type AdminSubscription = { note: string | null; createdAt: number; }; + +export type MindSearchConfig = { + enabled: boolean; + mode: 'off' | 'shadow' | 'assist'; + providers: { searxng: boolean; github: boolean; reader: boolean }; + settings: { searxngEndpoint: string; maxResults: number; timeoutMs: number; readerMaxChars: number }; +};