Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/ahhuu_fitnessdiary.json
T

1 line
90 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "ahhuu/fitnessdiary", "files": {"app/src/main/java/com/cz/fitnessdiary/service/DailyBriefingService.java": {"a8e4b93c9016240a60acf90e1d181be631703401": {"t": 1785745665.1336975, "n": 14400, "h": "fee13e46f1652082", "c": "package com.cz.fitnessdiary.service;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport com.cz.fitnessdiary.BuildConfig;\nimport com.cz.fitnessdiary.database.AppDatabase;\nimport com.cz.fitnessdiary.database.entity.User;\nimport com.cz.fitnessdiary.model.DailyHealthSnapshot;\nimport com.cz.fitnessdiary.model.HealthScoreBreakdown;\nimport com.cz.fitnessdiary.model.WeeklyTrend;\nimport com.cz.fitnessdiary.repository.HealthAggregationRepository;\nimport com.cz.fitnessdiary.utils.DateUtils;\nimport com.cz.fitnessdiary.utils.HealthScoreCalculator;\nimport com.google.gson.Gson;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonSyntaxException;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n/**\n * AI 日报服务 - v3.0\n * 负责生成、缓存每日健康简报。\n * 优先使用 DeepSeek AI 生成,失败或未配置时降级为本地规则引擎。\n */\npublic class DailyBriefingService {\n\n private static final String PREFS_NAME = \"daily_briefing_prefs\";\n private static final String KEY_CACHED_BRIEFING = \"cached_briefing\";\n private static final String KEY_CACHED_DATE = \"cached_date\";\n private static final String KEY_CACHED_AT = \"cached_at\";\n\n private static final long ONE_DAY_MS = 86400000L;\n\n private final Application application;\n private final HealthAggregationRepository aggregationRepo;\n private final SharedPreferences prefs;\n private final ExecutorService executor;\n private final Handler mainHandler;\n private final Gson gson;\n\n /**\n * 每日简报数据类\n */\n public static class DailyBriefing {\n public String greeting;\n public String scoreComment;\n public List<String> highlights;\n public String suggestion;\n public String motivation;\n public boolean isLocal;\n public long generatedAt;\n\n public DailyBriefing() {\n this.highlights = new ArrayList<>();\n }\n }\n\n /**\n * 简报生成回调接口\n */\n public interface DailyBriefingCallback {\n void onBriefingReady(DailyBriefing briefing);\n }\n\n public DailyBriefingService(Application application) {\n this.application = application;\n this.aggregationRepo = new HealthAggregationRepository(application);\n this.prefs = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);\n this.executor = Executors.newSingleThreadExecutor();\n this.mainHandler = new Handler(Looper.getMainLooper());\n this.gson = new Gson();\n }\n\n // ================================================================\n // 公共 API\n // ================================================================\n\n /**\n * 获取缓存的简报(仅在缓存日期为今日时有效)\n *\n * @return 缓存的 DailyBriefing,若无缓存或已过期则返回 null\n */\n public DailyBriefing getCachedBriefing() {\n long cachedDate = prefs.getLong(KEY_CACHED_DATE, 0);\n long today = DateUtils.getTodayStartTimestamp();\n if (cachedDate != today) {\n return null;\n }\n String json = prefs.getString(KEY_CACHED_BRIEFING, null);\n if (json == null || json.isEmpty()) {\n return null;\n }\n try {\n return gson.fromJson(json, DailyBriefing.class);\n } catch (JsonSyntaxException e) {\n prefs.edit().remove(KEY_CACHED_BRIEFING).apply();\n return null;\n }\n }\n\n /**\n * 清除缓存的简报\n */\n public void invalidateCache() {\n prefs.edit()\n .remove(KEY_CACHED_BRIEFING)\n .remove(KEY_CACHED_DATE)\n .remove(KEY_CACHED_AT)\n .apply();\n }\n\n /**\n * 生成今日简报(异步)\n * <p>\n * 流程:获取昨日数据快照 -> 评分 -> 尝试 AI 生成 -> 失败降级本地生成 -> 缓存 -> 回调\n *\n * @param callback 回调接口,在主线程执行\n */\n public void generateBriefing(DailyBriefingCallback callback) {\n executor.execute(() -> {\n try {\n // 1. 获取昨日数据\n long yesterday = DateUtils.getTodayStartTimestamp() - ONE_DAY_MS;\n DailyHealthSnapshot snapshot = aggregationRepo.getDateSnapshot(yesterday);\n HealthScoreCalculator.UserProfile profile = buildUserProfile();\n HealthScoreBreakdown breakdown = HealthScoreCalculator.calculateBreakdown(snapshot, profile);\n snapshot.healthScore = breakdown.totalScore;\n\n // 2. 尝试 AI 生成\n if (isDirectAiConfigured()) {\n callAiForBriefing(snapshot, breakdown, callback);\n } else {\n // 3. 降级为本地生成\n DailyBriefing briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n cacheBriefing(briefing);\n mainHandler.post(() -> callback.onBriefingReady(briefing));\n }\n } catch (Exception e) {\n // 异常情况下生成最简简报\n DailyBriefing fallback = createEmergencyBriefing();\n mainHandler.post(() -> callback.onBriefingReady(fallback));\n }\n });\n }\n\n // ================================================================\n // 内部方法\n // ================================================================\n\n /** Direct personal AI is optional; local generation remains the safe fallback. */\n private boolean isDirectAiConfigured() {\n return !BuildConfig.DEEPSEEK_API_KEY.trim().isEmpty();\n }\n\n /**\n * 调用 DeepSeek AI 生成简报\n */\n private void callAiForBriefing(DailyHealthSnapshot snapshot,\n HealthScoreBreakdown breakdown,\n DailyBriefingCallback callback) {\n List<WeeklyTrend> trends = aggregationRepo.getWeeklyTrends();\n String userPrompt = buildUserPrompt(snapshot, breakdown, trends);\n String systemPrompt = \"你是专业健康教练。基于用户健康数据生成今日简报。\"\n + \"以JSON格式输出,不要包含markdown代码块标记:\"\n + \"{\\\"greeting\\\":\\\"...\\\",\\\"scoreComment\\\":\\\"...\\\",\"\n + \"\\\"highlights\\\":[\\\"...\\\"],\\\"suggestion\\\":\\\"...\\\",\\\"motivation\\\":\\\"...\\\"}\";\n\n DeepSeekService.sendMessage(userPrompt, systemPrompt,\n new AICallback() {\n @Override\n public void onSuccess(String response, String reasoning) {\n DailyBriefing briefing = parseAiResponse(response);\n if (briefing == null) {\n // JSON 解析失败,降级本地生成\n briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n } else {\n briefing.isLocal = false;\n }\n cacheBriefing(briefing);\n callback.onBriefingReady(briefing);\n }\n\n @Override\n public void onError(String error) {\n // AI 调用失败,降级本地生成\n DailyBriefing briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n cacheBriefing(briefing);\n callback.onBriefingReady(briefing);\n }\n\n @Override\n public void onPartialUpdate(String content, String reasoning) {\n // 非流式模式,无需处理\n }\n });\n }\n\n /**\n * 构建 AI 用户 Prompt\n */\n private String buildUserPrompt(DailyHealthSnapshot snapshot,\n HealthScoreBreakdown breakdown,\n List<WeeklyTrend> trends) {\n StringBuilder sb = new StringBuilder();\n sb.append(\"请根据以下用户昨日健康数据生成一份简报。\\n\\n\");\n sb.append(\"【昨日数据】\\n\");\n sb.append(\"- 健康评分:\").append(breakdown.totalScore).append(\"/100\\n\");\n sb.append(\"- 运动消耗:\").append(snapshot.exerciseCalories).append(\" 千卡\\n\");\n sb.append(\"- 饮食摄入:\").append(snapshot.dietCalories).append(\" 千卡\\n\");\n sb.append(\"- 基础代谢:\").append(snapshot.bmr).append(\" 千卡\\n\");\n sb.append(\"- 热量平衡:\").append(snapshot.energyBalance >= 0 ? \"+\" : \"\")\n .append(snapshot.energyBalance).append(\" 千卡\\n\");\n sb.append(\"- 步数:\").append(snapshot.steps).append(\"\\n\");\n sb.append(\"- 睡眠时长:\").append(String.format(java.util.Locale.getDefault(),\n \"%.1f\", snapshot.sleepHours)).append(\" 小时\\n\");\n sb.append(\"- 睡眠质量:\").append(snapshot.sleepQuality).append(\"/5\\n\");\n sb.append(\"- 饮水量:\").append(snapshot.waterMl).append(\" ml\\n\");\n sb.append(\"- 完成计划:\").append(snapshot.completedPlans)\n .append(\"/\").append(snapshot.totalPlans).append(\"\\n\");\n sb.append(\"- 连续打卡:\").append(snapshot.consecutiveDays).append(\" 天\\n\");\n sb.append(\"- 心情等级:\").append(snapshot.moodLevel).append(\"/5\\n\");\n if (snapshot.currentPlanName != null) {\n sb.append(\"- 当前计划:\").append(snapshot.currentPlanName).append(\"\\n\");\n }\n\n // 评分明细\n sb.append(\"\\n【评分明细】\\n\");\n sb.append(\"- 运动评分:\").append(breakdown.exerciseScore).append(\"/25\\n\");\n sb.append(\"- 饮食评分:\").append(breakdown.dietScore).append(\"/25\\n\");\n sb.append(\"- 习惯评分:\").append(breakdown.habitsScore).append(\"/20\\n\");\n sb.append(\"- 身体指标评分:\").append(breakdown.bodyMetricsScore).append(\"/15\\n\");\n sb.append(\"- 坚持度评分:\").append(breakdown.consistencyScore).append(\"/15\\n\");\n\n // 7日趋势\n if (trends != null && !trends.isEmpty()) {\n sb.append(\"\\n【近7日趋势】\\n\");\n for (WeeklyTrend trend : trends) {\n sb.append(\"- \").append(trend.label).append(\":\").append(trend.getChangeText()).append(\"\\n\");\n }\n }\n\n sb.append(\"\\n请以JSON格式输出(仅JSON,不含markdown标记):\\n\");\n sb.append(\"{\\\"greeting\\\":\\\"问候语\\\",\\\"scoreComment\\\":\\\"评分评价\\\",\");\n sb.append(\"\\\"highlights\\\":[\\\"亮点1\\\",\\\"亮点2\\\"],\");\n sb.append(\"\\\"suggestion\\\":\\\"一条建议\\\",\\\"motivation\\\":\\\"一句鼓励\\\"}\");\n return sb.toString();\n }\n\n /**\n * 解析 AI 返回的 JSON 响应\n *\n * @param response AI 返回的原始字符串\n * @return 解析成功的 DailyBriefing,失败返回 null\n */\n private DailyBriefing parseAiResponse(String response) {\n if (response == null || response.trim().isEmpty()) {\n return null;\n }\n try {\n String json = response.trim();\n // 移除可能的 markdown 代码块标记\n if (json.startsWith(\"```\")) {\n int start = json.indexOf('\\n');\n if (start != -1) {\n json = json.substring(start + 1);\n }\n int end = json.lastIndexOf(\"```\");\n if (end != -1) {\n json = json.substring(0, end);\n }\n json = json.trim();\n }\n\n JsonObject obj = gson.fromJson(json, JsonObject.class);\n if (obj == null) {\n return null;\n }\n\n DailyBriefing briefing = new DailyBriefing();\n briefing.greeting = getJsonString(obj, \"greeting\");\n briefing.scoreComment = getJsonString(obj, \"scoreComment\");\n briefing.suggestion = getJsonString(obj, \"suggestion\");\n briefing.motivation = getJsonString(obj, \"motivation\");\n\n // 解析亮点列表\n briefing.highlights = new ArrayList<>();\n if (obj.has(\"highlights\") && obj.get(\"highlights\").isJsonArray()) {\n JsonArray arr = obj.getAsJsonArray(\"highlights\");\n for (int i = 0; i < arr.size(); i++) {\n briefing.highlights.add(arr.get(i).getAsString());\n }\n }\n\n briefing.isLocal = false;\n briefing.generatedAt = System.currentTimeMillis();\n return briefing;\n } catch (Exception e) {\n return null;\n }\n }\n\n /**\n * 安全地从 JsonObject 获取字符串字段\n */\n private static String getJsonString(JsonObject obj, String key) {\n if (obj.has(key) && !obj.get(key).isJsonNull()) {\n return obj.get(key).getAsString();\n }\n return \"\";\n }\n\n /**\n * 缓存简报数据\n */\n private void cacheBriefing(DailyBriefing briefing) {\n try {\n String json = gson.toJson(briefing);\n prefs.edit()\n .putString(KEY_CACHED_BRIEFING, json)\n .putLong(KEY_CACHED_DATE, DateUtils.getTodayStartTimestamp())\n .putLong(KEY_CACHED_AT, System.currentTimeMillis())\n .apply();\n } catch (Exception ignored) {\n // 缓存失败不影响使用\n }\n }\n\n /**\n * 构建用户健康档案配置\n */\n private HealthScoreCalculator.UserProfile buildUserProfile() {\n HealthScoreCalculator.UserProfile p = new HealthScoreCalculator.UserProfile();\n try {\n AppDatabase db = AppDatabase.getInstance(application);\n User user = db.userDao().getUserSync();\n if (user != null) {\n if (user.getDailyCalorieTarget() > 0) {\n p.dailyCalorieTarget = user.getDailyCalorieTarget();\n }\n if (user.getDailyWaterTarget() > 0) {\n p.waterTargetMl = user.getDailyWaterTarget();\n }\n p.weightKg = user.getWeight();\n p.heightCm = user.getHeight();\n p.age = user.getAge();\n p.gender = user.getGender() == 1 ? \"male\" : \"female\";\n int goalType = user.getGoalType();\n if (goalType == 0) {\n p.goalType = \"lose\";\n } else if (goalType == 1) {\n p.goalType = \"gain\";\n } else {\n p.goalType = \"maintain\";\n }\n }\n } catch (Exception ignored) {\n }\n return p;\n }\n\n /**\n * 紧急情况下的最简简报\n */\n private static DailyBriefing createEmergencyBriefing() {\n DailyBriefing briefing = new DailyBriefing();\n briefing.greeting = \"欢迎回来!\";\n briefing.scoreComment = \"暂时无法获取评分数据\";\n briefing.highlights = new ArrayList<>();\n briefing.highlights.add(\"数据加载中,请稍后刷新\");\n briefing.suggestion = \"下拉刷新页面重试\";\n briefing.motivation = \"保持好心情,健康每一天!\";\n briefing.isLocal = true;\n briefing.generatedAt = System.currentTimeMillis();\n return briefing;\n }\n}\n"}, "dc45b9ee10095ebcd3ecd5d961611fc04610a812": {"t": 1785755588.25273, "n": 14400, "h": "fee13e46f1652082", "c": "package com.cz.fitnessdiary.service;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport com.cz.fitnessdiary.BuildConfig;\nimport com.cz.fitnessdiary.database.AppDatabase;\nimport com.cz.fitnessdiary.database.entity.User;\nimport com.cz.fitnessdiary.model.DailyHealthSnapshot;\nimport com.cz.fitnessdiary.model.HealthScoreBreakdown;\nimport com.cz.fitnessdiary.model.WeeklyTrend;\nimport com.cz.fitnessdiary.repository.HealthAggregationRepository;\nimport com.cz.fitnessdiary.utils.DateUtils;\nimport com.cz.fitnessdiary.utils.HealthScoreCalculator;\nimport com.google.gson.Gson;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonSyntaxException;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n/**\n * AI 日报服务 - v3.0\n * 负责生成、缓存每日健康简报。\n * 优先使用 DeepSeek AI 生成,失败或未配置时降级为本地规则引擎。\n */\npublic class DailyBriefingService {\n\n private static final String PREFS_NAME = \"daily_briefing_prefs\";\n private static final String KEY_CACHED_BRIEFING = \"cached_briefing\";\n private static final String KEY_CACHED_DATE = \"cached_date\";\n private static final String KEY_CACHED_AT = \"cached_at\";\n\n private static final long ONE_DAY_MS = 86400000L;\n\n private final Application application;\n private final HealthAggregationRepository aggregationRepo;\n private final SharedPreferences prefs;\n private final ExecutorService executor;\n private final Handler mainHandler;\n private final Gson gson;\n\n /**\n * 每日简报数据类\n */\n public static class DailyBriefing {\n public String greeting;\n public String scoreComment;\n public List<String> highlights;\n public String suggestion;\n public String motivation;\n public boolean isLocal;\n public long generatedAt;\n\n public DailyBriefing() {\n this.highlights = new ArrayList<>();\n }\n }\n\n /**\n * 简报生成回调接口\n */\n public interface DailyBriefingCallback {\n void onBriefingReady(DailyBriefing briefing);\n }\n\n public DailyBriefingService(Application application) {\n this.application = application;\n this.aggregationRepo = new HealthAggregationRepository(application);\n this.prefs = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);\n this.executor = Executors.newSingleThreadExecutor();\n this.mainHandler = new Handler(Looper.getMainLooper());\n this.gson = new Gson();\n }\n\n // ================================================================\n // 公共 API\n // ================================================================\n\n /**\n * 获取缓存的简报(仅在缓存日期为今日时有效)\n *\n * @return 缓存的 DailyBriefing,若无缓存或已过期则返回 null\n */\n public DailyBriefing getCachedBriefing() {\n long cachedDate = prefs.getLong(KEY_CACHED_DATE, 0);\n long today = DateUtils.getTodayStartTimestamp();\n if (cachedDate != today) {\n return null;\n }\n String json = prefs.getString(KEY_CACHED_BRIEFING, null);\n if (json == null || json.isEmpty()) {\n return null;\n }\n try {\n return gson.fromJson(json, DailyBriefing.class);\n } catch (JsonSyntaxException e) {\n prefs.edit().remove(KEY_CACHED_BRIEFING).apply();\n return null;\n }\n }\n\n /**\n * 清除缓存的简报\n */\n public void invalidateCache() {\n prefs.edit()\n .remove(KEY_CACHED_BRIEFING)\n .remove(KEY_CACHED_DATE)\n .remove(KEY_CACHED_AT)\n .apply();\n }\n\n /**\n * 生成今日简报(异步)\n * <p>\n * 流程:获取昨日数据快照 -> 评分 -> 尝试 AI 生成 -> 失败降级本地生成 -> 缓存 -> 回调\n *\n * @param callback 回调接口,在主线程执行\n */\n public void generateBriefing(DailyBriefingCallback callback) {\n executor.execute(() -> {\n try {\n // 1. 获取昨日数据\n long yesterday = DateUtils.getTodayStartTimestamp() - ONE_DAY_MS;\n DailyHealthSnapshot snapshot = aggregationRepo.getDateSnapshot(yesterday);\n HealthScoreCalculator.UserProfile profile = buildUserProfile();\n HealthScoreBreakdown breakdown = HealthScoreCalculator.calculateBreakdown(snapshot, profile);\n snapshot.healthScore = breakdown.totalScore;\n\n // 2. 尝试 AI 生成\n if (isDirectAiConfigured()) {\n callAiForBriefing(snapshot, breakdown, callback);\n } else {\n // 3. 降级为本地生成\n DailyBriefing briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n cacheBriefing(briefing);\n mainHandler.post(() -> callback.onBriefingReady(briefing));\n }\n } catch (Exception e) {\n // 异常情况下生成最简简报\n DailyBriefing fallback = createEmergencyBriefing();\n mainHandler.post(() -> callback.onBriefingReady(fallback));\n }\n });\n }\n\n // ================================================================\n // 内部方法\n // ================================================================\n\n /** Direct personal AI is optional; local generation remains the safe fallback. */\n private boolean isDirectAiConfigured() {\n return !BuildConfig.DEEPSEEK_API_KEY.trim().isEmpty();\n }\n\n /**\n * 调用 DeepSeek AI 生成简报\n */\n private void callAiForBriefing(DailyHealthSnapshot snapshot,\n HealthScoreBreakdown breakdown,\n DailyBriefingCallback callback) {\n List<WeeklyTrend> trends = aggregationRepo.getWeeklyTrends();\n String userPrompt = buildUserPrompt(snapshot, breakdown, trends);\n String systemPrompt = \"你是专业健康教练。基于用户健康数据生成今日简报。\"\n + \"以JSON格式输出,不要包含markdown代码块标记:\"\n + \"{\\\"greeting\\\":\\\"...\\\",\\\"scoreComment\\\":\\\"...\\\",\"\n + \"\\\"highlights\\\":[\\\"...\\\"],\\\"suggestion\\\":\\\"...\\\",\\\"motivation\\\":\\\"...\\\"}\";\n\n DeepSeekService.sendMessage(userPrompt, systemPrompt,\n new AICallback() {\n @Override\n public void onSuccess(String response, String reasoning) {\n DailyBriefing briefing = parseAiResponse(response);\n if (briefing == null) {\n // JSON 解析失败,降级本地生成\n briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n } else {\n briefing.isLocal = false;\n }\n cacheBriefing(briefing);\n callback.onBriefingReady(briefing);\n }\n\n @Override\n public void onError(String error) {\n // AI 调用失败,降级本地生成\n DailyBriefing briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n cacheBriefing(briefing);\n callback.onBriefingReady(briefing);\n }\n\n @Override\n public void onPartialUpdate(String content, String reasoning) {\n // 非流式模式,无需处理\n }\n });\n }\n\n /**\n * 构建 AI 用户 Prompt\n */\n private String buildUserPrompt(DailyHealthSnapshot snapshot,\n HealthScoreBreakdown breakdown,\n List<WeeklyTrend> trends) {\n StringBuilder sb = new StringBuilder();\n sb.append(\"请根据以下用户昨日健康数据生成一份简报。\\n\\n\");\n sb.append(\"【昨日数据】\\n\");\n sb.append(\"- 健康评分:\").append(breakdown.totalScore).append(\"/100\\n\");\n sb.append(\"- 运动消耗:\").append(snapshot.exerciseCalories).append(\" 千卡\\n\");\n sb.append(\"- 饮食摄入:\").append(snapshot.dietCalories).append(\" 千卡\\n\");\n sb.append(\"- 基础代谢:\").append(snapshot.bmr).append(\" 千卡\\n\");\n sb.append(\"- 热量平衡:\").append(snapshot.energyBalance >= 0 ? \"+\" : \"\")\n .append(snapshot.energyBalance).append(\" 千卡\\n\");\n sb.append(\"- 步数:\").append(snapshot.steps).append(\"\\n\");\n sb.append(\"- 睡眠时长:\").append(String.format(java.util.Locale.getDefault(),\n \"%.1f\", snapshot.sleepHours)).append(\" 小时\\n\");\n sb.append(\"- 睡眠质量:\").append(snapshot.sleepQuality).append(\"/5\\n\");\n sb.append(\"- 饮水量:\").append(snapshot.waterMl).append(\" ml\\n\");\n sb.append(\"- 完成计划:\").append(snapshot.completedPlans)\n .append(\"/\").append(snapshot.totalPlans).append(\"\\n\");\n sb.append(\"- 连续打卡:\").append(snapshot.consecutiveDays).append(\" 天\\n\");\n sb.append(\"- 心情等级:\").append(snapshot.moodLevel).append(\"/5\\n\");\n if (snapshot.currentPlanName != null) {\n sb.append(\"- 当前计划:\").append(snapshot.currentPlanName).append(\"\\n\");\n }\n\n // 评分明细\n sb.append(\"\\n【评分明细】\\n\");\n sb.append(\"- 运动评分:\").append(breakdown.exerciseScore).append(\"/25\\n\");\n sb.append(\"- 饮食评分:\").append(breakdown.dietScore).append(\"/25\\n\");\n sb.append(\"- 习惯评分:\").append(breakdown.habitsScore).append(\"/20\\n\");\n sb.append(\"- 身体指标评分:\").append(breakdown.bodyMetricsScore).append(\"/15\\n\");\n sb.append(\"- 坚持度评分:\").append(breakdown.consistencyScore).append(\"/15\\n\");\n\n // 7日趋势\n if (trends != null && !trends.isEmpty()) {\n sb.append(\"\\n【近7日趋势】\\n\");\n for (WeeklyTrend trend : trends) {\n sb.append(\"- \").append(trend.label).append(\":\").append(trend.getChangeText()).append(\"\\n\");\n }\n }\n\n sb.append(\"\\n请以JSON格式输出(仅JSON,不含markdown标记):\\n\");\n sb.append(\"{\\\"greeting\\\":\\\"问候语\\\",\\\"scoreComment\\\":\\\"评分评价\\\",\");\n sb.append(\"\\\"highlights\\\":[\\\"亮点1\\\",\\\"亮点2\\\"],\");\n sb.append(\"\\\"suggestion\\\":\\\"一条建议\\\",\\\"motivation\\\":\\\"一句鼓励\\\"}\");\n return sb.toString();\n }\n\n /**\n * 解析 AI 返回的 JSON 响应\n *\n * @param response AI 返回的原始字符串\n * @return 解析成功的 DailyBriefing,失败返回 null\n */\n private DailyBriefing parseAiResponse(String response) {\n if (response == null || response.trim().isEmpty()) {\n return null;\n }\n try {\n String json = response.trim();\n // 移除可能的 markdown 代码块标记\n if (json.startsWith(\"```\")) {\n int start = json.indexOf('\\n');\n if (start != -1) {\n json = json.substring(start + 1);\n }\n int end = json.lastIndexOf(\"```\");\n if (end != -1) {\n json = json.substring(0, end);\n }\n json = json.trim();\n }\n\n JsonObject obj = gson.fromJson(json, JsonObject.class);\n if (obj == null) {\n return null;\n }\n\n DailyBriefing briefing = new DailyBriefing();\n briefing.greeting = getJsonString(obj, \"greeting\");\n briefing.scoreComment = getJsonString(obj, \"scoreComment\");\n briefing.suggestion = getJsonString(obj, \"suggestion\");\n briefing.motivation = getJsonString(obj, \"motivation\");\n\n // 解析亮点列表\n briefing.highlights = new ArrayList<>();\n if (obj.has(\"highlights\") && obj.get(\"highlights\").isJsonArray()) {\n JsonArray arr = obj.getAsJsonArray(\"highlights\");\n for (int i = 0; i < arr.size(); i++) {\n briefing.highlights.add(arr.get(i).getAsString());\n }\n }\n\n briefing.isLocal = false;\n briefing.generatedAt = System.currentTimeMillis();\n return briefing;\n } catch (Exception e) {\n return null;\n }\n }\n\n /**\n * 安全地从 JsonObject 获取字符串字段\n */\n private static String getJsonString(JsonObject obj, String key) {\n if (obj.has(key) && !obj.get(key).isJsonNull()) {\n return obj.get(key).getAsString();\n }\n return \"\";\n }\n\n /**\n * 缓存简报数据\n */\n private void cacheBriefing(DailyBriefing briefing) {\n try {\n String json = gson.toJson(briefing);\n prefs.edit()\n .putString(KEY_CACHED_BRIEFING, json)\n .putLong(KEY_CACHED_DATE, DateUtils.getTodayStartTimestamp())\n .putLong(KEY_CACHED_AT, System.currentTimeMillis())\n .apply();\n } catch (Exception ignored) {\n // 缓存失败不影响使用\n }\n }\n\n /**\n * 构建用户健康档案配置\n */\n private HealthScoreCalculator.UserProfile buildUserProfile() {\n HealthScoreCalculator.UserProfile p = new HealthScoreCalculator.UserProfile();\n try {\n AppDatabase db = AppDatabase.getInstance(application);\n User user = db.userDao().getUserSync();\n if (user != null) {\n if (user.getDailyCalorieTarget() > 0) {\n p.dailyCalorieTarget = user.getDailyCalorieTarget();\n }\n if (user.getDailyWaterTarget() > 0) {\n p.waterTargetMl = user.getDailyWaterTarget();\n }\n p.weightKg = user.getWeight();\n p.heightCm = user.getHeight();\n p.age = user.getAge();\n p.gender = user.getGender() == 1 ? \"male\" : \"female\";\n int goalType = user.getGoalType();\n if (goalType == 0) {\n p.goalType = \"lose\";\n } else if (goalType == 1) {\n p.goalType = \"gain\";\n } else {\n p.goalType = \"maintain\";\n }\n }\n } catch (Exception ignored) {\n }\n return p;\n }\n\n /**\n * 紧急情况下的最简简报\n */\n private static DailyBriefing createEmergencyBriefing() {\n DailyBriefing briefing = new DailyBriefing();\n briefing.greeting = \"欢迎回来!\";\n briefing.scoreComment = \"暂时无法获取评分数据\";\n briefing.highlights = new ArrayList<>();\n briefing.highlights.add(\"数据加载中,请稍后刷新\");\n briefing.suggestion = \"下拉刷新页面重试\";\n briefing.motivation = \"保持好心情,健康每一天!\";\n briefing.isLocal = true;\n briefing.generatedAt = System.currentTimeMillis();\n return briefing;\n }\n}\n"}, "c0aa34aafff3065356477d06482f6052b6e1d34f": {"t": 1785755589.7927232, "n": 14440, "h": "ef80cf5e1a73b714", "c": "package com.cz.fitnessdiary.service;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport com.cz.fitnessdiary.BuildConfig;\nimport com.cz.fitnessdiary.database.AppDatabase;\nimport com.cz.fitnessdiary.database.entity.User;\nimport com.cz.fitnessdiary.model.DailyHealthSnapshot;\nimport com.cz.fitnessdiary.model.HealthScoreBreakdown;\nimport com.cz.fitnessdiary.model.WeeklyTrend;\nimport com.cz.fitnessdiary.repository.HealthAggregationRepository;\nimport com.cz.fitnessdiary.utils.DateUtils;\nimport com.cz.fitnessdiary.utils.HealthScoreCalculator;\nimport com.google.gson.Gson;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonSyntaxException;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n/**\n * AI 日报服务 - v3.0\n * 负责生成、缓存每日健康简报。\n * 优先使用 DeepSeek AI 生成,失败或未配置时降级为本地规则引擎。\n */\npublic class DailyBriefingService {\n\n private static final String PREFS_NAME = \"daily_briefing_prefs\";\n private static final String KEY_CACHED_BRIEFING = \"cached_briefing\";\n private static final String KEY_CACHED_DATE = \"cached_date\";\n private static final String KEY_CACHED_AT = \"cached_at\";\n\n private static final long ONE_DAY_MS = 86400000L;\n\n private final Application application;\n private final HealthAggregationRepository aggregationRepo;\n private final SharedPreferences prefs;\n private final ExecutorService executor;\n private final Handler mainHandler;\n private final Gson gson;\n\n /**\n * 每日简报数据类\n */\n public static class DailyBriefing {\n public String greeting;\n public String scoreComment;\n public List<String> highlights;\n public String suggestion;\n public String motivation;\n public boolean isLocal;\n public long generatedAt;\n\n public DailyBriefing() {\n this.highlights = new ArrayList<>();\n }\n }\n\n /**\n * 简报生成回调接口\n */\n public interface DailyBriefingCallback {\n void onBriefingReady(DailyBriefing briefing);\n }\n\n public DailyBriefingService(Application application) {\n this.application = application;\n this.aggregationRepo = new HealthAggregationRepository(application);\n this.prefs = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);\n this.executor = Executors.newSingleThreadExecutor();\n this.mainHandler = new Handler(Looper.getMainLooper());\n this.gson = new Gson();\n }\n\n // ================================================================\n // 公共 API\n // ================================================================\n\n /**\n * 获取缓存的简报(仅在缓存日期为今日时有效)\n *\n * @return 缓存的 DailyBriefing,若无缓存或已过期则返回 null\n */\n public DailyBriefing getCachedBriefing() {\n long cachedDate = prefs.getLong(KEY_CACHED_DATE, 0);\n long today = DateUtils.getTodayStartTimestamp();\n if (cachedDate != today) {\n return null;\n }\n String json = prefs.getString(KEY_CACHED_BRIEFING, null);\n if (json == null || json.isEmpty()) {\n return null;\n }\n try {\n return gson.fromJson(json, DailyBriefing.class);\n } catch (JsonSyntaxException e) {\n prefs.edit().remove(KEY_CACHED_BRIEFING).apply();\n return null;\n }\n }\n\n /**\n * 清除缓存的简报\n */\n public void invalidateCache() {\n prefs.edit()\n .remove(KEY_CACHED_BRIEFING)\n .remove(KEY_CACHED_DATE)\n .remove(KEY_CACHED_AT)\n .apply();\n }\n\n /**\n * 生成今日简报(异步)\n * <p>\n * 流程:获取昨日数据快照 -> 评分 -> 尝试 AI 生成 -> 失败降级本地生成 -> 缓存 -> 回调\n *\n * @param callback 回调接口,在主线程执行\n */\n public void generateBriefing(DailyBriefingCallback callback) {\n executor.execute(() -> {\n try {\n // 1. 获取昨日数据\n long yesterday = DateUtils.getTodayStartTimestamp() - ONE_DAY_MS;\n DailyHealthSnapshot snapshot = aggregationRepo.getDateSnapshot(yesterday);\n HealthScoreCalculator.UserProfile profile = buildUserProfile();\n HealthScoreBreakdown breakdown = HealthScoreCalculator.calculateBreakdown(snapshot, profile);\n snapshot.healthScore = breakdown.totalScore;\n\n // 2. 尝试 AI 生成\n if (isApiKeyConfigured()) {\n callAiForBriefing(snapshot, breakdown, callback);\n } else {\n // 3. 降级为本地生成\n DailyBriefing briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n cacheBriefing(briefing);\n mainHandler.post(() -> callback.onBriefingReady(briefing));\n }\n } catch (Exception e) {\n // 异常情况下生成最简简报\n DailyBriefing fallback = createEmergencyBriefing();\n mainHandler.post(() -> callback.onBriefingReady(fallback));\n }\n });\n }\n\n // ================================================================\n // 内部方法\n // ================================================================\n\n /**\n * 判断是否有可用的 API 密钥\n */\n private boolean isApiKeyConfigured() {\n String deepseekKey = BuildConfig.DEEPSEEK_API_KEY;\n return deepseekKey != null && !deepseekKey.isEmpty() && !deepseekKey.contains(\"YOUR_\");\n }\n\n /**\n * 调用 DeepSeek AI 生成简报\n */\n private void callAiForBriefing(DailyHealthSnapshot snapshot,\n HealthScoreBreakdown breakdown,\n DailyBriefingCallback callback) {\n List<WeeklyTrend> trends = aggregationRepo.getWeeklyTrends();\n String userPrompt = buildUserPrompt(snapshot, breakdown, trends);\n String systemPrompt = \"你是专业健康教练。基于用户健康数据生成今日简报。\"\n + \"以JSON格式输出,不要包含markdown代码块标记:\"\n + \"{\\\"greeting\\\":\\\"...\\\",\\\"scoreComment\\\":\\\"...\\\",\"\n + \"\\\"highlights\\\":[\\\"...\\\"],\\\"suggestion\\\":\\\"...\\\",\\\"motivation\\\":\\\"...\\\"}\";\n\n DeepSeekService.sendMessage(userPrompt, systemPrompt,\n new AICallback() {\n @Override\n public void onSuccess(String response, String reasoning) {\n DailyBriefing briefing = parseAiResponse(response);\n if (briefing == null) {\n // JSON 解析失败,降级本地生成\n briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n } else {\n briefing.isLocal = false;\n }\n cacheBriefing(briefing);\n callback.onBriefingReady(briefing);\n }\n\n @Override\n public void onError(String error) {\n // AI 调用失败,降级本地生成\n DailyBriefing briefing = LocalBriefingGenerator.generate(snapshot, breakdown);\n cacheBriefing(briefing);\n callback.onBriefingReady(briefing);\n }\n\n @Override\n public void onPartialUpdate(String content, String reasoning) {\n // 非流式模式,无需处理\n }\n });\n }\n\n /**\n * 构建 AI 用户 Prompt\n */\n private String buildUserPrompt(DailyHealthSnapshot snapshot,\n HealthScoreBreakdown breakdown,\n List<WeeklyTrend> trends) {\n StringBuilder sb = new StringBuilder();\n sb.append(\"请根据以下用户昨日健康数据生成一份简报。\\n\\n\");\n sb.append(\"【昨日数据】\\n\");\n sb.append(\"- 健康评分:\").append(breakdown.totalScore).append(\"/100\\n\");\n sb.append(\"- 运动消耗:\").append(snapshot.exerciseCalories).append(\" 千卡\\n\");\n sb.append(\"- 饮食摄入:\").append(snapshot.dietCalories).append(\" 千卡\\n\");\n sb.append(\"- 基础代谢:\").append(snapshot.bmr).append(\" 千卡\\n\");\n sb.append(\"- 热量平衡:\").append(snapshot.energyBalance >= 0 ? \"+\" : \"\")\n .append(snapshot.energyBalance).append(\" 千卡\\n\");\n sb.append(\"- 步数:\").append(snapshot.steps).append(\"\\n\");\n sb.append(\"- 睡眠时长:\").append(String.format(java.util.Locale.getDefault(),\n \"%.1f\", snapshot.sleepHours)).append(\" 小时\\n\");\n sb.append(\"- 睡眠质量:\").append(snapshot.sleepQuality).append(\"/5\\n\");\n sb.append(\"- 饮水量:\").append(snapshot.waterMl).append(\" ml\\n\");\n sb.append(\"- 完成计划:\").append(snapshot.completedPlans)\n .append(\"/\").append(snapshot.totalPlans).append(\"\\n\");\n sb.append(\"- 连续打卡:\").append(snapshot.consecutiveDays).append(\" 天\\n\");\n sb.append(\"- 心情等级:\").append(snapshot.moodLevel).append(\"/5\\n\");\n if (snapshot.currentPlanName != null) {\n sb.append(\"- 当前计划:\").append(snapshot.currentPlanName).append(\"\\n\");\n }\n\n // 评分明细\n sb.append(\"\\n【评分明细】\\n\");\n sb.append(\"- 运动评分:\").append(breakdown.exerciseScore).append(\"/25\\n\");\n sb.append(\"- 饮食评分:\").append(breakdown.dietScore).append(\"/25\\n\");\n sb.append(\"- 习惯评分:\").append(breakdown.habitsScore).append(\"/20\\n\");\n sb.append(\"- 身体指标评分:\").append(breakdown.bodyMetricsScore).append(\"/15\\n\");\n sb.append(\"- 坚持度评分:\").append(breakdown.consistencyScore).append(\"/15\\n\");\n\n // 7日趋势\n if (trends != null && !trends.isEmpty()) {\n sb.append(\"\\n【近7日趋势】\\n\");\n for (WeeklyTrend trend : trends) {\n sb.append(\"- \").append(trend.label).append(\":\").append(trend.getChangeText()).append(\"\\n\");\n }\n }\n\n sb.append(\"\\n请以JSON格式输出(仅JSON,不含markdown标记):\\n\");\n sb.append(\"{\\\"greeting\\\":\\\"问候语\\\",\\\"scoreComment\\\":\\\"评分评价\\\",\");\n sb.append(\"\\\"highlights\\\":[\\\"亮点1\\\",\\\"亮点2\\\"],\");\n sb.append(\"\\\"suggestion\\\":\\\"一条建议\\\",\\\"motivation\\\":\\\"一句鼓励\\\"}\");\n return sb.toString();\n }\n\n /**\n * 解析 AI 返回的 JSON 响应\n *\n * @param response AI 返回的原始字符串\n * @return 解析成功的 DailyBriefing,失败返回 null\n */\n private DailyBriefing parseAiResponse(String response) {\n if (response == null || response.trim().isEmpty()) {\n return null;\n }\n try {\n String json = response.trim();\n // 移除可能的 markdown 代码块标记\n if (json.startsWith(\"```\")) {\n int start = json.indexOf('\\n');\n if (start != -1) {\n json = json.substring(start + 1);\n }\n int end = json.lastIndexOf(\"```\");\n if (end != -1) {\n json = json.substring(0, end);\n }\n json = json.trim();\n }\n\n JsonObject obj = gson.fromJson(json, JsonObject.class);\n if (obj == null) {\n return null;\n }\n\n DailyBriefing briefing = new DailyBriefing();\n briefing.greeting = getJsonString(obj, \"greeting\");\n briefing.scoreComment = getJsonString(obj, \"scoreComment\");\n briefing.suggestion = getJsonString(obj, \"suggestion\");\n briefing.motivation = getJsonString(obj, \"motivation\");\n\n // 解析亮点列表\n briefing.highlights = new ArrayList<>();\n if (obj.has(\"highlights\") && obj.get(\"highlights\").isJsonArray()) {\n JsonArray arr = obj.getAsJsonArray(\"highlights\");\n for (int i = 0; i < arr.size(); i++) {\n briefing.highlights.add(arr.get(i).getAsString());\n }\n }\n\n briefing.isLocal = false;\n briefing.generatedAt = System.currentTimeMillis();\n return briefing;\n } catch (Exception e) {\n return null;\n }\n }\n\n /**\n * 安全地从 JsonObject 获取字符串字段\n */\n private static String getJsonString(JsonObject obj, String key) {\n if (obj.has(key) && !obj.get(key).isJsonNull()) {\n return obj.get(key).getAsString();\n }\n return \"\";\n }\n\n /**\n * 缓存简报数据\n */\n private void cacheBriefing(DailyBriefing briefing) {\n try {\n String json = gson.toJson(briefing);\n prefs.edit()\n .putString(KEY_CACHED_BRIEFING, json)\n .putLong(KEY_CACHED_DATE, DateUtils.getTodayStartTimestamp())\n .putLong(KEY_CACHED_AT, System.currentTimeMillis())\n .apply();\n } catch (Exception ignored) {\n // 缓存失败不影响使用\n }\n }\n\n /**\n * 构建用户健康档案配置\n */\n private HealthScoreCalculator.UserProfile buildUserProfile() {\n HealthScoreCalculator.UserProfile p = new HealthScoreCalculator.UserProfile();\n try {\n AppDatabase db = AppDatabase.getInstance(application);\n User user = db.userDao().getUserSync();\n if (user != null) {\n if (user.getDailyCalorieTarget() > 0) {\n p.dailyCalorieTarget = user.getDailyCalorieTarget();\n }\n if (user.getDailyWaterTarget() > 0) {\n p.waterTargetMl = user.getDailyWaterTarget();\n }\n p.weightKg = user.getWeight();\n p.heightCm = user.getHeight();\n p.age = user.getAge();\n p.gender = user.getGender() == 1 ? \"male\" : \"female\";\n int goalType = user.getGoalType();\n if (goalType == 0) {\n p.goalType = \"lose\";\n } else if (goalType == 1) {\n p.goalType = \"gain\";\n } else {\n p.goalType = \"maintain\";\n }\n }\n } catch (Exception ignored) {\n }\n return p;\n }\n\n /**\n * 紧急情况下的最简简报\n */\n private static DailyBriefing createEmergencyBriefing() {\n DailyBriefing briefing = new DailyBriefing();\n briefing.greeting = \"欢迎回来!\";\n briefing.scoreComment = \"暂时无法获取评分数据\";\n briefing.highlights = new ArrayList<>();\n briefing.highlights.add(\"数据加载中,请稍后刷新\");\n briefing.suggestion = \"下拉刷新页面重试\";\n briefing.motivation = \"保持好心情,健康每一天!\";\n briefing.isLocal = true;\n briefing.generatedAt = System.currentTimeMillis();\n return briefing;\n }\n}\n"}}, "app/src/main/java/com/cz/fitnessdiary/service/DeepSeekService.kt": {"a8e4b93c9016240a60acf90e1d181be631703401": {"t": 1785745733.874441, "n": 5441, "h": "a51c565bca591d86", "c": "package com.cz.fitnessdiary.service\n\nimport com.cz.fitnessdiary.BuildConfig\nimport com.cz.fitnessdiary.database.entity.ChatMessageEntity\nimport com.google.gson.Gson\nimport com.google.gson.JsonArray\nimport com.google.gson.JsonObject\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport java.util.concurrent.TimeUnit\n\n/** Direct DeepSeek client for a private, single-user APK. */\nobject DeepSeekService {\n private const val API_URL = \"https://api.deepseek.com/chat/completions\"\n private val client = OkHttpClient.Builder()\n .connectTimeout(60, TimeUnit.SECONDS)\n .readTimeout(60, TimeUnit.SECONDS)\n .writeTimeout(60, TimeUnit.SECONDS)\n .build()\n private val gson = Gson()\n private val scope = CoroutineScope(Dispatchers.IO)\n\n @JvmStatic\n fun sendMessage(message: String, systemInstruction: String?, callback: AICallback) {\n sendMessage(message, systemInstruction, false, null, callback)\n }\n\n @JvmStatic\n fun sendMessage(\n message: String,\n systemInstruction: String?,\n thinking: Boolean,\n history: List<ChatMessageEntity>?,\n callback: AICallback\n ) {\n sendMessageWithPolicy(\n message,\n systemInstruction,\n thinking,\n history,\n false,\n callback\n )\n }\n\n @JvmStatic\n fun sendMessageWithPolicy(\n message: String,\n systemInstruction: String?,\n thinking: Boolean,\n history: List<ChatMessageEntity>?,\n structuredJson: Boolean,\n callback: AICallback\n ) {\n scope.launch {\n if (BuildConfig.DEEPSEEK_API_KEY.isBlank()) {\n withContext(Dispatchers.Main) {\n callback.onError(\"未配置 DeepSeek 本地密钥,请检查 local.properties\")\n }\n return@launch\n }\n try {\n val messages = JsonArray()\n messages.add(JsonObject().apply {\n addProperty(\"role\", \"system\")\n addProperty(\"content\", systemInstruction ?: DEFAULT_SYSTEM_PROMPT)\n })\n history?.forEach { item ->\n messages.add(JsonObject().apply {\n addProperty(\"role\", if (item.isUser) \"user\" else \"assistant\")\n addProperty(\"content\", item.content ?: \"\")\n })\n }\n messages.add(JsonObject().apply {\n addProperty(\"role\", \"user\")\n addProperty(\"content\", message)\n })\n\n val requestJson = JsonObject().apply {\n addProperty(\"model\", if (thinking) \"deepseek-v4-pro\" else \"deepseek-v4-flash\")\n add(\"messages\", messages)\n addProperty(\"stream\", false)\n if (thinking) {\n add(\"thinking\", JsonObject().apply { addProperty(\"type\", \"enabled\") })\n }\n if (structuredJson) {\n add(\"response_format\", JsonObject().apply {\n addProperty(\"type\", \"json_object\")\n })\n }\n }\n val request = Request.Builder()\n .url(API_URL)\n .header(\"Authorization\", \"Bearer ${BuildConfig.DEEPSEEK_API_KEY}\")\n .header(\"Content-Type\", \"application/json\")\n .post(gson.toJson(requestJson).toRequestBody(\"application/json\".toMediaType()))\n .build()\n\n client.newCall(request).execute().use { response ->\n val responseText = response.body?.string().orEmpty()\n if (!response.isSuccessful) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 请求失败(HTTP ${response.code})\")\n }\n return@launch\n }\n val root = gson.fromJson(responseText, JsonObject::class.java)\n val choice = root.getAsJsonArray(\"choices\")?.get(0)?.asJsonObject\n val messageObject = choice?.getAsJsonObject(\"message\")\n val content = messageObject?.get(\"content\")?.asString.orEmpty()\n val reasoning = messageObject?.get(\"reasoning_content\")?.asString\n val usage = root.getAsJsonObject(\"usage\")\n AiUsageStore.record(\n \"DeepSeek\",\n usage?.get(\"prompt_tokens\")?.asInt ?: 0,\n usage?.get(\"completion_tokens\")?.asInt ?: 0,\n usage?.get(\"prompt_cache_hit_tokens\")?.asInt ?: 0\n )\n withContext(Dispatchers.Main) { callback.onSuccess(content, reasoning) }\n }\n } catch (error: Exception) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 连接失败,请稍后重试\")\n }\n }\n }\n }\n\n private const val DEFAULT_SYSTEM_PROMPT =\n \"你是 FitnessDiary 的中文健身与营养助手,请简洁、准确地回答。\"\n}\n"}, "78338a0d09d172d26fe707da954511afb4f2d5c1": {"t": 1785755588.6194448, "n": 5441, "h": "a51c565bca591d86", "c": "package com.cz.fitnessdiary.service\n\nimport com.cz.fitnessdiary.BuildConfig\nimport com.cz.fitnessdiary.database.entity.ChatMessageEntity\nimport com.google.gson.Gson\nimport com.google.gson.JsonArray\nimport com.google.gson.JsonObject\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport java.util.concurrent.TimeUnit\n\n/** Direct DeepSeek client for a private, single-user APK. */\nobject DeepSeekService {\n private const val API_URL = \"https://api.deepseek.com/chat/completions\"\n private val client = OkHttpClient.Builder()\n .connectTimeout(60, TimeUnit.SECONDS)\n .readTimeout(60, TimeUnit.SECONDS)\n .writeTimeout(60, TimeUnit.SECONDS)\n .build()\n private val gson = Gson()\n private val scope = CoroutineScope(Dispatchers.IO)\n\n @JvmStatic\n fun sendMessage(message: String, systemInstruction: String?, callback: AICallback) {\n sendMessage(message, systemInstruction, false, null, callback)\n }\n\n @JvmStatic\n fun sendMessage(\n message: String,\n systemInstruction: String?,\n thinking: Boolean,\n history: List<ChatMessageEntity>?,\n callback: AICallback\n ) {\n sendMessageWithPolicy(\n message,\n systemInstruction,\n thinking,\n history,\n false,\n callback\n )\n }\n\n @JvmStatic\n fun sendMessageWithPolicy(\n message: String,\n systemInstruction: String?,\n thinking: Boolean,\n history: List<ChatMessageEntity>?,\n structuredJson: Boolean,\n callback: AICallback\n ) {\n scope.launch {\n if (BuildConfig.DEEPSEEK_API_KEY.isBlank()) {\n withContext(Dispatchers.Main) {\n callback.onError(\"未配置 DeepSeek 本地密钥,请检查 local.properties\")\n }\n return@launch\n }\n try {\n val messages = JsonArray()\n messages.add(JsonObject().apply {\n addProperty(\"role\", \"system\")\n addProperty(\"content\", systemInstruction ?: DEFAULT_SYSTEM_PROMPT)\n })\n history?.forEach { item ->\n messages.add(JsonObject().apply {\n addProperty(\"role\", if (item.isUser) \"user\" else \"assistant\")\n addProperty(\"content\", item.content ?: \"\")\n })\n }\n messages.add(JsonObject().apply {\n addProperty(\"role\", \"user\")\n addProperty(\"content\", message)\n })\n\n val requestJson = JsonObject().apply {\n addProperty(\"model\", if (thinking) \"deepseek-v4-pro\" else \"deepseek-v4-flash\")\n add(\"messages\", messages)\n addProperty(\"stream\", false)\n if (thinking) {\n add(\"thinking\", JsonObject().apply { addProperty(\"type\", \"enabled\") })\n }\n if (structuredJson) {\n add(\"response_format\", JsonObject().apply {\n addProperty(\"type\", \"json_object\")\n })\n }\n }\n val request = Request.Builder()\n .url(API_URL)\n .header(\"Authorization\", \"Bearer ${BuildConfig.DEEPSEEK_API_KEY}\")\n .header(\"Content-Type\", \"application/json\")\n .post(gson.toJson(requestJson).toRequestBody(\"application/json\".toMediaType()))\n .build()\n\n client.newCall(request).execute().use { response ->\n val responseText = response.body?.string().orEmpty()\n if (!response.isSuccessful) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 请求失败(HTTP ${response.code})\")\n }\n return@launch\n }\n val root = gson.fromJson(responseText, JsonObject::class.java)\n val choice = root.getAsJsonArray(\"choices\")?.get(0)?.asJsonObject\n val messageObject = choice?.getAsJsonObject(\"message\")\n val content = messageObject?.get(\"content\")?.asString.orEmpty()\n val reasoning = messageObject?.get(\"reasoning_content\")?.asString\n val usage = root.getAsJsonObject(\"usage\")\n AiUsageStore.record(\n \"DeepSeek\",\n usage?.get(\"prompt_tokens\")?.asInt ?: 0,\n usage?.get(\"completion_tokens\")?.asInt ?: 0,\n usage?.get(\"prompt_cache_hit_tokens\")?.asInt ?: 0\n )\n withContext(Dispatchers.Main) { callback.onSuccess(content, reasoning) }\n }\n } catch (error: Exception) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 连接失败,请稍后重试\")\n }\n }\n }\n }\n\n private const val DEFAULT_SYSTEM_PROMPT =\n \"你是 FitnessDiary 的中文健身与营养助手,请简洁、准确地回答。\"\n}\n"}, "dc45b9ee10095ebcd3ecd5d961611fc04610a812": {"t": 1785755589.9393427, "n": 5803, "h": "707213d9f7a5a4d8", "c": "package com.cz.fitnessdiary.service\n\nimport com.cz.fitnessdiary.BuildConfig\nimport com.cz.fitnessdiary.database.entity.ChatMessageEntity\nimport com.google.gson.Gson\nimport com.google.gson.JsonArray\nimport com.google.gson.JsonObject\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport java.util.concurrent.TimeUnit\n\n/** Direct DeepSeek client for a private, single-user APK. */\nobject DeepSeekService {\n private const val API_URL = \"https://api.deepseek.com/chat/completions\"\n private val client = OkHttpClient.Builder()\n .connectTimeout(60, TimeUnit.SECONDS)\n .readTimeout(60, TimeUnit.SECONDS)\n .writeTimeout(60, TimeUnit.SECONDS)\n .build()\n private val gson = Gson()\n private val scope = CoroutineScope(Dispatchers.IO)\n\n @JvmStatic\n fun sendMessage(message: String, systemInstruction: String?, callback: AICallback) {\n sendMessage(message, systemInstruction, false, null, callback)\n }\n\n @JvmStatic\n fun sendMessage(\n message: String,\n systemInstruction: String?,\n thinking: Boolean,\n history: List<ChatMessageEntity>?,\n callback: AICallback\n ) {\n sendMessageWithPolicy(\n message,\n systemInstruction,\n thinking,\n history,\n if (thinking) AiRequestPolicy.DEEP_THINKING_MAX_COMPLETION_TOKENS\n else AiRequestPolicy.ADVICE_MAX_COMPLETION_TOKENS,\n false,\n callback\n )\n }\n\n @JvmStatic\n fun sendMessageWithPolicy(\n message: String,\n systemInstruction: String?,\n thinking: Boolean,\n history: List<ChatMessageEntity>?,\n maxCompletionTokens: Int,\n structuredJson: Boolean,\n callback: AICallback\n ) {\n scope.launch {\n if (BuildConfig.DEEPSEEK_API_KEY.isBlank()) {\n withContext(Dispatchers.Main) {\n callback.onError(\"未配置 DeepSeek 本地密钥,请检查 local.properties\")\n }\n return@launch\n }\n try {\n val messages = JsonArray()\n messages.add(JsonObject().apply {\n addProperty(\"role\", \"system\")\n addProperty(\"content\", systemInstruction ?: DEFAULT_SYSTEM_PROMPT)\n })\n history?.takeLast(AiRequestPolicy.CHAT_MAX_HISTORY_MESSAGES)?.forEach { item ->\n messages.add(JsonObject().apply {\n addProperty(\"role\", if (item.isUser) \"user\" else \"assistant\")\n addProperty(\"content\", item.content?.take(700) ?: \"\")\n })\n }\n messages.add(JsonObject().apply {\n addProperty(\"role\", \"user\")\n addProperty(\"content\", message.take(AiRequestPolicy.CHAT_MAX_HISTORY_CHARS))\n })\n\n val requestJson = JsonObject().apply {\n addProperty(\"model\", if (thinking) \"deepseek-v4-pro\" else \"deepseek-v4-flash\")\n add(\"messages\", messages)\n addProperty(\"max_completion_tokens\", maxCompletionTokens)\n addProperty(\"stream\", false)\n if (thinking) {\n add(\"thinking\", JsonObject().apply { addProperty(\"type\", \"enabled\") })\n }\n if (structuredJson) {\n add(\"response_format\", JsonObject().apply {\n addProperty(\"type\", \"json_object\")\n })\n }\n }\n val request = Request.Builder()\n .url(API_URL)\n .header(\"Authorization\", \"Bearer ${BuildConfig.DEEPSEEK_API_KEY}\")\n .header(\"Content-Type\", \"application/json\")\n .post(gson.toJson(requestJson).toRequestBody(\"application/json\".toMediaType()))\n .build()\n\n client.newCall(request).execute().use { response ->\n val responseText = response.body?.string().orEmpty()\n if (!response.isSuccessful) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 请求失败(HTTP ${response.code})\")\n }\n return@launch\n }\n val root = gson.fromJson(responseText, JsonObject::class.java)\n val choice = root.getAsJsonArray(\"choices\")?.get(0)?.asJsonObject\n val messageObject = choice?.getAsJsonObject(\"message\")\n val content = messageObject?.get(\"content\")?.asString.orEmpty()\n val reasoning = messageObject?.get(\"reasoning_content\")?.asString\n val usage = root.getAsJsonObject(\"usage\")\n AiUsageStore.record(\n \"DeepSeek\",\n usage?.get(\"prompt_tokens\")?.asInt ?: 0,\n usage?.get(\"completion_tokens\")?.asInt ?: 0,\n usage?.get(\"prompt_cache_hit_tokens\")?.asInt ?: 0\n )\n withContext(Dispatchers.Main) { callback.onSuccess(content, reasoning) }\n }\n } catch (error: Exception) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 连接失败,请稍后重试\")\n }\n }\n }\n }\n\n private const val DEFAULT_SYSTEM_PROMPT =\n \"你是 FitnessDiary 的中文健身与营养助手,请简洁、准确地回答。\"\n}\n"}, "ba7a65ca90969e594d27ac21016ec16c8cd3acfc": {"t": 1785755591.5150433, "n": 4777, "h": "b75346ca025062ed", "c": "package com.cz.fitnessdiary.service\n\nimport com.cz.fitnessdiary.BuildConfig\nimport com.cz.fitnessdiary.database.entity.ChatMessageEntity\nimport com.google.gson.Gson\nimport com.google.gson.JsonObject\nimport com.google.gson.JsonArray\nimport okhttp3.*\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * DeepSeek AI 服务 - 标准 OpenAI 接口实现\n * 已升级至 DeepSeek-V4 系列:deepseek-v4-pro (深度推理) 与 deepseek-v4-flash (极速)\n * 适配 2026 年官方模型退休节点,提升时效与响应表现。\n */\nobject DeepSeekService {\n private val client = OkHttpClient.Builder()\n .connectTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .readTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .writeTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .build()\n private val gson = Gson()\n private val scope = CoroutineScope(Dispatchers.IO)\n private const val API_URL = \"https://api.deepseek.com/chat/completions\"\n\n /**\n * 发送消息(带对话历史)\n * @param message 当前用户消息\n * @param systemInstruction 系统指令\n * @param thinking 是否使用 DeepSeek-V4-Pro 推理模型\n * @param history 历史消息列表(已按时间排序, 最旧在前)\n * @param callback 回调接口\n */\n @JvmStatic\n @JvmOverloads\n fun sendMessage(\n message: String,\n systemInstruction: String? = null,\n thinking: Boolean = false,\n history: List<ChatMessageEntity>? = null,\n callback: AICallback\n ) {\n scope.launch {\n try {\n // 升级为最新的 V4 API,防止 2026年7月 旧版 API 彻底退休下线\n val modelName = if (thinking) \"deepseek-v4-pro\" else \"deepseek-v4-flash\"\n val finalSystemPrompt = systemInstruction ?: \"你是健身日记(FitnessDiary)应用的 AI 助手,身份是一位专业的健身教练和营养师。\" +\n \"在回答食物营养价值时,请务必以 ### [食物名] 开头,并明确列出 [热量]、[蛋白质]、[碳水] 等数值(以每100g为标准)。\" +\n \"请始终使用中文回答。\"\n\n val messagesArray = JsonArray().apply {\n // 1. 系统指令\n add(JsonObject().apply {\n addProperty(\"role\", \"system\")\n addProperty(\"content\", finalSystemPrompt)\n })\n // 2. 历史对话(仅内容摘要,减少 token)\n history?.forEach { msg ->\n add(JsonObject().apply {\n addProperty(\"role\", if (msg.isUser) \"user\" else \"assistant\")\n addProperty(\"content\", msg.content ?: \"\")\n })\n }\n // 3. 当前用户消息\n add(JsonObject().apply {\n addProperty(\"role\", \"user\")\n addProperty(\"content\", message)\n })\n }\n\n val jsonRequest = JsonObject().apply {\n addProperty(\"model\", modelName)\n add(\"messages\", messagesArray)\n addProperty(\"stream\", false)\n }\n\n val body = gson.toJson(jsonRequest).toRequestBody(\"application/json\".toMediaType())\n val request = Request.Builder()\n .url(API_URL)\n .addHeader(\"Authorization\", \"Bearer ${BuildConfig.DEEPSEEK_API_KEY}\")\n .post(body)\n .build()\n\n client.newCall(request).execute().use { response ->\n if (!response.isSuccessful) {\n val errorBody = response.body?.string()\n android.util.Log.e(\"DeepSeekService\", \"API Error: ${response.code} $errorBody\")\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 错误: ${response.code} ${response.message}\")\n }\n return@launch\n }\n\n val responseBody = response.body?.string() ?: \"\"\n val jsonResponse = gson.fromJson(responseBody, JsonObject::class.java)\n val choice = jsonResponse.getAsJsonArray(\"choices\")?.get(0)?.asJsonObject\n val messageObj = choice?.getAsJsonObject(\"message\")\n\n val content = messageObj?.get(\"content\")?.asString ?: \"\"\n val reasoning = messageObj?.get(\"reasoning_content\")?.asString\n\n withContext(Dispatchers.Main) {\n callback.onSuccess(content, reasoning)\n }\n }\n } catch (e: Exception) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 连接失败: ${e.message}\")\n }\n }\n }\n }\n}\n"}, "5889735ef255238396defe7c858accf5b254a516": {"t": 1785755592.7535744, "n": 4634, "h": "9ed10abae77572b8", "c": "package com.cz.fitnessdiary.service\n\nimport com.cz.fitnessdiary.BuildConfig\nimport com.cz.fitnessdiary.database.entity.ChatMessageEntity\nimport com.google.gson.Gson\nimport com.google.gson.JsonObject\nimport com.google.gson.JsonArray\nimport okhttp3.*\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * DeepSeek AI 服务 - 标准 OpenAI 接口实现\n * 支持携带对话历史以实现上下文记忆\n */\nobject DeepSeekService {\n private val client = OkHttpClient.Builder()\n .connectTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .readTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .writeTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .build()\n private val gson = Gson()\n private val scope = CoroutineScope(Dispatchers.IO)\n private const val API_URL = \"https://api.deepseek.com/chat/completions\"\n\n /**\n * 发送消息(带对话历史)\n * @param message 当前用户消息\n * @param systemInstruction 系统指令\n * @param thinking 是否使用 DeepSeek Reasoner 模型\n * @param history 历史消息列表(已按时间排序, 最旧在前)\n * @param callback 回调接口\n */\n @JvmStatic\n @JvmOverloads\n fun sendMessage(\n message: String,\n systemInstruction: String? = null,\n thinking: Boolean = false,\n history: List<ChatMessageEntity>? = null,\n callback: AICallback\n ) {\n scope.launch {\n try {\n val modelName = if (thinking) \"deepseek-reasoner\" else \"deepseek-chat\"\n val finalSystemPrompt = systemInstruction ?: \"你是健身日记(FitnessDiary)应用的 AI 助手,身份是一位专业的健身教练和营养师。\" +\n \"在回答食物营养价值时,请务必以 ### [食物名] 开头,并明确列出 [热量]、[蛋白质]、[碳水] 等数值(以每100g为标准)。\" +\n \"请始终使用中文回答。\"\n\n val messagesArray = JsonArray().apply {\n // 1. 系统指令\n add(JsonObject().apply {\n addProperty(\"role\", \"system\")\n addProperty(\"content\", finalSystemPrompt)\n })\n // 2. 历史对话(仅内容摘要,减少 token)\n history?.forEach { msg ->\n add(JsonObject().apply {\n addProperty(\"role\", if (msg.isUser) \"user\" else \"assistant\")\n addProperty(\"content\", msg.content ?: \"\")\n })\n }\n // 3. 当前用户消息\n add(JsonObject().apply {\n addProperty(\"role\", \"user\")\n addProperty(\"content\", message)\n })\n }\n\n val jsonRequest = JsonObject().apply {\n addProperty(\"model\", modelName)\n add(\"messages\", messagesArray)\n addProperty(\"stream\", false)\n }\n\n val body = gson.toJson(jsonRequest).toRequestBody(\"application/json\".toMediaType())\n val request = Request.Builder()\n .url(API_URL)\n .addHeader(\"Authorization\", \"Bearer ${BuildConfig.DEEPSEEK_API_KEY}\")\n .post(body)\n .build()\n\n client.newCall(request).execute().use { response ->\n if (!response.isSuccessful) {\n val errorBody = response.body?.string()\n android.util.Log.e(\"DeepSeekService\", \"API Error: ${response.code} $errorBody\")\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 错误: ${response.code} ${response.message}\")\n }\n return@launch\n }\n\n val responseBody = response.body?.string() ?: \"\"\n val jsonResponse = gson.fromJson(responseBody, JsonObject::class.java)\n val choice = jsonResponse.getAsJsonArray(\"choices\")?.get(0)?.asJsonObject\n val messageObj = choice?.getAsJsonObject(\"message\")\n\n val content = messageObj?.get(\"content\")?.asString ?: \"\"\n val reasoning = messageObj?.get(\"reasoning_content\")?.asString\n\n withContext(Dispatchers.Main) {\n callback.onSuccess(content, reasoning)\n }\n }\n } catch (e: Exception) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 连接失败: ${e.message}\")\n }\n }\n }\n }\n}\n"}, "1f039434b1e759cce552ffa50cc938810b777130": {"t": 1785755594.2938151, "n": 4681, "h": "347a453f5c1c3398", "c": "package com.cz.fitnessdiary.service\n\nimport com.cz.fitnessdiary.BuildConfig\nimport com.cz.fitnessdiary.database.entity.ChatMessageEntity\nimport com.google.gson.Gson\nimport com.google.gson.JsonObject\nimport com.google.gson.JsonArray\nimport okhttp3.*\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport java.io.IOException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * DeepSeek AI 服务 - 标准 OpenAI 接口实现\n * 支持携带对话历史以实现上下文记忆\n */\nobject DeepSeekService {\n private val client = OkHttpClient.Builder()\n .connectTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .readTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .writeTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .build()\n private val gson = Gson()\n private val scope = CoroutineScope(Dispatchers.IO)\n private const val API_URL = \"https://api.deepseek.com/chat/completions\"\n\n /**\n * 发送消息(带对话历史)\n * @param message 当前用户消息\n * @param systemInstruction 系统指令\n * @param thinking 是否使用 DeepSeek Reasoner 模型\n * @param history 历史消息列表(已按时间排序, 最旧在前)\n * @param callback 回调接口\n */\n @JvmStatic\n @JvmOverloads\n fun sendMessage(\n message: String,\n systemInstruction: String? = null,\n thinking: Boolean = false,\n history: List<ChatMessageEntity>? = null,\n callback: AICallback\n ) {\n scope.launch {\n try {\n val modelName = if (thinking) \"deepseek-reasoner\" else \"deepseek-chat\"\n val finalSystemPrompt = systemInstruction ?: \"你是健身日记(FitnessDiary)应用的 AI 助手,身份是一位专业的健身教练和营养师。\" +\n \"在回答食物营养价值时,请务必以 ### [食物名] 开头,并明确列出 [热量]、[蛋白质]、[碳水] 等数值(以每100g为标准)。\" +\n \"请始终使用中文回答。\"\n\n val messagesArray = JsonArray().apply {\n // 1. 系统指令\n add(JsonObject().apply {\n addProperty(\"role\", \"system\")\n addProperty(\"content\", finalSystemPrompt)\n })\n // 2. 历史对话(仅内容摘要,减少 token)\n history?.forEach { msg ->\n add(JsonObject().apply {\n addProperty(\"role\", if (msg.isUser) \"user\" else \"assistant\")\n addProperty(\"content\", msg.content ?: \"\")\n })\n }\n // 3. 当前用户消息\n add(JsonObject().apply {\n addProperty(\"role\", \"user\")\n addProperty(\"content\", message)\n })\n }\n\n val jsonRequest = JsonObject().apply {\n addProperty(\"model\", modelName)\n add(\"messages\", messagesArray)\n addProperty(\"stream\", false)\n }\n\n val body = gson.toJson(jsonRequest).toRequestBody(\"application/json\".toMediaType())\n val request = Request.Builder()\n .url(API_URL)\n .addHeader(\"Authorization\", \"Bearer ${BuildConfig.DEEPSEEK_API_KEY}\")\n .post(body)\n .build()\n\n client.newCall(request).execute().use { response ->\n if (!response.isSuccessful) {\n val errorBody = response.body?.string()\n android.util.Log.e(\"DeepSeekService\", \"API Error: ${response.code} $errorBody\")\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 错误: ${response.code} ${response.message}\")\n }\n return@launch\n }\n\n val responseBody = response.body?.string() ?: \"\"\n val jsonResponse = gson.fromJson(responseBody, JsonObject::class.java)\n val choice = jsonResponse.getAsJsonArray(\"choices\")?.get(0)?.asJsonObject\n val messageObj = choice?.getAsJsonObject(\"message\")\n \n val content = messageObj?.get(\"content\")?.asString ?: \"\"\n val reasoning = messageObj?.get(\"reasoning_content\")?.asString\n\n withContext(Dispatchers.Main) {\n callback.onSuccess(content, reasoning)\n }\n }\n } catch (e: Exception) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 连接失败: ${e.message}\")\n }\n }\n }\n }\n}\n"}, "cee9b66fe64f40110a01cff9cf7fd4b0e9b03d24": {"t": 1785755596.1297743, "n": 3862, "h": "618d785d5680a7e2", "c": "package com.cz.fitnessdiary.service\n\nimport com.cz.fitnessdiary.BuildConfig\nimport com.google.gson.Gson\nimport com.google.gson.JsonObject\nimport com.google.gson.JsonArray\nimport okhttp3.*\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport java.io.IOException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * DeepSeek AI 服务 - 标准 OpenAI 接口实现\n */\nobject DeepSeekService {\n private val client = OkHttpClient.Builder()\n .connectTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .readTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .writeTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .build()\n private val gson = Gson()\n private val scope = CoroutineScope(Dispatchers.IO)\n private const val API_URL = \"https://api.deepseek.com/chat/completions\"\n\n @JvmStatic\n @JvmOverloads\n fun sendMessage(message: String, systemInstruction: String? = null, thinking: Boolean = false, callback: AICallback) {\n scope.launch {\n try {\n val modelName = if (thinking) \"deepseek-reasoner\" else \"deepseek-chat\"\n val finalSystemPrompt = systemInstruction ?: \"你是健身日记(FitnessDiary)应用的 AI 助手,身份是一位专业的健身教练和营养师。\" +\n \"在回答食物营养价值时,请务必以 ### [食物名] 开头,并明确列出 [热量]、[蛋白质]、[碳水] 等数值(以每100g为标准)。\" +\n \"请始终使用中文回答。\"\n\n val jsonRequest = JsonObject().apply {\n addProperty(\"model\", modelName)\n add(\"messages\", JsonArray().apply {\n add(JsonObject().apply {\n addProperty(\"role\", \"system\")\n addProperty(\"content\", finalSystemPrompt)\n })\n add(JsonObject().apply {\n addProperty(\"role\", \"user\")\n addProperty(\"content\", message)\n })\n })\n addProperty(\"stream\", false)\n }\n\n val body = gson.toJson(jsonRequest).toRequestBody(\"application/json\".toMediaType())\n val request = Request.Builder()\n .url(API_URL)\n .addHeader(\"Authorization\", \"Bearer ${BuildConfig.DEEPSEEK_API_KEY}\")\n .post(body)\n .build()\n\n client.newCall(request).execute().use { response ->\n if (!response.isSuccessful) {\n val errorBody = response.body?.string()\n android.util.Log.e(\"DeepSeekService\", \"API Error: ${response.code} $errorBody\")\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 错误: ${response.code} ${response.message}\")\n }\n return@launch\n }\n\n val responseBody = response.body?.string() ?: \"\"\n val jsonResponse = gson.fromJson(responseBody, JsonObject::class.java)\n val choice = jsonResponse.getAsJsonArray(\"choices\")?.get(0)?.asJsonObject\n val messageObj = choice?.getAsJsonObject(\"message\")\n \n val content = messageObj?.get(\"content\")?.asString ?: \"\"\n val reasoning = messageObj?.get(\"reasoning_content\")?.asString\n\n withContext(Dispatchers.Main) {\n callback.onSuccess(content, reasoning)\n }\n }\n } catch (e: Exception) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 连接失败: ${e.message}\")\n }\n }\n }\n }\n}\n"}, "be6ee3ea60ff1b5c1dd6bbc0488299a0651ff919": {"t": 1785755597.412065, "n": 3880, "h": "3b47cd6ff091360c", "c": "package com.cz.fitnessdiary.service\n\nimport com.cz.fitnessdiary.BuildConfig\nimport com.google.gson.Gson\nimport com.google.gson.JsonObject\nimport com.google.gson.JsonArray\nimport okhttp3.*\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport java.io.IOException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * DeepSeek AI 服务 - 标准 OpenAI 接口实现\n */\nobject DeepSeekService {\n private val client = OkHttpClient.Builder()\n .connectTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .readTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .writeTimeout(60, java.util.concurrent.TimeUnit.SECONDS)\n .build()\n private val gson = Gson()\n private val scope = CoroutineScope(Dispatchers.IO)\n private const val API_URL = \"https://api.deepseek.com/chat/completions\"\n\n @JvmStatic\n @JvmOverloads\n fun sendMessage(message: String, systemInstruction: String? = null, thinking: Boolean = false, callback: GeminiService.GeminiCallback) {\n scope.launch {\n try {\n val modelName = if (thinking) \"deepseek-reasoner\" else \"deepseek-chat\"\n val finalSystemPrompt = systemInstruction ?: \"你是健身日记(FitnessDiary)应用的 AI 助手,身份是一位专业的健身教练和营养师。\" +\n \"在回答食物营养价值时,请务必以 ### [食物名] 开头,并明确列出 [热量]、[蛋白质]、[碳水] 等数值(以每100g为标准)。\" +\n \"请始终使用中文回答。\"\n\n val jsonRequest = JsonObject().apply {\n addProperty(\"model\", modelName)\n add(\"messages\", JsonArray().apply {\n add(JsonObject().apply {\n addProperty(\"role\", \"system\")\n addProperty(\"content\", finalSystemPrompt)\n })\n add(JsonObject().apply {\n addProperty(\"role\", \"user\")\n addProperty(\"content\", message)\n })\n })\n addProperty(\"stream\", false)\n }\n\n val body = gson.toJson(jsonRequest).toRequestBody(\"application/json\".toMediaType())\n val request = Request.Builder()\n .url(API_URL)\n .addHeader(\"Authorization\", \"Bearer ${BuildConfig.DEEPSEEK_API_KEY}\")\n .post(body)\n .build()\n\n client.newCall(request).execute().use { response ->\n if (!response.isSuccessful) {\n val errorBody = response.body?.string()\n android.util.Log.e(\"DeepSeekService\", \"API Error: ${response.code} $errorBody\")\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 错误: ${response.code} ${response.message}\")\n }\n return@launch\n }\n\n val responseBody = response.body?.string() ?: \"\"\n val jsonResponse = gson.fromJson(responseBody, JsonObject::class.java)\n val choice = jsonResponse.getAsJsonArray(\"choices\")?.get(0)?.asJsonObject\n val messageObj = choice?.getAsJsonObject(\"message\")\n \n val content = messageObj?.get(\"content\")?.asString ?: \"\"\n val reasoning = messageObj?.get(\"reasoning_content\")?.asString\n\n withContext(Dispatchers.Main) {\n callback.onSuccess(content, reasoning)\n }\n }\n } catch (e: Exception) {\n withContext(Dispatchers.Main) {\n callback.onError(\"DeepSeek 连接失败: ${e.message}\")\n }\n }\n }\n }\n}\n"}}}}