huaanglimeng/utils/aiRequestManager.js

221 lines
6.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* AI 请求稳态管理器
* 提供请求锁、超时控制、自动重试、JSON 解析保护、失败兜底
*/
// 请求锁
let isRequesting = false;
/**
* 安全的 JSON 解析
* @param {string} text - 待解析的文本
* @returns {Object} 解析结果
*/
function safeJSONParse(text) {
try {
return JSON.parse(text);
} catch (e) {
console.log('AI_JSON_FALLBACK: 首次解析失败,尝试提取 JSON 代码块');
// 尝试提取 JSON 代码块(可能被包裹在 ```json ``` 中)
const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/) || text.match(/```\s*([\s\S]*?)\s*```/);
if (jsonMatch && jsonMatch[1]) {
try {
return JSON.parse(jsonMatch[1]);
} catch (e2) {
console.error('AI_JSON_FALLBACK: JSON 代码块解析也失败');
}
}
// 完全失败,返回 fallback 状态
return {
status: 'fallback_text',
raw: text
};
}
}
/**
* 生成兜底解读内容
* @param {Array} drawnCards - 抽到的牌
* @param {Object} spread - 牌阵信息
* @returns {Object} 兜底解读
*/
function generateFallbackInterpretation(drawnCards, spread) {
const positions = drawnCards.map((card, index) => ({
posName: spread.positions[index],
posMeaning: `${card.name}${card.isReversed ? '(逆位)' : '(正位)'}提示你关注当前的能量变化,保持觉察。`
}));
return {
theme: '本次牌面提示你正处于一个需要观察和思考的阶段',
status: '你可能正在经历一些内在的调整',
influence: '当前能量正在变化之中',
advice: '给自己一点时间观察,避免过度解读当前状况,关注真实感受',
positions: positions
};
}
/**
* 执行单次 AI 请求
* @param {Object} config - 请求配置
* @returns {Promise} 请求结果
*/
function executeRequest(config) {
return new Promise((resolve, reject) => {
const { url, data, timeout = 90000 } = config;
wx.request({
url,
method: 'POST',
header: config.header,
data,
timeout,
success: (res) => {
if (res.data && res.data.choices && res.data.choices[0]) {
resolve(res.data.choices[0].message.content);
} else {
reject(new Error('AI 返回数据格式异常'));
}
},
fail: (err) => {
reject(err);
}
});
});
}
/**
* 带重试的请求执行
* @param {Object} config - 请求配置
* @param {number} retryCount - 当前重试次数
* @returns {Promise} 请求结果
*/
async function executeWithRetry(config, retryCount = 0) {
try {
const result = await executeRequest(config);
return result;
} catch (err) {
const shouldRetry = retryCount < 1 && (
err.errMsg?.includes('timeout') ||
err.errMsg?.includes('fail') ||
err.statusCode >= 500
);
if (shouldRetry) {
console.log(`AI_REQUEST_RETRY: 第 ${retryCount + 1} 次重试`);
// 等待 1500ms 后重试
await new Promise(resolve => setTimeout(resolve, 1500));
return executeWithRetry(config, retryCount + 1);
}
throw err;
}
}
/**
* 安全的 AI 请求(主函数)
* @param {Object} payload - 请求参数
* @returns {Promise<Object>} 请求结果
*/
async function safeAIRequest(payload) {
const {
url,
header,
data,
drawnCards,
spread,
onProgress
} = payload;
// 1. 请求锁检查
if (isRequesting) {
console.log('AI_REQUEST_BLOCKED: 请求进行中');
return {
status: 'blocked',
message: '请求进行中,请稍候'
};
}
isRequesting = true;
console.log('AI_REQUEST_START');
// 2. 进度提示
let progressTimer1, progressTimer2;
if (onProgress) {
onProgress('正在抽取牌面…');
progressTimer1 = setTimeout(() => onProgress('正在深入解读…'), 5000);
progressTimer2 = setTimeout(() => onProgress('解读较复杂,请稍候…'), 15000);
}
// 3. 手动超时监控95秒
let manualTimeout = false;
const timeoutTimer = setTimeout(() => {
manualTimeout = true;
console.log('AI_REQUEST_TIMEOUT: 手动超时触发');
}, 95000);
try {
// 4. 执行请求(带重试)
const rawResponse = await executeWithRetry({
url,
header,
data,
timeout: 90000
});
// 清除超时定时器
clearTimeout(timeoutTimer);
clearTimeout(progressTimer1);
clearTimeout(progressTimer2);
// 5. 检查手动超时
if (manualTimeout) {
throw new Error('Manual timeout');
}
// 6. 安全解析 JSON
const parsedResult = safeJSONParse(rawResponse);
// 7. 检查是否是 fallback 状态
if (parsedResult.status === 'fallback_text') {
console.log('AI_JSON_FALLBACK: 使用兜底解读');
isRequesting = false;
return {
status: 'fallback',
data: generateFallbackInterpretation(drawnCards, spread)
};
}
console.log('AI_REQUEST_SUCCESS');
isRequesting = false;
return {
status: 'success',
data: parsedResult
};
} catch (err) {
console.error('AI_REQUEST_FAILED:', err);
// 清除定时器
clearTimeout(timeoutTimer);
clearTimeout(progressTimer1);
clearTimeout(progressTimer2);
isRequesting = false;
// 返回兜底解读
return {
status: 'fallback',
data: generateFallbackInterpretation(drawnCards, spread),
error: err.errMsg || err.message
};
}
}
module.exports = {
safeAIRequest
};