Просмотр исходного кода

Merge branch 'cfclub' of http://git.iwintrue.com/liaoxg/cfc into cfclub

iwt 1 месяц назад
Родитель
Сommit
3df357a256

+ 66 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java

@@ -6,6 +6,7 @@ import com.etotem.cfc.dto.RecommendationQuery;
 import com.etotem.cfc.dto.RecommendationResult;
 import com.etotem.cfc.dto.TaskParseResult;
 import com.etotem.cfc.service.AIService;
+import com.etotem.cfc.service.AiGateway;
 import com.etotem.cfc.service.FamilyContextService;
 import com.etotem.cfc.service.HealthAnalysisService;
 import com.etotem.cfc.service.RecommendationService;
@@ -55,6 +56,9 @@ public class AIChatController {
     @Resource
     private com.etotem.cfc.service.ChatMirrorService chatMirrorService;
 
+    @Resource
+    private AiGateway aiGateway;
+
     @Operation(summary = "发送聊天消息(支持传入reportId以解读报告)")
     @PostMapping("/chat/send")
     public Result<Map<String, Object>> sendMessage(
@@ -410,4 +414,66 @@ public class AIChatController {
         return s;
     }
 
+    // ── 健康教练 ──
+
+    @Operation(summary = "发送健康教练消息")
+    @PostMapping("/health-coach/send")
+    public Result<Map<String, Object>> sendHealthCoachMessage(
+            @RequestAttribute("userId") Long userId,
+            @RequestBody Map<String, Object> params) {
+        String query = (String) params.get("query");
+        String conversationId = (String) params.get("conversationId");
+        if (query == null || query.trim().isEmpty()) {
+            return Result.error("消息不能为空");
+        }
+        Long memberId = params.get("memberId") != null ? Long.valueOf(params.get("memberId").toString()) : null;
+        Map<String, Object> inputs = new LinkedHashMap<>();
+        if (memberId != null) {
+            inputs.put("child_id", memberId);
+        }
+        Map<String, Object> resp = aiGateway.chat(query, userId, conversationId, inputs);
+        String answer = resp != null ? (String) resp.getOrDefault("answer", "") : "";
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("answer", answer);
+        result.put("conversationId", resp != null ? resp.getOrDefault("conversationId", "") : "");
+        result.put("recommendations", Collections.emptyList());
+        result.put("tasks", Collections.emptyList());
+        return Result.success(result);
+    }
+
+    // ── 健康管家 ──
+
+    @Operation(summary = "发送健康管家消息")
+    @PostMapping("/butler/send")
+    public Result<Map<String, Object>> sendHealthButlerMessage(
+            @RequestAttribute("userId") Long userId,
+            @RequestBody Map<String, Object> params) {
+        String query = (String) params.get("query");
+        String conversationId = (String) params.get("conversationId");
+        if (query == null || query.trim().isEmpty()) {
+            return Result.error("消息不能为空");
+        }
+        Map<String, Object> inputs = new LinkedHashMap<>();
+        if (params.get("memberId") != null) {
+            inputs.put("child_id", Long.valueOf(params.get("memberId").toString()));
+        }
+        if (params.get("reportId") != null) {
+            inputs.put("report_id", Long.valueOf(params.get("reportId").toString()));
+        }
+        if (params.get("focus") != null) {
+            inputs.put("focus", params.get("focus").toString());
+        }
+        Map<String, Object> resp = aiGateway.chat(query, userId, conversationId, inputs);
+        String answer = resp != null ? (String) resp.getOrDefault("answer", "") : "";
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("answer", answer);
+        result.put("conversationId", resp != null ? resp.getOrDefault("conversationId", "") : "");
+        result.put("recommendations", Collections.emptyList());
+        @SuppressWarnings("unchecked")
+        List<Object> tasks = resp != null && resp.get("tasks") instanceof List
+                ? (List<Object>) resp.get("tasks") : Collections.emptyList();
+        result.put("tasks", tasks);
+        return Result.success(result);
+    }
+
 }

+ 0 - 16
cfc-frontend/config.js

@@ -12,14 +12,6 @@
 
 let API_BASE_URL = 'http://cfc.iwintrue.com'
 
-// LangGraph 适配层地址(AI 对话/分析走这里,失败时回退到 Dify)
-// 默认本地开发:http://localhost:9000;远程环境按需修改
-let LANGGRAPH_BASE_URL = 'http://localhost:9000'
-
-// Dify fallback 地址(LangGraph 不可用时回退到此)
-// 默认本地开发:http://localhost:8083;远程环境按需修改
-let DIFY_BASE_URL = 'http://localhost:8083'
-
 try {
   const accountInfo = uni.getAccountInfoSync()
   const env = accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.envVersion
@@ -28,18 +20,12 @@ try {
     switch (env) {
       case 'develop':
         API_BASE_URL = 'https://cfc.etotem.com.cn'
-        LANGGRAPH_BASE_URL = 'https://ai.etotem.com.cn'
-        DIFY_BASE_URL = 'http://dify.iwintrue.com'
         break
       case 'trial':
         API_BASE_URL = 'https://cfc.iwintrue.com'
-        LANGGRAPH_BASE_URL = 'https://ai.etotem.com.cn'
-        DIFY_BASE_URL = 'http://dify.iwintrue.com'
         break
       case 'release':
         API_BASE_URL = 'https://cfc.etotem.com.cn'
-        LANGGRAPH_BASE_URL = 'https://ai.etotem.com.cn'
-        DIFY_BASE_URL = 'http://dify.iwintrue.com'
         break
     }
   }
@@ -49,8 +35,6 @@ try {
 
 export default {
   API_BASE_URL,
-  LANGGRAPH_BASE_URL,
-  DIFY_BASE_URL,
 
   api(path) {
     return API_BASE_URL + path

+ 4 - 214
cfc-frontend/utils/api.js

@@ -1,7 +1,6 @@
 import config from '@/config.js'
 
 const BASE_URL = config.API_BASE_URL
-const LANGGRAPH_URL = config.LANGGRAPH_BASE_URL
 
 var _redirectingToLogin = false
 
@@ -104,68 +103,6 @@ const request = (url, method = 'POST', data = {}, options = {}) => {
   })
 }
 
-//  LangGraph 适配层(含 Dify fallback) 
-//
-// langgraphRequest() 是 AI 对话/分析的统一入口:
-//   1. 先尝试调用 LangGraph 适配层(/api/v1/chat/completion 或 /api/v1/analysis/run)
-//   2. 若 LangGraph 不可达(网络错误/超时/非 2xx),自动回退到 Dify(/api/ai/*)
-//
-// 参数:
-//   langgraphPath  - LangGraph 端点路径(如 '/api/v1/chat/completion')
-//   langgraphBody  - 发给 LangGraph 的请求体(DifyChatRequest / DifyAnalysisRequest)
-//   difyFallbackPath - Dify 回退端点路径(如 '/api/ai/chat/send')
-//   difyFallbackData  - 发给 Dify 的请求体(原 request() 的 data 参数)
-//   difyFallbackMethod - Dify 回退请求方法(默认 'POST')
-//   difyFallbackOptions - Dify 回退请求 options(同 request() 的 options)
-//   transform - 可选,把 LangGraph 响应转成页面期望的 { code, data, ... } 结构
-//
-// 返回 Promise,resolve 形态与 request() 一致:{ code: 200, data: {...}, message: ... }
-const langgraphRequest = (opts) => {
-  var langgraphPath = opts.langgraphPath
-  var langgraphBody = opts.langgraphBody || {}
-  var difyFallbackPath = opts.difyFallbackPath
-  var difyFallbackData = opts.difyFallbackData || {}
-  var difyFallbackMethod = opts.difyFallbackMethod || 'POST'
-  var difyFallbackOptions = opts.difyFallbackOptions || {}
-  var transform = opts.transform
-
-  return new Promise((resolve, reject) => {
-    var token = uni.getStorageSync('token')
-
-    uni.request({
-      url: LANGGRAPH_URL + langgraphPath,
-      method: 'POST',
-      data: langgraphBody,
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': token ? ('Bearer ' + token) : ''
-      },
-      timeout: 30000,
-      success: (res) => {
-        // LangGraph 不可用(非 2xx)→ 回退 Dify
-        if (res.statusCode < 200 || res.statusCode >= 300) {
-          _fallbackToDify(difyFallbackPath, difyFallbackMethod, difyFallbackData, difyFallbackOptions, resolve, reject)
-          return
-        }
-        // LangGraph 成功 → 转成页面期望的 { code: 200, data: {...} } 结构
-        var lgData = res.data || {}
-        var pageData = transform ? transform(lgData) : lgData
-        resolve({ code: 200, message: 'ok', data: pageData })
-      },
-      fail: (err) => {
-        console.warn('[API] LangGraph不可达,回退Dify ' + LANGGRAPH_URL + langgraphPath, err)
-        // LangGraph 网络错误 → 回退 Dify
-        _fallbackToDify(difyFallbackPath, difyFallbackMethod, difyFallbackData, difyFallbackOptions, resolve, reject)
-      }
-    })
-  })
-}
-
-// 内部:Dify 回退(复用 request(),保持原有鉴权/错误处理/重定向逻辑)
-function _fallbackToDify(path, method, data, options, resolve, reject) {
-  request(path, method, data, options).then(resolve).catch(reject)
-}
-
 // 登录模块
 export const wechatLogin = (code, userInfo, phoneCode) => {
   var data = { code: code, nickname: userInfo.nickname, avatar: userInfo.avatar }
@@ -1324,49 +1261,7 @@ export const getMyPendingRequest = () => {
 
 // ===== AI 聊天 =====
 export const aiSendMessage = (data) => {
-  var query = data && data.query ? data.query : ''
-  var conversationId = data && data.conversationId ? data.conversationId : ''
-  var userId = uni.getStorageSync('userId') || ''
-  var langgraphBody = {
-    query: query,
-    user: userId,
-    user_id: userId,
-    conversation_id: conversationId,
-    response_mode: 'blocking',
-    bot_name: 'AI管家'
-  }
-  var inputs = {}
-  if (data && data.reportId) {
-    inputs.report_id = data.reportId
-  }
-  if (data && data.healthStatus) {
-    inputs.health_status = data.healthStatus
-  }
-  if (data && data.dietPrefs) {
-    inputs.diet_preferences = data.dietPrefs
-  }
-  if (Object.keys(inputs).length > 0) {
-    langgraphBody.inputs = inputs
-  }
-  return langgraphRequest({
-    langgraphPath: '/api/v1/chat/completion',
-    langgraphBody: langgraphBody,
-    difyFallbackPath: '/api/ai/chat/send',
-    difyFallbackData: data,
-    transform: function(lgRes) {
-      var content = ''
-      if (lgRes && lgRes.choices && lgRes.choices.length > 0) {
-        var msg = lgRes.choices[0].message
-        if (msg && msg.content) { content = msg.content }
-      }
-      return {
-        answer: content,
-        conversationId: conversationId,
-        recommendations: [],
-        tasks: []
-      }
-    }
-  })
+  return request('/api/ai/chat/send', 'POST', data)
 }
 
 export const aiGetConversations = () => {
@@ -1383,120 +1278,17 @@ export const aiDeleteConversation = (id) => {
 
 // 营养助手(基于体检报告)
 export const aiSendNutritionMessage = (data) => {
-  var query = data && data.query ? data.query : ''
-  var conversationId = data && data.conversationId ? data.conversationId : ''
-  var reportId = data && data.reportId ? data.reportId : null
-  var userId = uni.getStorageSync('userId') || ''
-  var langgraphBody = {
-    query: query,
-    user: userId,
-    user_id: userId,
-    conversation_id: conversationId,
-    response_mode: 'blocking',
-    bot_name: '营养助手'
-  }
-  if (reportId) {
-    langgraphBody.inputs = { report_id: reportId }
-  }
-  return langgraphRequest({
-    langgraphPath: '/api/v1/analysis/run',
-    langgraphBody: langgraphBody,
-    difyFallbackPath: '/api/ai/nutrition/send',
-    difyFallbackData: data,
-    transform: function(lgRes) {
-      var content = ''
-      if (lgRes && lgRes.choices && lgRes.choices.length > 0) {
-        var msg = lgRes.choices[0].message
-        if (msg && msg.content) { content = msg.content }
-      }
-      return {
-        answer: content,
-        conversationId: conversationId,
-        recommendations: [],
-        tasks: []
-      }
-    }
-  })
+  return request('/api/ai/nutrition/send', 'POST', data)
 }
 
 // 健康教练
 export const aiSendHealthCoachMessage = (data) => {
-  var query = data && data.query ? data.query : ''
-  var conversationId = data && data.conversationId ? data.conversationId : ''
-  var userId = uni.getStorageSync('userId') || ''
-  var langgraphBody = {
-    query: query,
-    user: userId,
-    user_id: userId,
-    conversation_id: conversationId,
-    response_mode: 'blocking',
-    bot_name: '健康教练'
-  }
-  if (data && data.memberId) {
-    langgraphBody.inputs = { child_id: data.memberId }
-  }
-  return langgraphRequest({
-    langgraphPath: '/api/v1/health/coach',
-    langgraphBody: langgraphBody,
-    difyFallbackPath: '/api/ai/chat/send',
-    difyFallbackData: data,
-    transform: function(lgRes) {
-      var content = ''
-      if (lgRes && lgRes.choices && lgRes.choices.length > 0) {
-        var msg = lgRes.choices[0].message
-        if (msg && msg.content) { content = msg.content }
-      }
-      return {
-        answer: content,
-        conversationId: conversationId,
-        recommendations: [],
-        tasks: []
-      }
-    }
-  })
+  return request('/api/ai/health-coach/send', 'POST', data)
 }
 
 // 健康管家(健康知识库检索 + 个性化建议 + 任务生成)
 export const aiSendHealthButlerMessage = (data) => {
-  var query = data && data.query ? data.query : ''
-  var conversationId = data && data.conversationId ? data.conversationId : ''
-  var userId = uni.getStorageSync('userId') || ''
-  var memberId = data && data.memberId ? data.memberId : null
-  var langgraphBody = {
-    query: query,
-    user: userId,
-    user_id: userId,
-    conversation_id: conversationId,
-    response_mode: 'blocking',
-    bot_name: '健康管家'
-  }
-  var inputs = {}
-  if (memberId) { inputs.child_id = memberId }
-  if (data && data.reportId) { inputs.report_id = data.reportId }
-  if (data && data.focus) { inputs.focus = data.focus }
-  if (Object.keys(inputs).length > 0) { langgraphBody.inputs = inputs }
-  return langgraphRequest({
-    langgraphPath: '/api/v1/health/butler',
-    langgraphBody: langgraphBody,
-    difyFallbackPath: '/api/ai/chat/send',
-    difyFallbackData: data,
-    transform: function(lgRes) {
-      var content = ''
-      if (lgRes && lgRes.choices && lgRes.choices.length > 0) {
-        var msg = lgRes.choices[0].message
-        if (msg && msg.content) { content = msg.content }
-      }
-      var tasks = []
-      var metadata = lgRes && lgRes.metadata
-      if (metadata && metadata.tasks) { tasks = metadata.tasks }
-      return {
-        answer: content,
-        conversationId: conversationId,
-        recommendations: [],
-        tasks: tasks
-      }
-    }
-  })
+  return request('/api/ai/butler/send', 'POST', data)
 }
 
 //  幸福邻里部落 - 服务商模块 
@@ -2706,8 +2498,6 @@ export const getNotificationList = () => request('/api/notification/list', 'POST
 export const readNotification = (data) => request('/api/notification/read', 'POST', data)
 // 专项调研
 export const submitProblemSurvey = (data) => request('/api/problem-survey/submit', 'POST', data)
-// 获取专项调研问卷(按问题域 code,未绑定模板时后端返回内置默认问卷)
-export const getProblemSurveyQuestions = (problemDomainCode) => request('/api/problem-survey/questions', 'POST', { problemDomainCode })
 // 套餐详情
 export const getProblemPackageDetail = (packageId) => request('/api/problem-package/detail', 'POST', { packageId })
 // 方案确认(生成方案任务 + 推进旅程 stage=6,packageId 可省略)