提示词保存
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package cn.iocoder.yudao.module.ai.chat.controller.app;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatConversationCreateMyReqVO;
|
||||
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatConversationRespVO;
|
||||
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatConversationUpdateMyReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
|
||||
import com.fhs.core.trans.anno.TransMethodResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "用户 App - AI 聊天对话")
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat/conversation")
|
||||
@Validated
|
||||
public class AppAiChatConversationController {
|
||||
|
||||
@Resource
|
||||
private AiChatConversationService chatConversationService;
|
||||
|
||||
@PostMapping("/create-my")
|
||||
@Operation(summary = "创建【我的】聊天对话")
|
||||
public CommonResult<Long> createChatConversationMy(@RequestBody @Valid AppAiChatConversationCreateMyReqVO createReqVO) {
|
||||
// 将 App VO 转换为 Admin VO
|
||||
AiChatConversationCreateMyReqVO adminReqVO = BeanUtils.toBean(createReqVO, AiChatConversationCreateMyReqVO.class);
|
||||
return success(chatConversationService.createChatConversationMy(adminReqVO, getLoginUserId()));
|
||||
}
|
||||
|
||||
@PutMapping("/update-my")
|
||||
@Operation(summary = "更新【我的】聊天对话")
|
||||
public CommonResult<Boolean> updateChatConversationMy(@RequestBody @Valid AppAiChatConversationUpdateMyReqVO updateReqVO) {
|
||||
// 将 App VO 转换为 Admin VO
|
||||
AiChatConversationUpdateMyReqVO adminReqVO = BeanUtils.toBean(updateReqVO, AiChatConversationUpdateMyReqVO.class);
|
||||
chatConversationService.updateChatConversationMy(adminReqVO, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/my-list")
|
||||
@Operation(summary = "获得【我的】聊天对话列表")
|
||||
@TransMethodResult
|
||||
public CommonResult<List<AppAiChatConversationRespVO>> getChatConversationMyList() {
|
||||
List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId());
|
||||
return success(BeanUtils.toBean(list, AppAiChatConversationRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get-my")
|
||||
@Operation(summary = "获得【我的】聊天对话")
|
||||
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
|
||||
@TransMethodResult
|
||||
public CommonResult<AppAiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) {
|
||||
AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
|
||||
if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
|
||||
conversation = null;
|
||||
}
|
||||
return success(BeanUtils.toBean(conversation, AppAiChatConversationRespVO.class));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-my")
|
||||
@Operation(summary = "删除聊天对话")
|
||||
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteChatConversationMy(@RequestParam("id") Long id) {
|
||||
chatConversationService.deleteChatConversationMy(id, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-by-unpinned")
|
||||
@Operation(summary = "删除未置顶的聊天对话")
|
||||
public CommonResult<Boolean> deleteChatConversationMyByUnpinned() {
|
||||
chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package cn.iocoder.yudao.module.ai.chat.controller.app;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatMessageRespVO;
|
||||
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatMessageSendReqVO;
|
||||
import cn.iocoder.yudao.module.ai.chat.vo.app.AppAiChatMessageSendRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "用户 App - 聊天消息")
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat/message")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppAiChatMessageController {
|
||||
|
||||
@Resource
|
||||
private AiChatMessageService chatMessageService;
|
||||
@Resource
|
||||
private AiChatConversationService chatConversationService;
|
||||
@Resource
|
||||
private AiKnowledgeSegmentService knowledgeSegmentService;
|
||||
@Resource
|
||||
private AiKnowledgeDocumentService knowledgeDocumentService;
|
||||
|
||||
@Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
|
||||
@PostMapping("/send")
|
||||
public CommonResult<AppAiChatMessageSendRespVO> sendMessage(@Valid @RequestBody AppAiChatMessageSendReqVO sendReqVO) {
|
||||
// 将 App VO 转换为 Admin VO
|
||||
AiChatMessageSendReqVO adminReqVO = BeanUtils.toBean(sendReqVO, AiChatMessageSendReqVO.class);
|
||||
// 调用 Service,然后转换响应
|
||||
var adminResp = chatMessageService.sendMessage(adminReqVO, getLoginUserId());
|
||||
// 手动转换 segments,因为内部类类型不同
|
||||
AppAiChatMessageSendRespVO appResp = convertSendRespVO(adminResp);
|
||||
return success(appResp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Admin 的 SendRespVO 转换为 App 的 SendRespVO
|
||||
* 主要处理 segments 字段的类型转换
|
||||
*/
|
||||
private AppAiChatMessageSendRespVO convertSendRespVO(cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO adminResp) {
|
||||
if (adminResp == null) {
|
||||
return null;
|
||||
}
|
||||
AppAiChatMessageSendRespVO appResp = new AppAiChatMessageSendRespVO();
|
||||
|
||||
// 转换 send
|
||||
if (adminResp.getSend() != null) {
|
||||
AppAiChatMessageSendRespVO.Message appSend = BeanUtils.toBean(adminResp.getSend(), AppAiChatMessageSendRespVO.Message.class);
|
||||
if (adminResp.getSend().getSegments() != null) {
|
||||
appSend.setSegments(convertKnowledgeSegments(adminResp.getSend().getSegments()));
|
||||
}
|
||||
appResp.setSend(appSend);
|
||||
}
|
||||
|
||||
// 转换 receive
|
||||
if (adminResp.getReceive() != null) {
|
||||
AppAiChatMessageSendRespVO.Message appReceive = BeanUtils.toBean(adminResp.getReceive(), AppAiChatMessageSendRespVO.Message.class);
|
||||
if (adminResp.getReceive().getSegments() != null) {
|
||||
appReceive.setSegments(convertKnowledgeSegments(adminResp.getReceive().getSegments()));
|
||||
}
|
||||
appResp.setReceive(appReceive);
|
||||
}
|
||||
|
||||
return appResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 KnowledgeSegment 列表
|
||||
*/
|
||||
private List<AppAiChatMessageSendRespVO.KnowledgeSegment> convertKnowledgeSegments(
|
||||
List<cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO.KnowledgeSegment> adminSegments) {
|
||||
if (adminSegments == null) {
|
||||
return null;
|
||||
}
|
||||
return convertList(adminSegments, segment -> {
|
||||
if (segment == null) {
|
||||
return null;
|
||||
}
|
||||
return new AppAiChatMessageSendRespVO.KnowledgeSegment()
|
||||
.setId(segment.getId())
|
||||
.setContent(segment.getContent())
|
||||
.setDocumentId(segment.getDocumentId())
|
||||
.setDocumentName(segment.getDocumentName());
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
|
||||
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<CommonResult<AppAiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AppAiChatMessageSendReqVO sendReqVO) {
|
||||
// 将 App VO 转换为 Admin VO
|
||||
AiChatMessageSendReqVO adminReqVO = BeanUtils.toBean(sendReqVO, AiChatMessageSendReqVO.class);
|
||||
// 调用 Service,然后转换响应流
|
||||
return chatMessageService.sendChatMessageStream(adminReqVO, getLoginUserId())
|
||||
.map(result -> {
|
||||
if (result.getData() != null) {
|
||||
// 手动转换 segments,因为内部类类型不同
|
||||
AppAiChatMessageSendRespVO appResp = convertSendRespVO(result.getData());
|
||||
return success(appResp);
|
||||
}
|
||||
return success((AppAiChatMessageSendRespVO) null);
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "获得指定对话的消息列表")
|
||||
@GetMapping("/list-by-conversation-id")
|
||||
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
|
||||
public CommonResult<List<AppAiChatMessageRespVO>> getChatMessageListByConversationId(
|
||||
@RequestParam("conversationId") Long conversationId) {
|
||||
AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId);
|
||||
if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
// 1. 获取消息列表
|
||||
List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
|
||||
if (CollUtil.isEmpty(messageList)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
|
||||
// 2. 拼接数据,主要是知识库段落信息
|
||||
Map<Long, AiKnowledgeSegmentDO> segmentMap = knowledgeSegmentService.getKnowledgeSegmentMap(convertListByFlatMap(messageList,
|
||||
message -> CollUtil.isEmpty(message.getSegmentIds()) ? null : message.getSegmentIds().stream()));
|
||||
Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
|
||||
convertList(segmentMap.values(), AiKnowledgeSegmentDO::getDocumentId));
|
||||
List<AppAiChatMessageRespVO> messageVOList = BeanUtils.toBean(messageList, AppAiChatMessageRespVO.class);
|
||||
for (int i = 0; i < messageList.size(); i++) {
|
||||
AiChatMessageDO message = messageList.get(i);
|
||||
if (CollUtil.isEmpty(message.getSegmentIds())) {
|
||||
continue;
|
||||
}
|
||||
// 设置知识库段落信息
|
||||
messageVOList.get(i).setSegments(convertList(message.getSegmentIds(), segmentId -> {
|
||||
AiKnowledgeSegmentDO segment = segmentMap.get(segmentId);
|
||||
if (segment == null) {
|
||||
return null;
|
||||
}
|
||||
AiKnowledgeDocumentDO document = documentMap.get(segment.getDocumentId());
|
||||
if (document == null) {
|
||||
return null;
|
||||
}
|
||||
return new AppAiChatMessageRespVO.KnowledgeSegment().setId(segment.getId()).setContent(segment.getContent())
|
||||
.setDocumentId(segment.getDocumentId()).setDocumentName(document.getName());
|
||||
}));
|
||||
}
|
||||
return success(messageVOList);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除消息")
|
||||
@DeleteMapping("/delete")
|
||||
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteChatMessage(@RequestParam("id") Long id) {
|
||||
chatMessageService.deleteChatMessage(id, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除指定对话的消息")
|
||||
@DeleteMapping("/delete-by-conversation-id")
|
||||
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
|
||||
chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.iocoder.yudao.module.ai.chat.vo.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "用户 App - AI 聊天对话创建【我的】 Request VO")
|
||||
@Data
|
||||
public class AppAiChatConversationCreateMyReqVO {
|
||||
|
||||
@Schema(description = "聊天角色编号", example = "666")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "知识库编号", example = "1204")
|
||||
private Long knowledgeId;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.iocoder.yudao.module.ai.chat.vo.app;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import com.fhs.core.trans.anno.Trans;
|
||||
import com.fhs.core.trans.constant.TransType;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "用户 App - AI 聊天对话 Response VO")
|
||||
@Data
|
||||
public class AppAiChatConversationRespVO implements VO {
|
||||
|
||||
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "对话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean pinned;
|
||||
|
||||
@Schema(description = "角色编号", example = "1")
|
||||
@Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = {"name", "avatar"}, refs = {"roleName", "roleAvatar"})
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = "name", ref = "modelName")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "模型名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description = "角色设定", example = "一个快乐的程序员")
|
||||
private String systemMessage;
|
||||
|
||||
@Schema(description = "温度参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.8")
|
||||
private Double temperature;
|
||||
|
||||
@Schema(description = "单条回复的最大 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
|
||||
private Integer maxTokens;
|
||||
|
||||
@Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer maxContexts;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// ========== 关联 role 信息 ==========
|
||||
|
||||
@Schema(description = "角色头像", example = "https://www.iocoder.cn/1.png")
|
||||
private String roleAvatar;
|
||||
|
||||
@Schema(description = "角色名字", example = "小黄")
|
||||
private String roleName;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.iocoder.yudao.module.ai.chat.vo.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "用户 App - AI 聊天对话更新【我的】 Request VO")
|
||||
@Data
|
||||
public class AppAiChatConversationUpdateMyReqVO {
|
||||
|
||||
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "对话编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "对话标题", example = "我是一个标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "是否置顶", example = "true")
|
||||
private Boolean pinned;
|
||||
|
||||
@Schema(description = "模型编号", example = "1")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "知识库编号", example = "1")
|
||||
private Long knowledgeId;
|
||||
|
||||
@Schema(description = "角色设定", example = "一个快乐的程序员")
|
||||
private String systemMessage;
|
||||
|
||||
@Schema(description = "温度参数", example = "0.8")
|
||||
private Double temperature;
|
||||
|
||||
@Schema(description = "单条回复的最大 Token 数量", example = "4096")
|
||||
private Integer maxTokens;
|
||||
|
||||
@Schema(description = "上下文的最大 Message 数量", example = "10")
|
||||
private Integer maxContexts;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package cn.iocoder.yudao.module.ai.chat.vo.app;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 App - AI 聊天消息 Response VO")
|
||||
@Data
|
||||
public class AppAiChatMessageRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Long conversationId;
|
||||
|
||||
@Schema(description = "回复消息编号", example = "1024")
|
||||
private Long replyId;
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
|
||||
private String type; // 参见 MessageType 枚举类
|
||||
|
||||
@Schema(description = "用户编号", example = "4096")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "角色编号", example = "888")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "推理内容", example = "要达到这个目标,你需要...")
|
||||
private String reasoningContent;
|
||||
|
||||
@Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean useContext;
|
||||
|
||||
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
|
||||
private List<Long> segmentIds;
|
||||
|
||||
@Schema(description = "知识库段落数组")
|
||||
private List<KnowledgeSegment> segments;
|
||||
|
||||
@Schema(description = "联网搜索的网页内容数组")
|
||||
private List<AiWebSearchResponse.WebPage> webSearchPages;
|
||||
|
||||
@Schema(description = "附件 URL 数组", example = "https://www.iocoder.cn/1.png")
|
||||
private List<String> attachmentUrls;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "知识库段落", example = "Java 开发手册")
|
||||
@Data
|
||||
public static class KnowledgeSegment {
|
||||
|
||||
@Schema(description = "段落编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long documentId;
|
||||
|
||||
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
|
||||
private String documentName;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.ai.chat.vo.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 App - AI 聊天消息发送 Request VO")
|
||||
@Data
|
||||
public class AppAiChatMessageSendReqVO {
|
||||
|
||||
@Schema(description = "聊天对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "聊天对话编号不能为空")
|
||||
private Long conversationId;
|
||||
|
||||
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "帮我写个 Java 算法")
|
||||
@NotEmpty(message = "聊天内容不能为空")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "是否携带上下文", example = "true")
|
||||
private Boolean useContext;
|
||||
|
||||
@Schema(description = "是否联网搜索", example = "true")
|
||||
private Boolean useSearch;
|
||||
|
||||
@Schema(description = "附件 URL 数组", example = "https://www.iocoder.cn/1.png")
|
||||
private List<String> attachmentUrls;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package cn.iocoder.yudao.module.ai.chat.vo.app;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 App - AI 聊天消息发送 Response VO")
|
||||
@Data
|
||||
public class AppAiChatMessageSendRespVO {
|
||||
|
||||
@Schema(description = "发送消息", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Message send;
|
||||
|
||||
@Schema(description = "接收消息", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Message receive;
|
||||
|
||||
@Schema(description = "消息")
|
||||
@Data
|
||||
public static class Message {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
|
||||
private String type; // 参见 MessageType 枚举类
|
||||
|
||||
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "推理内容", example = "要达到这个目标,你需要...")
|
||||
private String reasoningContent;
|
||||
|
||||
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
|
||||
private List<Long> segmentIds;
|
||||
|
||||
@Schema(description = "知识库段落数组")
|
||||
private List<KnowledgeSegment> segments;
|
||||
|
||||
@Schema(description = "联网搜索的网页内容数组")
|
||||
private List<AiWebSearchResponse.WebPage> webSearchPages;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "知识库段落", example = "Java 开发手册")
|
||||
@Data
|
||||
public static class KnowledgeSegment {
|
||||
|
||||
@Schema(description = "段落编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long documentId;
|
||||
|
||||
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
|
||||
private String documentName;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,54 +1,50 @@
|
||||
package cn.iocoder.yudao.module.ai.userprompt.controller;
|
||||
package cn.iocoder.yudao.module.ai.userprompt.controller.app;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import jakarta.validation.*;
|
||||
import jakarta.servlet.http.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.userprompt.vo.*;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.userprompt.UserPromptDO;
|
||||
import cn.iocoder.yudao.module.ai.userprompt.service.UserPromptService;
|
||||
import cn.iocoder.yudao.module.ai.userprompt.vo.UserPromptPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.userprompt.vo.UserPromptRespVO;
|
||||
import cn.iocoder.yudao.module.ai.userprompt.vo.UserPromptSaveReqVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "AI - 用户提示词")
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.USER_PROMPT_NOT_EXISTS;
|
||||
|
||||
@Tag(name = "用户 App - 用户提示词")
|
||||
@RestController
|
||||
@RequestMapping("/ai/user-prompt")
|
||||
@Validated
|
||||
public class UserPromptController {
|
||||
public class AppUserPromptController {
|
||||
|
||||
@Resource
|
||||
private UserPromptService userPromptService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建用户提示词")
|
||||
@PreAuthorize("@ss.hasPermission('ai:user-prompt:create')")
|
||||
public CommonResult<Long> createUserPrompt(@Valid @RequestBody UserPromptSaveReqVO createReqVO) {
|
||||
// 设置当前登录用户ID
|
||||
createReqVO.setUserId(getLoginUserId());
|
||||
return success(userPromptService.createUserPrompt(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新用户提示词")
|
||||
@PreAuthorize("@ss.hasPermission('ai:user-prompt:update')")
|
||||
public CommonResult<Boolean> updateUserPrompt(@Valid @RequestBody UserPromptSaveReqVO updateReqVO) {
|
||||
public CommonResult<Boolean> updateUserPrompt(@RequestBody UserPromptSaveReqVO updateReqVO) {
|
||||
// 设置当前登录用户ID,确保只能更新自己的提示词
|
||||
updateReqVO.setUserId(getLoginUserId());
|
||||
// 更新时,如果前端没有传递 sort、useCount、isPublic 等字段,后端会自动从数据库获取
|
||||
// 注意:移除了 @Valid 注解,因为这些字段在更新时可以为空,后端会自动填充
|
||||
userPromptService.updateUserPrompt(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
@@ -56,49 +52,36 @@ public class UserPromptController {
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除用户提示词")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('ai:user-prompt:delete')")
|
||||
public CommonResult<Boolean> deleteUserPrompt(@RequestParam("id") Long id) {
|
||||
// 验证是否为当前用户的提示词
|
||||
UserPromptDO userPrompt = userPromptService.getUserPrompt(id);
|
||||
if (userPrompt == null || !userPrompt.getUserId().equals(getLoginUserId())) {
|
||||
// 提示词不存在或不属于当前用户,返回错误
|
||||
throw exception(USER_PROMPT_NOT_EXISTS);
|
||||
}
|
||||
userPromptService.deleteUserPrompt(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除用户提示词")
|
||||
@PreAuthorize("@ss.hasPermission('ai:user-prompt:delete')")
|
||||
public CommonResult<Boolean> deleteUserPromptList(@RequestParam("ids") List<Long> ids) {
|
||||
userPromptService.deleteUserPromptListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得用户提示词")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:user-prompt:query')")
|
||||
public CommonResult<UserPromptRespVO> getUserPrompt(@RequestParam("id") Long id) {
|
||||
UserPromptDO userPrompt = userPromptService.getUserPrompt(id);
|
||||
// 验证是否为当前用户的提示词
|
||||
if (userPrompt != null && !userPrompt.getUserId().equals(getLoginUserId())) {
|
||||
userPrompt = null;
|
||||
}
|
||||
return success(BeanUtils.toBean(userPrompt, UserPromptRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得用户提示词分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:user-prompt:query')")
|
||||
public CommonResult<PageResult<UserPromptRespVO>> getUserPromptPage(@Valid UserPromptPageReqVO pageReqVO) {
|
||||
// 设置当前登录用户ID,只查询当前用户的提示词
|
||||
pageReqVO.setUserId(getLoginUserId());
|
||||
PageResult<UserPromptDO> pageResult = userPromptService.getUserPromptPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, UserPromptRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出用户提示词 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('ai:user-prompt:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportUserPromptExcel(@Valid UserPromptPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<UserPromptDO> list = userPromptService.getUserPromptPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "用户提示词.xls", "数据", UserPromptRespVO.class,
|
||||
BeanUtils.toBean(list, UserPromptRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,10 +44,35 @@ public class UserPromptServiceImpl implements UserPromptService {
|
||||
|
||||
@Override
|
||||
public void updateUserPrompt(UserPromptSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateUserPromptExists(updateReqVO.getId());
|
||||
// 更新
|
||||
UserPromptDO updateObj = BeanUtils.toBean(updateReqVO, UserPromptDO.class);
|
||||
// 1. 手动验证前端表单字段(与前端表单对应)
|
||||
if (updateReqVO.getName() == null || updateReqVO.getName().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("提示词名称不能为空");
|
||||
}
|
||||
if (updateReqVO.getContent() == null || updateReqVO.getContent().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("提示词内容不能为空");
|
||||
}
|
||||
if (updateReqVO.getStatus() == null) {
|
||||
throw new IllegalArgumentException("状态不能为空");
|
||||
}
|
||||
|
||||
// 2. 校验存在并获取现有记录
|
||||
UserPromptDO existing = validateUserPromptExists(updateReqVO.getId());
|
||||
|
||||
// 3. 手动设置要更新的字段(只更新前端表单中的字段)
|
||||
UserPromptDO updateObj = new UserPromptDO();
|
||||
updateObj.setId(updateReqVO.getId());
|
||||
updateObj.setName(updateReqVO.getName().trim());
|
||||
updateObj.setContent(updateReqVO.getContent().trim());
|
||||
updateObj.setCategory(updateReqVO.getCategory() != null ? updateReqVO.getCategory().trim() : null);
|
||||
updateObj.setStatus(updateReqVO.getStatus());
|
||||
|
||||
// 4. 自动填充前端表单中没有的字段(从数据库获取)
|
||||
updateObj.setSort(existing.getSort());
|
||||
updateObj.setUseCount(existing.getUseCount());
|
||||
updateObj.setIsPublic(existing.getIsPublic());
|
||||
updateObj.setUserId(existing.getUserId()); // 保持用户ID不变
|
||||
|
||||
// 5. 执行更新
|
||||
userPromptMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@@ -66,10 +91,12 @@ public class UserPromptServiceImpl implements UserPromptService {
|
||||
}
|
||||
|
||||
|
||||
private void validateUserPromptExists(Long id) {
|
||||
if (userPromptMapper.selectById(id) == null) {
|
||||
private UserPromptDO validateUserPromptExists(Long id) {
|
||||
UserPromptDO userPrompt = userPromptMapper.selectById(id);
|
||||
if (userPrompt == null) {
|
||||
throw exception(USER_PROMPT_NOT_EXISTS);
|
||||
}
|
||||
return userPrompt;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user