179 lines
5.3 KiB
JavaScript
179 lines
5.3 KiB
JavaScript
|
|
const axios = require('axios');
|
||
|
|
const {
|
||
|
|
DEFAULT_VOICE_ASSISTANT_PROFILE,
|
||
|
|
resolveAssistantProfile,
|
||
|
|
} = require('./assistantProfileConfig');
|
||
|
|
|
||
|
|
const assistantProfileCache = new Map();
|
||
|
|
|
||
|
|
function getAssistantProfileApiUrl() {
|
||
|
|
const value = String(process.env.ASSISTANT_PROFILE_API_URL || '').trim();
|
||
|
|
return value && !value.startsWith('your_') ? value : '';
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAssistantProfileApiMethod() {
|
||
|
|
const method = String(process.env.ASSISTANT_PROFILE_API_METHOD || 'GET').trim().toUpperCase();
|
||
|
|
return method === 'POST' ? 'POST' : 'GET';
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAssistantProfileTimeoutMs() {
|
||
|
|
const value = Number(process.env.ASSISTANT_PROFILE_API_TIMEOUT_MS || 5000);
|
||
|
|
return Number.isFinite(value) && value > 0 ? value : 5000;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAssistantProfileCacheTtlMs() {
|
||
|
|
const value = Number(process.env.ASSISTANT_PROFILE_CACHE_TTL_MS || 60000);
|
||
|
|
return Number.isFinite(value) && value >= 0 ? value : 60000;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAssistantProfileCacheKey(userId = null) {
|
||
|
|
return String(userId || 'global').trim() || 'global';
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseAssistantProfileHeaders() {
|
||
|
|
const raw = String(process.env.ASSISTANT_PROFILE_API_HEADERS || '').trim();
|
||
|
|
if (!raw) return {};
|
||
|
|
try {
|
||
|
|
const parsed = JSON.parse(raw);
|
||
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
return Object.fromEntries(
|
||
|
|
Object.entries(parsed)
|
||
|
|
.map(([key, value]) => [String(key || '').trim(), String(value || '').trim()])
|
||
|
|
.filter(([key, value]) => key && value)
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
console.warn('[AssistantProfile] parse headers failed:', error.message);
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function pickAssistantProfilePayload(payload) {
|
||
|
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
const candidates = [
|
||
|
|
payload.assistantProfile,
|
||
|
|
payload.profile,
|
||
|
|
payload.data?.assistantProfile,
|
||
|
|
payload.data?.profile,
|
||
|
|
payload.data,
|
||
|
|
payload,
|
||
|
|
];
|
||
|
|
for (const candidate of candidates) {
|
||
|
|
if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) {
|
||
|
|
return candidate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
function sanitizeAssistantProfilePayload(payload) {
|
||
|
|
const source = pickAssistantProfilePayload(payload);
|
||
|
|
return {
|
||
|
|
documents: source.documents,
|
||
|
|
email: source.email,
|
||
|
|
nickname: source.nickname,
|
||
|
|
wxl: source.wxl,
|
||
|
|
mobile: source.mobile,
|
||
|
|
wx_code: source.wx_code,
|
||
|
|
intro: source.intro,
|
||
|
|
sign: source.sign,
|
||
|
|
story: source.story,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
async function fetchRemoteAssistantProfile(userId = null) {
|
||
|
|
const url = getAssistantProfileApiUrl();
|
||
|
|
if (!url) {
|
||
|
|
return {
|
||
|
|
profile: resolveAssistantProfile(),
|
||
|
|
source: 'default',
|
||
|
|
cached: false,
|
||
|
|
fetchedAt: Date.now(),
|
||
|
|
configured: false,
|
||
|
|
error: null,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
const headers = {
|
||
|
|
Accept: 'application/json',
|
||
|
|
...parseAssistantProfileHeaders(),
|
||
|
|
};
|
||
|
|
const token = String(process.env.ASSISTANT_PROFILE_API_TOKEN || '').trim();
|
||
|
|
if (token) {
|
||
|
|
headers.Authorization = `Bearer ${token}`;
|
||
|
|
}
|
||
|
|
const method = getAssistantProfileApiMethod();
|
||
|
|
const timeout = getAssistantProfileTimeoutMs();
|
||
|
|
const params = userId ? { userId } : undefined;
|
||
|
|
const response = method === 'POST'
|
||
|
|
? await axios.post(url, userId ? { userId } : {}, { headers, timeout })
|
||
|
|
: await axios.get(url, { headers, timeout, params });
|
||
|
|
const profile = resolveAssistantProfile(sanitizeAssistantProfilePayload(response.data));
|
||
|
|
return {
|
||
|
|
profile,
|
||
|
|
source: 'remote_api',
|
||
|
|
cached: false,
|
||
|
|
fetchedAt: Date.now(),
|
||
|
|
configured: true,
|
||
|
|
error: null,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
async function getAssistantProfile(options = {}) {
|
||
|
|
const userId = String(options.userId || '').trim() || null;
|
||
|
|
const forceRefresh = !!options.forceRefresh;
|
||
|
|
const overrides = options.overrides && typeof options.overrides === 'object' ? options.overrides : null;
|
||
|
|
const cacheKey = getAssistantProfileCacheKey(userId);
|
||
|
|
const ttlMs = getAssistantProfileCacheTtlMs();
|
||
|
|
const cached = assistantProfileCache.get(cacheKey);
|
||
|
|
|
||
|
|
if (!forceRefresh && cached && (Date.now() - cached.fetchedAt) <= ttlMs) {
|
||
|
|
return {
|
||
|
|
profile: resolveAssistantProfile({ ...cached.profile, ...(overrides || {}) }),
|
||
|
|
source: cached.source,
|
||
|
|
cached: true,
|
||
|
|
fetchedAt: cached.fetchedAt,
|
||
|
|
configured: cached.configured,
|
||
|
|
error: null,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const fetched = await fetchRemoteAssistantProfile(userId);
|
||
|
|
assistantProfileCache.set(cacheKey, fetched);
|
||
|
|
return {
|
||
|
|
profile: resolveAssistantProfile({ ...fetched.profile, ...(overrides || {}) }),
|
||
|
|
source: fetched.source,
|
||
|
|
cached: false,
|
||
|
|
fetchedAt: fetched.fetchedAt,
|
||
|
|
configured: fetched.configured,
|
||
|
|
error: null,
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
const fallback = cached?.profile || DEFAULT_VOICE_ASSISTANT_PROFILE;
|
||
|
|
return {
|
||
|
|
profile: resolveAssistantProfile({ ...fallback, ...(overrides || {}) }),
|
||
|
|
source: cached ? 'cache_fallback' : 'default_fallback',
|
||
|
|
cached: !!cached,
|
||
|
|
fetchedAt: cached?.fetchedAt || null,
|
||
|
|
configured: !!getAssistantProfileApiUrl(),
|
||
|
|
error: error.message,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function clearAssistantProfileCache(userId = null) {
|
||
|
|
if (userId == null || String(userId).trim() === '') {
|
||
|
|
assistantProfileCache.clear();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
assistantProfileCache.delete(getAssistantProfileCacheKey(userId));
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
getAssistantProfile,
|
||
|
|
clearAssistantProfileCache,
|
||
|
|
};
|