/** * 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 };