1 line
12 KiB
JSON
1 line
12 KiB
JSON
{"repo": "albertzhuo123/usmle-miniprogram", "files": {"miniprogram/utils/storage.js": {"bad65e2e25d9e12c63e62ef06d3112623c0141c5": {"t": 1785745553.5154812, "n": 6288, "h": "e85d0c444f074a9f", "c": "const KEYS = {\n USER: 'usmle_user',\n SETTINGS: 'usmle_settings',\n RECORDS: 'usmle_records',\n WRONG_BOOK: 'usmle_wrongbook',\n FAVORITES: 'usmle_favorites',\n MOCK_HISTORY: 'usmle_mock_history',\n PROGRESS: 'usmle_progress',\n LOADED_PACKS: 'usmle_loaded_packs',\n DAILY_STATS: 'usmle_daily_stats',\n STREAK_CACHE: 'usmle_streak_cache',\n DEEPSEEK_API_KEY: 'usmle_deepseek_api_key'\n}\n\nconst MAX_RECORDS = 2000\n\nfunction getToday() {\n const d = new Date()\n return [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-')\n}\n\nfunction get(key) {\n try {\n return wx.getStorageSync(key) || null\n } catch (e) {\n return null\n }\n}\n\nfunction set(key, value) {\n try {\n wx.setStorageSync(key, value)\n } catch (e) {\n console.error('Storage write error', e)\n }\n}\n\nfunction getRecords() {\n return get(KEYS.RECORDS) || []\n}\n\nfunction addRecord(record) {\n let records = getRecords()\n records.push({\n qid: record.qid,\n selected: record.selected,\n isCorrect: record.isCorrect,\n ts: Date.now()\n })\n if (records.length > MAX_RECORDS) {\n records = records.slice(records.length - MAX_RECORDS)\n }\n set(KEYS.RECORDS, records)\n}\n\nfunction getWrongBook() {\n return get(KEYS.WRONG_BOOK) || {}\n}\n\nfunction addToWrongBook(qid, isCorrect) {\n const wb = getWrongBook()\n if (!wb[qid]) {\n if (isCorrect) return\n wb[qid] = { wrongCount: 0, rightCount: 0, lastWrongTs: 0, mastered: false }\n }\n if (isCorrect) {\n wb[qid].rightCount++\n if (wb[qid].rightCount >= 2) {\n wb[qid].mastered = true\n }\n } else {\n wb[qid].wrongCount++\n wb[qid].lastWrongTs = Date.now()\n wb[qid].mastered = false\n }\n set(KEYS.WRONG_BOOK, wb)\n}\n\nfunction removeFromWrongBook(qid) {\n const wb = getWrongBook()\n delete wb[qid]\n set(KEYS.WRONG_BOOK, wb)\n}\n\nfunction markWrongMastered(qid) {\n const wb = getWrongBook()\n if (wb[qid]) {\n wb[qid].mastered = true\n wb[qid].rightCount = 2\n set(KEYS.WRONG_BOOK, wb)\n }\n}\n\nfunction getFavorites() {\n return get(KEYS.FAVORITES) || []\n}\n\nfunction addFavorite(qid) {\n const favs = getFavorites()\n if (!favs.includes(qid)) {\n favs.push(qid)\n set(KEYS.FAVORITES, favs)\n }\n}\n\nfunction removeFavorite(qid) {\n let favs = getFavorites()\n favs = favs.filter(f => f !== qid)\n set(KEYS.FAVORITES, favs)\n}\n\nfunction isFavorite(qid) {\n const favs = getFavorites()\n return favs.includes(qid)\n}\n\nfunction getMockHistory() {\n const history = get(KEYS.MOCK_HISTORY) || []\n return history.slice(-20)\n}\n\nfunction addMockHistory(exam) {\n let history = get(KEYS.MOCK_HISTORY) || []\n history.push({\n ...exam,\n ts: Date.now()\n })\n if (history.length > 20) {\n history = history.slice(history.length - 20)\n }\n set(KEYS.MOCK_HISTORY, history)\n}\n\nfunction getProgress() {\n return get(KEYS.PROGRESS) || { cursor: 0 }\n}\n\nfunction setProgress(cursor) {\n set(KEYS.PROGRESS, { cursor })\n}\n\nfunction getStepProgress(step) {\n const key = KEYS.PROGRESS + '_' + step\n return get(key) || { cursor: 0 }\n}\n\nfunction setStepProgress(step, cursor) {\n const key = KEYS.PROGRESS + '_' + step\n set(key, { cursor })\n}\n\nfunction getDailyStats() {\n return get(KEYS.DAILY_STATS) || {}\n}\n\nfunction updateDailyStats(isCorrect) {\n const stats = getDailyStats()\n const today = getToday()\n if (!stats[today]) {\n stats[today] = { done: 0, correct: 0 }\n }\n stats[today].done++\n if (isCorrect) {\n stats[today].correct++\n }\n const keys = Object.keys(stats)\n if (keys.length > 60) {\n keys.slice(0, keys.length - 60).forEach(k => delete stats[k])\n }\n set(KEYS.DAILY_STATS, stats)\n\n const streak = getStreak()\n if (!streak.lastDate || streak.lastDate !== today) {\n const yesterday = getYesterday()\n if (streak.lastDate === yesterday) {\n streak.current++\n } else {\n streak.current = 1\n }\n streak.lastDate = today\n if (streak.current > streak.max) {\n streak.max = streak.current\n }\n set(KEYS.STREAK_CACHE, streak)\n }\n}\n\nfunction getStreak() {\n const defaultStreak = {\n current: 0,\n max: 0,\n lastDate: ''\n }\n return get(KEYS.STREAK_CACHE) || defaultStreak\n}\n\nfunction getYesterday() {\n const d = new Date()\n d.setDate(d.getDate() - 1)\n return [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-')\n}\n\nfunction getOverallStats() {\n const records = getRecords()\n const totalDone = records.length\n const correctCount = records.filter(r => r.isCorrect).length\n const wrongCount = totalDone - correctCount\n const correctRate = totalDone > 0 ? Math.round((correctCount / totalDone) * 100) : 0\n const wb = getWrongBook()\n const wrongTotal = Object.keys(wb).filter(k => !wb[k].mastered).length\n const streak = getStreak()\n const todayStats = getDailyStats()[getToday()] || { done: 0, correct: 0 }\n\n return {\n totalDone,\n correctCount,\n wrongCount,\n correctRate,\n wrongBookCount: wrongTotal,\n streakCurrent: streak.current,\n streakMax: streak.max,\n todayDone: todayStats.done,\n todayCorrect: todayStats.correct\n }\n}\n\nfunction getRecentDailyStats(days = 30) {\n const stats = getDailyStats()\n const result = []\n for (let i = days - 1; i >= 0; i--) {\n const d = new Date()\n d.setDate(d.getDate() - i)\n const key = [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-')\n result.push({\n date: key,\n done: (stats[key] && stats[key].done) || 0,\n correct: (stats[key] && stats[key].correct) || 0\n })\n }\n return result\n}\n\nfunction clearAllData() {\n Object.values(KEYS).forEach(k => {\n // 保留用户设置和 API Key,仅清除学习数据\n if (k === KEYS.SETTINGS || k === KEYS.DEEPSEEK_API_KEY || k === KEYS.USER) return\n try { wx.removeStorageSync(k) } catch (e) {}\n })\n}\n\nfunction getDeepseekApiKey() {\n return get(KEYS.DEEPSEEK_API_KEY) || ''\n}\n\nfunction setDeepseekApiKey(key) {\n set(KEYS.DEEPSEEK_API_KEY, key ? key.trim() : '')\n}\n\nmodule.exports = {\n KEYS,\n get,\n set,\n getRecords,\n addRecord,\n getWrongBook,\n addToWrongBook,\n removeFromWrongBook,\n markWrongMastered,\n getFavorites,\n addFavorite,\n removeFavorite,\n isFavorite,\n getMockHistory,\n addMockHistory,\n getProgress,\n setProgress,\n getStepProgress,\n setStepProgress,\n getDailyStats,\n updateDailyStats,\n getStreak,\n getOverallStats,\n getRecentDailyStats,\n clearAllData,\n getToday,\n getDeepseekApiKey,\n setDeepseekApiKey\n}\n"}}, "miniprogram/utils/deepseek.js": {"bad65e2e25d9e12c63e62ef06d3112623c0141c5": {"t": 1785746000.2049835, "n": 4527, "h": "fb28a59d8ac228c3", "c": "/**\n * DeepSeek API 服务模块\n * 用于调用 DeepSeek Chat API 对 USMLE 题目进行深度解析\n *\n * 时间:2026-07-18 22:02:49\n */\n\nconst API_BASE = 'https://api.deepseek.com'\nconst API_ENDPOINT = '/chat/completions'\nconst DEFAULT_MODEL = 'deepseek-v4-pro'\n\n/**\n * 构造 USMLE 专用提示词\n * @param {Object} question 题目对象\n * @param {number} selectedIndex 用户选择的选项索引\n */\nfunction buildUsmlePrompt(question, selectedIndex, step, userQuestion) {\n const stem = question.stem || question.question || ''\n const options = (question.options || []).map((opt, idx) => {\n const label = String.fromCharCode(65 + idx)\n const isCorrect = idx === question.answer\n const isUserChoice = idx === selectedIndex\n let tag = ''\n if (isCorrect) tag = ' [正确答案]'\n if (isUserChoice && !isCorrect) tag = ' [用户选择]'\n return `${label}. ${opt}${tag}`\n }).join('\\n')\n\n const userChoiceLabel = selectedIndex >= 0\n ? String.fromCharCode(65 + selectedIndex)\n : '未作答'\n const correctLabel = question.answer >= 0\n ? String.fromCharCode(65 + question.answer)\n : '未知'\n\n const effectiveStep = step || question.step || ''\n const stepInfo = effectiveStep && effectiveStep !== 'all'\n ? `本题所属范围:${effectiveStep === 'step1' ? 'Step 1' : 'Step 2/3'}`\n : ''\n\n const userQuestionBlock = userQuestion\n ? `\\n【学员疑惑】\\n${userQuestion}\\n`\n : ''\n\n const targetedSection = userQuestion\n ? `3. **针对性解答**:先复述学员的疑惑,然后给出针对性解答。\\n`\n : ''\n\n const wordLimit = userQuestion ? '600' : '400'\n\n return `你是一位资深的 USMLE(美国执业医师资格考试)辅导专家。请对以下题目进行简明解析,帮助考生快速掌握解题关键。\n\n【题目】\n${stem}\n\n【选项】\n${options}\n\n【作答信息】\n- 用户选择:${userChoiceLabel}\n- 正确答案:${correctLabel}\n${stepInfo ? '- ' + stepInfo : ''}${userQuestionBlock}\n\n【解析要求】\n请直接输出以下内容,不要展示完整的逐步推理过程,控制总字数在 ${wordLimit} 字以内:\n1. **解题思路**:一句话点明本题考点,然后简明扼要说明为什么正确答案是最佳选项。\n2. **选项辨析**:对每个错误选项,用一句话说明它为什么不对,以及它通常对应什么情境或鉴别诊断。\n${targetedSection}使用中文回答,医学专业术语可保留英文原文。`\n}\n\n/**\n * 调用 DeepSeek Chat API\n * @param {Object} params\n * @param {string} params.apiKey DeepSeek API Key\n * @param {Object} params.question 题目对象\n * @param {number} params.selectedIndex 用户选择索引\n * @param {string} [params.userQuestion] 学员的疑惑点\n * @returns {Promise<{thinking: string, content: string}>}\n */\nfunction explainQuestion({ apiKey, question, selectedIndex, step, userQuestion }) {\n return new Promise((resolve, reject) => {\n if (!apiKey || typeof apiKey !== 'string' || apiKey.trim().length === 0) {\n reject(new Error('请先前往“设置”页面填写 DeepSeek API Key'))\n return\n }\n\n if (!question) {\n reject(new Error('题目信息不完整'))\n return\n }\n\n const prompt = buildUsmlePrompt(question, selectedIndex, step, userQuestion)\n\n wx.request({\n url: `${API_BASE}${API_ENDPOINT}`,\n method: 'POST',\n timeout: 60000,\n header: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey.trim()}`\n },\n data: {\n model: DEFAULT_MODEL,\n messages: [\n { role: 'system', content: 'You are an expert USMLE tutor. Provide detailed, clinically-oriented explanations in Chinese, keeping medical terminology in English where appropriate.' },\n { role: 'user', content: prompt }\n ],\n thinking: { type: 'enabled' },\n reasoning_effort: 'high',\n stream: false\n },\n success: (res) => {\n if (res.statusCode !== 200 || !res.data) {\n let msg = `请求失败,状态码 ${res.statusCode}`\n if (res.data && res.data.error) {\n msg = `API 错误 (${res.statusCode}): ${res.data.error.message || JSON.stringify(res.data.error)}`\n } else if (typeof res.data === 'string') {\n msg = `请求失败 (${res.statusCode}): ${res.data.slice(0, 200)}`\n }\n reject(new Error(msg))\n return\n }\n\n const choice = res.data.choices && res.data.choices[0]\n if (!choice || !choice.message) {\n reject(new Error('API 返回格式异常,缺少 choices 或 message'))\n return\n }\n\n const content = choice.message.content || ''\n resolve({ thinking: '', content })\n },\n fail: (err) => {\n let msg = '网络请求失败'\n if (err && err.errMsg) {\n msg = err.errMsg\n if (err.errMsg.indexOf('fail url not in domain list') !== -1) {\n msg = '域名未加入白名单:请在小程序后台添加 https://api.deepseek.com 为 request 合法域名,或在开发者工具中关闭“校验合法域名”。'\n } else if (err.errMsg.indexOf('timeout') !== -1) {\n msg = '请求超时,请检查网络或稍后重试'\n }\n }\n reject(new Error(msg))\n }\n })\n })\n}\n\nmodule.exports = {\n explainQuestion,\n buildUsmlePrompt\n}\n"}}}} |