1 line
90 KiB
JSON
1 line
90 KiB
JSON
{"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"}}}} |