Files
memind/mindsearch-config.mjs

100 lines
4.2 KiB
JavaScript

const CONFIG_TABLE = 'h5_mindsearch_config';
const CONFIG_SCOPE = 'global';
export const MINDSEARCH_DEFAULT_CONFIG = Object.freeze({
enabled: false,
mode: 'off',
providers: { searxng: false, github: false, reader: false },
settings: { searxngEndpoint: '', maxResults: 10, timeoutMs: 8000, readerMaxChars: 12000 },
});
const clone = (value) => JSON.parse(JSON.stringify(value));
const bool = (value, fallback = false) => {
if (typeof value === 'boolean') return value;
if (value == null || value === '') return fallback;
return /^(1|true|yes|on)$/i.test(String(value));
};
export function normalizeMindSearchConfig(input = {}) {
const mode = ['off', 'shadow', 'assist'].includes(input.mode) ? input.mode : 'off';
const config = {
enabled: bool(input.enabled),
mode,
providers: {
searxng: bool(input.providers?.searxng),
github: bool(input.providers?.github),
reader: bool(input.providers?.reader),
},
settings: {
searxngEndpoint: String(input.settings?.searxngEndpoint ?? '').trim(),
maxResults: Math.max(1, Math.min(20, Number(input.settings?.maxResults ?? 10) || 10)),
timeoutMs: Math.max(1000, Math.min(30000, Number(input.settings?.timeoutMs ?? 8000) || 8000)),
readerMaxChars: Math.max(1000, Math.min(50000, Number(input.settings?.readerMaxChars ?? 12000) || 12000)),
},
};
if (!config.enabled || config.mode === 'off') {
config.enabled = false;
config.mode = 'off';
}
return config;
}
function envConfig(env = process.env) {
const enabled = bool(env.TKMIND_SEARCH_ENABLED);
return normalizeMindSearchConfig({
enabled,
mode: env.TKMIND_SEARCH_MODE || (enabled ? 'assist' : 'off'),
providers: {
searxng: env.TKMIND_SEARCH_PROVIDER_SEARXNG_ENABLED,
github: env.TKMIND_SEARCH_PROVIDER_GITHUB_ENABLED,
reader: env.TKMIND_SEARCH_READER_ENABLED,
},
});
}
export async function ensureMindSearchConfigSchema(pool) {
await pool.query(`CREATE TABLE IF NOT EXISTS ${CONFIG_TABLE} (
config_scope VARCHAR(32) PRIMARY KEY,
config_json JSON NOT NULL,
updated_by CHAR(36) NULL,
updated_at BIGINT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`);
}
export function createMindSearchConfigService(pool, { env = process.env } = {}) {
let ready = null;
const ensure = async () => {
if (!pool) return;
ready ??= ensureMindSearchConfigSchema(pool);
await ready;
};
const load = async () => {
const fallback = envConfig(env);
if (!pool) return { config: fallback, source: 'env' };
await ensure();
const [rows] = await pool.query(`SELECT config_json, updated_by, updated_at FROM ${CONFIG_TABLE} WHERE config_scope = ? LIMIT 1`, [CONFIG_SCOPE]);
if (!rows?.[0]) return { config: fallback, source: 'env' };
let stored = rows[0].config_json;
if (typeof stored === 'string') { try { stored = JSON.parse(stored); } catch { stored = {}; } }
return { config: normalizeMindSearchConfig(stored), source: 'admin', updatedBy: rows[0].updated_by ?? null, updatedAt: Number(rows[0].updated_at ?? 0) || null };
};
return {
ensureSchema: ensure,
getEffectiveConfig: async () => (await load()).config,
getAdminConfig: async () => load(),
updateAdminConfig: async (patch, { updatedBy = null } = {}) => {
const current = await load();
const next = normalizeMindSearchConfig({ ...current.config, ...patch, providers: { ...current.config.providers, ...(patch?.providers ?? {}) }, settings: { ...current.config.settings, ...(patch?.settings ?? {}) } });
if (!pool) return { config: next, source: 'env' };
await ensure();
const now = Date.now();
await pool.query(`INSERT INTO ${CONFIG_TABLE} (config_scope, config_json, updated_by, updated_at) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE config_json = VALUES(config_json), updated_by = VALUES(updated_by), updated_at = VALUES(updated_at)`, [CONFIG_SCOPE, JSON.stringify(next), updatedBy, now]);
return { config: next, source: 'admin', updatedBy, updatedAt: now };
},
getRuntimeState: async () => {
const config = await (async () => (await load()).config)();
return { enabled: config.enabled, mode: config.mode, providers: config.providers, effective: config.enabled && config.mode !== 'off' };
},
};
}