|
@@ -51,6 +51,9 @@ public class AIService {
|
|
|
@Value("${dify.tongue-api-key:}")
|
|
@Value("${dify.tongue-api-key:}")
|
|
|
private String tongueApiKey;
|
|
private String tongueApiKey;
|
|
|
|
|
|
|
|
|
|
+ @Value("${dify.emotion-api-key:}")
|
|
|
|
|
+ private String emotionApiKey;
|
|
|
|
|
+
|
|
|
@Resource
|
|
@Resource
|
|
|
private EmotionCheckinMapper emotionCheckinMapper;
|
|
private EmotionCheckinMapper emotionCheckinMapper;
|
|
|
|
|
|
|
@@ -82,6 +85,13 @@ public class AIService {
|
|
|
return headers;
|
|
return headers;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private HttpHeaders emotionAuthHeaders() {
|
|
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
|
+ headers.setBearerAuth(emotionApiKey);
|
|
|
|
|
+ return headers;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 发送聊天消息: 统一走 Python LangGraph(不含 Dify 备选)
|
|
* 发送聊天消息: 统一走 Python LangGraph(不含 Dify 备选)
|
|
|
*/
|
|
*/
|
|
@@ -320,6 +330,73 @@ public class AIService {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 照片情绪识别
|
|
|
|
|
+ * 调用 Dify 情绪识别 Workflow,输入图片 URL,返回结构化情绪列表
|
|
|
|
|
+ */
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ public Map<String, Object> sendEmotionRecognition(String imageUrl) {
|
|
|
|
|
+ if (emotionApiKey == null || emotionApiKey.isEmpty()) {
|
|
|
|
|
+ return mockEmotionResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String url = difyBaseUrl + "/workflows/run";
|
|
|
|
|
+ HttpHeaders headers = emotionAuthHeaders();
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> body = new LinkedHashMap<>();
|
|
|
|
|
+ body.put("inputs", Collections.emptyMap());
|
|
|
|
|
+ body.put("user", "emotion-recognition");
|
|
|
|
|
+ body.put("response_mode", "blocking");
|
|
|
|
|
+
|
|
|
|
|
+ // 将图片转为 Dify 可接收的格式(URL)
|
|
|
|
|
+ Map<String, Object> fileInput = new HashMap<>();
|
|
|
|
|
+ fileInput.put("type", "image");
|
|
|
|
|
+ fileInput.put("url", imageUrl);
|
|
|
|
|
+ Map<String, Object> inputsWithPhoto = new LinkedHashMap<>(); inputsWithPhoto.put("photo", fileInput); body.put("inputs", inputsWithPhoto);
|
|
|
|
|
+
|
|
|
|
|
+ HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);
|
|
|
|
|
+ try {
|
|
|
|
|
+ ResponseEntity<Map> resp = restTemplate.postForEntity(url, entity, Map.class);
|
|
|
|
|
+ Map<String, Object> respBody = resp.getBody();
|
|
|
|
|
+ if (respBody != null && respBody.containsKey("data")) {
|
|
|
|
|
+ Map<String, Object> data = (Map<String, Object>) respBody.get("data");
|
|
|
|
|
+ Map<String, Object> outputs = (Map<String, Object>) data.get("outputs");
|
|
|
|
|
+ if (outputs != null) {
|
|
|
|
|
+ return outputs;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return mockEmotionResult();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("Dify emotion recognition failed, using mock: {}", e.getMessage());
|
|
|
|
|
+ return mockEmotionResult();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 模拟情绪识别结果(开发阶段使用)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, Object> mockEmotionResult() {
|
|
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
+ List<Map<String, Object>> emotions = new ArrayList<>();
|
|
|
|
|
+ String[][] mockData = {
|
|
|
|
|
+ {"开心", "joy", "60", "#10B981"},
|
|
|
|
|
+ {"平静", "trust", "25", "#3B82F6"},
|
|
|
|
|
+ {"疲惫", "sadness", "10", "#9CA3AF"},
|
|
|
|
|
+ {"期待", "anticipation", "5", "#F59E0B"}
|
|
|
|
|
+ };
|
|
|
|
|
+ for (String[] item : mockData) {
|
|
|
|
|
+ Map<String, Object> em = new HashMap<>();
|
|
|
|
|
+ em.put("label", item[0]);
|
|
|
|
|
+ em.put("type", item[1]);
|
|
|
|
|
+ em.put("confidence", Double.parseDouble(item[2]));
|
|
|
|
|
+ em.put("color", item[3]);
|
|
|
|
|
+ emotions.add(em);
|
|
|
|
|
+ }
|
|
|
|
|
+ result.put("emotions", emotions);
|
|
|
|
|
+ result.put("mood_weather", "sunny");
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Build an emotional context prompt for the Dify AI conversation
|
|
* Build an emotional context prompt for the Dify AI conversation
|
|
|
* based on the child's recent checkin history.
|
|
* based on the child's recent checkin history.
|