人生轨迹功能完善
This commit is contained in:
Generated
+1
-1
@@ -7,8 +7,8 @@
|
|||||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
<outputRelativeToContentRoot value="true" />
|
<outputRelativeToContentRoot value="true" />
|
||||||
<module name="emotion-single" />
|
|
||||||
<module name="emotion-museum-backend" />
|
<module name="emotion-museum-backend" />
|
||||||
|
<module name="emotion-single" />
|
||||||
</profile>
|
</profile>
|
||||||
<profile name="Annotation profile for emotion-museum-server" enabled="true">
|
<profile name="Annotation profile for emotion-museum-server" enabled="true">
|
||||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
|||||||
Generated
+2
-2
@@ -9,12 +9,12 @@
|
|||||||
<remote-repository>
|
<remote-repository>
|
||||||
<option name="id" value="central" />
|
<option name="id" value="central" />
|
||||||
<option name="name" value="Central Repository" />
|
<option name="name" value="Central Repository" />
|
||||||
<option name="url" value="https://maven.aliyun.com/nexus/content/groups/public/" />
|
<option name="url" value="https://repo.huaweicloud.com/repository/maven/" />
|
||||||
</remote-repository>
|
</remote-repository>
|
||||||
<remote-repository>
|
<remote-repository>
|
||||||
<option name="id" value="central" />
|
<option name="id" value="central" />
|
||||||
<option name="name" value="Central Repository" />
|
<option name="name" value="Central Repository" />
|
||||||
<option name="url" value="https://repo.huaweicloud.com/repository/maven/" />
|
<option name="url" value="https://maven.aliyun.com/nexus/content/groups/public/" />
|
||||||
</remote-repository>
|
</remote-repository>
|
||||||
<remote-repository>
|
<remote-repository>
|
||||||
<option name="id" value="spring-ai" />
|
<option name="id" value="spring-ai" />
|
||||||
|
|||||||
@@ -68,4 +68,14 @@ public class EpicScriptCreateRequest extends BaseRequest {
|
|||||||
* 是否当前选中
|
* 是否当前选中
|
||||||
*/
|
*/
|
||||||
private Boolean isSelected;
|
private Boolean isSelected;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色信息(前端传入,用于AI生成)
|
||||||
|
*/
|
||||||
|
private String characterInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过往经历关键词(前端传入,用于AI生成)
|
||||||
|
*/
|
||||||
|
private String lifeEventsSummary;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ public class EpicScriptServiceImpl extends ServiceImpl<EpicScriptMapper, EpicScr
|
|||||||
if (plotJson == null) {
|
if (plotJson == null) {
|
||||||
plotJson = new java.util.HashMap<>();
|
plotJson = new java.util.HashMap<>();
|
||||||
}
|
}
|
||||||
plotJson.put("aiGeneratedContent", aiGeneratedContent);
|
plotJson.put("fullContent", aiGeneratedContent);
|
||||||
script.setPlotJson(plotJson);
|
script.setPlotJson(plotJson);
|
||||||
log.info("AI生成剧本内容成功,用户ID: {}, 内容长度: {}", currentUserId, aiGeneratedContent.length());
|
log.info("AI生成剧本内容成功,用户ID: {}, 内容长度: {}", currentUserId, aiGeneratedContent.length());
|
||||||
}
|
}
|
||||||
@@ -203,6 +203,16 @@ public class EpicScriptServiceImpl extends ServiceImpl<EpicScriptMapper, EpicScr
|
|||||||
private String assembleScriptInput(EpicScriptCreateRequest request) {
|
private String assembleScriptInput(EpicScriptCreateRequest request) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 角色信息
|
||||||
|
if (StringUtils.hasText(request.getCharacterInfo())) {
|
||||||
|
sb.append("【角色信息】").append(request.getCharacterInfo()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过往经历
|
||||||
|
if (StringUtils.hasText(request.getLifeEventsSummary())) {
|
||||||
|
sb.append("【过往经历】").append(request.getLifeEventsSummary()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
// 标题
|
// 标题
|
||||||
if (StringUtils.hasText(request.getTitle())) {
|
if (StringUtils.hasText(request.getTitle())) {
|
||||||
sb.append("【剧本标题】").append(request.getTitle()).append("\n");
|
sb.append("【剧本标题】").append(request.getTitle()).append("\n");
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import com.emotion.entity.LifeEvent;
|
|||||||
import com.emotion.mapper.LifeEventMapper;
|
import com.emotion.mapper.LifeEventMapper;
|
||||||
import com.emotion.service.LifeEventService;
|
import com.emotion.service.LifeEventService;
|
||||||
import com.emotion.util.UserContextHolder;
|
import com.emotion.util.UserContextHolder;
|
||||||
|
import com.emotion.service.AiChatService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -29,6 +32,7 @@ import java.util.stream.Collectors;
|
|||||||
* @date 2025-12-22
|
* @date 2025-12-22
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent>
|
public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent>
|
||||||
implements LifeEventService {
|
implements LifeEventService {
|
||||||
|
|
||||||
@@ -36,6 +40,14 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
|
|||||||
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_DATE_TIME;
|
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_DATE_TIME;
|
||||||
private static final DateTimeFormatter DATE_ONLY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
private static final DateTimeFormatter DATE_ONLY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coze工作流配置键 - AI疗愈
|
||||||
|
*/
|
||||||
|
private static final String COZE_HEALING_CONFIG_KEY = "coze.user.dairy.summary";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AiChatService aiChatService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<LifeEventResponse> getPageByCurrentUser(LifeEventPageRequest request) {
|
public PageResult<LifeEventResponse> getPageByCurrentUser(LifeEventPageRequest request) {
|
||||||
String currentUserId = UserContextHolder.getCurrentUserId();
|
String currentUserId = UserContextHolder.getCurrentUserId();
|
||||||
@@ -139,10 +151,74 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
|
|||||||
event.setEmotionScore(BigDecimal.valueOf(request.getEmotionScore()));
|
event.setEmotionScore(BigDecimal.valueOf(request.getEmotionScore()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调用Coze AI进行疗愈回复
|
||||||
|
String aiGeneratedContent = generateHealingByAi(request, currentUserId);
|
||||||
|
if (StringUtils.hasText(aiGeneratedContent)) {
|
||||||
|
event.setAiReply(aiGeneratedContent);
|
||||||
|
}
|
||||||
|
|
||||||
this.save(event);
|
this.save(event);
|
||||||
return convertToResponse(event);
|
return convertToResponse(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用Coze AI生成疗愈内容
|
||||||
|
*
|
||||||
|
* @param request 创建请求
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return AI生成的疗愈内容,失败时返回null
|
||||||
|
*/
|
||||||
|
private String generateHealingByAi(LifeEventCreateRequest request, String userId) {
|
||||||
|
try {
|
||||||
|
// 组装AI输入
|
||||||
|
String input = assembleHealingInput(request);
|
||||||
|
log.info("开始调用AI生成疗愈回复,用户ID: {}, 输入长度: {}", userId, input.length());
|
||||||
|
|
||||||
|
// 调用Coze工作流
|
||||||
|
String result = aiChatService.callWorkflowByConfigKey(COZE_HEALING_CONFIG_KEY, input, userId);
|
||||||
|
|
||||||
|
log.info("AI生成疗愈回复完成,用户ID: {}, 结果长度: {}", userId, result != null ? result.length() : 0);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("AI生成疗愈回复失败,用户ID: {}, 错误: {}", userId, e.getMessage(), e);
|
||||||
|
// AI调用失败不影响事件创建,返回null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组装AI疗愈输入内容
|
||||||
|
*
|
||||||
|
* @param request 创建请求
|
||||||
|
* @return 格式化的输入字符串
|
||||||
|
*/
|
||||||
|
private String assembleHealingInput(LifeEventCreateRequest request) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
if (StringUtils.hasText(request.getTitle())) {
|
||||||
|
sb.append("【事件标题】").append(request.getTitle()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发生时间
|
||||||
|
if (StringUtils.hasText(request.getEventDate())) {
|
||||||
|
sb.append("【发生时间】").append(request.getEventDate()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 经历详解
|
||||||
|
if (StringUtils.hasText(request.getContent())) {
|
||||||
|
sb.append("【经历详解】").append(request.getContent()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情绪类型
|
||||||
|
if (StringUtils.hasText(request.getEmotionType())) {
|
||||||
|
sb.append("【情绪类型】").append(request.getEmotionType()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LifeEventResponse updateEvent(LifeEventUpdateRequest request) {
|
public LifeEventResponse updateEvent(LifeEventUpdateRequest request) {
|
||||||
LifeEvent event = this.getById(request.getId());
|
LifeEvent event = this.getById(request.getId());
|
||||||
|
|||||||
+44
-10
@@ -71,20 +71,47 @@ def run_command(cmd, cwd=None, shell=True, capture=True):
|
|||||||
return False, "", str(e)
|
return False, "", str(e)
|
||||||
|
|
||||||
|
|
||||||
def exec_ssh_cmd(cmd):
|
def exec_ssh_cmd(cmd, timeout=30):
|
||||||
"""通过SSH执行远程命令"""
|
"""通过SSH执行远程命令"""
|
||||||
ssh_cmd = f'ssh {USERNAME}@{SERVER_IP} "{cmd}"'
|
ssh_cmd = f'ssh -o ConnectTimeout=10 -o BatchMode=yes {USERNAME}@{SERVER_IP} "{cmd}"'
|
||||||
return run_command(ssh_cmd)
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
ssh_cmd,
|
||||||
|
shell=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
return result.returncode == 0, result.stdout.strip(), result.stderr.strip()
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
log_error(f"SSH命令超时: {cmd}")
|
||||||
|
return False, "", "命令执行超时"
|
||||||
|
except Exception as e:
|
||||||
|
return False, "", str(e)
|
||||||
|
|
||||||
|
|
||||||
def scp_upload(local_path, remote_path, recursive=False):
|
def scp_upload(local_path, remote_path, recursive=False):
|
||||||
"""通过SCP上传文件或目录"""
|
"""通过SCP上传文件或目录"""
|
||||||
r_flag = "-r" if recursive else ""
|
r_flag = "-r" if recursive else ""
|
||||||
scp_cmd = f'scp {r_flag} "{local_path}" {USERNAME}@{SERVER_IP}:{remote_path}'
|
scp_cmd = f'scp -o ConnectTimeout=10 -o BatchMode=yes {r_flag} "{local_path}" {USERNAME}@{SERVER_IP}:{remote_path}'
|
||||||
success, stdout, stderr = run_command(scp_cmd)
|
try:
|
||||||
if not success:
|
result = subprocess.run(
|
||||||
log_error(f"SCP上传失败: {stderr}")
|
scp_cmd,
|
||||||
return success
|
shell=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=120 # 上传文件给更长时间
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
log_error(f"SCP上传失败: {result.stderr}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
log_error("SCP上传超时")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"SCP上传异常: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_env():
|
def check_env():
|
||||||
@@ -136,11 +163,15 @@ def deploy():
|
|||||||
|
|
||||||
# 1. 创建远程目录
|
# 1. 创建远程目录
|
||||||
log_info("创建远程目录...")
|
log_info("创建远程目录...")
|
||||||
exec_ssh_cmd(f"mkdir -p {release_path}")
|
success, _, err = exec_ssh_cmd(f"mkdir -p {release_path}")
|
||||||
|
if not success:
|
||||||
|
log_error(f"创建远程目录失败: {err}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# 2. 上传文件
|
# 2. 上传文件
|
||||||
log_info("上传文件到服务器...")
|
log_info("上传文件到服务器...")
|
||||||
for item in DIST_DIR.iterdir():
|
for item in DIST_DIR.iterdir():
|
||||||
|
log_info(f" 上传: {item.name}")
|
||||||
if item.is_file():
|
if item.is_file():
|
||||||
if not scp_upload(item, f"{release_path}/"):
|
if not scp_upload(item, f"{release_path}/"):
|
||||||
log_error("文件上传失败")
|
log_error("文件上传失败")
|
||||||
@@ -160,7 +191,10 @@ def deploy():
|
|||||||
# 检查目标路径是否为普通目录
|
# 检查目标路径是否为普通目录
|
||||||
exec_ssh_cmd(f"if [ -d '{LINK_PATH}' ] && [ ! -L '{LINK_PATH}' ]; then mv '{LINK_PATH}' '{LINK_PATH}_backup_$(date +%s)'; fi")
|
exec_ssh_cmd(f"if [ -d '{LINK_PATH}' ] && [ ! -L '{LINK_PATH}' ]; then mv '{LINK_PATH}' '{LINK_PATH}_backup_$(date +%s)'; fi")
|
||||||
# 创建软链接
|
# 创建软链接
|
||||||
exec_ssh_cmd(f"ln -snf '{release_path}' '{LINK_PATH}'")
|
success, _, err = exec_ssh_cmd(f"ln -snf '{release_path}' '{LINK_PATH}'")
|
||||||
|
if not success:
|
||||||
|
log_error(f"切换版本失败: {err}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
log_info(f"部署完成!当前版本指向: {release_path}")
|
log_info(f"部署完成!当前版本指向: {release_path}")
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,9 @@ const transformToBackendFormat = (frontendData) => {
|
|||||||
style,
|
style,
|
||||||
length,
|
length,
|
||||||
content,
|
content,
|
||||||
isSelected
|
isSelected,
|
||||||
|
character,
|
||||||
|
events
|
||||||
} = frontendData;
|
} = frontendData;
|
||||||
|
|
||||||
// 解析内容生成标题和各部分
|
// 解析内容生成标题和各部分
|
||||||
@@ -127,6 +129,55 @@ const transformToBackendFormat = (frontendData) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化角色信息
|
||||||
|
let characterInfo = '';
|
||||||
|
if (character) {
|
||||||
|
const parts = [
|
||||||
|
`姓名:${character.nickname || '未设置'}`,
|
||||||
|
`性别:${character.gender || '未设置'}`,
|
||||||
|
`MBTI:${character.mbti || '未设置'}`,
|
||||||
|
`星座:${character.zodiac || '未设置'}`,
|
||||||
|
`职业:${character.profession || '未设置'}`,
|
||||||
|
`兴趣爱好:${character.hobbies?.join(',') || '无'}`
|
||||||
|
];
|
||||||
|
|
||||||
|
if (character.future) {
|
||||||
|
if (character.future.vision) parts.push(`未来愿景:${character.future.vision}`);
|
||||||
|
if (character.future.ideal) parts.push(`理想生活:${character.future.ideal}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
characterInfo = parts.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化过往经历
|
||||||
|
let lifeEventsSummary = '';
|
||||||
|
const eventParts = [];
|
||||||
|
|
||||||
|
// 1. 核心记忆 (Childhood, Joy, Low from character data)
|
||||||
|
if (character) {
|
||||||
|
if (character.childhood?.text) {
|
||||||
|
eventParts.push(`【童年记忆】(${character.childhood.date || '未知时间'}): ${character.childhood.text}`);
|
||||||
|
}
|
||||||
|
if (character.joy?.text) {
|
||||||
|
eventParts.push(`【高光时刻】(${character.joy.date || '未知时间'}): ${character.joy.text}`);
|
||||||
|
}
|
||||||
|
if (character.low?.text) {
|
||||||
|
eventParts.push(`【至暗时刻】(${character.low.date || '未知时间'}): ${character.low.text}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 详细人生事件
|
||||||
|
if (events && Array.isArray(events)) {
|
||||||
|
events.forEach(e => {
|
||||||
|
const dateStr = e.time || e.eventDate || '未知时间';
|
||||||
|
const titleStr = e.title || '无标题';
|
||||||
|
const contentStr = e.content || '';
|
||||||
|
eventParts.push(`【人生事件】(${dateStr}) ${titleStr}${contentStr ? ': ' + contentStr : ''}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lifeEventsSummary = eventParts.join('\n');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
@@ -138,7 +189,9 @@ const transformToBackendFormat = (frontendData) => {
|
|||||||
plotClimax,
|
plotClimax,
|
||||||
plotEnding,
|
plotEnding,
|
||||||
plotJson: content ? { fullContent: content } : null,
|
plotJson: content ? { fullContent: content } : null,
|
||||||
isSelected
|
isSelected,
|
||||||
|
characterInfo,
|
||||||
|
lifeEventsSummary
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
|
|||||||
import { UserCog, PenTool, Sparkles, BookOpen, Loader2 } from 'lucide-react';
|
import { UserCog, PenTool, Sparkles, BookOpen, Loader2 } from 'lucide-react';
|
||||||
import { GlassCard, GlassButton, GlassInput, GlassSelect } from '../components/ui';
|
import { GlassCard, GlassButton, GlassInput, GlassSelect } from '../components/ui';
|
||||||
import useStore from '../store/useStore';
|
import useStore from '../store/useStore';
|
||||||
import { generateEpicScript } from '../services/ai';
|
|
||||||
import { scriptStyles, scriptLengths } from '../utils/constants';
|
import { scriptStyles, scriptLengths } from '../utils/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,12 +47,15 @@ const ScriptView = ({ onOpenProfile }) => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = await generateEpicScript(
|
// 直接调用后端创建接口,由后端调用AI生成
|
||||||
{ theme, style, length, character: registrationData },
|
await addScript({
|
||||||
lifeEvents
|
theme,
|
||||||
);
|
style,
|
||||||
|
length,
|
||||||
|
character: registrationData,
|
||||||
|
events: lifeEvents
|
||||||
|
});
|
||||||
|
|
||||||
addScript({ theme, style, length, content });
|
|
||||||
setTheme('');
|
setTheme('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to generate script:', error);
|
console.error('Failed to generate script:', error);
|
||||||
|
|||||||
@@ -3,7 +3,76 @@ import { Plus, Wind, Sparkles } from 'lucide-react';
|
|||||||
import { GlassCard, GlassButton, GlassInput, GlassTextarea } from '../components/ui';
|
import { GlassCard, GlassButton, GlassInput, GlassTextarea } from '../components/ui';
|
||||||
import Modal from '../components/Modal';
|
import Modal from '../components/Modal';
|
||||||
import useStore from '../store/useStore';
|
import useStore from '../store/useStore';
|
||||||
import { analyzeLifeEvent } from '../services/ai';
|
|
||||||
|
/**
|
||||||
|
* 格式化 AI 反馈内容的组件
|
||||||
|
*/
|
||||||
|
const FeedbackContent = ({ content }) => {
|
||||||
|
if (!content) return null;
|
||||||
|
|
||||||
|
// 检查是否为结构化格式 (包含分隔符 --- 和标题标识 ####)
|
||||||
|
const isStructured = content.includes('---') && content.includes('####');
|
||||||
|
|
||||||
|
if (!isStructured) {
|
||||||
|
return (
|
||||||
|
<p className="text-xs italic text-white/50 leading-loose whitespace-pre-wrap">
|
||||||
|
{content}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析结构化内容
|
||||||
|
const sections = content.split('---')
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(s => s && s.length > 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-5 mt-2">
|
||||||
|
{sections.map((section, index) => {
|
||||||
|
// 移除 #### 前缀
|
||||||
|
const cleanSection = section.replace(/^####\s*/, '');
|
||||||
|
|
||||||
|
// 提取标题 (通常在 【】 中)
|
||||||
|
const titleMatch = cleanSection.match(/【(.*?)】/);
|
||||||
|
const title = titleMatch ? titleMatch[1] : '';
|
||||||
|
|
||||||
|
// 提取正文
|
||||||
|
let body = cleanSection;
|
||||||
|
if (titleMatch) {
|
||||||
|
body = cleanSection.replace(titleMatch[0], '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!title && !body) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={index} className="text-xs leading-relaxed">
|
||||||
|
{title && (
|
||||||
|
<h5 className="text-orange-100 font-bold mb-2 flex items-center gap-2 text-[11px] tracking-wide">
|
||||||
|
{title}
|
||||||
|
</h5>
|
||||||
|
)}
|
||||||
|
<div className="text-white/60 pl-3 border-l-2 border-orange-200/10 space-y-1">
|
||||||
|
{body.split('\n').map((line, i) => {
|
||||||
|
const trimmedLine = line.trim();
|
||||||
|
if (!trimmedLine) return null;
|
||||||
|
// 简单的列表项处理
|
||||||
|
if (trimmedLine.startsWith('*') || trimmedLine.startsWith('-')) {
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex gap-2 pl-1">
|
||||||
|
<span className="text-orange-200/40">•</span>
|
||||||
|
<span>{trimmedLine.substring(1).trim()}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <p key={i}>{trimmedLine}</p>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TimelineView 组件
|
* TimelineView 组件
|
||||||
@@ -42,20 +111,16 @@ const TimelineView = () => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 调用 AI 分析
|
// 直接调用后端添加事件,由后端调用AI进行疗愈分析
|
||||||
const aiFeedback = await analyzeLifeEvent(eventForm);
|
await addLifeEvent({
|
||||||
|
...eventForm
|
||||||
// 添加事件
|
|
||||||
addLifeEvent({
|
|
||||||
...eventForm,
|
|
||||||
aiFeedback
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 重置表单并关闭模态框
|
// 重置表单并关闭模态框
|
||||||
setEventForm({ title: '', time: '', content: '' });
|
setEventForm({ title: '', time: '', content: '' });
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to analyze event:', error);
|
console.error('Failed to add event:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -124,15 +189,13 @@ const TimelineView = () => {
|
|||||||
{/* AI 反馈区域 - 仅在有反馈时显示 */}
|
{/* AI 反馈区域 - 仅在有反馈时显示 */}
|
||||||
{event.aiFeedback && (
|
{event.aiFeedback && (
|
||||||
<div className="ai-glow-card p-5 rounded-2xl bg-orange-200/[0.02] border border-orange-200/5">
|
<div className="ai-glow-card p-5 rounded-2xl bg-orange-200/[0.02] border border-orange-200/5">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Sparkles className="w-3 h-3 text-orange-200" />
|
<Sparkles className="w-3 h-3 text-orange-200" />
|
||||||
<span className="text-[9px] uppercase tracking-[0.2em] text-orange-200/60 font-bold">
|
<span className="text-[9px] uppercase tracking-[0.2em] text-orange-200/60 font-bold">
|
||||||
引路人洞察
|
引路人洞察
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs italic text-white/50 leading-loose">
|
<FeedbackContent content={event.aiFeedback} />
|
||||||
{event.aiFeedback}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</GlassCard>
|
</GlassCard>
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ alter table emotion_museum.t_user_profile
|
|||||||
alter table emotion_museum.t_user_profile
|
alter table emotion_museum.t_user_profile
|
||||||
add profession varchar(100) null comment '职业';
|
add profession varchar(100) null comment '职业';
|
||||||
|
|
||||||
-- 职业
|
-- 消息
|
||||||
|
alter table emotion_museum.t_message
|
||||||
|
add message_order varchar(100) null comment '职业';
|
||||||
|
|
||||||
|
-- AI
|
||||||
alter table emotion_museum.t_ai_config
|
alter table emotion_museum.t_ai_config
|
||||||
add client_id VARCHAR(200) COMMENT '客户端ID (OAuth认证)',
|
add client_id VARCHAR(200) COMMENT '客户端ID (OAuth认证)',
|
||||||
add client_secret VARCHAR(500) COMMENT '客户端密钥 (OAuth认证,加密存储)',
|
add client_secret VARCHAR(500) COMMENT '客户端密钥 (OAuth认证,加密存储)',
|
||||||
|
|||||||
@@ -71,3 +71,23 @@ INSERT INTO t_dictionary (
|
|||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
SELECT '字典数据初始化完成' AS result;
|
SELECT '字典数据初始化完成' AS result;
|
||||||
|
|
||||||
|
-- AI接口配置初始化脚本
|
||||||
|
INSERT INTO emotion_museum.t_ai_config (id, config_name, config_key, config_type, provider, api_base_url, api_token, api_version, model_name, bot_id, workflow_id, timeout_ms, retry_count, retry_delay_ms, max_tokens, temperature, top_p, support_stream, support_function_call, support_vision, support_file_upload, usage_scenario, priority, input_price_per_1k, output_price_per_1k, currency, rate_limit_per_minute, rate_limit_per_hour, rate_limit_per_day, is_enabled, is_default, environment, custom_headers, custom_params, webhook_url, health_check_url, health_check_interval_minutes, description, usage_notes, create_by, create_time, update_by, update_time, is_deleted, remarks, client_id, client_secret, grant_type) VALUES ('3d895ae5b3ea11f0a59104d4c4548e0b', 'Coze开发环境测试配置', 'coze.chat.development', 'coze', 'coze', 'https://api.coze.cn/v1/workflow/stream_run', 'sat_or7exwGUw4FtwOCakp5e9vhnPJpIQBMjv8XofyMqdmA2LMiJ3jC900dLAaZ7hdjd', 'v3', null, '7523042446285439016', '7523047462895796287', 10000, 2, 500, 2000, 0.80, 1.00, 1, 1, 0, 0, 'chat', 80, 0.000500, 0.001000, 'USD', 120, 7200, 172800, 1, 0, 'development', '{"Content-Type": "application/json"}', '{"parameters": {}}', null, null, 5, 'Coze平台开发环境测试配置,用于开发和调试', '1. 开发环境专用配置
|
||||||
|
2. 较短的超时时间和更高的请求限制
|
||||||
|
3. 用于功能测试和调试', null, '2025-10-28 18:38:28', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 18:52:50', 1, null, null, null, null);
|
||||||
|
INSERT INTO emotion_museum.t_ai_config (id, config_name, config_key, config_type, provider, api_base_url, api_token, api_version, model_name, bot_id, workflow_id, timeout_ms, retry_count, retry_delay_ms, max_tokens, temperature, top_p, support_stream, support_function_call, support_vision, support_file_upload, usage_scenario, priority, input_price_per_1k, output_price_per_1k, currency, rate_limit_per_minute, rate_limit_per_hour, rate_limit_per_day, is_enabled, is_default, environment, custom_headers, custom_params, webhook_url, health_check_url, health_check_interval_minutes, description, usage_notes, create_by, create_time, update_by, update_time, is_deleted, remarks, client_id, client_secret, grant_type) VALUES ('3d5cfb03b3ea11f0a59104d4c4548e0b', 'Coze聊天', 'coze.chat.default', 'coze', 'coze', 'https://api.coze.cn/v1/workflow/stream_run', 'sat_or7exwGUw4FtwOCakp5e9vhnPJpIQBMjv8XofyMqdmA2LMiJ3jC900dLAaZ7hdjd', '', '', '7523042446285439016', '7585860129955528710', 30000, 3, 1000, 4000, 0.70, 1.00, 1, 1, 0, 0, 'chat', 100, 0.001000, 0.002000, 'USD', 60, 3600, 86400, 1, 1, 'production', '{"Content-Type": "application/json"}', '{"parameters": {"input": "今天心情非常不好", "user_id": "19928748688"}}', '', '', 5, 'Coze平台的默认聊天机器人配置,用于日常情绪对话', '1. 需要配置有效的API Token
|
||||||
|
2. 需要配置Bot ID和Workflow ID
|
||||||
|
3. 支持流式输出和函数调用', null, '2025-10-28 18:38:28', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 19:03:39', 0, null, '', '', '');
|
||||||
|
INSERT INTO emotion_museum.t_ai_config (id, config_name, config_key, config_type, provider, api_base_url, api_token, api_version, model_name, bot_id, workflow_id, timeout_ms, retry_count, retry_delay_ms, max_tokens, temperature, top_p, support_stream, support_function_call, support_vision, support_file_upload, usage_scenario, priority, input_price_per_1k, output_price_per_1k, currency, rate_limit_per_minute, rate_limit_per_hour, rate_limit_per_day, is_enabled, is_default, environment, custom_headers, custom_params, webhook_url, health_check_url, health_check_interval_minutes, description, usage_notes, create_by, create_time, update_by, update_time, is_deleted, remarks, client_id, client_secret, grant_type) VALUES ('261808289317130240', 'Coze人生剧本生成', 'coze.course.life.generate', 'coze', 'coze', 'https://api.coze.cn/v1/workflow/stream_run', 'sat_or7exwGUw4FtwOCakp5e9vhnPJpIQBMjv8XofyMqdmA2LMiJ3jC900dLAaZ7hdjd', '', '', '7523042446285439016', '7586262962160762926', 30000, 3, 1000, 4000, 0.70, 1.00, 1, 0, 0, 0, 'content_generation', 0, 0.000000, 0.000000, 'USD', 60, 3600, 86400, 1, 0, 'production', '{}', '{"bot_id": "7523042446285439016", "parameters": {"input": "成为超级大富翁", "user_id": "19928748688"}, "workflow_id": "7586262962160762926"}', '', '', 5, '', '', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 18:52:40', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 19:10:29', 0, null, '', '', '');
|
||||||
|
INSERT INTO emotion_museum.t_ai_config (id, config_name, config_key, config_type, provider, api_base_url, api_token, api_version, model_name, bot_id, workflow_id, timeout_ms, retry_count, retry_delay_ms, max_tokens, temperature, top_p, support_stream, support_function_call, support_vision, support_file_upload, usage_scenario, priority, input_price_per_1k, output_price_per_1k, currency, rate_limit_per_minute, rate_limit_per_hour, rate_limit_per_day, is_enabled, is_default, environment, custom_headers, custom_params, webhook_url, health_check_url, health_check_interval_minutes, description, usage_notes, create_by, create_time, update_by, update_time, is_deleted, remarks, client_id, client_secret, grant_type) VALUES ('261823800167895040', 'Coze日记总结', 'coze.user.dairy.summary', 'coze', 'coze', 'https://api.coze.cn/v1/workflow/stream_run', 'sat_or7exwGUw4FtwOCakp5e9vhnPJpIQBMjv8XofyMqdmA2LMiJ3jC900dLAaZ7hdjd', '', '', '7529062814150295595', '7586224423528251402', 30000, 3, 1000, 4000, 0.70, 1.00, 1, 0, 0, 0, 'chat', 0, 0.000000, 0.000000, 'USD', 60, 3600, 86400, 1, 0, 'production', '{"Content-Type": "application/json"}', '{"parameters": {"input": "今天心情非常不好", "user_id": "19928748688"}}', '', '', 5, '', '', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 19:54:18', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 19:54:18', 0, null, '', '', '');
|
||||||
|
INSERT INTO emotion_museum.t_ai_config (id, config_name, config_key, config_type, provider, api_base_url, api_token, api_version, model_name, bot_id, workflow_id, timeout_ms, retry_count, retry_delay_ms, max_tokens, temperature, top_p, support_stream, support_function_call, support_vision, support_file_upload, usage_scenario, priority, input_price_per_1k, output_price_per_1k, currency, rate_limit_per_minute, rate_limit_per_hour, rate_limit_per_day, is_enabled, is_default, environment, custom_headers, custom_params, webhook_url, health_check_url, health_check_interval_minutes, description, usage_notes, create_by, create_time, update_by, update_time, is_deleted, remarks, client_id, client_secret, grant_type) VALUES ('261824790673756160', 'Coze用户故事', 'coze.user.chose.story', 'coze', 'coze', 'https://api.coze.cn/v1/workflow/stream_run', 'sat_or7exwGUw4FtwOCakp5e9vhnPJpIQBMjv8XofyMqdmA2LMiJ3jC900dLAaZ7hdjd', '', '', '', '7585870811114766387', 30000, 3, 1000, 4000, 0.70, 1.00, 1, 0, 0, 0, 'content_generation', 0, 0.000000, 0.000000, 'USD', 60, 3600, 86400, 1, 0, 'production', '{}', '{"parameters": {"input": "今天心情非常不好", "user_id": "19928748688"}, "workflow_id": "7585870811114766387"}', '', '', 5, '', '', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 19:58:15', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 19:58:15', 0, null, '', '', '');
|
||||||
|
INSERT INTO emotion_museum.t_ai_config (id, config_name, config_key, config_type, provider, api_base_url, api_token, api_version, model_name, bot_id, workflow_id, timeout_ms, retry_count, retry_delay_ms, max_tokens, temperature, top_p, support_stream, support_function_call, support_vision, support_file_upload, usage_scenario, priority, input_price_per_1k, output_price_per_1k, currency, rate_limit_per_minute, rate_limit_per_hour, rate_limit_per_day, is_enabled, is_default, environment, custom_headers, custom_params, webhook_url, health_check_url, health_check_interval_minutes, description, usage_notes, create_by, create_time, update_by, update_time, is_deleted, remarks, client_id, client_secret, grant_type) VALUES ('261825352626606080', 'Coze用户状态分析', 'coze.user.life.state', 'coze', 'coze', 'https://api.coze.cn/v1/workflow/stream_run', 'sat_or7exwGUw4FtwOCakp5e9vhnPJpIQBMjv8XofyMqdmA2LMiJ3jC900dLAaZ7hdjd', '', '', '', '7585860129955528710', 30000, 3, 1000, 4000, 0.70, 1.00, 1, 0, 0, 0, 'chat', 0, 0.000000, 0.000000, 'USD', 60, 3600, 86400, 1, 0, 'production', '{}', '{"parameters": {"input": "今天心情非常不好", "user_id": "19928748688"}, "workflow_id": "7585860129955528710"}', '', '', 5, '', '', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 20:00:29', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 20:00:29', 0, null, '', '', '');
|
||||||
|
INSERT INTO emotion_museum.t_ai_config (id, config_name, config_key, config_type, provider, api_base_url, api_token, api_version, model_name, bot_id, workflow_id, timeout_ms, retry_count, retry_delay_ms, max_tokens, temperature, top_p, support_stream, support_function_call, support_vision, support_file_upload, usage_scenario, priority, input_price_per_1k, output_price_per_1k, currency, rate_limit_per_minute, rate_limit_per_hour, rate_limit_per_day, is_enabled, is_default, environment, custom_headers, custom_params, webhook_url, health_check_url, health_check_interval_minutes, description, usage_notes, create_by, create_time, update_by, update_time, is_deleted, remarks, client_id, client_secret, grant_type) VALUES ('3d7898a2b3ea11f0a59104d4c4548e0b', 'Coze情绪分析配置', 'coze.emotion_analysis.default', 'coze', 'coze', 'https://api.coze.cn/v1/workflow/stream_run', 'sat_or7exwGUw4FtwOCakp5e9vhnPJpIQBMjv8XofyMqdmA2LMiJ3jC900dLAaZ7hdjd', 'v3', null, '7529062814150295595', '7523047462895796287', 45000, 3, 1500, 6000, 0.20, 1.00, 0, 1, 0, 0, 'emotion_analysis', 95, 0.001000, 0.002000, 'USD', 20, 1200, 28800, 1, 1, 'production', null, null, null, null, 5, 'Coze平台的情绪分析配置,用于分析用户聊天记录的情绪状态', '1. 使用最低的temperature以获得准确的情绪分析
|
||||||
|
2. 支持函数调用以获取结构化的分析结果
|
||||||
|
3. 增加超时时间以处理复杂的情绪分析', null, '2025-10-28 18:38:28', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 20:04:28', 1, null, null, null, null);
|
||||||
|
INSERT INTO emotion_museum.t_ai_config (id, config_name, config_key, config_type, provider, api_base_url, api_token, api_version, model_name, bot_id, workflow_id, timeout_ms, retry_count, retry_delay_ms, max_tokens, temperature, top_p, support_stream, support_function_call, support_vision, support_file_upload, usage_scenario, priority, input_price_per_1k, output_price_per_1k, currency, rate_limit_per_minute, rate_limit_per_hour, rate_limit_per_day, is_enabled, is_default, environment, custom_headers, custom_params, webhook_url, health_check_url, health_check_interval_minutes, description, usage_notes, create_by, create_time, update_by, update_time, is_deleted, remarks, client_id, client_secret, grant_type) VALUES ('3d6be3b6b3ea11f0a59104d4c4548e0b', 'Coze对话总结配置', 'coze.summary.default', 'coze', 'coze', 'https://api.coze.cn/v1/workflow/stream_run', 'sat_or7exwGUw4FtwOCakp5e9vhnPJpIQBMjv8XofyMqdmA2LMiJ3jC900dLAaZ7hdjd', 'v3', null, '7529062814150295595', '7523047462895796287', 30000, 3, 1000, 8000, 0.30, 1.00, 0, 0, 0, 0, 'summary', 90, 0.001000, 0.002000, 'USD', 30, 1800, 43200, 1, 1, 'production', null, null, null, null, 5, 'Coze平台的对话总结配置,专门用于生成对话摘要和情绪分析', '1. 使用较低的temperature以获得更稳定的总结结果
|
||||||
|
2. 增加max_tokens以支持长文本总结
|
||||||
|
3. 不支持流式输出', null, '2025-10-28 18:38:28', 'adce16b80c81fa25b7aa13a050f822cc', '2025-12-23 20:04:31', 1, null, null, null, null);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1515,7 +1515,7 @@ const handleSaveTestConfig = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 调用更新接口
|
// 调用更新接口
|
||||||
const res = await updateAiConfigFromTest(updateData)
|
const res = await updateAiConfigFromTest(updateData) as any
|
||||||
|
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
ElMessage.success('测试配置已保存')
|
ElMessage.success('测试配置已保存')
|
||||||
|
|||||||
Reference in New Issue
Block a user