huaanglimeng/pages/index/index.js

785 lines
30 KiB
JavaScript
Raw Normal View History

// AI 配置 (建议后续迁移至后端以保证 API Key 安全)
const DEEPSEEK_API_KEY = 'sk-f659b4c3aa954baaa6cbdbd5cae0b7d1'; // 请在此处替换您的 API Key
const DEEPSEEK_BASE_URL = 'https://api.deepseek.com/chat/completions';
// --- 数据导入 ---
const { minorArcana } = require('../../data/tarot/index');
const { getQuestionType } = require('../../utils/questionType');
const { generateSpreadStory } = require('../../utils/spreadStory');
const { validateQuestion, scoreQuestion } = require('../../utils/questionFilter');
const { safeAIRequest } = require('../../utils/aiRequestManager');
// --- Prompt 模板定义 ---
// 1. 基础版模板:平静、清晰、节制
const TAROT_PROMPT_BASE = `你是一位克制、温和、不制造依赖的塔罗解读者。
你的角色不是预测未来而是陪伴当下的人理解此刻的状态
解读边界
- 不给命令不下结论不使用你应该结果是一定会
- 不预测具体事件或时间不制造焦虑或依赖
语言风格
- 平静清晰节制
- 使用可能 / 正在 / 也许 / 可以感受到
- 像是轻声说明而不是说服或劝导避免情绪渲染与神秘化表达
解读结构
1. 12 句话描绘这张牌当下呈现的状态氛围
2. 结合关键词与核心含义解释它可能对应的内在状态
3. 用一句自然收口的话结束不引导行动
输出要求
- 总字数 120180
- 只输出解读文本不使用标题编号分段符号`;
// 2. 深夜版模板:更慢、更柔和、允许留白
const TAROT_PROMPT_NIGHT = `你是一位安静、克制、不制造依赖的塔罗解读者。
此刻的解读更像一次低声的陪伴而不是解释或分析
解读边界
- 不给建议不下判断不使用你应该结果是接下来会
- 不预测未来事件或变化不放大情绪不渲染痛苦
语言风格
- 更慢更柔和
- 使用也许 / 正在 / 像是 / 仿佛
- 允许留白不急着说清楚一切文字像夜晚一样安静
解读结构
1. 先用一句贴近当下感受的描述开头
2. 围绕关键词展开但重点放在感受本身
3. 用一句轻微可停住的句子结束不指向下一步
输出要求
- 只输出解读文本`;
// 3. 增强版模板:解读稳定器 + 高关联性结构化解读
const TAROT_PROMPT_ENHANCED = `📏 解读稳定规则(高优先级执行)
你必须保持解读风格稳定具体且一致不允许出现质量波动
解读长度控制
整体长度400700 不少于 300 不超过 900
各模块建议
- 问题回应23
- 牌面分析每张牌2句以内
- 当前状态23
- 发展趋势23
- 行动建议35条短句
禁止超长段落大段心理学科普冗长比喻
结构稳定规则必须严格执行
输出必须包含且仅包含以下5个部分不允许新增其他模块
1. 问题回应
2. 牌面分析
3. 当前状态
4. 发展趋势
5. 行动建议
解读深度控制
每段必须包含至少一个情境描述心理状态行为倾向关系互动
禁止单纯解释牌意抽象空话能量类泛词堆叠
情绪语气稳定器
必须温和理解用户提供支持
避免过度肯定一定会绝对否定绝不会命令式建议恐吓式预测
推荐句式"你当前可能正在经历…" "这可能让你感到…" "可以尝试…"
百科化防护规则非常重要
禁止出现"通常这张牌代表…" "根据牌义…" "在传统塔罗中…"
如果出现类似内容必须立即转化为与用户问题相关的情境说明
参考牌义权重限制
信息优先级用户问题 > 提问领域 > 抽到的牌 > 参考牌义
参考牌义只能用于理解不允许复述或改写复述
质量自检生成前内部执行
在输出前检查
开头是否回应问题
是否逐张牌关联问题
是否包含可执行建议
是否出现百科语句
是否结构完整
---
🎯 角色设定
你是一位专业温和具有洞察力的塔罗解读师
你的解读必须围绕用户提出的问题展开而不是输出通用牌义
本次解读优先级
用户问题 > 提问领域 > 抽到的牌 > 参考牌义
🧭 解读核心要求非常重要
1 解读必须紧密围绕用户问题展开
2 开头第一句话必须直接回应用户的问题情境
3 每一张牌都要说明它与该问题的具体关系
4 禁止输出通用百科式牌义解释
5 必须结合提问领域进行情境化分析
6 解读重点放在状态趋势与建议而非绝对预测
🎨 语言风格要求
温和具体有针对性
使用"你当前的情况可能是…"
避免绝对判断和命令式语气
不要出现"根据牌义来说…" "通常这张牌代表…"
🚫 禁止行为
不要复述参考牌义原文
不要脱离用户问题进行泛泛而谈
不要写百科解释
不要生成医疗法律金融建议
📤 输出格式
请严格按照以下 JSON 格式输出不要包含任何其他说明性文字
{
"theme": "直接回应用户问题的核心主题(必须提及问题关键词)",
"status": "用户在这个问题上的当前状态",
"influence": "影响这个问题的潜在因素",
"advice": "温和、可执行的个人成长建议",
"positions": [
{
"posName": "位置名称",
"posMeaning": "解释这张牌如何影响该问题情境60-100字必须联系问题"
}
]
}`;
// --- 牌阵定义 ---
const SPREADS = [
{
"id": "one_card_guidance",
"name": "单张指引",
"cardCount": 1,
"positions": ["当下的指引"],
"description": "为你此刻的状态提供一个温和而清晰的方向提示。",
"aiSchema": ["core_theme", "current_state", "action_advice"]
},
{
"id": "three_time_flow",
"name": "过去 · 现在 · 未来",
"cardCount": 3,
"positions": ["过去", "现在", "未来"],
"description": "帮助你理解事情的发展过程与可能走向。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "three_problem_solution",
"name": "问题 · 阻碍 · 建议",
"cardCount": 3,
"positions": ["问题核心", "当前阻碍", "行动建议"],
"description": "聚焦关键问题,找出当下最可行的应对方式。",
"aiSchema": ["core_theme", "current_state", "action_advice"]
},
{
"id": "two_choice_decision",
"name": "二选一抉择",
"cardCount": 4,
"positions": ["选择A的发展", "选择A的结果", "选择B的发展", "选择B的结果"],
"description": "对比两种选择的潜在走向,辅助理性决策。",
"aiSchema": ["core_theme", "potential_influence", "action_advice"]
},
{
"id": "five_situation_analysis",
"name": "现状分析",
"cardCount": 5,
"positions": ["现状", "内在因素", "外在影响", "行动方向", "可能结果"],
"description": "从内外层面拆解局势,明确下一步行动。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "relationship_spread",
"name": "关系洞察",
"cardCount": 5,
"positions": ["你的位置", "对方的位置", "关系现状", "隐藏影响", "未来趋势"],
"description": "理解一段关系中的互动模式与发展方向。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "timeline_spread",
"name": "时间之流",
"cardCount": 5,
"positions": ["远古根源", "过去影响", "当下状态", "近期发展", "未来趋势"],
"description": "追溯事件的时间线,看清发展脉络。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "diamond_spread",
"name": "钻石牌阵",
"cardCount": 5,
"positions": ["问题本质", "过去原因", "未来发展", "外部资源", "最佳行动"],
"description": "多角度剖析问题,找到解决之道。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "spiritual_guidance",
"name": "灵性指引",
"cardCount": 7,
"positions": ["当前能量", "内在阻碍", "潜在天赋", "灵性课题", "指导建议", "未来机遇", "最高指引"],
"description": "深入探索内在世界,获得灵性层面的启发。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "horseshoe_spread",
"name": "马蹄铁牌阵",
"cardCount": 7,
"positions": ["过去", "现在", "未来", "你的态度", "他人影响", "障碍", "最终结果"],
"description": "全面了解情况的来龙去脉与未来走向。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "celtic_cross",
"name": "凯尔特十字",
"cardCount": 10,
"positions": ["现状", "挑战", "根基", "过去", "可能性", "近期未来", "你的态度", "外部影响", "希望与恐惧", "最终结果"],
"description": "最经典的综合牌阵,深度解析生命议题。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "tree_of_life",
"name": "生命之树",
"cardCount": 10,
"positions": ["王冠", "智慧", "理解", "慈悲", "严厉", "美丽", "胜利", "荣耀", "基础", "王国"],
"description": "基于卡巴拉生命之树的深度灵性探索。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
}
];
2026-01-31 22:02:01 +08:00
Page({
data: {
todayDate: '', // 用于存储今天的日期
// 1. 状态管理: spread_select, idle, shuffling, drawing, flipping, revealed
state: 'spread_select', // 初始进入牌阵选择
selectedSpread: null,
question: '', // 用户输入的问题
questionType: '综合问题', // 问题类型
questionQuality: null, // 问题质量评分
drawnCardIndices: [], // 用户选中的牌索引
drawnCards: [], // 实际抽到的牌对象
revealedCount: 0, // 已翻开的数量
cardBackImage: '/images/beimian.png',
spreads: SPREADS,
// 动画数据
deckAnimation: {},
cardAnimation: {},
infoAnimation: {},
guideAnimation: {},
// AI 解读相关
aiResult: null,
isAiLoading: false,
aiLoadingText: '正在抽取牌面…', // 动态加载提示
spreadStory: '',
2026-01-31 22:02:01 +08:00
// 3. 前缀文案列表(随机抽取)
prefixList: [
"今天,这张牌提醒你:",
"此刻,宇宙传递的信息:",
"这张牌想告诉你:",
"请收下这份指引:"
],
// 2. 完整的 22 张大阿尔卡那牌组(包含逆位含义)
cardList: [
{
name: "愚者",
2026-01-31 22:02:01 +08:00
keyword: "新的开始",
meaning: "放下恐惧,勇敢迈出第一步,世界充满无限可能。",
reversedMeaning: "也许有些太冲动了?停下来检查一下背包,确保安全再出发。",
energy: "外放",
2026-01-31 22:02:01 +08:00
image: "/images/yuzhe.png"
},
{
name: "魔术师",
2026-01-31 22:02:01 +08:00
keyword: "创造力",
meaning: "你拥有实现目标所需的一切资源,现在是行动的时候。",
reversedMeaning: "你是否低估了自己的能力?或者在犹豫不决?相信自己,工具就在手边。",
energy: "外放",
2026-01-31 22:02:01 +08:00
image: "/images/moshushi.png"
},
{
name: "女祭司",
2026-01-31 22:02:01 +08:00
keyword: "直觉",
meaning: "倾听内心的声音,答案就在你潜意识的深处。",
reversedMeaning: "暂时听不见内心的声音了吗?别急着向外寻找,先让自己静下来。",
energy: "内敛",
2026-01-31 22:02:01 +08:00
image: "/images/nvjishi.png"
},
{
name: "皇后",
2026-01-31 22:02:01 +08:00
keyword: "丰饶",
meaning: "享受生活的美好,去创造、去关爱,收获也是一种能力。",
reversedMeaning: "是否忽略了对自己的关爱?或者有些过度保护?给彼此一点呼吸的空间。",
energy: "柔和",
2026-01-31 22:02:01 +08:00
image: "/images/nvhuang.png"
},
{
name: "皇帝",
2026-01-31 22:02:01 +08:00
keyword: "秩序",
meaning: "建立规则和结构,理性地掌控局面,承担起责任。",
reversedMeaning: "有时候太固执会让人疲惫,适度放权,弹性也是一种力量。",
energy: "坚实",
2026-01-31 22:02:01 +08:00
image: "/images/huangdi.png"
},
{
name: "教皇",
2026-01-31 22:02:01 +08:00
keyword: "指引",
meaning: "寻求传统智慧的帮助,或者成为他人的精神导师。",
reversedMeaning: "不必拘泥于陈规旧矩,试着打破常规,寻找适合你自己的那条路。",
energy: "庄重",
2026-01-31 22:02:01 +08:00
image: "/images/jiaohuang.png"
},
{
name: "恋人",
2026-01-31 22:02:01 +08:00
keyword: "选择",
meaning: "跟随你的心,在关系中寻找和谐,做出忠于自我的决定。",
reversedMeaning: "现在的关系是否有些失衡?先爱自己,才能更好地爱别人。",
energy: "柔和",
2026-01-31 22:02:01 +08:00
image: "/images/lianren.png"
},
{
name: "战车",
2026-01-31 22:02:01 +08:00
keyword: "意志",
meaning: "认准目标,克服相反的力量,胜利属于坚持到底的人。",
reversedMeaning: "感觉失去了方向控制?慢下来,重新调整导航,欲速则不达。",
energy: "激进",
2026-01-31 22:02:01 +08:00
image: "/images/zhanche.png"
},
{
name: "力量",
2026-01-31 22:02:01 +08:00
keyword: "勇气",
meaning: "真正的力量是温柔的坚持,用耐心驯服内心的野兽。",
reversedMeaning: "不要怀疑自己的韧性,面对内心的软弱并不是坏事,那是温柔的开始。",
energy: "柔和",
2026-01-31 22:02:01 +08:00
image: "/images/liliang.png"
},
{
name: "隐士",
2026-01-31 22:02:01 +08:00
keyword: "内省",
meaning: "暂时远离喧嚣,向内探索,寻找属于你自己的光。",
reversedMeaning: "`是不是一个人待太久了?试着走出洞穴,和外界建立一点连接吧。",
energy: "潜沉",
2026-01-31 22:02:01 +08:00
image: "/images/yinzhe.png"
},
{
name: "命运之轮",
2026-01-31 22:02:01 +08:00
keyword: "转折",
meaning: "改变是永恒的,顺势而为,抓住出现在你面前的机会。",
reversedMeaning: "运气暂时不在你这边,但没关系,低谷正是蓄力反弹的好时机。",
energy: "流转",
2026-01-31 22:02:01 +08:00
image: "/images/mingyunzhilun.png"
},
{
name: "正义",
2026-01-31 22:02:01 +08:00
keyword: "因果",
meaning: "诚实面对真相,所有的决定都会带来相应的结果。",
reversedMeaning: "即使结果不尽如人意,也要问心无愧。对自己诚实,比什么都重要。",
energy: "平衡",
2026-01-31 22:02:01 +08:00
image: "/images/zhengyi.png"
},
{
name: "倒吊人",
2026-01-31 22:02:01 +08:00
keyword: "换位",
meaning: "换一个角度看世界,有时候牺牲是为了更大的获得。",
reversedMeaning: "挣扎只会更累,不如彻底放松,换个角度看世界,也许心结就解开了。",
energy: "停滞",
2026-01-31 22:02:01 +08:00
image: "/images/daodiaoren.png"
},
{
name: "死神",
2026-01-31 22:02:01 +08:00
keyword: "新生",
meaning: "结束是为了新的开始,彻底告别过去,才能拥抱未来。",
reversedMeaning: "还在紧抓着过去不放吗?只有腾出双手,才能接住新的礼物。",
energy: "决绝",
2026-01-31 22:02:01 +08:00
image: "/images/sishen.png"
},
{
name: "节制",
2026-01-31 22:02:01 +08:00
keyword: "平衡",
meaning: "在极端之间寻找中庸之道,融合对立,通过耐心获得疗愈。",
reversedMeaning: "是不是有些失衡了?在这忙乱的世界里,找回你内心的节奏和中点。",
energy: "融合",
2026-01-31 22:02:01 +08:00
image: "/images/jiezhi.png"
},
{
name: "恶魔",
2026-01-31 22:02:01 +08:00
keyword: "束缚",
meaning: "觉察那些控制你的欲望或习惯,唯有觉知才能带来解脱。",
reversedMeaning: "别被眼前的诱惑蒙蔽,或者感觉被束缚。其实锁链是松的,你随时可以走。",
energy: "沉重",
2026-01-31 22:02:01 +08:00
image: "/images/emo.png"
},
{
name: "高塔",
2026-01-31 22:02:01 +08:00
keyword: "崩塌",
meaning: "不要害怕突如其来的剧变,它在摧毁虚假的根基,让你重建真实。",
reversedMeaning: "废墟中反而能看清地基。既然倒了,正好可以按照你真正想要的样子重建。",
energy: "剧烈",
2026-01-31 22:02:01 +08:00
image: "/images/ta.png"
},
{
name: "星星",
2026-01-31 22:02:01 +08:00
keyword: "希望",
meaning: "黑暗之后必有星光,保持信心,未来充满治愈与灵感。",
reversedMeaning: "暂时看不见星星也没关系,它们还在那里。给自己一点信心,黎明就在前方。",
energy: "明亮",
2026-01-31 22:02:01 +08:00
image: "/images/xingxing.png"
},
{
name: "月亮",
2026-01-31 22:02:01 +08:00
keyword: "幻象",
meaning: "直面内心的不安与迷茫,并非所有事情都如表象般真实。",
reversedMeaning: "恐惧只是长长的影子,别被它吓住。随着天亮,迷雾终会散去。",
energy: "幽静",
2026-01-31 22:02:01 +08:00
image: "/images/yueliang.png"
},
{
name: "太阳",
2026-01-31 22:02:01 +08:00
keyword: "喜悦",
meaning: "快乐、成功与活力,尽情地发光热,享受当下的幸福。",
2026-01-31 22:02:01 +08:00
reversedMeaning: "乌云暂时遮住了阳光,但这只是暂时的。保持乐观,你心里的光谁也偷不走。",
energy: "灼热",
2026-01-31 22:02:01 +08:00
image: "/images/taiyang.png"
},
{
name: "审判",
2026-01-31 22:02:01 +08:00
keyword: "觉醒",
meaning: "听到内心的召唤,回顾过往,做出决定,获得新生。",
reversedMeaning: "不要对他人的评价太敏感,也不要自我怀疑。如果你准备好了,就出发吧。",
energy: "回响",
2026-01-31 22:02:01 +08:00
image: "/images/shenpan.png"
},
{
name: "世界",
2026-01-31 22:02:01 +08:00
keyword: "圆满",
meaning: "一段旅程的完美终点,成就感与整合,准备开始新的循环。",
reversedMeaning: "机遇还没完全成熟,再耐心一点点。检查一下还有什么细节没完成?",
energy: "圆融",
2026-01-31 22:02:01 +08:00
image: "/images/shijie.png"
}
],
// 合并大牌和小牌
allCards: [],
aiResult: null
2026-01-31 22:02:01 +08:00
},
onLoad: function () {
// 获取当前日期
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const dateStr = `${year}-${month}-${day}`;
this.setData({
todayDate: dateStr,
allCards: [...this.data.cardList, ...minorArcana]
2026-01-31 22:02:01 +08:00
});
console.log(`塔罗牌已就位,共 ${this.data.allCards.length} 张。`)
2026-01-31 22:02:01 +08:00
},
// --- 1. 选择牌阵 ---
selectSpread: function (e) {
const id = e.currentTarget.dataset.id;
const spread = this.data.spreads.find(s => s.id === id);
this.setData({
selectedSpread: spread,
state: 'asking' // 先进入提问状态
});
},
// --- 1.5 确认问题并开始洗牌 ---
confirmQuestion: function () {
const question = this.data.question.trim();
console.log('confirmQuestion 被调用,问题:', question);
// 新增:过滤验证
const filterResult = validateQuestion(question);
console.log('过滤结果:', filterResult);
if (filterResult.status === 'reject') {
console.log('触发拒绝弹窗');
// 显示拒绝弹窗
wx.showModal({
title: '温馨提示',
content: filterResult.reason,
showCancel: false,
confirmText: '我知道了'
});
return;
}
if (filterResult.status === 'rewrite') {
console.log('触发改写建议弹窗');
// 显示改写建议弹窗 - 简化版本
const firstSuggestion = filterResult.suggestions[0];
const modalContent = `${filterResult.reason}\n\n${firstSuggestion}`;
console.log('弹窗内容:', modalContent);
wx.showModal({
title: '提问建议',
content: modalContent,
confirmText: '使用建议',
cancelText: '继续提问',
success: (res) => {
console.log('弹窗回调:', res);
if (res.confirm) {
// 使用建议问题
this.setData({
question: firstSuggestion,
questionQuality: scoreQuestion(firstSuggestion)
});
}
// 无论选择哪个,都继续流程
this.proceedToShuffle();
},
fail: (err) => {
console.error('弹窗显示失败:', err);
// 即使弹窗失败,也继续流程
this.proceedToShuffle();
}
});
return;
}
console.log('通过验证,继续流程');
// pass 状态:直接继续
this.proceedToShuffle();
},
// 抽离洗牌逻辑
proceedToShuffle: function () {
const questionType = getQuestionType(this.data.question);
this.setData({
questionType: questionType,
state: 'shuffling'
});
// 模拟洗牌感应 2.5s
setTimeout(() => {
this.setData({ state: 'drawing' });
}, 2500);
},
// 输入问题
onQuestionInput: function (e) {
const question = e.detail.value;
const quality = scoreQuestion(question);
this.setData({
question: question,
questionQuality: quality
});
},
// --- 2. 抽牌逻辑 ---
onCardTap: function (e) {
const index = e.currentTarget.dataset.index;
let { drawnCardIndices, selectedSpread } = this.data;
if (drawnCardIndices.includes(index)) {
drawnCardIndices = drawnCardIndices.filter(i => i !== index);
} else if (drawnCardIndices.length < selectedSpread.cardCount) {
drawnCardIndices.push(index);
}
this.setData({ drawnCardIndices });
},
// --- 3. 确认开启 ---
confirmDraw: function () {
const { drawnCardIndices, allCards } = this.data;
const drawnCards = drawnCardIndices.map(() => {
const randomIndex = Math.floor(Math.random() * allCards.length);
const card = Object.assign({}, allCards[randomIndex]);
card.isReversed = Math.random() >= 0.5;
// 如果这张牌没有图片(如小牌),使用背面图作为占位符,或保持原有逻辑
if (!card.image) {
card.image = this.data.cardBackImage;
}
return card;
});
2026-01-31 22:02:01 +08:00
this.setData({
drawnCards,
state: 'flipping',
revealedCount: 0
2026-01-31 22:02:01 +08:00
});
},
2026-01-31 22:02:01 +08:00
// --- 4. 逐一翻牌 ---
revealNext: function (e) {
const index = e.currentTarget.dataset.index;
if (index === this.data.revealedCount) {
const nextCount = index + 1;
this.setData({
revealedCount: nextCount
});
// 如果全部翻开,触发解读
if (nextCount === this.data.selectedSpread.cardCount) {
this.setData({ state: 'revealed' });
this.showInterpretation();
}
}
},
// --- 展示解读内容 ---
showInterpretation: function () {
const infoAnim = wx.createAnimation({
duration: 800,
timingFunction: 'ease'
});
infoAnim.opacity(1).translateY(0).step();
this.setData({ infoAnimation: infoAnim.export() });
// 异步获取 AI 解读
this.fetchAiInterpretation();
2026-01-31 22:02:01 +08:00
},
// --- 获取 AI 解读主逻辑 ---
fetchAiInterpretation: async function () {
const { drawnCards, selectedSpread, question, questionType } = this.data;
if (!drawnCards.length) return;
2026-01-31 22:02:01 +08:00
this.setData({
isAiLoading: true,
aiResult: null,
aiLoadingText: '正在抽取牌面…'
});
// 1. 构建卡牌信息
const cardDetails = drawnCards.map((card, index) => {
return `位置[${selectedSpread.positions[index]}]${card.name} (${card.isReversed ? '逆位' : '正位'}),关键词:${card.keyword || (card.keywords ? card.keywords.join(',') : '')}`;
}).join('\n');
// 2. 构建参考牌义(供 AI 理解用)
const cardMeanings = drawnCards.map((card, index) => {
const meaning = card.isReversed ? (card.reversedMeaning || card.description) : (card.meaning || card.description);
return `${card.name} (${card.isReversed ? '逆位' : '正位'})${meaning}`;
}).join('\n');
// 3. 构建完整的用户提示
const userPrompt = `📥 输入信息
用户问题
${question || '未指定具体问题'}
提问领域
${questionType}
牌阵
${selectedSpread.name}
抽到的牌
${cardDetails}
参考牌义内部理解用
${cardMeanings}
参考牌义仅用于理解不允许逐字复述请严格围绕用户的问题进行解读`;
console.log("正在获取深度解读...");
console.log("用户问题:", question);
console.log("提问领域:", questionType);
// 4. 使用稳态请求管理器
const result = await safeAIRequest({
url: DEEPSEEK_BASE_URL,
header: {
'Authorization': `Bearer ${DEEPSEEK_API_KEY}`,
'Content-Type': 'application/json'
},
data: {
model: "deepseek-chat",
messages: [
{ role: "system", content: TAROT_PROMPT_ENHANCED },
{ role: "user", content: userPrompt }
],
stream: false,
response_format: { type: "json_object" }
},
drawnCards,
spread: selectedSpread,
onProgress: (text) => {
this.setData({ aiLoadingText: text });
}
2026-01-31 22:02:01 +08:00
});
// 5. 处理结果
if (result.status === 'blocked') {
wx.showToast({
title: result.message,
icon: 'none',
duration: 2000
});
this.setData({ isAiLoading: false });
return;
}
// 6. 生成整体趋势
const spreadStory = generateSpreadStory(drawnCards);
// 7. 更新数据
this.setData({
aiResult: result.data,
spreadStory: spreadStory,
isAiLoading: false
});
// 8. 如果是 fallback显示提示
if (result.status === 'fallback' && result.error) {
wx.showToast({
title: 'AI 解读失败,已使用备用解读',
icon: 'none',
duration: 3000
});
}
},
handleAiFallback: function () {
// 构建一个基础的回退对象
const fallback = {
theme: "能量感应受阻",
status: "当前思绪可能较为杂乱",
influence: "外界干扰或网络波动",
advice: "建议深呼吸,稍后再次尝试开启心灵对话",
positions: this.data.drawnCards.map((card, index) => ({
posName: this.data.selectedSpread.positions[index],
posMeaning: `${card.name}${card.isReversed ? '(逆位)' : '(正位)'}${card.isReversed ? (card.reversedMeaning || card.description) : (card.meaning || card.description)}`
}))
};
this.setData({
aiResult: fallback,
isAiLoading: false
});
},
resetSpread: function () {
this.setData({
state: 'spread_select',
selectedSpread: null,
drawnCardIndices: [],
drawnCards: [],
revealedCount: 0,
aiResult: null
});
},
// --- 导航优化:统一返回逻辑 ---
handleBack: function () {
wx.navigateBack({
delta: 1
});
}
})