Commit 1a903560 by shun peng

fix: 修改对话名称自动填写、添加智能体接口

parent 576ea648
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>
\ No newline at end of file
package cn.iocoder.yudao.module.ai.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package cn.iocoder.yudao.module.ai.controller.admin.agent;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.agent.vo.AgentCreateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.agent.vo.AgentMsgReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.agent.vo.AgentUsedRespVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 智能体")
@RestController
@RequestMapping("/ai/agent")
@Validated
public class AgentChatController {
@Autowired
private RestTemplate restTemplate;
@Resource
private AiApiKeyService apiKeyService;
@Resource
private AiChatConversationService chatConversationService;
@Resource
private AiChatRoleService chatRoleService;
@Resource
private AiChatModelService chatModelService;
@PostMapping("/create-agent-records")
@Operation(summary = "创建对话过的智能体记录")
public CommonResult<Long> createChatAgentRecord(@RequestBody @Valid AgentCreateReqVO createReqVO) {
return success(chatConversationService.createUsedAgentRecord(createReqVO, getLoginUserId()));
}
@GetMapping("/get-used-agents")
@Operation(summary = "获得对话过的智能体列表")
public CommonResult<List<AgentUsedRespVO>> getChatAgentList() {
List<AiChatConversationDO> list = chatConversationService.getChatAgentListByUserId(getLoginUserId());
List<AgentUsedRespVO> result = new ArrayList<>(list.size());
for (int i = 0; i < list.size(); i++) {
AiChatConversationDO aiChatConversationDO = list.get(i);
AiChatRoleDO aiChatRoleDO = chatRoleService.getChatRole(aiChatConversationDO.getRoleId());
AgentUsedRespVO agentUsedRespVO = new AgentUsedRespVO();
agentUsedRespVO.setId(aiChatConversationDO.getId());
agentUsedRespVO.setRoleId(aiChatConversationDO.getRoleId());
agentUsedRespVO.setRoleAvatar(aiChatRoleDO.getAvatar());
agentUsedRespVO.setCreateTime(aiChatConversationDO.getCreateTime());
agentUsedRespVO.setRoleName(aiChatRoleDO.getName());
result.add(agentUsedRespVO);
}
return success(result);
}
@PostMapping("/get-messages")
@Operation(summary = "获得智能体对话消息")
public ResponseEntity<?> getAgentMessages(@RequestBody @Valid AgentMsgReqVO reqVO) {
AiChatRoleDO aiRole = chatRoleService.getChatRole(reqVO.getRoleId());
AiChatConversationDO conversation = chatConversationService.getChatConversation(reqVO.getConversationId());
AiChatModelDO model = chatModelService.getChatModel(conversation.getModelId());
AiApiKeyDO apiKeyDO = apiKeyService.getApiKey(model.getKeyId());
// 目标服务的URL
String baseUrl = apiKeyDO.getUrl();
// String baseUrl = "http://218.77.58.8:8088/api";
// 构建请求参数
String appId = aiRole.getAppId();
String chatId = String.valueOf(reqVO.getConversationId());
// String appId = "66f225d52b2887652b418f82";
// String chatId = "1781604279872581766";
boolean loadCustomFeedbacks = true;
// 构建URL与请求参数
String requestUrl = baseUrl + "/core/chat/init?appId=" + appId + "&chatId=" + chatId + "&loadCustomFeedbacks=" + loadCustomFeedbacks;
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + apiKeyDO.getApiKey());
// 构建请求实体
HttpEntity<String> entity = new HttpEntity<>(headers);
// 发送请求并接收响应
ResponseEntity<String> response = restTemplate.exchange(requestUrl, HttpMethod.GET, entity, String.class);
// 返回响应
return response;
}
// @PostMapping("/list")
// @Operation(summary = "获得智能体列表")
// public ResponseEntity<?> getAgentList(@RequestBody @Valid AgentMsgReqVO reqVO) {
// // 构建请求参数
// Map<String, String> params = new HashMap<>();
// params.put("parentId", reqVO.getParentId());
// params.put("searchKey", reqVO.getSearchKey());
//
//
// AiApiKeyDO apiKeyDO = apiKeyService.getApiKeyByName(reqVO.getName());
//
// // 目标服务的URL
// String url = apiKeyDO.getUrl();
//
// // 设置请求头
// HttpHeaders headers = new HttpHeaders();
// headers.setContentType(MediaType.APPLICATION_JSON);
// headers.set("Authorization", "Bearer " + apiKeyDO.getApiKey());
//
// // 构建请求实体
// HttpEntity<Map<String, String>> entity = new HttpEntity<>(params, headers);
//
// // 发送请求并接收响应
// ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
//
// // 返回响应
// return response;
// }
}
package cn.iocoder.yudao.module.ai.controller.admin.agent.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - Agent Record Create Request VO")
@Data
public class AgentCreateReqVO {
@Schema(description = "智能体编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Long roleId;
}
package cn.iocoder.yudao.module.ai.controller.admin.agent.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - Agent Message Request VO")
@Data
public class AgentMsgReqVO {
@Schema(description = "智能体编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Long roleId;
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long conversationId;
}
package cn.iocoder.yudao.module.ai.controller.admin.agent.vo;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
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 = "管理后台 - Used Agent List Request VO")
@Data
public class AgentUsedRespVO {
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@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)
private LocalDateTime createTime;
// ========== 关联 role 信息 ==========
@Schema(description = "智能体头像", example = "https://www.iocoder.cn/1.png")
private String roleAvatar;
@Schema(description = "智能体名字", example = "小黄")
private String roleName;
}
......@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.ai.controller.admin.chat;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
......@@ -23,6 +25,13 @@ import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
......@@ -56,10 +65,24 @@ public class AiChatMessageController {
return success(chatMessageService.sendMessage(sendReqVO, getLoginUserId()));
}
@Resource
private AiModelFactory modelFactory;
@Operation(summary = "test", description = "test")
@PermitAll
@PostMapping("/test")
public String test() {
ChatModel model = modelFactory.getOrCreateChatModel(AiPlatformEnum.OPENAI,
"fastgpt-x2RF9hkc90AUk3iFSsNaWphpA1jV78i9kUTNsmogjqpKQJcEamEvIrpq3T", "http://218.77.58.8:8088/api");
return model.call(new UserMessage("你是谁"), new AssistantMessage("我是孙悟空"), new UserMessage("用python写helloworld代码"));
}
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) throws MalformedURLException {
// 禁用ruoyi的上下文能力
sendReqVO.setUseContext(false);
return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
}
......
......@@ -54,4 +54,7 @@ public class AiChatRoleRespVO implements VO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "appId for fastgpt")
private String appId;
}
\ No newline at end of file
......@@ -29,4 +29,7 @@ public class AiChatRoleSaveMyReqVO {
@NotEmpty(message = "角色设定不能为空")
private String systemMessage;
@Schema(description = "appId for fastgpt", example = "66f225d52b2887652b418f82")
private String appId;
}
\ No newline at end of file
......@@ -51,4 +51,7 @@ public class AiChatRoleSaveReqVO {
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "appId for fastgpt", example = "66f225d52b2887652b418f82")
private String appId;
}
\ No newline at end of file
......@@ -79,4 +79,6 @@ public class AiChatRoleDO extends BaseDO {
*/
private Integer status;
private String appId;
}
......@@ -35,4 +35,14 @@ public interface AiChatConversationMapper extends BaseMapperX<AiChatConversation
.orderByDesc(AiChatConversationDO::getId));
}
default List<AiChatConversationDO> selectListWithCon(Long userId){
return selectList(new LambdaQueryWrapperX<AiChatConversationDO>()
.eq(AiChatConversationDO::getUserId, userId)
.isNotNull(AiChatConversationDO::getRoleId));
}
default AiChatConversationDO selectByRoleId(Long roleId) {
return selectOne(new LambdaQueryWrapperX<AiChatConversationDO>()
.eq(AiChatConversationDO::getRoleId, roleId));
}
}
......@@ -56,4 +56,10 @@ public interface AiChatMessageMapper extends BaseMapperX<AiChatMessageDO> {
.orderByDesc(AiChatMessageDO::getId));
}
default Long selectCounts(Long conId){
return selectCount(new LambdaQueryWrapperX<AiChatMessageDO>()
.eqIfPresent(AiChatMessageDO::getConversationId, conId)
.eqIfPresent(AiChatMessageDO::getDeleted, 0)
);
}
}
package cn.iocoder.yudao.module.ai.service.chat;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.ai.controller.admin.agent.vo.AgentCreateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
......@@ -87,4 +88,7 @@ public interface AiChatConversationService {
*/
PageResult<AiChatConversationDO> getChatConversationPage(AiChatConversationPageReqVO pageReqVO);
List<AiChatConversationDO> getChatAgentListByUserId(Long loginUserId);
Long createUsedAgentRecord(AgentCreateReqVO createReqVO, Long loginUserId);
}
......@@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ai.controller.admin.agent.vo.AgentCreateReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
......@@ -154,4 +155,36 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
return chatConversationMapper.selectChatConversationPage(pageReqVO);
}
@Override
public List<AiChatConversationDO> getChatAgentListByUserId(Long userId){
return chatConversationMapper.selectListWithCon(userId);
}
@Override
public Long createUsedAgentRecord(AgentCreateReqVO createReqVO, Long userId) {
// 1.1 获得 AiChatRoleDO 聊天角色
AiChatRoleDO role = chatRoleService.validateChatRole(createReqVO.getRoleId());
// 1.2 获得 AiChatModelDO 聊天模型
AiChatModelDO model = chatModalService.validateChatModel(role.getModelId());
Assert.notNull(model, "智能体未配置推理模型");
validateChatModel(model);
AiChatConversationDO conversationExist= chatConversationMapper.selectByRoleId(createReqVO.getRoleId());
if(conversationExist != null){
return conversationExist.getId();
}
// 2. 创建 AiChatConversationDO 聊天对话
AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false)
.setModelId(model.getId()).setModel(model.getModel())
.setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts());
if (role != null) {
conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage());
} else {
conversation.setTitle(AiChatConversationDO.TITLE_DEFAULT);
}
chatConversationMapper.insert(conversation);
return conversation.getId();
}
}
......@@ -9,6 +9,8 @@ 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 cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
......@@ -107,12 +109,22 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId());
// 2. 插入 user 发送消息
Long count = chatMessageMapper.selectCounts(conversation.getId());
if(count == 0){
AiChatConversationUpdateMyReqVO updateMyReq = BeanUtils.toBean(conversation, AiChatConversationUpdateMyReqVO.class);
updateMyReq.setTitle(sendReqVO.getContent());
chatConversationService.updateChatConversationMy(updateMyReq, userId);
}
AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext());
// 3.1 插入 assistant 接收消息
AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
// AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
// userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
AiChatMessageDO assistantMessage = new AiChatMessageDO().setConversationId(conversation.getId()).setReplyId(userMessage.getId())
.setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(conversation.getRoleId())
.setType(MessageType.ASSISTANT.getValue()).setContent("").setUseContext(sendReqVO.getUseContext());
assistantMessage.setCreateTime(LocalDateTime.now());
// 3.2 构建 Prompt,并进行调用
Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO);
......@@ -130,13 +142,20 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
.setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)));
}).doOnComplete(() -> {
// 忽略租户,因为 Flux 异步无法透传租户
TenantUtils.executeIgnore(() ->
chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())));
// TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())));
TenantUtils.executeIgnore(() -> {
assistantMessage.setContent(contentBuffer.toString());
chatMessageMapper.insert(assistantMessage);
});
}).doOnError(throwable -> {
log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
// 忽略租户,因为 Flux 异步无法透传租户
TenantUtils.executeIgnore(() ->
chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())));
// TenantUtils.executeIgnore(() ->
// chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())));
TenantUtils.executeIgnore(() -> {
assistantMessage.setContent(throwable.getMessage());
chatMessageMapper.insert(assistantMessage);
});
}).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
}
......@@ -164,7 +183,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
// 2. 构建 ChatOptions 对象
AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(),
conversation.getTemperature(), conversation.getMaxTokens());
conversation.getTemperature(), conversation.getMaxTokens(), String.valueOf(conversation.getId()));
return new Prompt(chatMessages, chatOptions);
}
......
......@@ -108,7 +108,7 @@ public class AiMindMapServiceImpl implements AiMindMapService {
List<Message> chatMessages = buildMessages(generateReqVO, systemMessage);
// 2. 构建 options 对象
AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());
ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens(), "");
return new Prompt(chatMessages, options);
}
......
......@@ -53,6 +53,14 @@ public interface AiApiKeyService {
AiApiKeyDO getApiKey(Long id);
/**
* 通过名称获得 API 密钥
*
* @param name 名称
* @return API 密钥
*/
AiApiKeyDO getApiKeyByName(String name);
/**
* 校验 API 密钥
*
* @param id 比那好
......
......@@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageR
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
......@@ -80,6 +81,12 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
public AiApiKeyDO getApiKey(Long id) {
return apiKeyMapper.selectById(id);
}
@Override
public AiApiKeyDO getApiKeyByName(String name) {
QueryWrapper<AiApiKeyDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", name);
return apiKeyMapper.selectOne(queryWrapper);
}
@Override
public AiApiKeyDO validateApiKey(Long id) {
......
......@@ -126,7 +126,7 @@ public class AiWriteServiceImpl implements AiWriteService {
List<Message> chatMessages = buildMessages(generateReqVO, systemMessage);
// 2. 构建 options 对象
AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens());
ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens(), "");
return new Prompt(chatMessages, options);
}
......
......@@ -30,7 +30,7 @@ public enum AiPlatformEnum {
MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney
SUNO("Suno", "Suno"), // Suno AI
;
FAST_GPT("FastGPT", "FastGPT");
/**
* 平台
......
......@@ -10,6 +10,8 @@ import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration;
import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties;
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
import cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTApi;
import cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTChatModel;
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
......@@ -93,6 +95,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
return buildAzureOpenAiChatModel(apiKey, url);
case OLLAMA:
return buildOllamaChatModel(url);
case FAST_GPT:
return buildFastGPTModel(apiKey, url);
default:
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
}
......@@ -119,6 +123,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
return SpringUtil.getBean(AzureOpenAiChatModel.class);
case OLLAMA:
return SpringUtil.getBean(OllamaChatModel.class);
case FAST_GPT:
return SpringUtil.getBean(FastGPTChatModel.class);
default:
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
}
......@@ -340,4 +346,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
return new TongYiAutoConfiguration().tongYiTextEmbeddingClient(SpringUtil.getBean(TextEmbedding.class), connectionProperties);
}
private static FastGPTChatModel buildFastGPTModel(String openAiToken, String url) {
url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL);
FastGPTApi openAiApi = new FastGPTApi(url, openAiToken);
return new FastGPTChatModel(openAiApi);
}
}
package cn.iocoder.yudao.framework.ai.core.model.fastgpt;
import java.util.HashMap;
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
import org.springframework.ai.chat.metadata.EmptyRateLimit;
import org.springframework.ai.chat.metadata.EmptyUsage;
import org.springframework.ai.chat.metadata.RateLimit;
import org.springframework.ai.chat.metadata.Usage;
import org.springframework.ai.openai.metadata.OpenAiRateLimit;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public class FastGPTResponseMetadata extends HashMap<String, Object> implements ChatResponseMetadata {
protected static final String AI_METADATA_STRING = "{ @type: %1$s, id: %2$s, usage: %3$s, rateLimit: %4$s }";
private final String id;
@Nullable
private RateLimit rateLimit;
private final Usage usage;
public static cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTResponseMetadata from(FastGPTApi.ChatCompletion result) {
Assert.notNull(result, "OpenAI ChatCompletionResult must not be null");
FastGPTUsage usage = FastGPTUsage.from(result.usage());
cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTResponseMetadata chatResponseMetadata = new cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTResponseMetadata(result.id(), usage);
return chatResponseMetadata;
}
protected FastGPTResponseMetadata(String id, FastGPTUsage usage) {
this(id, usage, (OpenAiRateLimit)null);
}
protected FastGPTResponseMetadata(String id, FastGPTUsage usage, @Nullable OpenAiRateLimit rateLimit) {
this.id = id;
this.usage = usage;
this.rateLimit = rateLimit;
}
public String getId() {
return this.id;
}
@Nullable
public RateLimit getRateLimit() {
RateLimit rateLimit = this.rateLimit;
return (RateLimit)(rateLimit != null ? rateLimit : new EmptyRateLimit());
}
public Usage getUsage() {
Usage usage = this.usage;
return (Usage)(usage != null ? usage : new EmptyUsage());
}
public cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTResponseMetadata withRateLimit(RateLimit rateLimit) {
this.rateLimit = rateLimit;
return this;
}
public String toString() {
return "{ @type: %1$s, id: %2$s, usage: %3$s, rateLimit: %4$s }".formatted(this.getClass().getName(), this.getId(), this.getUsage(), this.getRateLimit());
}
}
package cn.iocoder.yudao.framework.ai.core.model.fastgpt;
import java.util.ArrayList;
import java.util.List;
import org.springframework.util.CollectionUtils;
public class FastGPTStreamFunctionCallingHelper {
public FastGPTStreamFunctionCallingHelper() {
}
public FastGPTApi.ChatCompletionChunk merge(FastGPTApi.ChatCompletionChunk previous, FastGPTApi.ChatCompletionChunk current) {
if (previous == null) {
return current;
} else {
String id = current.id() != null ? current.id() : previous.id();
Long created = current.created() != null ? current.created() : previous.created();
String model = current.model() != null ? current.model() : previous.model();
String systemFingerprint = current.systemFingerprint() != null ? current.systemFingerprint() : previous.systemFingerprint();
String object = current.object() != null ? current.object() : previous.object();
FastGPTApi.ChatCompletionChunk.ChunkChoice previousChoice0 = CollectionUtils.isEmpty(previous.choices()) ? null : (FastGPTApi.ChatCompletionChunk.ChunkChoice)previous.choices().get(0);
FastGPTApi.ChatCompletionChunk.ChunkChoice currentChoice0 = CollectionUtils.isEmpty(current.choices()) ? null : (FastGPTApi.ChatCompletionChunk.ChunkChoice)current.choices().get(0);
FastGPTApi.ChatCompletionChunk.ChunkChoice choice = this.merge(previousChoice0, currentChoice0);
List<FastGPTApi.ChatCompletionChunk.ChunkChoice> chunkChoices = choice == null ? List.of() : List.of(choice);
return new FastGPTApi.ChatCompletionChunk(id, chunkChoices, created, model, systemFingerprint, object);
}
}
private FastGPTApi.ChatCompletionChunk.ChunkChoice merge(FastGPTApi.ChatCompletionChunk.ChunkChoice previous, FastGPTApi.ChatCompletionChunk.ChunkChoice current) {
if (previous == null) {
return current;
} else {
FastGPTApi.ChatCompletionFinishReason finishReason = current.finishReason() != null ? current.finishReason() : previous.finishReason();
Integer index = current.index() != null ? current.index() : previous.index();
FastGPTApi.ChatCompletionMessage message = this.merge(previous.delta(), current.delta());
FastGPTApi.LogProbs logprobs = current.logprobs() != null ? current.logprobs() : previous.logprobs();
return new FastGPTApi.ChatCompletionChunk.ChunkChoice(finishReason, index, message, logprobs);
}
}
private FastGPTApi.ChatCompletionMessage merge(FastGPTApi.ChatCompletionMessage previous, FastGPTApi.ChatCompletionMessage current) {
String content = current.content() != null ? current.content() : "" + (previous.content() != null ? previous.content() : "");
FastGPTApi.ChatCompletionMessage.Role role = current.role() != null ? current.role() : previous.role();
role = role != null ? role : FastGPTApi.ChatCompletionMessage.Role.ASSISTANT;
String name = current.name() != null ? current.name() : previous.name();
String toolCallId = current.toolCallId() != null ? current.toolCallId() : previous.toolCallId();
List<FastGPTApi.ChatCompletionMessage.ToolCall> toolCalls = new ArrayList();
FastGPTApi.ChatCompletionMessage.ToolCall lastPreviousTooCall = null;
if (previous.toolCalls() != null) {
lastPreviousTooCall = (FastGPTApi.ChatCompletionMessage.ToolCall)previous.toolCalls().get(previous.toolCalls().size() - 1);
if (previous.toolCalls().size() > 1) {
toolCalls.addAll(previous.toolCalls().subList(0, previous.toolCalls().size() - 1));
}
}
if (current.toolCalls() != null) {
if (current.toolCalls().size() > 1) {
throw new IllegalStateException("Currently only one tool call is supported per message!");
}
FastGPTApi.ChatCompletionMessage.ToolCall currentToolCall = (FastGPTApi.ChatCompletionMessage.ToolCall)current.toolCalls().iterator().next();
if (currentToolCall.id() != null) {
if (lastPreviousTooCall != null) {
toolCalls.add(lastPreviousTooCall);
}
toolCalls.add(currentToolCall);
} else {
toolCalls.add(this.merge(lastPreviousTooCall, currentToolCall));
}
} else if (lastPreviousTooCall != null) {
toolCalls.add(lastPreviousTooCall);
}
return new FastGPTApi.ChatCompletionMessage(content, role, name, toolCallId, toolCalls);
}
private FastGPTApi.ChatCompletionMessage.ToolCall merge(FastGPTApi.ChatCompletionMessage.ToolCall previous, FastGPTApi.ChatCompletionMessage.ToolCall current) {
if (previous == null) {
return current;
} else {
String id = current.id() != null ? current.id() : previous.id();
String type = current.type() != null ? current.type() : previous.type();
FastGPTApi.ChatCompletionMessage.ChatCompletionFunction function = this.merge(previous.function(), current.function());
return new FastGPTApi.ChatCompletionMessage.ToolCall(id, type, function);
}
}
private FastGPTApi.ChatCompletionMessage.ChatCompletionFunction merge(FastGPTApi.ChatCompletionMessage.ChatCompletionFunction previous, FastGPTApi.ChatCompletionMessage.ChatCompletionFunction current) {
if (previous == null) {
return current;
} else {
String name = current.name() != null ? current.name() : previous.name();
StringBuilder arguments = new StringBuilder();
if (previous.arguments() != null) {
arguments.append(previous.arguments());
}
if (current.arguments() != null) {
arguments.append(current.arguments());
}
return new FastGPTApi.ChatCompletionMessage.ChatCompletionFunction(name, arguments.toString());
}
}
public boolean isStreamingToolFunctionCall(FastGPTApi.ChatCompletionChunk chatCompletion) {
if (chatCompletion != null && !CollectionUtils.isEmpty(chatCompletion.choices())) {
FastGPTApi.ChatCompletionChunk.ChunkChoice choice = (FastGPTApi.ChatCompletionChunk.ChunkChoice)chatCompletion.choices().get(0);
if (choice != null && choice.delta() != null) {
return !CollectionUtils.isEmpty(choice.delta().toolCalls());
} else {
return false;
}
} else {
return false;
}
}
public boolean isStreamingToolFunctionCallFinish(FastGPTApi.ChatCompletionChunk chatCompletion) {
if (chatCompletion != null && !CollectionUtils.isEmpty(chatCompletion.choices())) {
FastGPTApi.ChatCompletionChunk.ChunkChoice choice = (FastGPTApi.ChatCompletionChunk.ChunkChoice)chatCompletion.choices().get(0);
if (choice != null && choice.delta() != null) {
return choice.finishReason() == FastGPTApi.ChatCompletionFinishReason.TOOL_CALLS;
} else {
return false;
}
} else {
return false;
}
}
public FastGPTApi.ChatCompletion chunkToChatCompletion(FastGPTApi.ChatCompletionChunk chunk) {
List<FastGPTApi.ChatCompletion.Choice> choices = chunk.choices().stream().map((chunkChoice) -> {
return new FastGPTApi.ChatCompletion.Choice(chunkChoice.finishReason(), chunkChoice.index(), chunkChoice.delta(), chunkChoice.logprobs());
}).toList();
return new FastGPTApi.ChatCompletion(chunk.id(), choices, chunk.created(), chunk.model(), chunk.systemFingerprint(), "chat.completion", (FastGPTApi.Usage)null);
}
}
package cn.iocoder.yudao.framework.ai.core.model.fastgpt;
import org.springframework.ai.chat.metadata.Usage;
import org.springframework.util.Assert;
public class FastGPTUsage implements Usage {
private final FastGPTApi.Usage usage;
public static cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTUsage from(FastGPTApi.Usage usage) {
return new cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTUsage(usage);
}
protected FastGPTUsage(FastGPTApi.Usage usage) {
Assert.notNull(usage, "OpenAI Usage must not be null");
this.usage = usage;
}
protected FastGPTApi.Usage getUsage() {
return this.usage;
}
public Long getPromptTokens() {
return this.getUsage().promptTokens().longValue();
}
public Long getGenerationTokens() {
return this.getUsage().completionTokens().longValue();
}
public Long getTotalTokens() {
return this.getUsage().totalTokens().longValue();
}
public String toString() {
return this.getUsage().toString();
}
}
......@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.ai.core.util;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
import cn.iocoder.yudao.framework.ai.core.model.fastgpt.FastGPTOptions;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
......@@ -20,7 +21,7 @@ import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
*/
public class AiUtils {
public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens, String chatId) {
Float temperatureF = temperature != null ? temperature.floatValue() : null;
//noinspection EnhancedSwitchMigration
switch (platform) {
......@@ -41,6 +42,9 @@ public class AiUtils {
return AzureOpenAiChatOptions.builder().withDeploymentName(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
case OLLAMA:
return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
case FAST_GPT:
return FastGPTOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).withChatId(chatId).build();
// return FastGPTOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
default:
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
}
......
......@@ -32,6 +32,9 @@
<el-form-item label="角色设定" prop="systemMessage">
<el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" />
</el-form-item>
<el-form-item label="AppId" prop="appId">
<el-input v-model="formData.appId" placeholder="请输入FastGPT的AppId" />
</el-form-item>
<el-form-item label="是否公开" prop="publicStatus" v-if="!isUser">
<el-radio-group v-model="formData.publicStatus">
<el-radio
......@@ -91,7 +94,8 @@ const formData = ref({
description: undefined,
systemMessage: undefined,
publicStatus: true,
status: CommonStatusEnum.ENABLE
status: CommonStatusEnum.ENABLE,
appId: undefined
})
const formRef = ref() // 表单 Ref
const chatModelList = ref([] as ChatModelVO[]) // 聊天模型列表
......@@ -176,7 +180,8 @@ const resetForm = () => {
description: undefined,
systemMessage: undefined,
publicStatus: true,
status: CommonStatusEnum.ENABLE
status: CommonStatusEnum.ENABLE,
appId: undefined
}
formRef.value?.resetFields()
}
......
......@@ -69,6 +69,7 @@
<el-table-column label="角色类别" align="center" prop="category" />
<el-table-column label="角色描述" align="center" prop="description" />
<el-table-column label="角色设定" align="center" prop="systemMessage" />
<el-table-column label="AppId" align="center" prop="appId" />
<el-table-column label="是否公开" align="center" prop="publicStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.publicStatus" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment