91e140d402
Memind CI / Test, build, and release guards (push) Failing after 8s
Give WeChat MP its own chat.general→page.generate LLM refinement layer with memindadm toggles, shadow mode, and canary openids so service account routing stays independent of the H5 chatIntentRouter. Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
6.0 KiB
JavaScript
176 lines
6.0 KiB
JavaScript
const CONFIG_TABLE = 'h5_wechat_admin_config';
|
|
const CONFIG_KEY = 'intent_router';
|
|
|
|
function normalizeBoolean(value, fallback = false) {
|
|
if (value == null || value === '') return fallback;
|
|
if (typeof value === 'boolean') return value;
|
|
const normalized = String(value).trim().toLowerCase();
|
|
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
return fallback;
|
|
}
|
|
|
|
function normalizeString(value) {
|
|
if (value == null) return '';
|
|
return String(value).trim();
|
|
}
|
|
|
|
function normalizeNumber(value, fallback) {
|
|
if (value == null || value === '') return fallback;
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
}
|
|
|
|
function parseConfigJson(value) {
|
|
if (!value) return {};
|
|
if (typeof value === 'object') return value;
|
|
try {
|
|
return JSON.parse(String(value));
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function normalizeOpenidList(value) {
|
|
return [...new Set(String(value ?? '')
|
|
.split(/[\s,]+/u)
|
|
.map((item) => item.trim())
|
|
.filter(Boolean))].slice(0, 1000);
|
|
}
|
|
|
|
async function ensureConfigTable(pool) {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS ${CONFIG_TABLE} (
|
|
config_key VARCHAR(64) 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 defaultsFromEnv(env = process.env) {
|
|
return {
|
|
enabled: normalizeBoolean(env.MEMIND_WECHAT_INTENT_LLM_ENABLED, false),
|
|
shadowMode: normalizeBoolean(env.MEMIND_WECHAT_INTENT_LLM_SHADOW, true),
|
|
modelProviderKeyId: normalizeString(env.MEMIND_WECHAT_INTENT_MODEL_PROVIDER_KEY_ID),
|
|
model: normalizeString(env.MEMIND_WECHAT_INTENT_MODEL),
|
|
minConfidence: normalizeNumber(env.MEMIND_WECHAT_INTENT_MIN_CONFIDENCE, 0.65),
|
|
timeoutMs: normalizeNumber(env.MEMIND_WECHAT_INTENT_TIMEOUT_MS, 4000),
|
|
canaryOpenids: normalizeOpenidList(env.MEMIND_WECHAT_INTENT_CANARY_OPENIDS),
|
|
};
|
|
}
|
|
|
|
export function createWechatIntentRouterConfigService(pool, { env = process.env } = {}) {
|
|
let ensurePromise = null;
|
|
|
|
async function ensureReady() {
|
|
if (!ensurePromise) ensurePromise = ensureConfigTable(pool);
|
|
await ensurePromise;
|
|
}
|
|
|
|
async function readRow() {
|
|
await ensureReady();
|
|
const [rows] = await pool.query(
|
|
`SELECT config_json, updated_by, updated_at
|
|
FROM ${CONFIG_TABLE}
|
|
WHERE config_key = ?
|
|
LIMIT 1`,
|
|
[CONFIG_KEY],
|
|
);
|
|
return rows[0] ?? null;
|
|
}
|
|
|
|
function mergeConfig(row) {
|
|
const defaults = defaultsFromEnv(env);
|
|
const stored = parseConfigJson(row?.config_json);
|
|
return {
|
|
enabled: normalizeBoolean(stored.enabled, defaults.enabled),
|
|
shadowMode: normalizeBoolean(stored.shadowMode, defaults.shadowMode),
|
|
modelProviderKeyId: normalizeString(stored.modelProviderKeyId) || defaults.modelProviderKeyId || null,
|
|
model: normalizeString(stored.model) || defaults.model || null,
|
|
minConfidence: normalizeNumber(stored.minConfidence, defaults.minConfidence),
|
|
timeoutMs: normalizeNumber(stored.timeoutMs, defaults.timeoutMs),
|
|
canaryOpenids: stored.canaryOpenids != null
|
|
? normalizeOpenidList(stored.canaryOpenids)
|
|
: defaults.canaryOpenids,
|
|
};
|
|
}
|
|
|
|
return {
|
|
async getConfig() {
|
|
const row = await readRow();
|
|
return {
|
|
...mergeConfig(row),
|
|
updatedAt: row?.updated_at ? Number(row.updated_at) : null,
|
|
updatedBy: row?.updated_by ?? null,
|
|
};
|
|
},
|
|
|
|
async getRuntimeState() {
|
|
const config = await this.getConfig();
|
|
const overrides = {
|
|
MEMIND_WECHAT_INTENT_LLM_ENABLED: config.enabled ? '1' : '0',
|
|
MEMIND_WECHAT_INTENT_LLM_SHADOW: config.shadowMode ? '1' : '0',
|
|
MEMIND_WECHAT_INTENT_MODEL_PROVIDER_KEY_ID: config.modelProviderKeyId ?? '',
|
|
MEMIND_WECHAT_INTENT_MODEL: config.model ?? '',
|
|
MEMIND_WECHAT_INTENT_MIN_CONFIDENCE: String(config.minConfidence),
|
|
MEMIND_WECHAT_INTENT_TIMEOUT_MS: String(config.timeoutMs),
|
|
MEMIND_WECHAT_INTENT_CANARY_OPENIDS: config.canaryOpenids.join(','),
|
|
};
|
|
return {
|
|
source: 'admin-db',
|
|
updatedAt: config.updatedAt,
|
|
updatedBy: config.updatedBy,
|
|
fingerprint: JSON.stringify(config),
|
|
overrides,
|
|
config,
|
|
};
|
|
},
|
|
|
|
async updateConfig(payload = {}, { updatedBy = null } = {}) {
|
|
const current = await this.getConfig();
|
|
const next = {
|
|
enabled: payload.enabled === undefined
|
|
? current.enabled
|
|
: normalizeBoolean(payload.enabled, current.enabled),
|
|
shadowMode: payload.shadowMode === undefined
|
|
? current.shadowMode
|
|
: normalizeBoolean(payload.shadowMode, current.shadowMode),
|
|
modelProviderKeyId: payload.modelProviderKeyId === undefined
|
|
? (current.modelProviderKeyId ?? '')
|
|
: normalizeString(payload.modelProviderKeyId),
|
|
model: payload.model === undefined
|
|
? (current.model ?? '')
|
|
: normalizeString(payload.model),
|
|
minConfidence: payload.minConfidence === undefined
|
|
? current.minConfidence
|
|
: normalizeNumber(payload.minConfidence, current.minConfidence),
|
|
timeoutMs: payload.timeoutMs === undefined
|
|
? current.timeoutMs
|
|
: normalizeNumber(payload.timeoutMs, current.timeoutMs),
|
|
canaryOpenids: payload.canaryOpenids === undefined
|
|
? current.canaryOpenids
|
|
: normalizeOpenidList(payload.canaryOpenids),
|
|
};
|
|
const now = Date.now();
|
|
await pool.query(
|
|
`INSERT INTO ${CONFIG_TABLE} (config_key, 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_KEY, JSON.stringify(next), updatedBy, now],
|
|
);
|
|
return this.getConfig();
|
|
},
|
|
};
|
|
}
|
|
|
|
export const wechatIntentRouterConfigInternals = {
|
|
normalizeBoolean,
|
|
normalizeOpenidList,
|
|
defaultsFromEnv,
|
|
};
|