100 lines
4.2 KiB
JavaScript
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' };
|
|
},
|
|
};
|
|
}
|