{"repo": "c-shunyi/pink-ledger", "files": {"pink-ledger-node/.env": {"829a14e8a8dbde9b40fd4602fd3ebca074e6ae36": {"t": 1785745091.7968197, "n": 413, "h": "2173677013a2a6a7", "c": "# JWT Configuration\nJWT_SECRET=pink_ledger_super_secret_key_2024_secure_random_string_please_change_in_production\nJWT_EXPIRES_IN=60d\n\n# Server Configuration\nPORT=8860\nNODE_ENV=development\n\n# Database Configuration\nDB_PATH=./database.sqlite\n\n# 微信小程序配置(用于微信一键登录)\nWECHAT_APPID=wxc140eef540b899db\nWECHAT_APP_SECRET=9ecd9693ad3627ae9bef4b92e5c0b25f\n\nDEEPSEEK_API_KEY=sk-GwTsGxoPmDCsO47P6aT5oF2IWoJS7taY5EL6KNbqrIs2Yo3V"}, "37095846540c73810416deebe57c7c3d530f76a6": {"t": 1785747744.9143991, "n": 413, "h": "2173677013a2a6a7", "c": "# JWT Configuration\nJWT_SECRET=pink_ledger_super_secret_key_2024_secure_random_string_please_change_in_production\nJWT_EXPIRES_IN=60d\n\n# Server Configuration\nPORT=8860\nNODE_ENV=development\n\n# Database Configuration\nDB_PATH=./database.sqlite\n\n# 微信小程序配置(用于微信一键登录)\nWECHAT_APPID=wxc140eef540b899db\nWECHAT_APP_SECRET=9ecd9693ad3627ae9bef4b92e5c0b25f\n\nDEEPSEEK_API_KEY=sk-GwTsGxoPmDCsO47P6aT5oF2IWoJS7taY5EL6KNbqrIs2Yo3V"}, "6e1823d59d3d0f9cc732c52b1a95c67b7a7e7b38": {"t": 1785747747.224378, "n": 292, "h": "3ddf7195f6148056", "c": "# JWT Configuration\nJWT_SECRET=pink_ledger_super_secret_key_2024_secure_random_string_please_change_in_production\nJWT_EXPIRES_IN=7d\n\n# Server Configuration\nPORT=8860\nNODE_ENV=development\n\n# Database Configuration\nDB_PATH=./database.sqlite\n\n# Additional Security (optional)\n# BCRYPT_ROUNDS=12\n"}, "8d4bf7b2b6e0440ad129ceccc087e0a455317a89": {"t": 1785747748.3251812, "n": 292, "h": "65b3d848bac5744f", "c": "# JWT Configuration\nJWT_SECRET=pink_ledger_super_secret_key_2024_secure_random_string_please_change_in_production\nJWT_EXPIRES_IN=7d\n\n# Server Configuration\nPORT=3000\nNODE_ENV=development\n\n# Database Configuration\nDB_PATH=./database.sqlite\n\n# Additional Security (optional)\n# BCRYPT_ROUNDS=12\n"}}, "demo.js": {"829a14e8a8dbde9b40fd4602fd3ebca074e6ae36": {"t": 1785809872.143768, "n": 2163, "h": "f73501aac5f9d420", "c": "/**\n * DMXAPI 对话接口调用示例\n * 功能:使用 gpt-5-mini 模型进行智能对话\n */\n\nconst https = require('https');\n\n// ==================== API 配置 ====================\n\n// API 接口地址\nconst url = \"https://www.dmxapi.cn/v1/chat/completions\";\n\n// 请求头配置\nconst headers = {\n \"Authorization\": \"sk-GwTsGxoPmDCsO47P6aT5oF2IWoJS7taY5EL6KNbqrIs2Yo3V\", // 替换为你的 DMXAPI 令牌\n \"Content-Type\": \"application/json\"\n};\n\n// ==================== 请求参数 ====================\n\n// 构造请求数据\nconst payload = {\n model: \"DeepSeek-V3.2\", // 选择使用的模型\n messages: [\n {\n role: \"system\",\n content: `\n 你是一个账单处理系统,并且只返回JSON数据,不需要标记是什么数据,只要返回JSON` // 系统提示词:定义 AI 助手的角色\n },\n {\n role: \"user\",\n content: `\n 分类数据为:\n [{type: \"餐饮\", id: \"25\"},\n {type: \"零食\", id: \"22\"},\n {type: \"日用\", id: \"23\"},\n {type: \"交通\", id: \"24\"}]\n `\n },\n {\n role: \"user\",\n content: `\n 我的账单是:早餐在麦当劳吃了2个汉堡,花费了25元,然后打车回家花了了了40\n `\n },\n {\n role: \"user\",\n content: `\n 基于分类数据整理账单\n 请分类并且返回JSON格式数据\n [{\"typeId\": xxx, \"money\": xxx, \"remark\": xxx}]\n `\n }\n ]\n};\n\n// ==================== 发送请求 ====================\n\nconst data = JSON.stringify(payload);\n\nconst options = {\n method: 'POST',\n headers: {\n ...headers,\n 'Content-Length': Buffer.byteLength(data)\n }\n};\n\nconst req = https.request(url, options, (res) => {\n let responseData = '';\n\n // 接收数据\n res.on('data', (chunk) => {\n responseData += chunk;\n });\n\n // 请求完成\n res.on('end', () => {\n try {\n console.log(\"=\".repeat(50));\n console.log(\"API 响应结果:\");\n console.log(\"=\".repeat(50));\n console.log(JSON.stringify(JSON.parse(responseData), null, 2));\n } catch (error) {\n console.error(\"❌ 解析响应失败:\", error.message);\n console.log(\"原始响应:\", responseData);\n }\n });\n});\n\n// 错误处理\nreq.on('error', (error) => {\n console.error(`❌ 请求失败: ${error.message}`);\n});\n\n// 发送请求数据\nreq.write(data);\nreq.end();\n"}}, "pink-ledger-node/src/controllers/aiController.js": {"829a14e8a8dbde9b40fd4602fd3ebca074e6ae36": {"t": 1785809874.6288776, "n": 4832, "h": "dcd038cd7a3bcc0b", "c": "const axios = require('axios');\nconst { Op } = require('sequelize');\nconst { Category } = require('../models');\nconst sendResponse = require('../utils/response');\n\nconst DATE_REGEX = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nconst buildCategoryData = (categories) => categories.map((cat) => ({\n id: cat.id,\n name: cat.name,\n type: cat.type\n}));\n\nconst normalizeBills = (bills, categories) => {\n const categoryMap = new Map(categories.map((cat) => [cat.id, cat]));\n const today = new Date().toISOString().split('T')[0];\n\n return bills\n .map((bill) => {\n const categoryId = Number(bill.categoryId);\n const category = categoryMap.get(categoryId);\n const amount = Number(bill.amount);\n const date = typeof bill.date === 'string' && DATE_REGEX.test(bill.date)\n ? bill.date\n : today;\n\n if (!category || !Number.isFinite(amount) || amount <= 0) {\n return null;\n }\n\n return {\n categoryId,\n type: category.type,\n amount,\n date,\n description: String(bill.description || bill.remark || '').trim()\n };\n })\n .filter(Boolean);\n};\n\n// AI 智能解析账单\nexports.parseSmartBilling = async (req, res) => {\n try {\n const { text } = req.body;\n const userId = req.userId;\n\n if (!text || text.trim().length === 0) {\n return sendResponse(res, {\n code: 400,\n msg: '请输入账单描述'\n });\n }\n\n if (!process.env.DEEPSEEK_API_KEY) {\n return sendResponse(res, {\n code: 500,\n msg: '未配置 DEEPSEEK_API_KEY'\n });\n }\n\n const categories = await Category.findAll({\n where: {\n [Op.or]: [\n { isSystem: true },\n { userId }\n ]\n },\n attributes: ['id', 'name', 'type'],\n order: [['sortOrder', 'ASC']]\n });\n\n if (categories.length === 0) {\n return sendResponse(res, {\n code: 400,\n msg: '暂无可用分类,请先创建分类'\n });\n }\n\n const aiResult = await callDeepSeekAPI(text.trim(), buildCategoryData(categories));\n\n if (!aiResult.success) {\n return sendResponse(res, {\n code: 500,\n msg: aiResult.error || 'AI 解析失败'\n });\n }\n\n const parsedBills = normalizeBills(aiResult.data, categories);\n\n if (parsedBills.length === 0) {\n return sendResponse(res, {\n code: 400,\n msg: 'AI 未能识别出有效的账单信息'\n });\n }\n\n return sendResponse(res, {\n code: 200,\n msg: '解析成功',\n data: { bills: parsedBills }\n });\n } catch (error) {\n console.error('AI 智能解析失败:', error);\n return sendResponse(res, {\n code: 500,\n msg: 'AI 解析失败,请稍后重试'\n });\n }\n};\n\nconst extractJson = (content) => {\n const jsonMatch = content.match(/```json\\s*([\\s\\S]*?)\\s*```/i)\n || content.match(/```\\s*([\\s\\S]*?)\\s*```/i);\n return jsonMatch ? jsonMatch[1] : content;\n};\n\nconst callDeepSeekAPI = async (userText, categories) => {\n const apiUrl = process.env.DEEPSEEK_API_URL || 'https://www.dmxapi.cn/v1/chat/completions';\n const apiKey = process.env.DEEPSEEK_API_KEY;\n const model = process.env.DEEPSEEK_MODEL || 'DeepSeek-V3.2';\n const timeout = parseInt(process.env.DEEPSEEK_TIMEOUT_MS, 10) || 30000;\n\n const today = new Date().toISOString().split('T')[0];\n const payload = {\n model,\n messages: [\n {\n role: 'system',\n content: `你是一个账单处理系统,只返回 JSON 数据,不需要任何其他文字说明。\n今天日期:${today}\n规则:\n1. 从用户描述中提取所有账单项\n2. 每笔账单必须包含:categoryId(分类ID)、amount(金额,正数)、date(交易日期,YYYY-MM-DD)、description(备注)\n3. 如果用户未明确日期,date 使用今天(YYYY-MM-DD)\n4. 根据分类的 type 字段判断收入还是支出\n5. 如果无法识别分类,使用最相近的分类\n6. 金额必须是数字,不要包含货币符号\n7. 返回格式:[{\"categoryId\": 数字, \"amount\": 数字, \"date\": \"YYYY-MM-DD\", \"description\": \"字符串\"}]`\n },\n {\n role: 'user',\n content: `可用分类数据:\\n${JSON.stringify(categories, null, 2)}`\n },\n {\n role: 'user',\n content: `用户账单描述:${userText}`\n },\n {\n role: 'user',\n content: '请解析账单并返回 JSON 数组格式:[{\"categoryId\": xxx, \"amount\": xxx, \"date\": \"YYYY-MM-DD\", \"description\": \"xxx\"}]'\n }\n ],\n temperature: 0.2\n };\n\n try {\n const response = await axios.post(apiUrl, payload, {\n headers: {\n // 如需 Bearer 前缀,请在环境变量中自行拼接\n Authorization: apiKey,\n 'Content-Type': 'application/json'\n },\n timeout\n });\n\n const content = response.data?.choices?.[0]?.message?.content;\n if (!content) {\n return { success: false, error: 'AI 响应格式异常' };\n }\n\n const jsonText = extractJson(content.trim());\n const parsed = JSON.parse(jsonText);\n const bills = Array.isArray(parsed) ? parsed : parsed?.bills;\n\n if (!Array.isArray(bills)) {\n return { success: false, error: 'AI 返回数据格式错误' };\n }\n\n return { success: true, data: bills };\n } catch (error) {\n console.error('调用 AI API 失败:', error?.response?.data || error.message || error);\n return { success: false, error: 'AI 请求失败' };\n }\n};\n"}}}}