195 lines
5.2 KiB
JavaScript
195 lines
5.2 KiB
JavaScript
/**
|
||
* AI 请求稳态管理器
|
||
* 提供请求锁、超时控制、自动重试、JSON 解析保护、失败兜底
|
||
*/
|
||
|
||
// 请求锁
|
||
let isRequesting = false;
|
||
|
||
/**
|
||
* 安全的 JSON 解析
|
||
*/
|
||
function safeJSONParse(text) {
|
||
try {
|
||
return JSON.parse(text);
|
||
} catch (e) {
|
||
console.log('AI_JSON_FALLBACK: 首次解析失败,尝试提取 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 代码块解析也失败');
|
||
}
|
||
}
|
||
return { status: 'fallback_text', raw: text };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成兜底解读内容
|
||
*/
|
||
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 请求
|
||
*/
|
||
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);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 带重试的请求执行
|
||
*/
|
||
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} 次重试`);
|
||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||
return executeWithRetry(config, retryCount + 1);
|
||
}
|
||
|
||
throw err;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 安全的 AI 请求(主函数)
|
||
*/
|
||
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);
|
||
|
||
if (manualTimeout) {
|
||
throw new Error('Manual timeout');
|
||
}
|
||
|
||
// 5. 安全解析 JSON
|
||
const parsedResult = safeJSONParse(rawResponse);
|
||
|
||
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
|
||
};
|