Commit a8d7da329f35bbd410fa3ea793cea8482467fe9f

Authored by 芋道源码
Committed by Gitee
2 parents 2a9dffb6 dd0f8a71

!1256 Spring AI 1.0.0 M6 适配:增加知识库、工具调用(function calling)、工作流、豆包/混元/硅基流动等模型的接入

Merge pull request !1256 from 芋道源码/feature/ai
Showing 180 changed files with 4714 additions and 7044 deletions

Too many changes.

To preserve performance only 100 of 180 files are displayed.

.image/common/ai-feature.png

20.7 KB | W: | H:

24.9 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
sql/mysql/ruoyi-vue-pro.sql
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 Target Server Version : 80200 (8.2.0) 11 Target Server Version : 80200 (8.2.0)
12 File Encoding : 65001 12 File Encoding : 65001
13 13
14 - Date: 31/12/2024 09:16:18 14 + Date: 14/03/2025 22:52:31
15 */ 15 */
16 16
17 SET NAMES utf8mb4; 17 SET NAMES utf8mb4;
@@ -91,7 +91,7 @@ CREATE TABLE `infra_api_error_log` ( @@ -91,7 +91,7 @@ CREATE TABLE `infra_api_error_log` (
91 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 91 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
92 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 92 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
93 PRIMARY KEY (`id`) USING BTREE 93 PRIMARY KEY (`id`) USING BTREE
94 -) ENGINE = InnoDB AUTO_INCREMENT = 21226 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; 94 +) ENGINE = InnoDB AUTO_INCREMENT = 21417 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
95 95
96 -- ---------------------------- 96 -- ----------------------------
97 -- Records of infra_api_error_log 97 -- Records of infra_api_error_log
@@ -128,7 +128,7 @@ CREATE TABLE `infra_codegen_column` ( @@ -128,7 +128,7 @@ CREATE TABLE `infra_codegen_column` (
128 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 128 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
129 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 129 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
130 PRIMARY KEY (`id`) USING BTREE 130 PRIMARY KEY (`id`) USING BTREE
131 -) ENGINE = InnoDB AUTO_INCREMENT = 2483 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义'; 131 +) ENGINE = InnoDB AUTO_INCREMENT = 2538 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
132 132
133 -- ---------------------------- 133 -- ----------------------------
134 -- Records of infra_codegen_column 134 -- Records of infra_codegen_column
@@ -166,7 +166,7 @@ CREATE TABLE `infra_codegen_table` ( @@ -166,7 +166,7 @@ CREATE TABLE `infra_codegen_table` (
166 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 166 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
167 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 167 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
168 PRIMARY KEY (`id`) USING BTREE 168 PRIMARY KEY (`id`) USING BTREE
169 -) ENGINE = InnoDB AUTO_INCREMENT = 187 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义'; 169 +) ENGINE = InnoDB AUTO_INCREMENT = 191 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
170 170
171 -- ---------------------------- 171 -- ----------------------------
172 -- Records of infra_codegen_table 172 -- Records of infra_codegen_table
@@ -250,7 +250,7 @@ CREATE TABLE `infra_file` ( @@ -250,7 +250,7 @@ CREATE TABLE `infra_file` (
250 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 250 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
251 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 251 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
252 PRIMARY KEY (`id`) USING BTREE 252 PRIMARY KEY (`id`) USING BTREE
253 -) ENGINE = InnoDB AUTO_INCREMENT = 1577 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; 253 +) ENGINE = InnoDB AUTO_INCREMENT = 1655 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
254 254
255 -- ---------------------------- 255 -- ----------------------------
256 -- Records of infra_file 256 -- Records of infra_file
@@ -444,7 +444,7 @@ CREATE TABLE `system_dict_data` ( @@ -444,7 +444,7 @@ CREATE TABLE `system_dict_data` (
444 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 444 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
445 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 445 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
446 PRIMARY KEY (`id`) USING BTREE 446 PRIMARY KEY (`id`) USING BTREE
447 -) ENGINE = InnoDB AUTO_INCREMENT = 1683 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; 447 +) ENGINE = InnoDB AUTO_INCREMENT = 1694 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
448 448
449 -- ---------------------------- 449 -- ----------------------------
450 -- Records of system_dict_data 450 -- Records of system_dict_data
@@ -831,7 +831,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st @@ -831,7 +831,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
831 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1551, 2, '描述模式', '2', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:37', '1', '2024-06-28 01:22:24', b'0'); 831 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1551, 2, '描述模式', '2', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:37', '1', '2024-06-28 01:22:24', b'0');
832 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1552, 8, 'Suno', 'Suno', 'ai_platform', 0, '', '', '', '1', '2024-06-29 09:13:36', '1', '2024-06-29 09:13:41', b'0'); 832 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1552, 8, 'Suno', 'Suno', 'ai_platform', 0, '', '', '', '1', '2024-06-29 09:13:36', '1', '2024-06-29 09:13:41', b'0');
833 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1553, 9, 'DeepSeek', 'DeepSeek', 'ai_platform', 0, '', '', '', '1', '2024-07-06 12:04:30', '1', '2024-07-06 12:05:20', b'0'); 833 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1553, 9, 'DeepSeek', 'DeepSeek', 'ai_platform', 0, '', '', '', '1', '2024-07-06 12:04:30', '1', '2024-07-06 12:05:20', b'0');
834 -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1554, 10, '智谱', 'ZhiPu', 'ai_platform', 0, '', '', '', '1', '2024-07-06 18:00:35', '1', '2024-07-06 18:00:35', b'0'); 834 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1554, 13, '智谱', 'ZhiPu', 'ai_platform', 0, '', '', '', '1', '2024-07-06 18:00:35', '1', '2025-02-24 20:18:41', b'0');
835 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1555, 4, '长', '4', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:49:03', '1', '2024-07-07 15:49:03', b'0'); 835 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1555, 4, '长', '4', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:49:03', '1', '2024-07-07 15:49:03', b'0');
836 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1556, 5, '段落', '5', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:49:54', '1', '2024-07-07 15:49:54', b'0'); 836 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1556, 5, '段落', '5', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:49:54', '1', '2024-07-07 15:49:54', b'0');
837 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1557, 6, '文章', '6', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:05', '1', '2024-07-07 15:50:05', b'0'); 837 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1557, 6, '文章', '6', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:05', '1', '2024-07-07 15:50:05', b'0');
@@ -888,13 +888,23 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st @@ -888,13 +888,23 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
888 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1673, 2, 'OPC UA', '2', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:31', '1', '2024-09-06 22:26:31', b'0'); 888 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1673, 2, 'OPC UA', '2', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:31', '1', '2024-09-06 22:26:31', b'0');
889 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1674, 3, 'ZigBee', '3', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:39', '1', '2024-09-06 22:26:39', b'0'); 889 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1674, 3, 'ZigBee', '3', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:39', '1', '2024-09-06 22:26:39', b'0');
890 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1675, 4, 'BLE', '4', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:48', '1', '2024-09-06 22:26:48', b'0'); 890 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1675, 4, 'BLE', '4', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:48', '1', '2024-09-06 22:26:48', b'0');
891 -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1676, 0, '未激活', '0', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:34', '1', '2024-09-21 08:13:34', b'0');  
892 -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1677, 1, '在线', '1', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:48', '1', '2024-09-21 08:13:48', b'0');  
893 -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1678, 2, '离线', '2', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:59', '1', '2024-09-21 08:13:59', b'0');  
894 -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1679, 3, '已禁用', '3', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:14:13', '1', '2024-09-21 08:14:13', b'0'); 891 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1676, 0, '未激活', '0', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:34', '1', '2025-01-28 16:09:27', b'0');
  892 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1677, 1, '在线', '1', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:48', '1', '2025-01-28 16:09:26', b'0');
  893 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1678, 2, '离线', '2', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:59', '1', '2025-01-28 16:09:25', b'0');
895 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1680, 1, '属性', '1', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:01', '1', '2024-09-29 20:09:41', b'0'); 894 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1680, 1, '属性', '1', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:01', '1', '2024-09-29 20:09:41', b'0');
896 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1681, 2, '服务', '2', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:11', '1', '2024-09-29 20:08:23', b'0'); 895 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1681, 2, '服务', '2', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:11', '1', '2024-09-29 20:08:23', b'0');
897 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1682, 3, '事件', '3', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:20', '1', '2024-09-29 20:08:20', b'0'); 896 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1682, 3, '事件', '3', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:20', '1', '2024-09-29 20:08:20', b'0');
  897 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1683, 10, '字节豆包', 'DouBao', 'ai_platform', 0, '', '', '', '1', '2025-02-23 19:51:40', '1', '2025-02-23 19:52:02', b'0');
  898 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1684, 11, '腾讯混元', 'HunYuan', 'ai_platform', 0, '', '', '', '1', '2025-02-23 20:58:04', '1', '2025-02-23 20:58:04', b'0');
  899 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1685, 12, '硅基流动', 'SiliconFlow', 'ai_platform', 0, '', '', '', '1', '2025-02-24 20:19:09', '1', '2025-02-24 20:19:09', b'0');
  900 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1686, 1, '聊天', '1', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:26:34', '1', '2025-03-03 12:26:34', b'0');
  901 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1687, 2, '图像', '2', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:27:23', '1', '2025-03-03 12:27:23', b'0');
  902 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1688, 3, '音频', '3', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:27:51', '1', '2025-03-03 12:27:51', b'0');
  903 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1689, 4, '视频', '4', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:03', '1', '2025-03-03 12:28:03', b'0');
  904 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1690, 5, '向量', '5', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:15', '1', '2025-03-03 12:28:15', b'0');
  905 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1691, 6, '重排', '6', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:26', '1', '2025-03-03 12:28:26', b'0');
  906 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1692, 14, 'MiniMax', 'MiniMax', 'ai_platform', 0, '', '', '', '1', '2025-03-11 20:04:51', '1', '2025-03-11 20:04:51', b'0');
  907 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1693, 15, '月之暗灭', 'Moonshot', 'ai_platform', 0, '', '', '', '1', '2025-03-11 20:05:08', '1', '2025-03-11 20:05:08', b'0');
898 COMMIT; 908 COMMIT;
899 909
900 -- ---------------------------- 910 -- ----------------------------
@@ -914,7 +924,7 @@ CREATE TABLE `system_dict_type` ( @@ -914,7 +924,7 @@ CREATE TABLE `system_dict_type` (
914 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 924 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
915 `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间', 925 `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
916 PRIMARY KEY (`id`) USING BTREE 926 PRIMARY KEY (`id`) USING BTREE
917 -) ENGINE = InnoDB AUTO_INCREMENT = 640 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表'; 927 +) ENGINE = InnoDB AUTO_INCREMENT = 641 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
918 928
919 -- ---------------------------- 929 -- ----------------------------
920 -- Records of system_dict_type 930 -- Records of system_dict_type
@@ -1014,13 +1024,14 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat @@ -1014,13 +1024,14 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
1014 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (628, 'AI 写作类型', 'ai_write_type', 0, '', '1', '2024-07-10 21:25:29', '1', '2024-07-10 21:25:29', b'0', '1970-01-01 00:00:00'); 1024 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (628, 'AI 写作类型', 'ai_write_type', 0, '', '1', '2024-07-10 21:25:29', '1', '2024-07-10 21:25:29', b'0', '1970-01-01 00:00:00');
1015 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (629, 'BPM 流程模型类型', 'bpm_model_type', 0, '', '1', '2024-08-26 15:21:43', '1', '2024-08-26 15:21:43', b'0', '1970-01-01 00:00:00'); 1025 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (629, 'BPM 流程模型类型', 'bpm_model_type', 0, '', '1', '2024-08-26 15:21:43', '1', '2024-08-26 15:21:43', b'0', '1970-01-01 00:00:00');
1016 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (630, 'IOT 接入网关协议', 'iot_protocol_type', 0, '', '1', '2024-09-06 22:20:17', '1', '2024-09-06 22:20:17', b'0', '1970-01-01 00:00:00'); 1026 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (630, 'IOT 接入网关协议', 'iot_protocol_type', 0, '', '1', '2024-09-06 22:20:17', '1', '2024-09-06 22:20:17', b'0', '1970-01-01 00:00:00');
1017 -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (631, 'IOT 设备状态', 'iot_device_status', 0, '', '1', '2024-09-21 08:12:55', '1', '2024-09-21 08:12:55', b'0', '1970-01-01 00:00:00'); 1027 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (631, 'IOT 设备状态', 'iot_device_state', 0, '', '1', '2024-09-21 08:12:55', '1', '2025-01-28 16:09:42', b'0', '1970-01-01 00:00:00');
1018 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (632, 'IOT 物模型功能类型', 'iot_product_function_type', 0, '', '1', '2024-09-29 20:02:36', '1', '2024-09-29 20:09:26', b'0', '1970-01-01 00:00:00'); 1028 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (632, 'IOT 物模型功能类型', 'iot_product_function_type', 0, '', '1', '2024-09-29 20:02:36', '1', '2024-09-29 20:09:26', b'0', '1970-01-01 00:00:00');
1019 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (634, 'IOT 数据格式', 'iot_data_format', 0, '', '1', '2024-08-10 11:52:58', '1', '2024-09-06 14:30:14', b'0', '1970-01-01 00:00:00'); 1029 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (634, 'IOT 数据格式', 'iot_data_format', 0, '', '1', '2024-08-10 11:52:58', '1', '2024-09-06 14:30:14', b'0', '1970-01-01 00:00:00');
1020 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (635, 'IOT 产品设备类型', 'iot_product_device_type', 0, '', '1', '2024-08-10 11:54:30', '1', '2024-08-10 04:06:56', b'0', '1970-01-01 00:00:00'); 1030 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (635, 'IOT 产品设备类型', 'iot_product_device_type', 0, '', '1', '2024-08-10 11:54:30', '1', '2024-08-10 04:06:56', b'0', '1970-01-01 00:00:00');
1021 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (637, 'IOT 产品状态', 'iot_product_status', 0, '', '1', '2024-08-10 12:06:09', '1', '2024-08-10 12:06:09', b'0', '1970-01-01 00:00:00'); 1031 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (637, 'IOT 产品状态', 'iot_product_status', 0, '', '1', '2024-08-10 12:06:09', '1', '2024-08-10 12:06:09', b'0', '1970-01-01 00:00:00');
1022 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (638, 'IOT 数据校验级别', 'iot_validate_type', 0, '', '1', '2024-09-06 20:05:13', '1', '2024-09-06 20:05:13', b'0', '1970-01-01 00:00:00'); 1032 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (638, 'IOT 数据校验级别', 'iot_validate_type', 0, '', '1', '2024-09-06 20:05:13', '1', '2024-09-06 20:05:13', b'0', '1970-01-01 00:00:00');
1023 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (639, 'IOT 联网方式', 'iot_net_type', 0, '', '1', '2024-09-06 22:04:13', '1', '2024-09-06 22:04:13', b'0', '1970-01-01 00:00:00'); 1033 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (639, 'IOT 联网方式', 'iot_net_type', 0, '', '1', '2024-09-06 22:04:13', '1', '2024-09-06 22:04:13', b'0', '1970-01-01 00:00:00');
  1034 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (640, 'AI 模型类型', 'ai_model_type', 0, '', '1', '2025-03-03 12:24:07', '1', '2025-03-03 12:24:07', b'0', '1970-01-01 00:00:00');
1024 COMMIT; 1035 COMMIT;
1025 1036
1026 -- ---------------------------- 1037 -- ----------------------------
@@ -1044,7 +1055,7 @@ CREATE TABLE `system_login_log` ( @@ -1044,7 +1055,7 @@ CREATE TABLE `system_login_log` (
1044 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 1055 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1045 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 1056 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
1046 PRIMARY KEY (`id`) USING BTREE 1057 PRIMARY KEY (`id`) USING BTREE
1047 -) ENGINE = InnoDB AUTO_INCREMENT = 3415 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; 1058 +) ENGINE = InnoDB AUTO_INCREMENT = 3442 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
1048 1059
1049 -- ---------------------------- 1060 -- ----------------------------
1050 -- Records of system_login_log 1061 -- Records of system_login_log
@@ -1175,7 +1186,7 @@ CREATE TABLE `system_menu` ( @@ -1175,7 +1186,7 @@ CREATE TABLE `system_menu` (
1175 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 1186 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
1176 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 1187 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1177 PRIMARY KEY (`id`) USING BTREE 1188 PRIMARY KEY (`id`) USING BTREE
1178 -) ENGINE = InnoDB AUTO_INCREMENT = 2913 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表'; 1189 +) ENGINE = InnoDB AUTO_INCREMENT = 2925 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
1179 1190
1180 -- ---------------------------- 1191 -- ----------------------------
1181 -- Records of system_menu 1192 -- Records of system_menu
@@ -1373,7 +1384,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i @@ -1373,7 +1384,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
1373 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1241, '文件配置删除', 'infra:file-config:delete', 3, 4, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0'); 1384 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1241, '文件配置删除', 'infra:file-config:delete', 3, 4, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
1374 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1242, '文件配置导出', 'infra:file-config:export', 3, 5, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0'); 1385 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1242, '文件配置导出', 'infra:file-config:export', 3, 5, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
1375 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1243, '文件管理', '', 2, 6, 2, 'file', 'ep:files', NULL, '', 0, b'1', b'1', b'1', '1', '2022-03-16 23:47:40', '1', '2024-04-23 00:02:11', b'0'); 1386 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1243, '文件管理', '', 2, 6, 2, 'file', 'ep:files', NULL, '', 0, b'1', b'1', b'1', '1', '2022-03-16 23:47:40', '1', '2024-04-23 00:02:11', b'0');
1376 -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:avatar', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-04-23 01:03:15', '1', '2024-09-06 09:19:42', b'0'); 1387 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:avatar', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-04-23 01:03:15', '104', '2025-01-04 10:59:37', b'0');
1377 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1255, '数据源配置', '', 2, 1, 2, 'data-source-config', 'ep:data-analysis', 'infra/dataSourceConfig/index', 'InfraDataSourceConfig', 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '1', '2024-02-29 08:51:25', b'0'); 1388 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1255, '数据源配置', '', 2, 1, 2, 'data-source-config', 'ep:data-analysis', 'infra/dataSourceConfig/index', 'InfraDataSourceConfig', 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '1', '2024-02-29 08:51:25', b'0');
1378 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1256, '数据源配置查询', 'infra:data-source-config:query', 3, 1, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0'); 1389 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1256, '数据源配置查询', 'infra:data-source-config:query', 3, 1, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
1379 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1257, '数据源配置创建', 'infra:data-source-config:create', 3, 2, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0'); 1390 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1257, '数据源配置创建', 'infra:data-source-config:create', 3, 2, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
@@ -1978,11 +1989,11 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i @@ -1978,11 +1989,11 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
1978 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2763, 'API 密钥创建', 'ai:api-key:create', 3, 2, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:26', b'0'); 1989 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2763, 'API 密钥创建', 'ai:api-key:create', 3, 2, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:26', b'0');
1979 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2764, 'API 密钥更新', 'ai:api-key:update', 3, 3, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:42', b'0'); 1990 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2764, 'API 密钥更新', 'ai:api-key:update', 3, 3, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:42', b'0');
1980 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2765, 'API 密钥删除', 'ai:api-key:delete', 3, 4, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:48', b'0'); 1991 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2765, 'API 密钥删除', 'ai:api-key:delete', 3, 4, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:48', b'0');
1981 -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2767, '聊天模型', '', 2, 0, 2760, 'chat-model', 'fa-solid:abacus', 'ai/model/chatModel/index.vue', 'AiChatModel', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-10 22:44:16', b'0');  
1982 -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2768, '聊天模型查询', 'ai:chat-model:query', 3, 1, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:02', b'0');  
1983 -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2769, '聊天模型创建', 'ai:chat-model:create', 3, 2, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:12', b'0');  
1984 -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2770, '聊天模型更新', 'ai:chat-model:update', 3, 3, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:18', b'0');  
1985 -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2771, '聊天模型删除', 'ai:chat-model:delete', 3, 4, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:23', b'0'); 1992 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2767, '模型配置', '', 2, 0, 2760, 'model', 'fa-solid:abacus', 'ai/model/model/index.vue', 'AiModel', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:57:41', b'0');
  1993 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2768, '聊天模型查询', 'ai:model:query', 3, 1, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:19:46', b'0');
  1994 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2769, '聊天模型创建', 'ai:model:create', 3, 2, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:10', b'0');
  1995 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2770, '聊天模型更新', 'ai:model:update', 3, 3, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:14', b'0');
  1996 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2771, '聊天模型删除', 'ai:model:delete', 3, 4, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:27', b'0');
1986 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2773, '聊天角色', '', 2, 0, 2760, 'chat-role', 'fa:user-secret', 'ai/model/chatRole/index.vue', 'AiChatRole', 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '1', '2024-05-13 20:41:45', b'0'); 1997 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2773, '聊天角色', '', 2, 0, 2760, 'chat-role', 'fa:user-secret', 'ai/model/chatRole/index.vue', 'AiChatRole', 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '1', '2024-05-13 20:41:45', b'0');
1987 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2774, '聊天角色查询', 'ai:chat-role:query', 3, 1, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0'); 1998 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2774, '聊天角色查询', 'ai:chat-role:query', 3, 1, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0');
1988 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2775, '聊天角色创建', 'ai:chat-role:create', 3, 2, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0'); 1999 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2775, '聊天角色创建', 'ai:chat-role:create', 3, 2, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0');
@@ -2008,7 +2019,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i @@ -2008,7 +2019,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
2008 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2795, 'AI 写作删除', 'ai:write:delete', 3, 4, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0'); 2019 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2795, 'AI 写作删除', 'ai:write:delete', 3, 4, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0');
2009 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiMusic', 0, b'1', b'1', b'1', '1', '2024-07-17 09:21:12', '1', '2024-07-29 21:11:52', b'0'); 2020 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiMusic', 0, b'1', b'1', b'1', '1', '2024-07-17 09:21:12', '1', '2024-07-29 21:11:52', b'0');
2010 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2797, '客服中心', '', 2, 100, 2362, 'kefu', 'fa-solid:user-alt', 'mall/promotion/kefu/index', 'KeFu', 0, b'1', b'1', b'1', '1', '2024-07-17 23:49:05', '1', '2024-07-17 23:49:16', b'0'); 2021 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2797, '客服中心', '', 2, 100, 2362, 'kefu', 'fa-solid:user-alt', 'mall/promotion/kefu/index', 'KeFu', 0, b'1', b'1', b'1', '1', '2024-07-17 23:49:05', '1', '2024-07-17 23:49:16', b'0');
2011 -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2798, 'AI 思维导图', '', 2, 5, 2758, 'mind-map', 'fa:sitemap', 'ai/mindmap/index/index.vue', 'AiMindMap', 0, b'1', b'1', b'1', '1', '2024-07-29 21:31:59', '1', '2024-07-29 21:33:20', b'0'); 2022 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2798, 'AI 思维导图', '', 2, 6, 2758, 'mind-map', 'fa:sitemap', 'ai/mindmap/index/index.vue', 'AiMindMap', 0, b'1', b'1', b'1', '1', '2024-07-29 21:31:59', '1', '2025-03-02 18:57:31', b'0');
2012 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2799, '导图管理', '', 2, 14, 2760, 'mind-map', 'fa:map', 'ai/mindmap/manager/index', 'AiMindMapManager', 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '1', '2024-08-10 17:24:28', b'0'); 2023 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2799, '导图管理', '', 2, 14, 2760, 'mind-map', 'fa:map', 'ai/mindmap/manager/index', 'AiMindMapManager', 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '1', '2024-08-10 17:24:28', b'0');
2013 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2800, '思维导图查询', 'ai:mind-map:query', 3, 1, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0'); 2024 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2800, '思维导图查询', 'ai:mind-map:query', 3, 1, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0');
2014 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2801, '思维导图删除', 'ai:mind-map:delete', 3, 4, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0'); 2025 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2801, '思维导图删除', 'ai:mind-map:delete', 3, 4, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0');
@@ -2045,6 +2056,18 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i @@ -2045,6 +2056,18 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
2045 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2910, 'IoT 产品物模型删除', 'iot:think-model-function:delete', 3, 4, 2906, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0'); 2056 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2910, 'IoT 产品物模型删除', 'iot:think-model-function:delete', 3, 4, 2906, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0');
2046 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2911, 'IoT 产品物模型导出', 'iot:think-model-function:export', 3, 5, 2906, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0'); 2057 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2911, 'IoT 产品物模型导出', 'iot:think-model-function:export', 3, 5, 2906, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0');
2047 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2912, '创建推广员', 'trade:brokerage-user:create', 3, 7, 2346, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-12-01 14:32:39', '1', '2024-12-01 14:32:39', b'0'); 2058 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2912, '创建推广员', 'trade:brokerage-user:create', 3, 7, 2346, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-12-01 14:32:39', '1', '2024-12-01 14:32:39', b'0');
  2059 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2913, '流程清理', 'bpm:model:clean', 3, 7, 1193, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-01-17 19:32:06', '1', '2025-01-17 19:32:06', b'0');
  2060 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2914, '积分商城活动关闭', 'promotion:point-activity:close', 3, 6, 2808, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-01-23 20:23:34', '1', '2025-01-23 20:23:34', b'0');
  2061 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2915, 'AI 知识库', '', 2, 5, 2758, 'knowledge', 'ep:notebook', 'ai/knowledge/knowledge/index', 'AiKnowledge', 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '1', '2025-03-02 18:58:37', b'0');
  2062 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2916, 'AI 知识库查询', 'ai:knowledge:query', 3, 1, 2915, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', b'0');
  2063 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2917, 'AI 知识库创建', 'ai:knowledge:create', 3, 2, 2915, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', b'0');
  2064 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2918, 'AI 知识库更新', 'ai:knowledge:update', 3, 3, 2915, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', b'0');
  2065 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2919, 'AI 知识库删除', 'ai:knowledge:delete', 3, 4, 2915, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', b'0');
  2066 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2920, '工具管理', '', 2, 0, 2760, 'tool', 'fa-solid:tools', 'ai/model/tool/index.vue', 'AiTool', 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '1', '2025-03-14 19:20:18', b'0');
  2067 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2921, '工具查询', 'ai:tool:query', 3, 1, 2920, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', b'0');
  2068 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2922, '工具创建', 'ai:tool:create', 3, 2, 2920, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', b'0');
  2069 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2923, '工具更新', 'ai:tool:update', 3, 3, 2920, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', b'0');
  2070 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2924, '工具删除', 'ai:tool:delete', 3, 4, 2920, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', b'0');
2048 COMMIT; 2071 COMMIT;
2049 2072
2050 -- ---------------------------- 2073 -- ----------------------------
@@ -2166,7 +2189,7 @@ CREATE TABLE `system_oauth2_access_token` ( @@ -2166,7 +2189,7 @@ CREATE TABLE `system_oauth2_access_token` (
2166 PRIMARY KEY (`id`) USING BTREE, 2189 PRIMARY KEY (`id`) USING BTREE,
2167 INDEX `idx_access_token`(`access_token` ASC) USING BTREE, 2190 INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
2168 INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE 2191 INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
2169 -) ENGINE = InnoDB AUTO_INCREMENT = 12055 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; 2192 +) ENGINE = InnoDB AUTO_INCREMENT = 13666 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
2170 2193
2171 -- ---------------------------- 2194 -- ----------------------------
2172 -- Records of system_oauth2_access_token 2195 -- Records of system_oauth2_access_token
@@ -2288,7 +2311,7 @@ CREATE TABLE `system_oauth2_refresh_token` ( @@ -2288,7 +2311,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
2288 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 2311 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2289 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 2312 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2290 PRIMARY KEY (`id`) USING BTREE 2313 PRIMARY KEY (`id`) USING BTREE
2291 -) ENGINE = InnoDB AUTO_INCREMENT = 1711 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; 2314 +) ENGINE = InnoDB AUTO_INCREMENT = 1732 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
2292 2315
2293 -- ---------------------------- 2316 -- ----------------------------
2294 -- Records of system_oauth2_refresh_token 2317 -- Records of system_oauth2_refresh_token
@@ -2322,7 +2345,7 @@ CREATE TABLE `system_operate_log` ( @@ -2322,7 +2345,7 @@ CREATE TABLE `system_operate_log` (
2322 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 2345 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2323 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 2346 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2324 PRIMARY KEY (`id`) USING BTREE 2347 PRIMARY KEY (`id`) USING BTREE
2325 -) ENGINE = InnoDB AUTO_INCREMENT = 9064 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; 2348 +) ENGINE = InnoDB AUTO_INCREMENT = 9065 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
2326 2349
2327 -- ---------------------------- 2350 -- ----------------------------
2328 -- Records of system_operate_log 2351 -- Records of system_operate_log
@@ -2783,9 +2806,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -2783,9 +2806,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
2783 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2139, 2, 1011, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 2806 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2139, 2, 1011, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2784 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2140, 2, 1012, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 2807 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2140, 2, 1012, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2785 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2141, 2, 1013, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 2808 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2141, 2, 1013, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2786 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2142, 2, 1014, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);  
2787 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2143, 2, 1015, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 2809 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2143, 2, 1015, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2788 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2144, 2, 1016, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);  
2789 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2145, 2, 1017, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 2810 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2145, 2, 1017, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2790 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2146, 2, 1018, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 2811 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2146, 2, 1018, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2791 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2147, 2, 1019, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 2812 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2147, 2, 1019, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
@@ -3305,7 +3326,7 @@ CREATE TABLE `system_sms_code` ( @@ -3305,7 +3326,7 @@ CREATE TABLE `system_sms_code` (
3305 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 3326 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3306 PRIMARY KEY (`id`) USING BTREE, 3327 PRIMARY KEY (`id`) USING BTREE,
3307 INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号' 3328 INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
3308 -) ENGINE = InnoDB AUTO_INCREMENT = 645 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码'; 3329 +) ENGINE = InnoDB AUTO_INCREMENT = 646 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
3309 3330
3310 -- ---------------------------- 3331 -- ----------------------------
3311 -- Records of system_sms_code 3332 -- Records of system_sms_code
@@ -3346,7 +3367,7 @@ CREATE TABLE `system_sms_log` ( @@ -3346,7 +3367,7 @@ CREATE TABLE `system_sms_log` (
3346 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 3367 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
3347 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 3368 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3348 PRIMARY KEY (`id`) USING BTREE 3369 PRIMARY KEY (`id`) USING BTREE
3349 -) ENGINE = InnoDB AUTO_INCREMENT = 1241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; 3370 +) ENGINE = InnoDB AUTO_INCREMENT = 1255 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
3350 3371
3351 -- ---------------------------- 3372 -- ----------------------------
3352 -- Records of system_sms_log 3373 -- Records of system_sms_log
@@ -3588,7 +3609,7 @@ CREATE TABLE `system_user_role` ( @@ -3588,7 +3609,7 @@ CREATE TABLE `system_user_role` (
3588 `deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除', 3609 `deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除',
3589 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 3610 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3590 PRIMARY KEY (`id`) USING BTREE 3611 PRIMARY KEY (`id`) USING BTREE
3591 -) ENGINE = InnoDB AUTO_INCREMENT = 47 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表'; 3612 +) ENGINE = InnoDB AUTO_INCREMENT = 48 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
3592 3613
3593 -- ---------------------------- 3614 -- ----------------------------
3594 -- Records of system_user_role 3615 -- Records of system_user_role
@@ -3604,12 +3625,12 @@ INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_t @@ -3604,12 +3625,12 @@ INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_t
3604 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (15, 111, 110, '110', '2022-02-23 13:14:38', '110', '2022-02-23 13:14:38', b'0', 121); 3625 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (15, 111, 110, '110', '2022-02-23 13:14:38', '110', '2022-02-23 13:14:38', b'0', 121);
3605 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (16, 113, 111, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122); 3626 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (16, 113, 111, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
3606 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (18, 1, 2, '1', '2022-05-12 20:39:29', '1', '2022-05-12 20:39:29', b'0', 1); 3627 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (18, 1, 2, '1', '2022-05-12 20:39:29', '1', '2022-05-12 20:39:29', b'0', 1);
3607 -INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (20, 104, 101, '1', '2022-05-28 15:43:57', '1', '2022-05-28 15:43:57', b'0', 1);  
3608 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (22, 115, 2, '1', '2022-07-21 22:08:30', '1', '2022-07-21 22:08:30', b'0', 1); 3628 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (22, 115, 2, '1', '2022-07-21 22:08:30', '1', '2022-07-21 22:08:30', b'0', 1);
3609 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (35, 112, 1, '1', '2024-03-15 20:00:24', '1', '2024-03-15 20:00:24', b'0', 1); 3629 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (35, 112, 1, '1', '2024-03-15 20:00:24', '1', '2024-03-15 20:00:24', b'0', 1);
3610 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (36, 118, 1, '1', '2024-03-17 09:12:08', '1', '2024-03-17 09:12:08', b'0', 1); 3630 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (36, 118, 1, '1', '2024-03-17 09:12:08', '1', '2024-03-17 09:12:08', b'0', 1);
3611 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (38, 114, 101, '1', '2024-03-24 22:23:03', '1', '2024-03-24 22:23:03', b'0', 1); 3631 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (38, 114, 101, '1', '2024-03-24 22:23:03', '1', '2024-03-24 22:23:03', b'0', 1);
3612 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (46, 117, 1, '1', '2024-10-02 10:16:11', '1', '2024-10-02 10:16:11', b'0', 1); 3632 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (46, 117, 1, '1', '2024-10-02 10:16:11', '1', '2024-10-02 10:16:11', b'0', 1);
  3633 +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (47, 104, 2, '1', '2025-01-04 10:40:33', '1', '2025-01-04 10:40:33', b'0', 1);
3613 COMMIT; 3634 COMMIT;
3614 3635
3615 -- ---------------------------- 3636 -- ----------------------------
@@ -3644,10 +3665,10 @@ CREATE TABLE `system_users` ( @@ -3644,10 +3665,10 @@ CREATE TABLE `system_users` (
3644 -- Records of system_users 3665 -- Records of system_users
3645 -- ---------------------------- 3666 -- ----------------------------
3646 BEGIN; 3667 BEGIN;
3647 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, '0:0:0:0:0:0:0:1', '2024-12-28 20:29:58', 'admin', '2021-01-05 17:03:47', NULL, '2024-12-28 20:29:58', b'0', 1); 3668 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, '0:0:0:0:0:0:0:1', '2025-03-13 12:44:59', 'admin', '2021-01-05 17:03:47', NULL, '2025-03-13 12:44:59', b'0', 1);
3648 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$04$IgUse/ibRzAZ3rngCThmtemJeoh15Ux1TQ2hIMe4iwt/K3LcFHEda', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-11-02 14:00:46', '', '2021-01-07 09:07:17', NULL, '2024-11-02 14:00:46', b'0', 1); 3669 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$04$IgUse/ibRzAZ3rngCThmtemJeoh15Ux1TQ2hIMe4iwt/K3LcFHEda', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-11-02 14:00:46', '', '2021-01-07 09:07:17', NULL, '2024-11-02 14:00:46', b'0', 1);
3649 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2024-08-11 17:48:12', b'0', 1); 3670 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2024-08-11 17:48:12', b'0', 1);
3650 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$jDFLttgfik0QqJKAbfhMa.2A9xXoZmAIxakdFJUzkX.MgBKT6ddo6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-09-17 15:05:43', '', '2021-01-21 02:13:53', NULL, '2024-09-17 15:05:43', b'0', 1); 3671 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2025-01-04 10:40:49', '', '2021-01-21 02:13:53', NULL, '2025-01-04 10:40:49', b'0', 1);
3651 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2022-02-27 08:26:51', b'0', 118); 3672 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2022-02-27 08:26:51', b'0', 118);
3652 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2022-02-27 08:26:53', b'0', 119); 3673 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2022-02-27 08:26:53', b'0', 119);
3653 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2022-02-27 08:26:56', b'0', 120); 3674 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2022-02-27 08:26:56', b'0', 120);
yudao-dependencies/pom.xml
@@ -60,7 +60,7 @@ @@ -60,7 +60,7 @@
60 <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version> 60 <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
61 <commons-net.version>3.11.1</commons-net.version> 61 <commons-net.version>3.11.1</commons-net.version>
62 <jsch.version>0.1.55</jsch.version> 62 <jsch.version>0.1.55</jsch.version>
63 - <tika-core.version>2.9.2</tika-core.version> 63 + <tika-core.version>3.1.0</tika-core.version> <!-- TODO 芋艿:升级后,需要看看 jdk8 的兼容性;也要同步 cloud -->
64 <ip2region.version>2.7.0</ip2region.version> 64 <ip2region.version>2.7.0</ip2region.version>
65 <bizlog-sdk.version>3.0.6</bizlog-sdk.version> 65 <bizlog-sdk.version>3.0.6</bizlog-sdk.version>
66 <netty.version>4.1.116.Final</netty.version> 66 <netty.version>4.1.116.Final</netty.version>
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
@@ -7,6 +7,7 @@ import com.google.common.collect.HashMultimap; @@ -7,6 +7,7 @@ import com.google.common.collect.HashMultimap;
7 import com.google.common.collect.Multimap; 7 import com.google.common.collect.Multimap;
8 import jakarta.annotation.Resource; 8 import jakarta.annotation.Resource;
9 import jakarta.annotation.security.PermitAll; 9 import jakarta.annotation.security.PermitAll;
  10 +import jakarta.servlet.DispatcherType;
10 import org.springframework.boot.autoconfigure.AutoConfiguration; 11 import org.springframework.boot.autoconfigure.AutoConfiguration;
11 import org.springframework.boot.autoconfigure.AutoConfigureOrder; 12 import org.springframework.boot.autoconfigure.AutoConfigureOrder;
12 import org.springframework.context.ApplicationContext; 13 import org.springframework.context.ApplicationContext;
@@ -142,7 +143,9 @@ public class YudaoWebSecurityConfigurerAdapter { @@ -142,7 +143,9 @@ public class YudaoWebSecurityConfigurerAdapter {
142 // ②:每个项目的自定义规则 143 // ②:每个项目的自定义规则
143 .authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c))) 144 .authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))
144 // ③:兜底规则,必须认证 145 // ③:兜底规则,必须认证
145 - .authorizeHttpRequests(c -> c.anyRequest().authenticated()); 146 + .authorizeHttpRequests(c -> c
  147 + .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // WebFlux 异步请求,无需认证,目的:SSE 场景
  148 + .anyRequest().authenticated());
146 149
147 // 添加 Token Filter 150 // 添加 Token Filter
148 httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); 151 httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
@@ -35,11 +35,7 @@ public enum AiChatRoleEnum { @@ -35,11 +35,7 @@ public enum AiChatRoleEnum {
35 ### 微信 35 ### 微信
36 除此之外不要任何解释性语句。 36 除此之外不要任何解释性语句。
37 """), 37 """),
38 -  
39 - AI_KNOWLEDGE_ROLE("知识库助手", """  
40 - 给你提供一些数据参考:{info},请回答我的问题。  
41 - 请你跟进数据参考与工具返回结果回复用户的请求。  
42 - """); 38 + ;
43 39
44 /** 40 /**
45 * 角色名 41 * 角色名
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
@@ -12,49 +12,53 @@ public interface ErrorCodeConstants { @@ -12,49 +12,53 @@ public interface ErrorCodeConstants {
12 // ========== API 密钥 1-040-000-000 ========== 12 // ========== API 密钥 1-040-000-000 ==========
13 ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在"); 13 ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在");
14 ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!"); 14 ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!");
15 - ErrorCode API_KEY_MIDJOURNEY_NOT_FOUND = new ErrorCode(1_040_000_900, "Midjourney 模型不存在");  
16 - ErrorCode API_KEY_SUNO_NOT_FOUND = new ErrorCode(1_040_000_901, "Suno 模型不存在");  
17 - ErrorCode API_KEY_IMAGE_NODE_FOUND = new ErrorCode(1_040_000_902, "平台({}) 图片模型未配置");  
18 15
19 - // ========== API 聊天模型 1-040-001-000 ==========  
20 - ErrorCode CHAT_MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!");  
21 - ErrorCode CHAT_MODEL_DISABLE = new ErrorCode(1_040_001_001, "模型({})已禁用!");  
22 - ErrorCode CHAT_MODEL_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "操作失败,找不到默认聊天模型"); 16 + // ========== API 模型 1-040-001-000 ==========
  17 + ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!");
  18 + ErrorCode MODEL_DISABLE = new ErrorCode(1_040_001_001, "模型({})已禁用!");
  19 + ErrorCode MODEL_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "操作失败,找不到默认模型");
  20 + ErrorCode MODEL_USE_TYPE_ERROR = new ErrorCode(1_040_001_003, "操作失败,该模型的模型类型不正确");
23 21
24 - // ========== API 聊天模型 1-040-002-000 ========== 22 + // ========== API 聊天角色 1-040-002-000 ==========
25 ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在"); 23 ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在");
26 ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "聊天角色({})已禁用!"); 24 ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "聊天角色({})已禁用!");
27 25
28 // ========== API 聊天会话 1-040-003-000 ========== 26 // ========== API 聊天会话 1-040-003-000 ==========
29 -  
30 ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对话不存在!"); 27 ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对话不存在!");
31 ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "操作失败,该聊天模型的配置不完整"); 28 ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "操作失败,该聊天模型的配置不完整");
32 29
33 // ========== API 聊天消息 1-040-004-000 ========== 30 // ========== API 聊天消息 1-040-004-000 ==========
34 -  
35 ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); 31 ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
36 ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "对话生成异常!"); 32 ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "对话生成异常!");
37 33
38 // ========== API 绘画 1-040-005-000 ========== 34 // ========== API 绘画 1-040-005-000 ==========
39 -  
40 - ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!");  
41 - ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}");  
42 - ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}");  
43 - ErrorCode IMAGE_FAIL = new ErrorCode(1_022_005_002, "图片绘画失败! {}"); 35 + ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_040_005_000, "图片不存在!");
  36 + ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_040_005_001, "Midjourney 提交失败!原因:{}");
  37 + ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_040_005_002, "Midjourney 按钮 customId 不存在! {}");
44 38
45 // ========== API 音乐 1-040-006-000 ========== 39 // ========== API 音乐 1-040-006-000 ==========
46 - ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!"); 40 + ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_040_006_000, "音乐不存在!");
47 41
48 - // ========== API 写作 1-022-007-000 ==========  
49 - ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!");  
50 - ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_022_07_001, "写作生成异常!"); 42 + // ========== API 写作 1-040-007-000 ==========
  43 + ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_040_007_000, "作文不存在!");
  44 + ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_040_07_001, "写作生成异常!");
51 45
52 // ========== API 思维导图 1-040-008-000 ========== 46 // ========== API 思维导图 1-040-008-000 ==========
53 ErrorCode MIND_MAP_NOT_EXISTS = new ErrorCode(1_040_008_000, "思维导图不存在!"); 47 ErrorCode MIND_MAP_NOT_EXISTS = new ErrorCode(1_040_008_000, "思维导图不存在!");
54 48
55 - // ========== API 知识库 1-022-008-000 ==========  
56 - ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_022_008_000, "知识库不存在!");  
57 - ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_022_008_001, "文档不存在!");  
58 - ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_022_008_002, "段落不存在!"); 49 + // ========== API 知识库 1-040-009-000 ==========
  50 + ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_040_009_000, "知识库不存在!");
  51 +
  52 + ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_040_009_101, "文档不存在!");
  53 + ErrorCode KNOWLEDGE_DOCUMENT_FILE_EMPTY = new ErrorCode(1_040_009_102, "文档内容为空!");
  54 + ErrorCode KNOWLEDGE_DOCUMENT_FILE_DOWNLOAD_FAIL = new ErrorCode(1_040_009_102, "文件下载失败!");
  55 + ErrorCode KNOWLEDGE_DOCUMENT_FILE_READ_FAIL = new ErrorCode(1_040_009_102, "文档加载失败!");
  56 +
  57 + ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_040_009_202, "段落不存在!");
  58 + ErrorCode KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG = new ErrorCode(1_040_009_203, "内容 Token 数为 {},超过最大限制 {}");
  59 +
  60 + // ========== AI 工具 1-040-010-000 ==========
  61 + ErrorCode TOOL_NOT_EXISTS = new ErrorCode(1_040_010_000, "工具不存在");
  62 + ErrorCode TOOL_NAME_NOT_EXISTS = new ErrorCode(1_040_010_001, "工具({})找不到 Bean");
59 63
60 } 64 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java
@@ -12,15 +12,18 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessage @@ -12,15 +12,18 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessage
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
13 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 13 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
14 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 14 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
  15 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  16 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
15 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 17 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
16 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; 18 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
17 import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService; 19 import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
  20 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
  21 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
18 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; 22 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
19 import io.swagger.v3.oas.annotations.Operation; 23 import io.swagger.v3.oas.annotations.Operation;
20 import io.swagger.v3.oas.annotations.Parameter; 24 import io.swagger.v3.oas.annotations.Parameter;
21 import io.swagger.v3.oas.annotations.tags.Tag; 25 import io.swagger.v3.oas.annotations.tags.Tag;
22 import jakarta.annotation.Resource; 26 import jakarta.annotation.Resource;
23 -import jakarta.annotation.security.PermitAll;  
24 import jakarta.validation.Valid; 27 import jakarta.validation.Valid;
25 import lombok.extern.slf4j.Slf4j; 28 import lombok.extern.slf4j.Slf4j;
26 import org.springframework.http.MediaType; 29 import org.springframework.http.MediaType;
@@ -33,7 +36,7 @@ import java.util.List; @@ -33,7 +36,7 @@ import java.util.List;
33 import java.util.Map; 36 import java.util.Map;
34 37
35 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 38 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
36 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
37 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 40 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
38 41
39 @Tag(name = "管理后台 - 聊天消息") 42 @Tag(name = "管理后台 - 聊天消息")
@@ -48,6 +51,10 @@ public class AiChatMessageController { @@ -48,6 +51,10 @@ public class AiChatMessageController {
48 private AiChatConversationService chatConversationService; 51 private AiChatConversationService chatConversationService;
49 @Resource 52 @Resource
50 private AiChatRoleService chatRoleService; 53 private AiChatRoleService chatRoleService;
  54 + @Resource
  55 + private AiKnowledgeSegmentService knowledgeSegmentService;
  56 + @Resource
  57 + private AiKnowledgeDocumentService knowledgeDocumentService;
51 58
52 @Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢") 59 @Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
53 @PostMapping("/send") 60 @PostMapping("/send")
@@ -57,7 +64,6 @@ public class AiChatMessageController { @@ -57,7 +64,6 @@ public class AiChatMessageController {
57 64
58 @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快") 65 @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
59 @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 66 @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
60 - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题  
61 public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) { 67 public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
62 return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId()); 68 return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
63 } 69 }
@@ -71,8 +77,38 @@ public class AiChatMessageController { @@ -71,8 +77,38 @@ public class AiChatMessageController {
71 if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) { 77 if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
72 return success(Collections.emptyList()); 78 return success(Collections.emptyList());
73 } 79 }
  80 + // 1. 获取消息列表
74 List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId); 81 List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
75 - return success(BeanUtils.toBean(messageList, AiChatMessageRespVO.class)); 82 + if (CollUtil.isEmpty(messageList)) {
  83 + return success(Collections.emptyList());
  84 + }
  85 +
  86 + // 2. 拼接数据,主要是知识库段落信息
  87 + Map<Long, AiKnowledgeSegmentDO> segmentMap = knowledgeSegmentService.getKnowledgeSegmentMap(convertListByFlatMap(messageList,
  88 + message -> CollUtil.isEmpty(message.getSegmentIds()) ? null : message.getSegmentIds().stream()));
  89 + Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
  90 + convertList(segmentMap.values(), AiKnowledgeSegmentDO::getDocumentId));
  91 + List<AiChatMessageRespVO> messageVOList = BeanUtils.toBean(messageList, AiChatMessageRespVO.class);
  92 + for (int i = 0; i < messageList.size(); i++) {
  93 + AiChatMessageDO message = messageList.get(i);
  94 + if (CollUtil.isEmpty(message.getSegmentIds())) {
  95 + continue;
  96 + }
  97 + // 设置知识库段落信息
  98 + messageVOList.get(i).setSegments(convertList(message.getSegmentIds(), segmentId -> {
  99 + AiKnowledgeSegmentDO segment = segmentMap.get(segmentId);
  100 + if (segment == null) {
  101 + return null;
  102 + }
  103 + AiKnowledgeDocumentDO document = documentMap.get(segment.getDocumentId());
  104 + if (document == null) {
  105 + return null;
  106 + }
  107 + return new AiChatMessageRespVO.KnowledgeSegment().setId(segment.getId()).setContent(segment.getContent())
  108 + .setDocumentId(segment.getDocumentId()).setDocumentName(document.getName());
  109 + }));
  110 + }
  111 + return success(messageVOList);
76 } 112 }
77 113
78 @Operation(summary = "删除消息") 114 @Operation(summary = "删除消息")
@@ -105,7 +141,8 @@ public class AiChatMessageController { @@ -105,7 +141,8 @@ public class AiChatMessageController {
105 Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap( 141 Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap(
106 convertSet(pageResult.getList(), AiChatMessageDO::getRoleId)); 142 convertSet(pageResult.getList(), AiChatMessageDO::getRoleId));
107 return success(BeanUtils.toBean(pageResult, AiChatMessageRespVO.class, 143 return success(BeanUtils.toBean(pageResult, AiChatMessageRespVO.class,
108 - respVO -> MapUtils.findAndThen(roleMap, respVO.getRoleId(), role -> respVO.setRoleName(role.getName())))); 144 + respVO -> MapUtils.findAndThen(roleMap, respVO.getRoleId(),
  145 + role -> respVO.setRoleName(role.getName()))));
109 } 146 }
110 147
111 @Operation(summary = "管理员删除消息") 148 @Operation(summary = "管理员删除消息")
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation; 1 package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
2 2
3 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 3 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
5 import com.fhs.core.trans.anno.Trans; 5 import com.fhs.core.trans.anno.Trans;
6 import com.fhs.core.trans.constant.TransType; 6 import com.fhs.core.trans.constant.TransType;
@@ -31,7 +31,7 @@ public class AiChatConversationRespVO implements VO { @@ -31,7 +31,7 @@ public class AiChatConversationRespVO implements VO {
31 private Long roleId; 31 private Long roleId;
32 32
33 @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 33 @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
34 - @Trans(type = TransType.SIMPLE, target = AiChatModelDO.class, fields = "name", ref = "modelName") 34 + @Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = "name", ref = "modelName")
35 private Long modelId; 35 private Long modelId;
36 36
37 @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922") 37 @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922")
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java
@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
  7 +import java.util.List;
7 8
8 @Schema(description = "管理后台 - AI 聊天消息 Response VO") 9 @Schema(description = "管理后台 - AI 聊天消息 Response VO")
9 @Data 10 @Data
@@ -39,6 +40,12 @@ public class AiChatMessageRespVO { @@ -39,6 +40,12 @@ public class AiChatMessageRespVO {
39 @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") 40 @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
40 private Boolean useContext; 41 private Boolean useContext;
41 42
  43 + @Schema(description = "知识库段落编号数组", example = "[1,2,3]")
  44 + private List<Long> segmentIds;
  45 +
  46 + @Schema(description = "知识库段落数组")
  47 + private List<KnowledgeSegment> segments;
  48 +
42 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51") 49 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51")
43 private LocalDateTime createTime; 50 private LocalDateTime createTime;
44 51
@@ -47,4 +54,22 @@ public class AiChatMessageRespVO { @@ -47,4 +54,22 @@ public class AiChatMessageRespVO {
47 @Schema(description = "角色名字", example = "小黄") 54 @Schema(description = "角色名字", example = "小黄")
48 private String roleName; 55 private String roleName;
49 56
  57 + @Schema(description = "知识库段落", example = "Java 开发手册")
  58 + @Data
  59 + public static class KnowledgeSegment {
  60 +
  61 + @Schema(description = "段落编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  62 + private Long id;
  63 +
  64 + @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
  65 + private String content;
  66 +
  67 + @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  68 + private Long documentId;
  69 +
  70 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
  71 + private String documentName;
  72 +
  73 + }
  74 +
50 } 75 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java
@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
  7 +import java.util.List;
7 8
8 @Schema(description = "管理后台 - AI 聊天消息发送 Response VO") 9 @Schema(description = "管理后台 - AI 聊天消息发送 Response VO")
9 @Data 10 @Data
@@ -28,6 +29,12 @@ public class AiChatMessageSendRespVO { @@ -28,6 +29,12 @@ public class AiChatMessageSendRespVO {
28 @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") 29 @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
29 private String content; 30 private String content;
30 31
  32 + @Schema(description = "知识库段落编号数组", example = "[1,2,3]")
  33 + private List<Long> segmentIds;
  34 +
  35 + @Schema(description = "知识库段落数组")
  36 + private List<AiChatMessageRespVO.KnowledgeSegment> segments;
  37 +
31 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 38 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
32 private LocalDateTime createTime; 39 private LocalDateTime createTime;
33 40
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageDrawReqVO.java
@@ -14,18 +14,15 @@ import java.util.Map; @@ -14,18 +14,15 @@ import java.util.Map;
14 @Data 14 @Data
15 public class AiImageDrawReqVO { 15 public class AiImageDrawReqVO {
16 16
17 - @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")  
18 - private String platform; // 参见 AiPlatformEnum 枚举 17 + @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  18 + @NotNull(message = "模型编号不能为空")
  19 + private Long modelId;
19 20
20 @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城") 21 @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城")
21 @NotEmpty(message = "提示词不能为空") 22 @NotEmpty(message = "提示词不能为空")
22 @Size(max = 1200, message = "提示词最大 1200") 23 @Size(max = 1200, message = "提示词最大 1200")
23 private String prompt; 24 private String prompt;
24 25
25 - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6")  
26 - @NotEmpty(message = "模型不能为空")  
27 - private String model;  
28 -  
29 /** 26 /**
30 * 1. dall-e-2 模型:256x256、512x512、1024x1024 27 * 1. dall-e-2 模型:256x256、512x512、1024x1024
31 * 2. dall-e-3 模型:1024x1024, 1792x1024, 或 1024x1792 28 * 2. dall-e-3 模型:1024x1024, 1792x1024, 或 1024x1792
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyImagineReqVO.java
@@ -13,9 +13,9 @@ public class AiMidjourneyImagineReqVO { @@ -13,9 +13,9 @@ public class AiMidjourneyImagineReqVO {
13 @NotEmpty(message = "提示词不能为空!") 13 @NotEmpty(message = "提示词不能为空!")
14 private String prompt; 14 private String prompt;
15 15
16 - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "midjourney")  
17 - @NotEmpty(message = "模型不能为空")  
18 - private String model; // 参考 MidjourneyApi.ModelEnum 16 + @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + @NotNull(message = "模型编号不能为空")
  18 + private Long modelId;
19 19
20 @Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 20 @Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
21 @NotNull(message = "图片宽度不能为空") 21 @NotNull(message = "图片宽度不能为空")
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.http 0 → 100644
  1 +### 创建知识库
  2 +POST {{baseUrl}}/ai/knowledge/create
  3 +Content-Type: application/json
  4 +Authorization: {{token}}
  5 +tenant-id: {{adminTenantId}}
  6 +
  7 +{
  8 + "name": "测试标题",
  9 + "description": "测试描述",
  10 + "embeddingModelId": 30,
  11 + "topK": 3,
  12 + "similarityThreshold": 0.5,
  13 + "status": 0
  14 +}
  15 +
  16 +### 更新知识库
  17 +PUT {{baseUrl}}/ai/knowledge/update
  18 +Content-Type: application/json
  19 +Authorization: {{token}}
  20 +tenant-id: {{adminTenantId}}
  21 +
  22 +{
  23 + "id": 1,
  24 + "name": "测试标题(更新)",
  25 + "description": "测试描述",
  26 + "embeddingModelId": 30,
  27 + "topK": 5,
  28 + "similarityThreshold": 0.6,
  29 + "status": 0
  30 +}
  31 +
  32 +### 获取知识库分页
  33 +GET {{baseUrl}}/ai/knowledge/page?pageNo=1&pageSize=10
  34 +Authorization: {{token}}
  35 +tenant-id: {{adminTenantId}}
0 \ No newline at end of file 36 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;  
7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO;
9 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; 9 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; 10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
11 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; 11 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
12 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
13 import io.swagger.v3.oas.annotations.tags.Tag; 14 import io.swagger.v3.oas.annotations.tags.Tag;
14 import jakarta.annotation.Resource; 15 import jakarta.annotation.Resource;
15 import jakarta.validation.Valid; 16 import jakarta.validation.Valid;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
16 import org.springframework.validation.annotation.Validated; 18 import org.springframework.validation.annotation.Validated;
17 import org.springframework.web.bind.annotation.*; 19 import org.springframework.web.bind.annotation.*;
18 20
  21 +import java.util.List;
  22 +
19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 23 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
21 25
22 @Tag(name = "管理后台 - AI 知识库") 26 @Tag(name = "管理后台 - AI 知识库")
23 @RestController 27 @RestController
@@ -30,21 +34,42 @@ public class AiKnowledgeController { @@ -30,21 +34,42 @@ public class AiKnowledgeController {
30 34
31 @GetMapping("/page") 35 @GetMapping("/page")
32 @Operation(summary = "获取知识库分页") 36 @Operation(summary = "获取知识库分页")
  37 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
33 public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePage(@Valid AiKnowledgePageReqVO pageReqVO) { 38 public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePage(@Valid AiKnowledgePageReqVO pageReqVO) {
34 - PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(getLoginUserId(), pageReqVO); 39 + PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(pageReqVO);
35 return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class)); 40 return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class));
36 } 41 }
37 42
  43 + @GetMapping("/get")
  44 + @Operation(summary = "获得知识库")
  45 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  46 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  47 + public CommonResult<AiKnowledgeRespVO> getKnowledge(@RequestParam("id") Long id) {
  48 + AiKnowledgeDO knowledge = knowledgeService.getKnowledge(id);
  49 + return success(BeanUtils.toBean(knowledge, AiKnowledgeRespVO.class));
  50 + }
  51 +
38 @PostMapping("/create") 52 @PostMapping("/create")
39 @Operation(summary = "创建知识库") 53 @Operation(summary = "创建知识库")
40 - public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeCreateReqVO createReqVO) {  
41 - return success(knowledgeService.createKnowledge(createReqVO, getLoginUserId())); 54 + @PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
  55 + public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO createReqVO) {
  56 + return success(knowledgeService.createKnowledge(createReqVO));
42 } 57 }
43 58
44 @PutMapping("/update") 59 @PutMapping("/update")
45 @Operation(summary = "更新知识库") 60 @Operation(summary = "更新知识库")
46 - public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeUpdateReqVO updateReqVO) {  
47 - knowledgeService.updateKnowledge(updateReqVO, getLoginUserId()); 61 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
  62 + public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO updateReqVO) {
  63 + knowledgeService.updateKnowledge(updateReqVO);
48 return success(true); 64 return success(true);
49 } 65 }
  66 +
  67 + @GetMapping("/simple-list")
  68 + @Operation(summary = "获得知识库的精简列表")
  69 + public CommonResult<List<AiKnowledgeRespVO>> getKnowledgeSimpleList() {
  70 + List<AiKnowledgeDO> list = knowledgeService.getKnowledgeSimpleListByStatus(CommonStatusEnum.ENABLE.getStatus());
  71 + return success(convertList(list, knowledge -> new AiKnowledgeRespVO()
  72 + .setId(knowledge.getId()).setName(knowledge.getName())));
  73 + }
  74 +
50 } 75 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.http 0 → 100644
  1 +### 创建知识文档
  2 +POST {{baseUrl}}/ai/knowledge/document/create
  3 +Content-Type: application/json
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenantId}}
  6 +
  7 +{
  8 + "knowledgeId": 2,
  9 + "name": "测试文档",
  10 + "url": "https://static.iocoder.cn/README.md",
  11 + "segmentMaxTokens": 800
  12 +}
  13 +
  14 +### 批量创建知识文档
  15 +POST {{baseUrl}}/ai/knowledge/document/create-list
  16 +Content-Type: application/json
  17 +Authorization: Bearer {{token}}
  18 +tenant-id: {{adminTenantId}}
  19 +
  20 +{
  21 + "knowledgeId": 1,
  22 + "list": [
  23 + {
  24 + "name": "测试文档1",
  25 + "url": "https://static.iocoder.cn/README.md",
  26 + "segmentMaxTokens": 800
  27 + },
  28 + {
  29 + "name": "测试文档2",
  30 + "url": "https://static.iocoder.cn/README_yudao.md",
  31 + "segmentMaxTokens": 400
  32 + }
  33 + ]
  34 +}
  35 +
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java
@@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge; @@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;  
7 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentRespVO;  
8 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO; 6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.*;
9 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; 8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
11 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; 9 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
@@ -13,9 +11,12 @@ import io.swagger.v3.oas.annotations.Operation; @@ -13,9 +11,12 @@ import io.swagger.v3.oas.annotations.Operation;
13 import io.swagger.v3.oas.annotations.tags.Tag; 11 import io.swagger.v3.oas.annotations.tags.Tag;
14 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
15 import jakarta.validation.Valid; 13 import jakarta.validation.Valid;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
16 import org.springframework.validation.annotation.Validated; 15 import org.springframework.validation.annotation.Validated;
17 import org.springframework.web.bind.annotation.*; 16 import org.springframework.web.bind.annotation.*;
18 17
  18 +import java.util.List;
  19 +
19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 20 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 21
21 @Tag(name = "管理后台 - AI 知识库文档") 22 @Tag(name = "管理后台 - AI 知识库文档")
@@ -27,25 +28,63 @@ public class AiKnowledgeDocumentController { @@ -27,25 +28,63 @@ public class AiKnowledgeDocumentController {
27 @Resource 28 @Resource
28 private AiKnowledgeDocumentService documentService; 29 private AiKnowledgeDocumentService documentService;
29 30
30 - @PostMapping("/create")  
31 - @Operation(summary = "新建文档")  
32 - public CommonResult<Long> createKnowledgeDocument(@Valid AiKnowledgeDocumentCreateReqVO reqVO) {  
33 - Long knowledgeDocumentId = documentService.createKnowledgeDocument(reqVO);  
34 - return success(knowledgeDocumentId);  
35 - }  
36 -  
37 @GetMapping("/page") 31 @GetMapping("/page")
38 @Operation(summary = "获取文档分页") 32 @Operation(summary = "获取文档分页")
39 - public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) { 33 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  34 + public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(
  35 + @Valid AiKnowledgeDocumentPageReqVO pageReqVO) {
40 PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO); 36 PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO);
41 return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class)); 37 return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class));
42 } 38 }
43 39
  40 + @GetMapping("/get")
  41 + @Operation(summary = "获取文档详情")
  42 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  43 + public CommonResult<AiKnowledgeDocumentRespVO> getKnowledgeDocument(@RequestParam("id") Long id) {
  44 + AiKnowledgeDocumentDO document = documentService.getKnowledgeDocument(id);
  45 + return success(BeanUtils.toBean(document, AiKnowledgeDocumentRespVO.class));
  46 + }
  47 +
  48 + @PostMapping("/create")
  49 + @Operation(summary = "新建文档(单个)")
  50 + @PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
  51 + public CommonResult<Long> createKnowledgeDocument(@RequestBody @Valid AiKnowledgeDocumentCreateReqVO reqVO) {
  52 + Long id = documentService.createKnowledgeDocument(reqVO);
  53 + return success(id);
  54 + }
  55 +
  56 + @PostMapping("/create-list")
  57 + @Operation(summary = "新建文档(多个)")
  58 + @PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
  59 + public CommonResult<List<Long>> createKnowledgeDocumentList(
  60 + @RequestBody @Valid AiKnowledgeDocumentCreateListReqVO reqVO) {
  61 + List<Long> ids = documentService.createKnowledgeDocumentList(reqVO);
  62 + return success(ids);
  63 + }
  64 +
44 @PutMapping("/update") 65 @PutMapping("/update")
45 @Operation(summary = "更新文档") 66 @Operation(summary = "更新文档")
  67 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
46 public CommonResult<Boolean> updateKnowledgeDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) { 68 public CommonResult<Boolean> updateKnowledgeDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) {
47 documentService.updateKnowledgeDocument(reqVO); 69 documentService.updateKnowledgeDocument(reqVO);
48 return success(true); 70 return success(true);
49 } 71 }
50 72
  73 + @PutMapping("/update-status")
  74 + @Operation(summary = "更新文档状态")
  75 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
  76 + public CommonResult<Boolean> updateKnowledgeDocumentStatus(
  77 + @Valid @RequestBody AiKnowledgeDocumentUpdateStatusReqVO reqVO) {
  78 + documentService.updateKnowledgeDocumentStatus(reqVO);
  79 + return success(true);
  80 + }
  81 +
  82 + @DeleteMapping("/delete")
  83 + @Operation(summary = "删除文档")
  84 + @PreAuthorize("@ss.hasPermission('ai:knowledge:delete')")
  85 + public CommonResult<Boolean> deleteKnowledgeDocument(@RequestParam("id") Long id) {
  86 + documentService.deleteKnowledgeDocument(id);
  87 + return success(true);
  88 + }
  89 +
51 } 90 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.http 0 → 100644
  1 +### 切片内容
  2 +GET {{baseUrl}}/ai/knowledge/segment/split?url=https://static.iocoder.cn/README_yudao.md&segmentMaxTokens=800
  3 +Content-Type: application/json
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenantId}}
  6 +
  7 +### 搜索段落内容
  8 +GET {{baseUrl}}/ai/knowledge/segment/search?knowledgeId=2&content=如何使用这个产品&topK=5&similarityThreshold=0.1
  9 +Content-Type: application/json
  10 +Authorization: Bearer {{token}}
  11 +tenant-id: {{adminTenantId}}
  12 +
  13 +### 获取文档处理列表
  14 +GET {{baseUrl}}/ai/knowledge/segment/get-process-list?documentIds=1,2,3
  15 +Content-Type: application/json
  16 +Authorization: Bearer {{token}}
  17 +tenant-id: {{adminTenantId}}
0 \ No newline at end of file 18 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;  
7 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentRespVO;  
8 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;  
9 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; 8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.*;
  9 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  11 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
11 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; 12 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
  13 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  14 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
12 import io.swagger.v3.oas.annotations.Operation; 15 import io.swagger.v3.oas.annotations.Operation;
  16 +import io.swagger.v3.oas.annotations.Parameter;
  17 +import io.swagger.v3.oas.annotations.Parameters;
13 import io.swagger.v3.oas.annotations.tags.Tag; 18 import io.swagger.v3.oas.annotations.tags.Tag;
14 import jakarta.annotation.Resource; 19 import jakarta.annotation.Resource;
15 import jakarta.validation.Valid; 20 import jakarta.validation.Valid;
  21 +import org.hibernate.validator.constraints.URL;
  22 +import org.springframework.security.access.prepost.PreAuthorize;
16 import org.springframework.validation.annotation.Validated; 23 import org.springframework.validation.annotation.Validated;
17 import org.springframework.web.bind.annotation.*; 24 import org.springframework.web.bind.annotation.*;
18 25
  26 +import java.util.Collections;
  27 +import java.util.List;
  28 +import java.util.Map;
  29 +
19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
20 32
21 @Tag(name = "管理后台 - AI 知识库段落") 33 @Tag(name = "管理后台 - AI 知识库段落")
22 @RestController 34 @RestController
@@ -26,26 +38,93 @@ public class AiKnowledgeSegmentController { @@ -26,26 +38,93 @@ public class AiKnowledgeSegmentController {
26 38
27 @Resource 39 @Resource
28 private AiKnowledgeSegmentService segmentService; 40 private AiKnowledgeSegmentService segmentService;
  41 + @Resource
  42 + private AiKnowledgeDocumentService documentService;
  43 +
  44 + @GetMapping("/get")
  45 + @Operation(summary = "获取段落详情")
  46 + @Parameter(name = "id", description = "段落编号", required = true, example = "1024")
  47 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  48 + public CommonResult<AiKnowledgeSegmentRespVO> getKnowledgeSegment(@RequestParam("id") Long id) {
  49 + AiKnowledgeSegmentDO segment = segmentService.getKnowledgeSegment(id);
  50 + return success(BeanUtils.toBean(segment, AiKnowledgeSegmentRespVO.class));
  51 + }
29 52
30 @GetMapping("/page") 53 @GetMapping("/page")
31 @Operation(summary = "获取段落分页") 54 @Operation(summary = "获取段落分页")
32 - public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { 55 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  56 + public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(
  57 + @Valid AiKnowledgeSegmentPageReqVO pageReqVO) {
33 PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); 58 PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO);
34 return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); 59 return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class));
35 } 60 }
36 61
  62 + @PostMapping("/create")
  63 + @Operation(summary = "创建段落")
  64 + @PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
  65 + public CommonResult<Long> createKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO createReqVO) {
  66 + return success(segmentService.createKnowledgeSegment(createReqVO));
  67 + }
  68 +
37 @PutMapping("/update") 69 @PutMapping("/update")
38 @Operation(summary = "更新段落内容") 70 @Operation(summary = "更新段落内容")
39 - public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentUpdateReqVO reqVO) { 71 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
  72 + public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO reqVO) {
40 segmentService.updateKnowledgeSegment(reqVO); 73 segmentService.updateKnowledgeSegment(reqVO);
41 return success(true); 74 return success(true);
42 } 75 }
43 76
44 @PutMapping("/update-status") 77 @PutMapping("/update-status")
45 @Operation(summary = "启禁用段落内容") 78 @Operation(summary = "启禁用段落内容")
46 - public CommonResult<Boolean> updateKnowledgeSegmentStatus(@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) { 79 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
  80 + public CommonResult<Boolean> updateKnowledgeSegmentStatus(
  81 + @Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
47 segmentService.updateKnowledgeSegmentStatus(reqVO); 82 segmentService.updateKnowledgeSegmentStatus(reqVO);
48 return success(true); 83 return success(true);
49 } 84 }
50 85
  86 + @GetMapping("/split")
  87 + @Operation(summary = "切片内容")
  88 + @Parameters({
  89 + @Parameter(name = "url", description = "文档 URL", required = true),
  90 + @Parameter(name = "segmentMaxTokens", description = "分段的最大 Token 数", required = true)
  91 + })
  92 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  93 + public CommonResult<List<AiKnowledgeSegmentRespVO>> splitContent(
  94 + @RequestParam("url") @URL String url,
  95 + @RequestParam(value = "segmentMaxTokens") Integer segmentMaxTokens) {
  96 + List<AiKnowledgeSegmentDO> segments = segmentService.splitContent(url, segmentMaxTokens);
  97 + return success(BeanUtils.toBean(segments, AiKnowledgeSegmentRespVO.class));
  98 + }
  99 +
  100 + @GetMapping("/get-process-list")
  101 + @Operation(summary = "获取文档处理列表")
  102 + @Parameter(name = "documentIds", description = "文档编号列表", required = true, example = "1,2,3")
  103 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  104 + public CommonResult<List<AiKnowledgeSegmentProcessRespVO>> getKnowledgeSegmentProcessList(
  105 + @RequestParam("documentIds") List<Long> documentIds) {
  106 + List<AiKnowledgeSegmentProcessRespVO> list = segmentService.getKnowledgeSegmentProcessList(documentIds);
  107 + return success(list);
  108 + }
  109 +
  110 + @GetMapping("/search")
  111 + @Operation(summary = "搜索段落内容")
  112 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  113 + public CommonResult<List<AiKnowledgeSegmentSearchRespVO>> searchKnowledgeSegment(
  114 + @Valid AiKnowledgeSegmentSearchReqVO reqVO) {
  115 + // 1. 搜索段落
  116 + List<AiKnowledgeSegmentSearchRespBO> segments = segmentService
  117 + .searchKnowledgeSegment(BeanUtils.toBean(reqVO, AiKnowledgeSegmentSearchReqBO.class));
  118 + if (CollUtil.isEmpty(segments)) {
  119 + return success(Collections.emptyList());
  120 + }
  121 +
  122 + // 2. 拼接 VO
  123 + Map<Long, AiKnowledgeDocumentDO> documentMap = documentService.getKnowledgeDocumentMap(convertSet(
  124 + segments, AiKnowledgeSegmentSearchRespBO::getDocumentId));
  125 + return success(BeanUtils.toBean(segments, AiKnowledgeSegmentSearchRespVO.class,
  126 + segment -> MapUtils.findAndThen(documentMap, segment.getDocumentId(),
  127 + document -> segment.setDocumentName(document.getName()))));
  128 + }
  129 +
51 } 130 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotEmpty;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +import org.hibernate.validator.constraints.URL;
  9 +
  10 +import java.util.List;
  11 +
  12 +@Schema(description = "管理后台 - AI 知识库文档批量创建 Request VO")
  13 +@Data
  14 +public class AiKnowledgeDocumentCreateListReqVO {
  15 +
  16 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
  17 + @NotNull(message = "知识库编号不能为空")
  18 + private Long knowledgeId;
  19 +
  20 + @Schema(description = "分段的最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
  21 + @NotNull(message = "分段的最大 Token 数不能为空")
  22 + private Integer segmentMaxTokens;
  23 +
  24 + @Schema(description = "文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + @NotEmpty(message = "文档列表不能为空")
  26 + private List<Document> list;
  27 +
  28 + @Schema(description = "文档")
  29 + @Data
  30 + public static class Document {
  31 +
  32 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "三方登陆")
  33 + @NotBlank(message = "文档名称不能为空")
  34 + private String name;
  35 +
  36 + @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
  37 + @URL(message = "文档 URL 格式不正确")
  38 + private String url;
  39 +
  40 + }
  41 +
  42 +}
0 \ No newline at end of file 43 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentPageReqVO.java
@@ -8,6 +8,9 @@ import lombok.Data; @@ -8,6 +8,9 @@ import lombok.Data;
8 @Data 8 @Data
9 public class AiKnowledgeDocumentPageReqVO extends PageParam { 9 public class AiKnowledgeDocumentPageReqVO extends PageParam {
10 10
  11 + @Schema(description = "知识库编号", example = "1")
  12 + private Long knowledgeId;
  13 +
11 @Schema(description = "文档名称", example = "Java 开发手册") 14 @Schema(description = "文档名称", example = "Java 开发手册")
12 private String name; 15 private String name;
13 16
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
2 2
3 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
4 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 4 import lombok.Data;
6 5
7 -@Schema(description = "管理后台 - AI 知识库-文档 Response VO") 6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - AI 知识库文档 Response VO")
8 @Data 9 @Data
9 -public class AiKnowledgeDocumentRespVO extends PageParam { 10 +public class AiKnowledgeDocumentRespVO {
10 11
11 - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") 12 + @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
12 private Long id; 13 private Long id;
13 14
14 @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") 15 @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
15 private Long knowledgeId; 16 private Long knowledgeId;
16 17
17 - @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册") 18 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
18 private String name; 19 private String name;
19 20
20 - @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 是一门面向对象的语言.....") 21 + @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
  22 + private String url;
  23 +
  24 + @Schema(description = "文档内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 是一门面向对象的语言.....")
21 private String content; 25 private String content;
22 26
23 - @Schema(description = "文档 url", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")  
24 - private String url; 27 + @Schema(description = "文档内容长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  28 + private Integer contentLength;
25 29
26 - @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 30 + @Schema(description = "文档 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
27 private Integer tokens; 31 private Integer tokens;
28 32
29 - @Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008")  
30 - private Integer wordCount; 33 + @Schema(description = "分片最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
  34 + private Integer segmentMaxTokens;
31 35
32 - @Schema(description = "切片状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
33 - private Integer sliceStatus; 36 + @Schema(description = "召回次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  37 + private Integer retrievalCount;
34 38
35 - @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 39 + @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
36 private Integer status; 40 private Integer status;
37 41
  42 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  43 + private LocalDateTime createTime;
  44 +
38 } 45 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateReqVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
2 2
3 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
4 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 5 import lombok.Data;
8 6
9 -  
10 -@Schema(description = "管理后台 - AI 更新 知识库-文档 Request VO") 7 +@Schema(description = "管理后台 - AI 知识库文档更新 Request VO")
11 @Data 8 @Data
12 public class AiKnowledgeDocumentUpdateReqVO { 9 public class AiKnowledgeDocumentUpdateReqVO {
13 10
14 -  
15 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") 11 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
16 @NotNull(message = "编号不能为空") 12 @NotNull(message = "编号不能为空")
17 private Long id; 13 private Long id;
18 14
19 - @Schema(description = "是否启用", example = "1")  
20 - @InEnum(CommonStatusEnum.class)  
21 - private Integer status;  
22 -  
23 @Schema(description = "名称", example = "Java 开发手册") 15 @Schema(description = "名称", example = "Java 开发手册")
24 private String name; 16 private String name;
25 17
  18 + @Schema(description = "分片最大 Token 数", example = "1000")
  19 + private Integer segmentMaxTokens;
  20 +
26 } 21 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateStatusReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - AI 知识库文档更新状态 Request VO")
  10 +@Data
  11 +public class AiKnowledgeDocumentUpdateStatusReqVO {
  12 +
  13 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
  14 + @NotNull(message = "编号不能为空")
  15 + private Long id;
  16 +
  17 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  18 + @NotNull(message = "状态不能为空")
  19 + @InEnum(CommonStatusEnum.class)
  20 + private Integer status;
  21 +
  22 +}
0 \ No newline at end of file 23 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java
@@ -23,24 +23,8 @@ public class AiKnowledgeDocumentCreateReqVO { @@ -23,24 +23,8 @@ public class AiKnowledgeDocumentCreateReqVO {
23 @URL(message = "文档 URL 格式不正确") 23 @URL(message = "文档 URL 格式不正确")
24 private String url; 24 private String url;
25 25
26 - @Schema(description = "每个段落的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")  
27 - @NotNull(message = "每个段落的目标 token 数不能为空")  
28 - private Integer defaultSegmentTokens;  
29 -  
30 - @Schema(description = "每个段落的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350")  
31 - @NotNull(message = "每个段落的最小字符数不能为空")  
32 - private Integer minSegmentWordCount;  
33 -  
34 - @Schema(description = "丢弃阈值:低于此阈值的段落会被丢弃", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")  
35 - @NotNull(message = "丢弃阈值不能为空")  
36 - private Integer minChunkLengthToEmbed;  
37 -  
38 - @Schema(description = "最大段落数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")  
39 - @NotNull(message = "最大段落数不能为空")  
40 - private Integer maxNumSegments;  
41 -  
42 - @Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")  
43 - @NotNull(message = "分块是否保留分隔符不能为空")  
44 - private Boolean keepSeparator; 26 + @Schema(description = "分段的最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
  27 + @NotNull(message = "分段的最大 Token 数不能为空")
  28 + private Integer segmentMaxTokens;
45 29
46 } 30 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgePageReqVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
4 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 7 import lombok.Data;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
6 13
7 @Schema(description = "管理后台 - AI 知识库的分页 Request VO") 14 @Schema(description = "管理后台 - AI 知识库的分页 Request VO")
8 @Data 15 @Data
9 public class AiKnowledgePageReqVO extends PageParam { 16 public class AiKnowledgePageReqVO extends PageParam {
10 17
11 - @Schema(description = "知识库名称", example = "Java 开发手册") 18 + @Schema(description = "知识库名称", example = "芋艿")
12 private String name; 19 private String name;
13 20
  21 + @Schema(description = "是否启用", example = "1")
  22 + @InEnum(CommonStatusEnum.class)
  23 + private Integer status;
  24 +
  25 + @Schema(description = "创建时间")
  26 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  27 + private LocalDateTime[] createTime;
  28 +
14 } 29 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeRespVO.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
  6 +import java.time.LocalDateTime;
6 7
7 @Schema(description = "管理后台 - AI 知识库 Response VO") 8 @Schema(description = "管理后台 - AI 知识库 Response VO")
8 @Data 9 @Data
@@ -17,10 +18,22 @@ public class AiKnowledgeRespVO { @@ -17,10 +18,22 @@ public class AiKnowledgeRespVO {
17 @Schema(description = "知识库描述", example = "帮助你快速构建系统") 18 @Schema(description = "知识库描述", example = "帮助你快速构建系统")
18 private String description; 19 private String description;
19 20
20 - @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")  
21 - private Long modelId; 21 + @Schema(description = "向量模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")
  22 + private Long embeddingModelId;
22 23
23 - @Schema(description = "模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen-72b-chat")  
24 - private String model; 24 + @Schema(description = "向量模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen-72b-chat")
  25 + private String embeddingModel;
  26 +
  27 + @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  28 + private Integer topK;
  29 +
  30 + @Schema(description = "相似度阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.7")
  31 + private Double similarityThreshold;
  32 +
  33 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  34 + private Integer status;
  35 +
  36 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  37 + private LocalDateTime createTime;
25 38
26 } 39 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeSaveReqVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
3 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotBlank; 6 import jakarta.validation.constraints.NotBlank;
5 import jakarta.validation.constraints.NotNull; 7 import jakarta.validation.constraints.NotNull;
6 import lombok.Data; 8 import lombok.Data;
7 9
8 -import java.util.List;  
9 -  
10 -@Schema(description = "管理后台 - AI 知识库创建 Request VO") 10 +@Schema(description = "管理后台 - AI 知识库新增/修改 Request VO")
11 @Data 11 @Data
12 -public class AiKnowledgeCreateReqVO { 12 +public class AiKnowledgeSaveReqVO {
  13 +
  14 + @Schema(description = "对话编号", example = "1204")
  15 + private Long id;
13 16
14 @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南") 17 @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
15 @NotBlank(message = "知识库名称不能为空") 18 @NotBlank(message = "知识库名称不能为空")
@@ -18,19 +21,21 @@ public class AiKnowledgeCreateReqVO { @@ -18,19 +21,21 @@ public class AiKnowledgeCreateReqVO {
18 @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档") 21 @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档")
19 private String description; 22 private String description;
20 23
21 - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]")  
22 - private List<Long> visibilityPermissions; 24 + @Schema(description = "向量模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + @NotNull(message = "向量模型不能为空")
  26 + private Long embeddingModelId;
23 27
24 - @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
25 - @NotNull(message = "嵌入模型不能为空")  
26 - private Long modelId; 28 + @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  29 + @NotNull(message = "topK 不能为空")
  30 + private Integer topK;
27 31
28 @Schema(description = "相似性阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5") 32 @Schema(description = "相似性阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5")
29 @NotNull(message = "相似性阈值不能为空") 33 @NotNull(message = "相似性阈值不能为空")
30 private Double similarityThreshold; 34 private Double similarityThreshold;
31 35
32 - @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")  
33 - @NotNull(message = "topK 不能为空")  
34 - private Integer topK; 36 + @Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + @NotNull(message = "是否启用不能为空")
  38 + @InEnum(CommonStatusEnum.class)
  39 + private Integer status;
35 40
36 } 41 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java deleted
1 -package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import jakarta.validation.constraints.NotBlank;  
5 -import jakarta.validation.constraints.NotNull;  
6 -import lombok.Data;  
7 -  
8 -import java.util.List;  
9 -  
10 -@Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO")  
11 -@Data  
12 -public class AiKnowledgeUpdateReqVO {  
13 -  
14 - @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")  
15 - @NotNull(message = "知识库编号不能为空")  
16 - private Long id;  
17 -  
18 - @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "")  
19 - @NotBlank(message = "知识库名称不能为空")  
20 - private String name;  
21 -  
22 - @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "")  
23 - private String description;  
24 -  
25 - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")  
26 - private List<Long> visibilityPermissions;  
27 -  
28 - @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
29 - @NotNull(message = "嵌入模型不能为空")  
30 - private Long modelId;  
31 -  
32 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentPageReqVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
4 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 7 import lombok.Data;
6 8
@@ -8,13 +10,14 @@ import lombok.Data; @@ -8,13 +10,14 @@ import lombok.Data;
8 @Data 10 @Data
9 public class AiKnowledgeSegmentPageReqVO extends PageParam { 11 public class AiKnowledgeSegmentPageReqVO extends PageParam {
10 12
11 - @Schema(description = "分段状态", example = "1")  
12 - private Integer status;  
13 -  
14 @Schema(description = "文档编号", example = "1") 13 @Schema(description = "文档编号", example = "1")
15 private Integer documentId; 14 private Integer documentId;
16 15
17 @Schema(description = "分段内容关键字", example = "Java 开发") 16 @Schema(description = "分段内容关键字", example = "Java 开发")
18 - private String keyword; 17 + private String content;
  18 +
  19 + @Schema(description = "分段状态", example = "1")
  20 + @InEnum(CommonStatusEnum.class)
  21 + private Integer status;
19 22
20 } 23 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentProcessRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - AI 知识库段落向量进度 Response VO")
  7 +@Data
  8 +public class AiKnowledgeSegmentProcessRespVO {
  9 +
  10 + @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  11 + private Long documentId;
  12 +
  13 + @Schema(description = "总段落数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  14 + private Long count;
  15 +
  16 + @Schema(description = "已向量化段落数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  17 + private Long embeddingCount;
  18 +
  19 +}
0 \ No newline at end of file 20 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentRespVO.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 -@Schema(description = "管理后台 - AI 知识库-文档 Response VO") 6 +@Schema(description = "管理后台 - AI 知识库文档分片 Response VO")
7 @Data 7 @Data
8 public class AiKnowledgeSegmentRespVO { 8 public class AiKnowledgeSegmentRespVO {
9 9
@@ -22,13 +22,19 @@ public class AiKnowledgeSegmentRespVO { @@ -22,13 +22,19 @@ public class AiKnowledgeSegmentRespVO {
22 @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册") 22 @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
23 private String content; 23 private String content;
24 24
  25 + @Schema(description = "切片内容长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  26 + private Integer contentLength;
  27 +
25 @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 28 @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
26 private Integer tokens; 29 private Integer tokens;
27 30
28 - @Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008")  
29 - private Integer wordCount; 31 + @Schema(description = "召回次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  32 + private Integer retrievalCount;
30 33
31 @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 34 @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
32 private Integer status; 35 private Integer status;
33 36
  37 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  38 + private Long createTime;
  39 +
34 } 40 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentUpdateReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSaveReqVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
4 import lombok.Data; 5 import lombok.Data;
5 6
6 -  
7 -@Schema(description = "管理后台 - AI 更新 知识库-段落 request VO") 7 +@Schema(description = "管理后台 - AI 新增/修改知识库段落 request VO")
8 @Data 8 @Data
9 -public class AiKnowledgeSegmentUpdateReqVO { 9 +public class AiKnowledgeSegmentSaveReqVO {
10 10
11 - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") 11 + @Schema(description = "编号", example = "24790")
12 private Long id; 12 private Long id;
13 13
  14 + @Schema(description = "知识库文档编号", example = "1024")
  15 + private Long documentId;
  16 +
14 @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册") 17 @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
  18 + @NotEmpty(message = "切片内容不能为空")
15 private String content; 19 private String content;
16 20
17 } 21 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchReqVO.java
@@ -3,15 +3,25 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; @@ -3,15 +3,25 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
  6 +import jakarta.validation.constraints.NotEmpty;
  7 +import jakarta.validation.constraints.NotNull;
6 8
7 -@Schema(description = "管理后台 - AI 知识库段落召回 Request VO") 9 +@Schema(description = "管理后台 - AI 知识库段落搜索 Request VO")
8 @Data 10 @Data
9 public class AiKnowledgeSegmentSearchReqVO { 11 public class AiKnowledgeSegmentSearchReqVO {
10 12
11 - @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") 13 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotNull(message = "知识库编号不能为空")
12 private Long knowledgeId; 15 private Long knowledgeId;
13 16
14 - @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线") 17 + @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "如何使用这个产品")
  18 + @NotEmpty(message = "内容不能为空")
15 private String content; 19 private String content;
16 20
  21 + @Schema(description = "最大返回数量", example = "5")
  22 + private Integer topK;
  23 +
  24 + @Schema(description = "相似度阈值", example = "0.7")
  25 + private Double similarityThreshold;
  26 +
17 } 27 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - AI 知识库段落搜索 Response VO")
  7 +@Data
  8 +public class AiKnowledgeSegmentSearchRespVO extends AiKnowledgeSegmentRespVO {
  9 +
  10 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
  11 + private String documentName;
  12 +
  13 + @Schema(description = "相似度分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.95")
  14 + private Double score;
  15 +
  16 +}
0 \ No newline at end of file 17 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java
@@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.Operation; @@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.Operation;
12 import io.swagger.v3.oas.annotations.Parameter; 12 import io.swagger.v3.oas.annotations.Parameter;
13 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
14 import jakarta.annotation.Resource; 14 import jakarta.annotation.Resource;
15 -import jakarta.annotation.security.PermitAll;  
16 import jakarta.validation.Valid; 15 import jakarta.validation.Valid;
17 import org.springframework.http.MediaType; 16 import org.springframework.http.MediaType;
18 import org.springframework.security.access.prepost.PreAuthorize; 17 import org.springframework.security.access.prepost.PreAuthorize;
@@ -32,7 +31,6 @@ public class AiMindMapController { @@ -32,7 +31,6 @@ public class AiMindMapController {
32 31
33 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 32 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
34 @Operation(summary = "导图生成(流式)", description = "流式返回,响应较快") 33 @Operation(summary = "导图生成(流式)", description = "流式返回,响应较快")
35 - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题  
36 public Flux<CommonResult<String>> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) { 34 public Flux<CommonResult<String>> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) {
37 return mindMapService.generateMindMap(generateReqVO, getLoginUserId()); 35 return mindMapService.generateMindMap(generateReqVO, getLoginUserId());
38 } 36 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapPageReqVO.java
@@ -13,8 +13,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ @@ -13,8 +13,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
13 13
14 @Schema(description = "管理后台 - AI 思维导图分页 Request VO") 14 @Schema(description = "管理后台 - AI 思维导图分页 Request VO")
15 @Data 15 @Data
16 -@EqualsAndHashCode(callSuper = true)  
17 -@ToString(callSuper = true)  
18 public class AiMindMapPageReqVO extends PageParam { 16 public class AiMindMapPageReqVO extends PageParam {
19 17
20 @Schema(description = "用户编号", example = "4325") 18 @Schema(description = "用户编号", example = "4325")
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiApiKeyController.java
@@ -6,9 +6,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -6,9 +6,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
7 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyRespVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyRespVO;
8 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
9 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelRespVO; 9 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO;
10 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; 10 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
11 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
12 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 11 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
13 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
14 import io.swagger.v3.oas.annotations.Parameter; 13 import io.swagger.v3.oas.annotations.Parameter;
@@ -76,9 +75,9 @@ public class AiApiKeyController { @@ -76,9 +75,9 @@ public class AiApiKeyController {
76 75
77 @GetMapping("/simple-list") 76 @GetMapping("/simple-list")
78 @Operation(summary = "获得 API 密钥分页列表") 77 @Operation(summary = "获得 API 密钥分页列表")
79 - public CommonResult<List<AiChatModelRespVO>> getApiKeySimpleList() { 78 + public CommonResult<List<AiModelRespVO>> getApiKeySimpleList() {
80 List<AiApiKeyDO> list = apiKeyService.getApiKeyList(); 79 List<AiApiKeyDO> list = apiKeyService.getApiKeyList();
81 - return success(convertList(list, key -> new AiChatModelRespVO().setId(key.getId()).setName(key.getName()))); 80 + return success(convertList(list, key -> new AiModelRespVO().setId(key.getId()).setName(key.getName())));
82 } 81 }
83 82
84 } 83 }
85 \ No newline at end of file 84 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatModelController.java deleted
1 -package cn.iocoder.yudao.module.ai.controller.admin.model;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
4 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
5 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
6 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;  
7 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelRespVO;  
8 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO;  
9 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
10 -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;  
11 -import io.swagger.v3.oas.annotations.Operation;  
12 -import io.swagger.v3.oas.annotations.Parameter;  
13 -import io.swagger.v3.oas.annotations.tags.Tag;  
14 -import jakarta.annotation.Resource;  
15 -import jakarta.validation.Valid;  
16 -import org.springframework.security.access.prepost.PreAuthorize;  
17 -import org.springframework.validation.annotation.Validated;  
18 -import org.springframework.web.bind.annotation.*;  
19 -  
20 -import java.util.List;  
21 -  
22 -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;  
23 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;  
24 -  
25 -@Tag(name = "管理后台 - AI 聊天模型")  
26 -@RestController  
27 -@RequestMapping("/ai/chat-model")  
28 -@Validated  
29 -public class AiChatModelController {  
30 -  
31 - @Resource  
32 - private AiChatModelService chatModelService;  
33 -  
34 - @PostMapping("/create")  
35 - @Operation(summary = "创建聊天模型")  
36 - @PreAuthorize("@ss.hasPermission('ai:chat-model:create')")  
37 - public CommonResult<Long> createChatModel(@Valid @RequestBody AiChatModelSaveReqVO createReqVO) {  
38 - return success(chatModelService.createChatModel(createReqVO));  
39 - }  
40 -  
41 - @PutMapping("/update")  
42 - @Operation(summary = "更新聊天模型")  
43 - @PreAuthorize("@ss.hasPermission('ai:chat-model:update')")  
44 - public CommonResult<Boolean> updateChatModel(@Valid @RequestBody AiChatModelSaveReqVO updateReqVO) {  
45 - chatModelService.updateChatModel(updateReqVO);  
46 - return success(true);  
47 - }  
48 -  
49 - @DeleteMapping("/delete")  
50 - @Operation(summary = "删除聊天模型")  
51 - @Parameter(name = "id", description = "编号", required = true)  
52 - @PreAuthorize("@ss.hasPermission('ai:chat-model:delete')")  
53 - public CommonResult<Boolean> deleteChatModel(@RequestParam("id") Long id) {  
54 - chatModelService.deleteChatModel(id);  
55 - return success(true);  
56 - }  
57 -  
58 - @GetMapping("/get")  
59 - @Operation(summary = "获得聊天模型")  
60 - @Parameter(name = "id", description = "编号", required = true, example = "1024")  
61 - @PreAuthorize("@ss.hasPermission('ai:chat-model:query')")  
62 - public CommonResult<AiChatModelRespVO> getChatModel(@RequestParam("id") Long id) {  
63 - AiChatModelDO chatModel = chatModelService.getChatModel(id);  
64 - return success(BeanUtils.toBean(chatModel, AiChatModelRespVO.class));  
65 - }  
66 -  
67 - @GetMapping("/page")  
68 - @Operation(summary = "获得聊天模型分页")  
69 - @PreAuthorize("@ss.hasPermission('ai:chat-model:query')")  
70 - public CommonResult<PageResult<AiChatModelRespVO>> getChatModelPage(@Valid AiChatModelPageReqVO pageReqVO) {  
71 - PageResult<AiChatModelDO> pageResult = chatModelService.getChatModelPage(pageReqVO);  
72 - return success(BeanUtils.toBean(pageResult, AiChatModelRespVO.class));  
73 - }  
74 -  
75 - @GetMapping("/simple-list")  
76 - @Operation(summary = "获得聊天模型列表")  
77 - @Parameter(name = "status", description = "状态", required = true, example = "1")  
78 - public CommonResult<List<AiChatModelRespVO>> getChatModelSimpleList(@RequestParam("status") Integer status) {  
79 - List<AiChatModelDO> list = chatModelService.getChatModelListByStatus(status);  
80 - return success(convertList(list, model -> new AiChatModelRespVO().setId(model.getId())  
81 - .setName(model.getName()).setModel(model.getModel())));  
82 - }  
83 -  
84 -}  
85 \ No newline at end of file 0 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java
@@ -75,7 +75,7 @@ public class AiChatRoleController { @@ -75,7 +75,7 @@ public class AiChatRoleController {
75 @GetMapping("/category-list") 75 @GetMapping("/category-list")
76 @Operation(summary = "获得聊天角色的分类列表") 76 @Operation(summary = "获得聊天角色的分类列表")
77 public CommonResult<List<String>> getChatRoleCategoryList() { 77 public CommonResult<List<String>> getChatRoleCategoryList() {
78 - return success(chatRoleService.getChatRoleCategoryList()); 78 + return success(chatRoleService.getChatRoleCategoryList());
79 } 79 }
80 80
81 // ========== 角色管理 ========== 81 // ========== 角色管理 ==========
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  11 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import jakarta.annotation.Resource;
  16 +import jakarta.validation.Valid;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  25 +
  26 +@Tag(name = "管理后台 - AI 模型")
  27 +@RestController
  28 +@RequestMapping("/ai/model")
  29 +@Validated
  30 +public class AiModelController {
  31 +
  32 + @Resource
  33 + private AiModelService modelService;
  34 +
  35 + @PostMapping("/create")
  36 + @Operation(summary = "创建模型")
  37 + @PreAuthorize("@ss.hasPermission('ai:model:create')")
  38 + public CommonResult<Long> createModel(@Valid @RequestBody AiModelSaveReqVO createReqVO) {
  39 + return success(modelService.createModel(createReqVO));
  40 + }
  41 +
  42 + @PutMapping("/update")
  43 + @Operation(summary = "更新模型")
  44 + @PreAuthorize("@ss.hasPermission('ai:model:update')")
  45 + public CommonResult<Boolean> updateModel(@Valid @RequestBody AiModelSaveReqVO updateReqVO) {
  46 + modelService.updateModel(updateReqVO);
  47 + return success(true);
  48 + }
  49 +
  50 + @DeleteMapping("/delete")
  51 + @Operation(summary = "删除模型")
  52 + @Parameter(name = "id", description = "编号", required = true)
  53 + @PreAuthorize("@ss.hasPermission('ai:model:delete')")
  54 + public CommonResult<Boolean> deleteModel(@RequestParam("id") Long id) {
  55 + modelService.deleteModel(id);
  56 + return success(true);
  57 + }
  58 +
  59 + @GetMapping("/get")
  60 + @Operation(summary = "获得模型")
  61 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  62 + @PreAuthorize("@ss.hasPermission('ai:model:query')")
  63 + public CommonResult<AiModelRespVO> getModel(@RequestParam("id") Long id) {
  64 + AiModelDO model = modelService.getModel(id);
  65 + return success(BeanUtils.toBean(model, AiModelRespVO.class));
  66 + }
  67 +
  68 + @GetMapping("/page")
  69 + @Operation(summary = "获得模型分页")
  70 + @PreAuthorize("@ss.hasPermission('ai:model:query')")
  71 + public CommonResult<PageResult<AiModelRespVO>> getModelPage(@Valid AiModelPageReqVO pageReqVO) {
  72 + PageResult<AiModelDO> pageResult = modelService.getModelPage(pageReqVO);
  73 + return success(BeanUtils.toBean(pageResult, AiModelRespVO.class));
  74 + }
  75 +
  76 + @GetMapping("/simple-list")
  77 + @Operation(summary = "获得模型列表")
  78 + @Parameter(name = "type", description = "类型", required = true, example = "1")
  79 + @Parameter(name = "platform", description = "平台", example = "midjourney")
  80 + public CommonResult<List<AiModelRespVO>> getModelSimpleList(
  81 + @RequestParam("type") Integer type,
  82 + @RequestParam(value = "platform", required = false) String platform) {
  83 + List<AiModelDO> list = modelService.getModelListByStatusAndType(
  84 + CommonStatusEnum.ENABLE.getStatus(), type, platform);
  85 + return success(convertList(list, model -> new AiModelRespVO().setId(model.getId())
  86 + .setName(model.getName()).setModel(model.getModel()).setPlatform(model.getPlatform())));
  87 + }
  88 +
  89 +}
0 \ No newline at end of file 90 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiToolController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolRespVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolSaveReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
  11 +import cn.iocoder.yudao.module.ai.service.model.AiToolService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import jakarta.annotation.Resource;
  16 +import jakarta.validation.Valid;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  25 +
  26 +@Tag(name = "管理后台 - AI 工具")
  27 +@RestController
  28 +@RequestMapping("/ai/tool")
  29 +@Validated
  30 +public class AiToolController {
  31 +
  32 + @Resource
  33 + private AiToolService toolService;
  34 +
  35 + @PostMapping("/create")
  36 + @Operation(summary = "创建工具")
  37 + @PreAuthorize("@ss.hasPermission('ai:tool:create')")
  38 + public CommonResult<Long> createTool(@Valid @RequestBody AiToolSaveReqVO createReqVO) {
  39 + return success(toolService.createTool(createReqVO));
  40 + }
  41 +
  42 + @PutMapping("/update")
  43 + @Operation(summary = "更新工具")
  44 + @PreAuthorize("@ss.hasPermission('ai:tool:update')")
  45 + public CommonResult<Boolean> updateTool(@Valid @RequestBody AiToolSaveReqVO updateReqVO) {
  46 + toolService.updateTool(updateReqVO);
  47 + return success(true);
  48 + }
  49 +
  50 + @DeleteMapping("/delete")
  51 + @Operation(summary = "删除工具")
  52 + @Parameter(name = "id", description = "编号", required = true)
  53 + @PreAuthorize("@ss.hasPermission('ai:tool:delete')")
  54 + public CommonResult<Boolean> deleteTool(@RequestParam("id") Long id) {
  55 + toolService.deleteTool(id);
  56 + return success(true);
  57 + }
  58 +
  59 + @GetMapping("/get")
  60 + @Operation(summary = "获得工具")
  61 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  62 + @PreAuthorize("@ss.hasPermission('ai:tool:query')")
  63 + public CommonResult<AiToolRespVO> getTool(@RequestParam("id") Long id) {
  64 + AiToolDO tool = toolService.getTool(id);
  65 + return success(BeanUtils.toBean(tool, AiToolRespVO.class));
  66 + }
  67 +
  68 + @GetMapping("/page")
  69 + @Operation(summary = "获得工具分页")
  70 + @PreAuthorize("@ss.hasPermission('ai:tool:query')")
  71 + public CommonResult<PageResult<AiToolRespVO>> getToolPage(@Valid AiToolPageReqVO pageReqVO) {
  72 + PageResult<AiToolDO> pageResult = toolService.getToolPage(pageReqVO);
  73 + return success(BeanUtils.toBean(pageResult, AiToolRespVO.class));
  74 + }
  75 +
  76 + @GetMapping("/simple-list")
  77 + @Operation(summary = "获得工具列表")
  78 + public CommonResult<List<AiToolRespVO>> getToolSimpleList() {
  79 + List<AiToolDO> list = toolService.getToolListByStatus(CommonStatusEnum.ENABLE.getStatus());
  80 + return success(convertList(list, tool -> new AiToolRespVO()
  81 + .setId(tool.getId()).setName(tool.getName())));
  82 + }
  83 +
  84 +}
0 \ No newline at end of file 85 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleRespVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole; 1 package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole;
2 2
3 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 3 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
4 import com.fhs.core.trans.anno.Trans; 4 import com.fhs.core.trans.anno.Trans;
5 import com.fhs.core.trans.constant.TransType; 5 import com.fhs.core.trans.constant.TransType;
6 import com.fhs.core.trans.vo.VO; 6 import com.fhs.core.trans.vo.VO;
@@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 8 import lombok.Data;
9 9
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
  11 +import java.util.List;
11 12
12 @Schema(description = "管理后台 - AI 聊天角色 Response VO") 13 @Schema(description = "管理后台 - AI 聊天角色 Response VO")
13 @Data 14 @Data
@@ -20,7 +21,7 @@ public class AiChatRoleRespVO implements VO { @@ -20,7 +21,7 @@ public class AiChatRoleRespVO implements VO {
20 private Long userId; 21 private Long userId;
21 22
22 @Schema(description = "模型编号", example = "17640") 23 @Schema(description = "模型编号", example = "17640")
23 - @Trans(type = TransType.SIMPLE, target = AiChatModelDO.class, fields = {"name", "model"}, refs = {"modelName", "model"}) 24 + @Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = { "name", "model" }, refs = { "modelName", "model" })
24 private Long modelId; 25 private Long modelId;
25 @Schema(description = "模型名字", example = "张三") 26 @Schema(description = "模型名字", example = "张三")
26 private String modelName; 27 private String modelName;
@@ -45,6 +46,12 @@ public class AiChatRoleRespVO implements VO { @@ -45,6 +46,12 @@ public class AiChatRoleRespVO implements VO {
45 @Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED) 46 @Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED)
46 private String systemMessage; 47 private String systemMessage;
47 48
  49 + @Schema(description = "引用的知识库编号列表", example = "1,2,3")
  50 + private List<Long> knowledgeIds;
  51 +
  52 + @Schema(description = "引用的工具编号列表", example = "1,2,3")
  53 + private List<Long> toolIds;
  54 +
48 @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 55 @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
49 private Boolean publicStatus; 56 private Boolean publicStatus;
50 57
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveMyReqVO.java
@@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotEmpty; @@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotEmpty;
5 import lombok.Data; 5 import lombok.Data;
6 import org.hibernate.validator.constraints.URL; 6 import org.hibernate.validator.constraints.URL;
7 7
  8 +import java.util.List;
  9 +
8 @Schema(description = "管理后台 - AI 聊天角色新增/修改【我的】 Request VO") 10 @Schema(description = "管理后台 - AI 聊天角色新增/修改【我的】 Request VO")
9 @Data 11 @Data
10 public class AiChatRoleSaveMyReqVO { 12 public class AiChatRoleSaveMyReqVO {
@@ -29,4 +31,10 @@ public class AiChatRoleSaveMyReqVO { @@ -29,4 +31,10 @@ public class AiChatRoleSaveMyReqVO {
29 @NotEmpty(message = "角色设定不能为空") 31 @NotEmpty(message = "角色设定不能为空")
30 private String systemMessage; 32 private String systemMessage;
31 33
  34 + @Schema(description = "引用的知识库编号列表", example = "1,2,3")
  35 + private List<Long> knowledgeIds;
  36 +
  37 + @Schema(description = "引用的工具编号列表", example = "1,2,3")
  38 + private List<Long> toolIds;
  39 +
32 } 40 }
33 \ No newline at end of file 41 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveReqVO.java
@@ -7,6 +7,8 @@ import lombok.*; @@ -7,6 +7,8 @@ import lombok.*;
7 import jakarta.validation.constraints.*; 7 import jakarta.validation.constraints.*;
8 import org.hibernate.validator.constraints.URL; 8 import org.hibernate.validator.constraints.URL;
9 9
  10 +import java.util.List;
  11 +
10 @Schema(description = "管理后台 - AI 聊天角色新增/修改 Request VO") 12 @Schema(description = "管理后台 - AI 聊天角色新增/修改 Request VO")
11 @Data 13 @Data
12 public class AiChatRoleSaveReqVO { 14 public class AiChatRoleSaveReqVO {
@@ -42,6 +44,12 @@ public class AiChatRoleSaveReqVO { @@ -42,6 +44,12 @@ public class AiChatRoleSaveReqVO {
42 @NotEmpty(message = "角色设定不能为空") 44 @NotEmpty(message = "角色设定不能为空")
43 private String systemMessage; 45 private String systemMessage;
44 46
  47 + @Schema(description = "引用的知识库编号列表", example = "1,2,3")
  48 + private List<Long> knowledgeIds;
  49 +
  50 + @Schema(description = "引用的工具编号列表", example = "1,2,3")
  51 + private List<Long> toolIds;
  52 +
45 @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 53 @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
46 @NotNull(message = "是否公开不能为空") 54 @NotNull(message = "是否公开不能为空")
47 private Boolean publicStatus; 55 private Boolean publicStatus;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelPageReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelPageReqVO.java
1 -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel; 1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model;
2 2
3 import lombok.*; 3 import lombok.*;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
5 import cn.iocoder.yudao.framework.common.pojo.PageParam; 5 import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 6
7 -@Schema(description = "管理后台 - API 聊天模型分页 Request VO") 7 +@Schema(description = "管理后台 - API 模型分页 Request VO")
8 @Data 8 @Data
9 -public class AiChatModelPageReqVO extends PageParam { 9 +public class AiModelPageReqVO extends PageParam {
10 10
11 @Schema(description = "模型名字", example = "张三") 11 @Schema(description = "模型名字", example = "张三")
12 private String name; 12 private String name;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelRespVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelRespVO.java
1 -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel; 1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
7 7
8 -@Schema(description = "管理后台 - AI 聊天模型 Response VO") 8 +@Schema(description = "管理后台 - AI 模型 Response VO")
9 @Data 9 @Data
10 -public class AiChatModelRespVO { 10 +public class AiModelRespVO {
11 11
12 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2630") 12 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2630")
13 private Long id; 13 private Long id;
@@ -24,6 +24,9 @@ public class AiChatModelRespVO { @@ -24,6 +24,9 @@ public class AiChatModelRespVO {
24 @Schema(description = "模型平台", example = "OpenAI") 24 @Schema(description = "模型平台", example = "OpenAI")
25 private String platform; 25 private String platform;
26 26
  27 + @Schema(description = "模型类型", example = "1")
  28 + private Integer type;
  29 +
27 @Schema(description = "排序", example = "1") 30 @Schema(description = "排序", example = "1")
28 private Integer sort; 31 private Integer sort;
29 32
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelSaveReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelSaveReqVO.java
1 -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel; 1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model;
2 2
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
  4 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 6 import cn.iocoder.yudao.framework.common.validation.InEnum;
5 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
6 -import lombok.*;  
7 -import jakarta.validation.constraints.*; 8 +import jakarta.validation.constraints.NotEmpty;
  9 +import jakarta.validation.constraints.NotNull;
  10 +import lombok.Data;
8 11
9 -@Schema(description = "管理后台 - API 聊天模型新增/修改 Request VO") 12 +@Schema(description = "管理后台 - API 模型新增/修改 Request VO")
10 @Data 13 @Data
11 -public class AiChatModelSaveReqVO { 14 +public class AiModelSaveReqVO {
12 15
13 @Schema(description = "编号", example = "2630") 16 @Schema(description = "编号", example = "2630")
14 private Long id; 17 private Long id;
@@ -27,8 +30,14 @@ public class AiChatModelSaveReqVO { @@ -27,8 +30,14 @@ public class AiChatModelSaveReqVO {
27 30
28 @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") 31 @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
29 @NotEmpty(message = "模型平台不能为空") 32 @NotEmpty(message = "模型平台不能为空")
  33 + @InEnum(AiPlatformEnum.class)
30 private String platform; 34 private String platform;
31 35
  36 + @Schema(description = "模型类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + @NotNull(message = "模型类型不能为空")
  38 + @InEnum(AiModelTypeEnum.class)
  39 + private Integer type;
  40 +
32 @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 41 @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
33 @NotNull(message = "排序不能为空") 42 @NotNull(message = "排序不能为空")
34 private Integer sort; 43 private Integer sort;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import lombok.ToString;
  10 +import org.springframework.format.annotation.DateTimeFormat;
  11 +
  12 +import java.time.LocalDateTime;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  15 +
  16 +@Schema(description = "管理后台 - AI 工具分页 Request VO")
  17 +@Data
  18 +public class AiToolPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "工具名称", example = "王五")
  21 + private String name;
  22 +
  23 + @Schema(description = "工具描述", example = "你猜")
  24 + private String description;
  25 +
  26 + @Schema(description = "状态", example = "1")
  27 + @InEnum(CommonStatusEnum.class)
  28 + private Integer status;
  29 +
  30 + @Schema(description = "创建时间")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime[] createTime;
  33 +
  34 +}
0 \ No newline at end of file 35 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - AI 工具 Response VO")
  9 +@Data
  10 +public class AiToolRespVO {
  11 +
  12 + @Schema(description = "工具编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19661")
  13 + private Long id;
  14 +
  15 + @Schema(description = "工具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  16 + private String name;
  17 +
  18 + @Schema(description = "工具描述", example = "你猜")
  19 + private String description;
  20 +
  21 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  22 + private Integer status;
  23 +
  24 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + private LocalDateTime createTime;
  26 +
  27 +}
0 \ No newline at end of file 28 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotEmpty;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - AI 工具新增/修改 Request VO")
  10 +@Data
  11 +public class AiToolSaveReqVO {
  12 +
  13 + @Schema(description = "工具编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19661")
  14 + private Long id;
  15 +
  16 + @Schema(description = "工具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  17 + @NotEmpty(message = "工具名称不能为空")
  18 + private String name;
  19 +
  20 + @Schema(description = "工具描述", example = "你猜")
  21 + private String description;
  22 +
  23 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  24 + @InEnum(CommonStatusEnum.class)
  25 + private Integer status;
  26 +
  27 +}
0 \ No newline at end of file 28 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/AiWriteController.java
@@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.Operation; @@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.Operation;
12 import io.swagger.v3.oas.annotations.Parameter; 12 import io.swagger.v3.oas.annotations.Parameter;
13 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
14 import jakarta.annotation.Resource; 14 import jakarta.annotation.Resource;
15 -import jakarta.annotation.security.PermitAll;  
16 import jakarta.validation.Valid; 15 import jakarta.validation.Valid;
17 import org.springframework.http.MediaType; 16 import org.springframework.http.MediaType;
18 import org.springframework.security.access.prepost.PreAuthorize; 17 import org.springframework.security.access.prepost.PreAuthorize;
@@ -32,7 +31,6 @@ public class AiWriteController { @@ -32,7 +31,6 @@ public class AiWriteController {
32 31
33 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 32 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
34 @Operation(summary = "写作生成(流式)", description = "流式返回,响应较快") 33 @Operation(summary = "写作生成(流式)", description = "流式返回,响应较快")
35 - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题  
36 public Flux<CommonResult<String>> generateWriteContent(@RequestBody @Valid AiWriteGenerateReqVO generateReqVO) { 34 public Flux<CommonResult<String>> generateWriteContent(@RequestBody @Valid AiWriteGenerateReqVO generateReqVO) {
37 return writeService.generateWriteContent(generateReqVO, getLoginUserId()); 35 return writeService.generateWriteContent(generateReqVO, getLoginUserId());
38 } 36 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java
@@ -13,8 +13,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ @@ -13,8 +13,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
13 13
14 @Schema(description = "管理后台 - AI 写作分页 Request VO") 14 @Schema(description = "管理后台 - AI 写作分页 Request VO")
15 @Data 15 @Data
16 -@EqualsAndHashCode(callSuper = true)  
17 -@ToString(callSuper = true)  
18 public class AiWritePageReqVO extends PageParam { 16 public class AiWritePageReqVO extends PageParam {
19 17
20 @Schema(description = "用户编号", example = "28404") 18 @Schema(description = "用户编号", example = "28404")
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat; 1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;  
5 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
7 import com.baomidou.mybatisplus.annotation.KeySequence; 6 import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableId; 7 import com.baomidou.mybatisplus.annotation.TableId;
9 import com.baomidou.mybatisplus.annotation.TableName; 8 import com.baomidou.mybatisplus.annotation.TableName;
@@ -22,7 +21,6 @@ import java.time.LocalDateTime; @@ -22,7 +21,6 @@ import java.time.LocalDateTime;
22 @TableName("ai_chat_conversation") 21 @TableName("ai_chat_conversation")
23 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 22 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
24 @Data 23 @Data
25 -@EqualsAndHashCode(callSuper = true)  
26 @Builder 24 @Builder
27 @NoArgsConstructor 25 @NoArgsConstructor
28 @AllArgsConstructor 26 @AllArgsConstructor
@@ -66,20 +64,15 @@ public class AiChatConversationDO extends BaseDO { @@ -66,20 +64,15 @@ public class AiChatConversationDO extends BaseDO {
66 private Long roleId; 64 private Long roleId;
67 65
68 /** 66 /**
69 - * 知识库编号  
70 - * <p>  
71 - * 关联 {@link AiKnowledgeDO#getId()}  
72 - */  
73 - private Long knowledgeId;  
74 -  
75 - /**  
76 * 模型编号 67 * 模型编号
77 * 68 *
78 - * 关联 {@link AiChatModelDO#getId()} 字段 69 + * 关联 {@link AiModelDO#getId()} 字段
79 */ 70 */
80 private Long modelId; 71 private Long modelId;
81 /** 72 /**
82 * 模型标志 73 * 模型标志
  74 + *
  75 + * 冗余 {@link AiModelDO#getModel()} 字段
83 */ 76 */
84 private String model; 77 private String model;
85 78
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat; 1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
4 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 5 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
5 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
7 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableField; 9 import com.baomidou.mybatisplus.annotation.TableField;
9 import com.baomidou.mybatisplus.annotation.TableId; 10 import com.baomidou.mybatisplus.annotation.TableId;
10 import com.baomidou.mybatisplus.annotation.TableName; 11 import com.baomidou.mybatisplus.annotation.TableName;
11 -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;  
12 import lombok.*; 12 import lombok.*;
13 import org.springframework.ai.chat.messages.MessageType; 13 import org.springframework.ai.chat.messages.MessageType;
14 14
@@ -20,10 +20,9 @@ import java.util.List; @@ -20,10 +20,9 @@ import java.util.List;
20 * @since 2024/4/14 17:35 20 * @since 2024/4/14 17:35
21 * @since 2024/4/14 17:35 21 * @since 2024/4/14 17:35
22 */ 22 */
23 -@TableName("ai_chat_message") 23 +@TableName(value = "ai_chat_message", autoResultMap = true)
24 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 24 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
25 @Data 25 @Data
26 -@EqualsAndHashCode(callSuper = true)  
27 @Builder 26 @Builder
28 @NoArgsConstructor 27 @NoArgsConstructor
29 @AllArgsConstructor 28 @AllArgsConstructor
@@ -71,23 +70,16 @@ public class AiChatMessageDO extends BaseDO { @@ -71,23 +70,16 @@ public class AiChatMessageDO extends BaseDO {
71 */ 70 */
72 private Long roleId; 71 private Long roleId;
73 72
74 -  
75 - /**  
76 - * 段落编号数组  
77 - *  
78 - * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段  
79 - */  
80 - @TableField(typeHandler = JacksonTypeHandler.class)  
81 - private List<Long> segmentIds;  
82 -  
83 /** 73 /**
84 * 模型标志 74 * 模型标志
  75 + *
  76 + * 冗余 {@link AiModelDO#getModel()}
85 */ 77 */
86 private String model; 78 private String model;
87 /** 79 /**
88 * 模型编号 80 * 模型编号
89 * 81 *
90 - * 关联 {@link AiChatModelDO#getId()} 字段 82 + * 关联 {@link AiModelDO#getId()} 字段
91 */ 83 */
92 private Long modelId; 84 private Long modelId;
93 85
@@ -101,4 +93,12 @@ public class AiChatMessageDO extends BaseDO { @@ -101,4 +93,12 @@ public class AiChatMessageDO extends BaseDO {
101 */ 93 */
102 private Boolean useContext; 94 private Boolean useContext;
103 95
  96 + /**
  97 + * 知识库段落编号数组
  98 + *
  99 + * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段
  100 + */
  101 + @TableField(typeHandler = LongListTypeHandler.class)
  102 + private List<Long> segmentIds;
  103 +
104 } 104 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.image; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.image;
2 2
3 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 3 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
6 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; 6 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
7 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 7 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
8 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -53,9 +53,15 @@ public class AiImageDO extends BaseDO { @@ -53,9 +53,15 @@ public class AiImageDO extends BaseDO {
53 */ 53 */
54 private String platform; 54 private String platform;
55 /** 55 /**
56 - * 模型 56 + * 模型编号
57 * 57 *
58 - * 冗余 {@link AiChatModelDO#getModel()} 58 + * 关联 {@link AiModelDO#getId()}
  59 + */
  60 + private Long modelId;
  61 + /**
  62 + * 模型标识
  63 + *
  64 + * 冗余 {@link AiModelDO#getModel()}
59 */ 65 */
60 private String model; 66 private String model;
61 67
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
@@ -2,15 +2,12 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge; @@ -2,15 +2,12 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
2 2
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 -import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; 5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
6 import com.baomidou.mybatisplus.annotation.KeySequence; 6 import com.baomidou.mybatisplus.annotation.KeySequence;
7 -import com.baomidou.mybatisplus.annotation.TableField;  
8 import com.baomidou.mybatisplus.annotation.TableId; 7 import com.baomidou.mybatisplus.annotation.TableId;
9 import com.baomidou.mybatisplus.annotation.TableName; 8 import com.baomidou.mybatisplus.annotation.TableName;
10 import lombok.Data; 9 import lombok.Data;
11 10
12 -import java.util.List;  
13 -  
14 /** 11 /**
15 * AI 知识库 DO 12 * AI 知识库 DO
16 * 13 *
@@ -27,12 +24,6 @@ public class AiKnowledgeDO extends BaseDO { @@ -27,12 +24,6 @@ public class AiKnowledgeDO extends BaseDO {
27 @TableId 24 @TableId
28 private Long id; 25 private Long id;
29 /** 26 /**
30 - * 用户编号  
31 - * <p>  
32 - * 关联 AdminUserDO 的 userId 字段  
33 - */  
34 - private Long userId;  
35 - /**  
36 * 知识库名称 27 * 知识库名称
37 */ 28 */
38 private String name; 29 private String name;
@@ -42,20 +33,17 @@ public class AiKnowledgeDO extends BaseDO { @@ -42,20 +33,17 @@ public class AiKnowledgeDO extends BaseDO {
42 private String description; 33 private String description;
43 34
44 /** 35 /**
45 - * 可见权限,选择哪些人可见  
46 - * <p>  
47 - * -1 所有人可见,其他为各自用户编号  
48 - */  
49 - @TableField(typeHandler = LongListTypeHandler.class)  
50 - private List<Long> visibilityPermissions;  
51 - /**  
52 - * 嵌入模型编号 36 + * 向量模型编号
  37 + *
  38 + * 关联 {@link AiModelDO#getId()}
53 */ 39 */
54 - private Long modelId; 40 + private Long embeddingModelId;
55 /** 41 /**
56 * 模型标识 42 * 模型标识
  43 + *
  44 + * 冗余 {@link AiModelDO#getModel()}
57 */ 45 */
58 - private String model; 46 + private String embeddingModel;
59 47
60 /** 48 /**
61 * topK 49 * topK
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
2 2
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 -import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;  
6 import com.baomidou.mybatisplus.annotation.KeySequence; 5 import com.baomidou.mybatisplus.annotation.KeySequence;
7 import com.baomidou.mybatisplus.annotation.TableId; 6 import com.baomidou.mybatisplus.annotation.TableId;
8 import com.baomidou.mybatisplus.annotation.TableName; 7 import com.baomidou.mybatisplus.annotation.TableName;
@@ -30,57 +29,35 @@ public class AiKnowledgeDocumentDO extends BaseDO { @@ -30,57 +29,35 @@ public class AiKnowledgeDocumentDO extends BaseDO {
30 */ 29 */
31 private Long knowledgeId; 30 private Long knowledgeId;
32 /** 31 /**
33 - * 文件名称 32 + * 文档名称
34 */ 33 */
35 private String name; 34 private String name;
36 /** 35 /**
37 - * 内容  
38 - */  
39 - private String content;  
40 - /**  
41 * 文件 URL 36 * 文件 URL
42 */ 37 */
43 private String url; 38 private String url;
44 /** 39 /**
45 - * 文档 token 数量 40 + * 内容
46 */ 41 */
47 - private Integer tokens; 42 + private String content;
48 /** 43 /**
49 - * 文档字符数 44 + * 文档长度
50 */ 45 */
51 - private Integer wordCount;  
52 - 46 + private Integer contentLength;
53 47
54 - // ========== 自定义分段所用参数 ==========  
55 - // TODO @新:3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。  
56 /** 48 /**
57 - * 每个文本块的目标 token 数  
58 - */  
59 - private Integer defaultSegmentTokens;  
60 - /**  
61 - * 每个文本块的最小字符数  
62 - */  
63 - private Integer minSegmentWordCount;  
64 - /**  
65 - * 低于此值的块会被丢弃  
66 - */  
67 - private Integer minChunkLengthToEmbed;  
68 - /**  
69 - * 最大块数 49 + * 文档 token 数量
70 */ 50 */
71 - private Integer maxNumSegments; 51 + private Integer tokens;
72 /** 52 /**
73 - * 分块是否保留分隔符 53 + * 分片最大 Token 数
74 */ 54 */
75 - private Boolean keepSeparator;  
76 - // =================================== 55 + private Integer segmentMaxTokens;
77 56
78 /** 57 /**
79 - * 切片状态  
80 - * <p>  
81 - * 枚举 {@link AiKnowledgeDocumentStatusEnum} 58 + * 召回次数
82 */ 59 */
83 - private Integer sliceStatus; 60 + private Integer retrievalCount;
84 61
85 /** 62 /**
86 * 状态 63 * 状态
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
@@ -17,7 +17,10 @@ import lombok.Data; @@ -17,7 +17,10 @@ import lombok.Data;
17 @Data 17 @Data
18 public class AiKnowledgeSegmentDO extends BaseDO { 18 public class AiKnowledgeSegmentDO extends BaseDO {
19 19
20 - public static final String FIELD_KNOWLEDGE_ID = "knowledgeId"; 20 + /**
  21 + * 向量库的编号 - 空值
  22 + */
  23 + public static final String VECTOR_ID_EMPTY = "";
21 24
22 /** 25 /**
23 * 编号 26 * 编号
@@ -25,10 +28,6 @@ public class AiKnowledgeSegmentDO extends BaseDO { @@ -25,10 +28,6 @@ public class AiKnowledgeSegmentDO extends BaseDO {
25 @TableId 28 @TableId
26 private Long id; 29 private Long id;
27 /** 30 /**
28 - * 向量库的编号  
29 - */  
30 - private String vectorId;  
31 - /**  
32 * 知识库编号 31 * 知识库编号
33 * <p> 32 * <p>
34 * 关联 {@link AiKnowledgeDO#getId()} 33 * 关联 {@link AiKnowledgeDO#getId()}
@@ -45,13 +44,24 @@ public class AiKnowledgeSegmentDO extends BaseDO { @@ -45,13 +44,24 @@ public class AiKnowledgeSegmentDO extends BaseDO {
45 */ 44 */
46 private String content; 45 private String content;
47 /** 46 /**
48 - * 字符数 47 + * 切片内容长度
49 */ 48 */
50 - private Integer wordCount; 49 + private Integer contentLength;
  50 +
  51 + /**
  52 + * 向量库的编号
  53 + */
  54 + private String vectorId;
51 /** 55 /**
52 * token 数量 56 * token 数量
53 */ 57 */
54 private Integer tokens; 58 private Integer tokens;
  59 +
  60 + /**
  61 + * 召回次数
  62 + */
  63 + private Integer retrievalCount;
  64 +
55 /** 65 /**
56 * 状态 66 * 状态
57 * <p> 67 * <p>
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.mindmap; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.mindmap;
2 2
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
5 import com.baomidou.mybatisplus.annotation.KeySequence; 6 import com.baomidou.mybatisplus.annotation.KeySequence;
6 import com.baomidou.mybatisplus.annotation.TableId; 7 import com.baomidou.mybatisplus.annotation.TableId;
7 import com.baomidou.mybatisplus.annotation.TableName; 8 import com.baomidou.mybatisplus.annotation.TableName;
@@ -37,6 +38,12 @@ public class AiMindMapDO extends BaseDO { @@ -37,6 +38,12 @@ public class AiMindMapDO extends BaseDO {
37 */ 38 */
38 private String platform; 39 private String platform;
39 /** 40 /**
  41 + * 模型编号
  42 + *
  43 + * 关联 {@link AiModelDO#getId()}
  44 + */
  45 + private Long modelId;
  46 + /**
40 * 模型 47 * 模型
41 */ 48 */
42 private String model; 49 private String model;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java
@@ -16,7 +16,6 @@ import lombok.*; @@ -16,7 +16,6 @@ import lombok.*;
16 @TableName("ai_api_key") 16 @TableName("ai_api_key")
17 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 17 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
18 @Data 18 @Data
19 -@EqualsAndHashCode(callSuper = true)  
20 @Builder 19 @Builder
21 @NoArgsConstructor 20 @NoArgsConstructor
22 @AllArgsConstructor 21 @AllArgsConstructor
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java
@@ -2,11 +2,16 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.model; @@ -2,11 +2,16 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.model;
2 2
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
  6 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
5 import com.baomidou.mybatisplus.annotation.KeySequence; 7 import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableField;
6 import com.baomidou.mybatisplus.annotation.TableId; 9 import com.baomidou.mybatisplus.annotation.TableId;
7 import com.baomidou.mybatisplus.annotation.TableName; 10 import com.baomidou.mybatisplus.annotation.TableName;
8 import lombok.*; 11 import lombok.*;
9 12
  13 +import java.util.List;
  14 +
10 /** 15 /**
11 * AI 聊天角色 DO 16 * AI 聊天角色 DO
12 * 17 *
@@ -16,7 +21,6 @@ import lombok.*; @@ -16,7 +21,6 @@ import lombok.*;
16 @TableName(value = "ai_chat_role", autoResultMap = true) 21 @TableName(value = "ai_chat_role", autoResultMap = true)
17 @KeySequence("ai_chat_role_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 22 @KeySequence("ai_chat_role_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
18 @Data 23 @Data
19 -@EqualsAndHashCode(callSuper = true)  
20 @Builder 24 @Builder
21 @NoArgsConstructor 25 @NoArgsConstructor
22 @AllArgsConstructor 26 @AllArgsConstructor
@@ -58,11 +62,26 @@ public class AiChatRoleDO extends BaseDO { @@ -58,11 +62,26 @@ public class AiChatRoleDO extends BaseDO {
58 /** 62 /**
59 * 模型编号 63 * 模型编号
60 * 64 *
61 - * 关联 {@link AiChatModelDO#getId()} 字段 65 + * 关联 {@link AiModelDO#getId()} 字段
62 */ 66 */
63 private Long modelId; 67 private Long modelId;
64 68
65 /** 69 /**
  70 + * 引用的知识库编号列表
  71 + *
  72 + * 关联 {@link AiKnowledgeDO#getId()} 字段
  73 + */
  74 + @TableField(typeHandler = LongListTypeHandler.class)
  75 + private List<Long> knowledgeIds;
  76 + /**
  77 + * 引用的工具编号列表
  78 + *
  79 + * 关联 {@link AiToolDO#getId()} 字段
  80 + */
  81 + @TableField(typeHandler = LongListTypeHandler.class)
  82 + private List<Long> toolIds;
  83 +
  84 + /**
66 * 是否公开 85 * 是否公开
67 * 86 *
68 * 1. true - 公开;由管理员在【角色管理】所创建 87 * 1. true - 公开;由管理员在【角色管理】所创建
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java
1 package cn.iocoder.yudao.module.ai.dal.dataobject.model; 1 package cn.iocoder.yudao.module.ai.dal.dataobject.model;
2 2
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 6 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
@@ -9,21 +10,20 @@ import com.baomidou.mybatisplus.annotation.TableName; @@ -9,21 +10,20 @@ import com.baomidou.mybatisplus.annotation.TableName;
9 import lombok.*; 10 import lombok.*;
10 11
11 /** 12 /**
12 - * AI 聊天模型 DO 13 + * AI 模型 DO
13 * 14 *
14 - * 默认聊天模型:{@link #status} 为开启,并且 {@link #sort} 排序第一 15 + * 默认模型:{@link #status} 为开启,并且 {@link #sort} 排序第一
15 * 16 *
16 * @author fansili 17 * @author fansili
17 * @since 2024/4/24 19:39 18 * @since 2024/4/24 19:39
18 */ 19 */
19 -@TableName("ai_chat_model")  
20 -@KeySequence("ai_chat_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 20 +@TableName("ai_model")
  21 +@KeySequence("ai_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
21 @Data 22 @Data
22 -@EqualsAndHashCode(callSuper = true)  
23 @Builder 23 @Builder
24 @NoArgsConstructor 24 @NoArgsConstructor
25 @AllArgsConstructor 25 @AllArgsConstructor
26 -public class AiChatModelDO extends BaseDO { 26 +public class AiModelDO extends BaseDO {
27 27
28 /** 28 /**
29 * 编号 29 * 编号
@@ -50,6 +50,12 @@ public class AiChatModelDO extends BaseDO { @@ -50,6 +50,12 @@ public class AiChatModelDO extends BaseDO {
50 * 枚举 {@link AiPlatformEnum} 50 * 枚举 {@link AiPlatformEnum}
51 */ 51 */
52 private String platform; 52 private String platform;
  53 + /**
  54 + * 类型
  55 + *
  56 + * 枚举 {@link AiModelTypeEnum}
  57 + */
  58 + private Integer type;
53 59
54 /** 60 /**
55 * 排序值 61 * 排序值
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiToolDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.dataobject.model;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.ai.service.model.tool.DirectoryListToolFunction;
  5 +import cn.iocoder.yudao.module.ai.service.model.tool.WeatherQueryToolFunction;
  6 +import com.baomidou.mybatisplus.annotation.KeySequence;
  7 +import com.baomidou.mybatisplus.annotation.TableId;
  8 +import com.baomidou.mybatisplus.annotation.TableName;
  9 +import lombok.*;
  10 +
  11 +/**
  12 + * AI 工具 DO
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@TableName("ai_tool")
  17 +@KeySequence("ai_tool_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  18 +@Data
  19 +@Builder
  20 +@NoArgsConstructor
  21 +@AllArgsConstructor
  22 +public class AiToolDO extends BaseDO {
  23 +
  24 + /**
  25 + * 工具编号
  26 + */
  27 + @TableId
  28 + private Long id;
  29 + /**
  30 + * 工具名称
  31 + *
  32 + * 对应 Bean 的名字,例如说:
  33 + * 1. {@link DirectoryListToolFunction} 的 Bean 名字是 directory_list
  34 + * 2. {@link WeatherQueryToolFunction} 的 Bean 名字是 weather_query
  35 + */
  36 + private String name;
  37 + /**
  38 + * 工具描述
  39 + */
  40 + private String description;
  41 + /**
  42 + * 状态
  43 + *
  44 + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
  45 + */
  46 + private Integer status;
  47 +
  48 +}
0 \ No newline at end of file 49 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
@@ -84,6 +84,7 @@ public class AiMusicDO extends BaseDO { @@ -84,6 +84,7 @@ public class AiMusicDO extends BaseDO {
84 * 枚举 {@link AiPlatformEnum} 84 * 枚举 {@link AiPlatformEnum}
85 */ 85 */
86 private String platform; 86 private String platform;
  87 + // TODO @芋艿:modelId?
87 /** 88 /**
88 * 模型 89 * 模型
89 */ 90 */
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.write; @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.write;
2 2
3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  6 +import cn.iocoder.yudao.module.ai.enums.DictTypeConstants;
5 import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum; 7 import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum;
6 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
7 import com.baomidou.mybatisplus.annotation.TableId; 9 import com.baomidou.mybatisplus.annotation.TableId;
@@ -45,6 +47,12 @@ public class AiWriteDO extends BaseDO { @@ -45,6 +47,12 @@ public class AiWriteDO extends BaseDO {
45 */ 47 */
46 private String platform; 48 private String platform;
47 /** 49 /**
  50 + * 模型编号
  51 + *
  52 + * 关联 {@link AiModelDO#getId()}
  53 + */
  54 + private Long modelId;
  55 + /**
48 * 模型 56 * 模型
49 */ 57 */
50 private String model; 58 private String model;
@@ -66,25 +74,25 @@ public class AiWriteDO extends BaseDO { @@ -66,25 +74,25 @@ public class AiWriteDO extends BaseDO {
66 /** 74 /**
67 * 长度提示词 75 * 长度提示词
68 * 76 *
69 - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_LENGTH} 77 + * 字典:{@link DictTypeConstants#AI_WRITE_LENGTH}
70 */ 78 */
71 private Integer length; 79 private Integer length;
72 /** 80 /**
73 * 格式提示词 81 * 格式提示词
74 * 82 *
75 - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_FORMAT} 83 + * 字典:{@link DictTypeConstants#AI_WRITE_FORMAT}
76 */ 84 */
77 private Integer format; 85 private Integer format;
78 /** 86 /**
79 * 语气提示词 87 * 语气提示词
80 * 88 *
81 - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_TONE} 89 + * 字典:{@link DictTypeConstants#AI_WRITE_TONE}
82 */ 90 */
83 private Integer tone; 91 private Integer tone;
84 /** 92 /**
85 * 语言提示词 93 * 语言提示词
86 * 94 *
87 - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_LANGUAGE} 95 + * 字典:{@link DictTypeConstants#AI_WRITE_LANGUAGE}
88 */ 96 */
89 private Integer language; 97 private Integer language;
90 98
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java
@@ -5,10 +5,14 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -5,10 +5,14 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
9 10
  11 +import java.util.Collection;
  12 +import java.util.List;
  13 +
10 /** 14 /**
11 - * AI 知识库-文档 Mapper 15 + * AI 知识库文档 Mapper
12 * 16 *
13 * @author xiaoxin 17 * @author xiaoxin
14 */ 18 */
@@ -17,8 +21,19 @@ public interface AiKnowledgeDocumentMapper extends BaseMapperX&lt;AiKnowledgeDocume @@ -17,8 +21,19 @@ public interface AiKnowledgeDocumentMapper extends BaseMapperX&lt;AiKnowledgeDocume
17 21
18 default PageResult<AiKnowledgeDocumentDO> selectPage(AiKnowledgeDocumentPageReqVO reqVO) { 22 default PageResult<AiKnowledgeDocumentDO> selectPage(AiKnowledgeDocumentPageReqVO reqVO) {
19 return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeDocumentDO>() 23 return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeDocumentDO>()
  24 + .eqIfPresent(AiKnowledgeDocumentDO::getKnowledgeId, reqVO.getKnowledgeId())
20 .likeIfPresent(AiKnowledgeDocumentDO::getName, reqVO.getName()) 25 .likeIfPresent(AiKnowledgeDocumentDO::getName, reqVO.getName())
21 .orderByDesc(AiKnowledgeDocumentDO::getId)); 26 .orderByDesc(AiKnowledgeDocumentDO::getId));
22 } 27 }
23 28
  29 + default void updateRetrievalCountIncr(Collection<Long> ids) {
  30 + update(new LambdaUpdateWrapper<AiKnowledgeDocumentDO>()
  31 + .setSql(" retrieval_count = retrieval_count + 1")
  32 + .in(AiKnowledgeDocumentDO::getId, ids));
  33 + }
  34 +
  35 + default List<AiKnowledgeDocumentDO> selectListByStatus(Integer status) {
  36 + return selectList(AiKnowledgeDocumentDO::getStatus, status);
  37 + }
  38 +
24 } 39 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
1 package cn.iocoder.yudao.module.ai.dal.mysql.knowledge; 1 package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
2 2
3 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -8,19 +7,26 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnow @@ -8,19 +7,26 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnow
8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
9 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
10 9
  10 +import java.util.List;
  11 +
11 /** 12 /**
12 - * AI 知识库基础信息 Mapper 13 + * AI 知识库 Mapper
13 * 14 *
14 * @author xiaoxin 15 * @author xiaoxin
15 */ 16 */
16 @Mapper 17 @Mapper
17 public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> { 18 public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> {
18 19
19 - default PageResult<AiKnowledgeDO> selectPage(Long userId, AiKnowledgePageReqVO pageReqVO) { 20 + default PageResult<AiKnowledgeDO> selectPage(AiKnowledgePageReqVO pageReqVO) {
20 return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>() 21 return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>()
21 - .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus())  
22 .likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName()) 22 .likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName())
23 - .and(e -> e.apply("FIND_IN_SET(" + userId + ",visibility_permissions)").or(m -> m.apply("FIND_IN_SET(-1,visibility_permissions)"))) 23 + .eqIfPresent(AiKnowledgeDO::getStatus, pageReqVO.getStatus())
  24 + .betweenIfPresent(AiKnowledgeDO::getCreateTime, pageReqVO.getCreateTime())
24 .orderByDesc(AiKnowledgeDO::getId)); 25 .orderByDesc(AiKnowledgeDO::getId));
25 } 26 }
  27 +
  28 + default List<AiKnowledgeDO> selectListByStatus(Integer status) {
  29 + return selectList(AiKnowledgeDO::getStatus, status);
  30 + }
  31 +
26 } 32 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java
@@ -3,14 +3,19 @@ package cn.iocoder.yudao.module.ai.dal.mysql.knowledge; @@ -3,14 +3,19 @@ package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  10 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  11 +import com.github.yulichang.wrapper.MPJLambdaWrapper;
8 import org.apache.ibatis.annotations.Mapper; 12 import org.apache.ibatis.annotations.Mapper;
9 13
  14 +import java.util.Collection;
10 import java.util.List; 15 import java.util.List;
11 16
12 /** 17 /**
13 - * AI 知识库-分片 Mapper 18 + * AI 知识库分片 Mapper
14 * 19 *
15 * @author xiaoxin 20 * @author xiaoxin
16 */ 21 */
@@ -20,8 +25,8 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment @@ -20,8 +25,8 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment
20 default PageResult<AiKnowledgeSegmentDO> selectPage(AiKnowledgeSegmentPageReqVO reqVO) { 25 default PageResult<AiKnowledgeSegmentDO> selectPage(AiKnowledgeSegmentPageReqVO reqVO) {
21 return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeSegmentDO>() 26 return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
22 .eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId()) 27 .eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId())
  28 + .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getContent())
23 .eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus()) 29 .eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus())
24 - .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword())  
25 .orderByDesc(AiKnowledgeSegmentDO::getId)); 30 .orderByDesc(AiKnowledgeSegmentDO::getId));
26 } 31 }
27 32
@@ -31,4 +36,32 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment @@ -31,4 +36,32 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment
31 .orderByDesc(AiKnowledgeSegmentDO::getId)); 36 .orderByDesc(AiKnowledgeSegmentDO::getId));
32 } 37 }
33 38
  39 + default List<AiKnowledgeSegmentDO> selectListByDocumentId(Long documentId) {
  40 + return selectList(new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
  41 + .eq(AiKnowledgeSegmentDO::getDocumentId, documentId)
  42 + .orderByDesc(AiKnowledgeSegmentDO::getId));
  43 + }
  44 +
  45 + default List<AiKnowledgeSegmentDO> selectListByKnowledgeIdAndStatus(Long knowledgeId, Integer status) {
  46 + return selectList(AiKnowledgeSegmentDO::getKnowledgeId, knowledgeId,
  47 + AiKnowledgeSegmentDO::getStatus, status);
  48 + }
  49 +
  50 + default List<AiKnowledgeSegmentProcessRespVO> selectProcessList(Collection<Long> documentIds) {
  51 + MPJLambdaWrapper<AiKnowledgeSegmentDO> wrapper = new MPJLambdaWrapperX<AiKnowledgeSegmentDO>()
  52 + .selectAs(AiKnowledgeSegmentDO::getDocumentId, AiKnowledgeSegmentProcessRespVO::getDocumentId)
  53 + .selectCount(AiKnowledgeSegmentDO::getId, "count")
  54 + .select("COUNT(CASE WHEN vector_id > '" + AiKnowledgeSegmentDO.VECTOR_ID_EMPTY
  55 + + "' THEN 1 ELSE NULL END) AS embeddingCount")
  56 + .in(AiKnowledgeSegmentDO::getDocumentId, documentIds)
  57 + .groupBy(AiKnowledgeSegmentDO::getDocumentId);
  58 + return selectJoinList(AiKnowledgeSegmentProcessRespVO.class, wrapper);
  59 + }
  60 +
  61 + default void updateRetrievalCountIncrByIds(List<Long> ids) {
  62 + update(new LambdaUpdateWrapper<AiKnowledgeSegmentDO>()
  63 + .setSql(" retrieval_count = retrieval_count + 1")
  64 + .in(AiKnowledgeSegmentDO::getId, ids));
  65 + }
  66 +
34 } 67 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.mysql.model;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +
  11 +import javax.annotation.Nullable;
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * API 模型 Mapper
  16 + *
  17 + * @author fansili
  18 + */
  19 +@Mapper
  20 +public interface AiChatMapper extends BaseMapperX<AiModelDO> {
  21 +
  22 + default AiModelDO selectFirstByStatus(Integer type, Integer status) {
  23 + return selectOne(new QueryWrapperX<AiModelDO>()
  24 + .eq("type", type)
  25 + .eq("status", status)
  26 + .limitN(1)
  27 + .orderByAsc("sort"));
  28 + }
  29 +
  30 + default PageResult<AiModelDO> selectPage(AiModelPageReqVO reqVO) {
  31 + return selectPage(reqVO, new LambdaQueryWrapperX<AiModelDO>()
  32 + .likeIfPresent(AiModelDO::getName, reqVO.getName())
  33 + .eqIfPresent(AiModelDO::getModel, reqVO.getModel())
  34 + .eqIfPresent(AiModelDO::getPlatform, reqVO.getPlatform())
  35 + .orderByAsc(AiModelDO::getSort));
  36 + }
  37 +
  38 + default List<AiModelDO> selectListByStatusAndType(Integer status, Integer type,
  39 + @Nullable String platform) {
  40 + return selectList(new LambdaQueryWrapperX<AiModelDO>()
  41 + .eq(AiModelDO::getStatus, status)
  42 + .eq(AiModelDO::getType, type)
  43 + .eqIfPresent(AiModelDO::getPlatform, platform)
  44 + .orderByAsc(AiModelDO::getSort));
  45 + }
  46 +
  47 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatModelMapper.java deleted
1 -package cn.iocoder.yudao.module.ai.dal.mysql.model;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
4 -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;  
5 -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;  
6 -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;  
7 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;  
8 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
9 -import org.apache.ibatis.annotations.Mapper;  
10 -  
11 -import java.util.Collection;  
12 -import java.util.List;  
13 -  
14 -/**  
15 - * API 聊天模型 Mapper  
16 - *  
17 - * @author fansili  
18 - */  
19 -@Mapper  
20 -public interface AiChatModelMapper extends BaseMapperX<AiChatModelDO> {  
21 -  
22 - default AiChatModelDO selectFirstByStatus(Integer status) {  
23 - return selectOne(new QueryWrapperX<AiChatModelDO>()  
24 - .eq("status", status)  
25 - .limitN(1)  
26 - .orderByAsc("sort"));  
27 - }  
28 -  
29 - default PageResult<AiChatModelDO> selectPage(AiChatModelPageReqVO reqVO) {  
30 - return selectPage(reqVO, new LambdaQueryWrapperX<AiChatModelDO>()  
31 - .likeIfPresent(AiChatModelDO::getName, reqVO.getName())  
32 - .eqIfPresent(AiChatModelDO::getModel, reqVO.getModel())  
33 - .eqIfPresent(AiChatModelDO::getPlatform, reqVO.getPlatform())  
34 - .orderByAsc(AiChatModelDO::getSort));  
35 - }  
36 -  
37 - default List<AiChatModelDO> selectList(Integer status) {  
38 - return selectList(new LambdaQueryWrapperX<AiChatModelDO>()  
39 - .eq(AiChatModelDO::getStatus, status)  
40 - .orderByAsc(AiChatModelDO::getSort));  
41 - }  
42 -  
43 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiToolMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.mysql.model;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * AI 工具 Mapper
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Mapper
  18 +public interface AiToolMapper extends BaseMapperX<AiToolDO> {
  19 +
  20 + default PageResult<AiToolDO> selectPage(AiToolPageReqVO reqVO) {
  21 + return selectPage(reqVO, new LambdaQueryWrapperX<AiToolDO>()
  22 + .likeIfPresent(AiToolDO::getName, reqVO.getName())
  23 + .eqIfPresent(AiToolDO::getDescription, reqVO.getDescription())
  24 + .eqIfPresent(AiToolDO::getStatus, reqVO.getStatus())
  25 + .betweenIfPresent(AiToolDO::getCreateTime, reqVO.getCreateTime())
  26 + .orderByDesc(AiToolDO::getId));
  27 + }
  28 +
  29 + default List<AiToolDO> selectListByStatus(Integer status) {
  30 + return selectList(new LambdaQueryWrapperX<AiToolDO>()
  31 + .eq(AiToolDO::getStatus, status)
  32 + .orderByDesc(AiToolDO::getId));
  33 + }
  34 +
  35 +}
0 \ No newline at end of file 36 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java
1 /** 1 /**
2 - * crm 模块的 web 拓展封装 2 + * ai 模块的 web 拓展封装
3 */ 3 */
4 -package cn.iocoder.yudao.module.crm.framework.web; 4 +package cn.iocoder.yudao.module.ai.framework.web;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java
@@ -4,17 +4,18 @@ import cn.hutool.core.collection.CollUtil; @@ -4,17 +4,18 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.util.ObjUtil; 5 import cn.hutool.core.util.ObjUtil;
6 import cn.hutool.core.util.ObjectUtil; 6 import cn.hutool.core.util.ObjectUtil;
  7 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; 10 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
10 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; 11 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
11 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO; 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
12 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 13 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
13 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 14 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
14 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
15 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper; 16 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper;
16 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; 17 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
17 -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 18 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
18 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; 19 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
19 import jakarta.annotation.Resource; 20 import jakarta.annotation.Resource;
20 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
@@ -44,7 +45,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -44,7 +45,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
44 private AiChatConversationMapper chatConversationMapper; 45 private AiChatConversationMapper chatConversationMapper;
45 46
46 @Resource 47 @Resource
47 - private AiChatModelService chatModalService; 48 + private AiModelService modalService;
48 @Resource 49 @Resource
49 private AiChatRoleService chatRoleService; 50 private AiChatRoleService chatRoleService;
50 @Resource 51 @Resource
@@ -54,9 +55,9 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -54,9 +55,9 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
54 public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) { 55 public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) {
55 // 1.1 获得 AiChatRoleDO 聊天角色 56 // 1.1 获得 AiChatRoleDO 聊天角色
56 AiChatRoleDO role = createReqVO.getRoleId() != null ? chatRoleService.validateChatRole(createReqVO.getRoleId()) : null; 57 AiChatRoleDO role = createReqVO.getRoleId() != null ? chatRoleService.validateChatRole(createReqVO.getRoleId()) : null;
57 - // 1.2 获得 AiChatModelDO 聊天模型  
58 - AiChatModelDO model = role != null && role.getModelId() != null ? chatModalService.validateChatModel(role.getModelId())  
59 - : chatModalService.getRequiredDefaultChatModel(); 58 + // 1.2 获得 AiModelDO 聊天模型
  59 + AiModelDO model = role != null && role.getModelId() != null ? modalService.validateModel(role.getModelId())
  60 + : modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType());
60 Assert.notNull(model, "必须找到默认模型"); 61 Assert.notNull(model, "必须找到默认模型");
61 validateChatModel(model); 62 validateChatModel(model);
62 63
@@ -67,7 +68,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -67,7 +68,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
67 68
68 // 2. 创建 AiChatConversationDO 聊天对话 69 // 2. 创建 AiChatConversationDO 聊天对话
69 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false) 70 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false)
70 - .setModelId(model.getId()).setModel(model.getModel()).setKnowledgeId(createReqVO.getKnowledgeId()) 71 + .setModelId(model.getId()).setModel(model.getModel())
71 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()); 72 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts());
72 if (role != null) { 73 if (role != null) {
73 conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage()); 74 conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage());
@@ -86,9 +87,9 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -86,9 +87,9 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
86 throw exception(CHAT_CONVERSATION_NOT_EXISTS); 87 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
87 } 88 }
88 // 1.2 校验模型是否存在(修改模型的情况) 89 // 1.2 校验模型是否存在(修改模型的情况)
89 - AiChatModelDO model = null; 90 + AiModelDO model = null;
90 if (updateReqVO.getModelId() != null) { 91 if (updateReqVO.getModelId() != null) {
91 - model = chatModalService.validateChatModel(updateReqVO.getModelId()); 92 + model = modalService.validateModel(updateReqVO.getModelId());
92 } 93 }
93 94
94 // 1.3 校验知识库是否存在 95 // 1.3 校验知识库是否存在
@@ -139,10 +140,11 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -139,10 +140,11 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
139 chatConversationMapper.deleteById(id); 140 chatConversationMapper.deleteById(id);
140 } 141 }
141 142
142 - private void validateChatModel(AiChatModelDO model) { 143 + private void validateChatModel(AiModelDO model) {
143 if (ObjectUtil.isAllNotEmpty(model.getTemperature(), model.getMaxTokens(), model.getMaxContexts())) { 144 if (ObjectUtil.isAllNotEmpty(model.getTemperature(), model.getMaxTokens(), model.getMaxContexts())) {
144 return; 145 return;
145 } 146 }
  147 + Assert.equals(model.getType(), AiModelTypeEnum.CHAT.getType(), "模型类型不正确:" + model);
146 throw exception(CHAT_CONVERSATION_MODEL_ERROR); 148 throw exception(CHAT_CONVERSATION_MODEL_ERROR);
147 } 149 }
148 150
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -10,19 +10,24 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -10,19 +10,24 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 11 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
  13 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; 15 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
15 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;  
16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
17 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 17 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
18 -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;  
19 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 18 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  19 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  20 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  21 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
20 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; 22 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
21 -import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;  
22 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; 23 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
  24 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
23 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; 25 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
24 -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;  
25 -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 26 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  27 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
  28 +import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  29 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
  30 +import cn.iocoder.yudao.module.ai.service.model.AiToolService;
26 import jakarta.annotation.Resource; 31 import jakarta.annotation.Resource;
27 import lombok.extern.slf4j.Slf4j; 32 import lombok.extern.slf4j.Slf4j;
28 import org.springframework.ai.chat.messages.Message; 33 import org.springframework.ai.chat.messages.Message;
@@ -34,18 +39,19 @@ import org.springframework.ai.chat.model.ChatResponse; @@ -34,18 +39,19 @@ import org.springframework.ai.chat.model.ChatResponse;
34 import org.springframework.ai.chat.model.StreamingChatModel; 39 import org.springframework.ai.chat.model.StreamingChatModel;
35 import org.springframework.ai.chat.prompt.ChatOptions; 40 import org.springframework.ai.chat.prompt.ChatOptions;
36 import org.springframework.ai.chat.prompt.Prompt; 41 import org.springframework.ai.chat.prompt.Prompt;
37 -import org.springframework.ai.chat.prompt.PromptTemplate;  
38 import org.springframework.stereotype.Service; 42 import org.springframework.stereotype.Service;
39 import org.springframework.transaction.annotation.Transactional; 43 import org.springframework.transaction.annotation.Transactional;
40 import reactor.core.publisher.Flux; 44 import reactor.core.publisher.Flux;
41 45
42 import java.time.LocalDateTime; 46 import java.time.LocalDateTime;
43 import java.util.*; 47 import java.util.*;
  48 +import java.util.stream.Collectors;
44 49
45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 50 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
46 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; 51 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
47 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 52 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
48 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 53 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  54 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
49 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; 55 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS;
50 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST; 56 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST;
51 57
@@ -58,138 +64,199 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_N @@ -58,138 +64,199 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_N
58 @Slf4j 64 @Slf4j
59 public class AiChatMessageServiceImpl implements AiChatMessageService { 65 public class AiChatMessageServiceImpl implements AiChatMessageService {
60 66
  67 + /**
  68 + * 知识库转 {@link UserMessage} 的内容模版
  69 + */
  70 + private static final String KNOWLEDGE_USER_MESSAGE_TEMPLATE = "使用 <Reference></Reference> 标记中的内容作为本次对话的参考:\n\n" +
  71 + "%s\n\n" + // 多个 <Reference></Reference> 的拼接
  72 + "回答要求:\n- 避免提及你是从 <Reference></Reference> 获取的知识。";
  73 +
61 @Resource 74 @Resource
62 private AiChatMessageMapper chatMessageMapper; 75 private AiChatMessageMapper chatMessageMapper;
63 76
64 @Resource 77 @Resource
65 private AiChatConversationService chatConversationService; 78 private AiChatConversationService chatConversationService;
66 @Resource 79 @Resource
67 - private AiChatModelService chatModalService; 80 + private AiChatRoleService chatRoleService;
68 @Resource 81 @Resource
69 - private AiApiKeyService apiKeyService; 82 + private AiModelService modalService;
70 @Resource 83 @Resource
71 private AiKnowledgeSegmentService knowledgeSegmentService; 84 private AiKnowledgeSegmentService knowledgeSegmentService;
  85 + @Resource
  86 + private AiKnowledgeDocumentService knowledgeDocumentService;
  87 + @Resource
  88 + private AiToolService toolService;
72 89
73 @Transactional(rollbackFor = Exception.class) 90 @Transactional(rollbackFor = Exception.class)
74 public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { 91 public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) {
75 // 1.1 校验对话存在 92 // 1.1 校验对话存在
76 - AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); 93 + AiChatConversationDO conversation = chatConversationService
  94 + .validateChatConversationExists(sendReqVO.getConversationId());
77 if (ObjUtil.notEqual(conversation.getUserId(), userId)) { 95 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
78 throw exception(CHAT_CONVERSATION_NOT_EXISTS); 96 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
79 } 97 }
80 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); 98 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
81 // 1.2 校验模型 99 // 1.2 校验模型
82 - AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());  
83 - ChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); 100 + AiModelDO model = modalService.validateModel(conversation.getModelId());
  101 + ChatModel chatModel = modalService.getChatModel(model.getId());
  102 +
  103 + // 2. 知识库找回
  104 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(),
  105 + conversation);
84 106
85 - // 2. 插入 user 发送消息 107 + // 3. 插入 user 发送消息
86 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, 108 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
87 - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext()); 109 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext(),
  110 + null);
88 111
89 // 3.1 插入 assistant 接收消息 112 // 3.1 插入 assistant 接收消息
90 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, 113 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
91 - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());  
92 -  
93 - // 3.2 召回段落  
94 - List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId()); 114 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext(),
  115 + knowledgeSegments);
95 116
96 - // 3.3 创建 chat 需要的 Prompt  
97 - Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); 117 + // 3.2 创建 chat 需要的 Prompt
  118 + Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO);
98 ChatResponse chatResponse = chatModel.call(prompt); 119 ChatResponse chatResponse = chatModel.call(prompt);
99 120
100 - // 3.4 段式返回  
101 - String newContent = chatResponse.getResult().getOutput().getContent();  
102 - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)).setContent(newContent));  
103 - return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))  
104 - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); 121 + // 3.3 更新响应内容
  122 + String newContent = chatResponse.getResult().getOutput().getText();
  123 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent));
  124 + // 3.4 响应结果
  125 + List<AiChatMessageRespVO.KnowledgeSegment> segments = BeanUtils.toBean(knowledgeSegments,
  126 + AiChatMessageRespVO.KnowledgeSegment.class,
  127 + segment -> {
  128 + AiKnowledgeDocumentDO document = knowledgeDocumentService
  129 + .getKnowledgeDocument(segment.getDocumentId());
  130 + segment.setDocumentName(document != null ? document.getName() : null);
  131 + });
  132 + return new AiChatMessageSendRespVO()
  133 + .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  134 + .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class)
  135 + .setContent(newContent).setSegments(segments));
105 } 136 }
106 137
107 @Override 138 @Override
108 - public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) { 139 + public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO,
  140 + Long userId) {
109 // 1.1 校验对话存在 141 // 1.1 校验对话存在
110 - AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); 142 + AiChatConversationDO conversation = chatConversationService
  143 + .validateChatConversationExists(sendReqVO.getConversationId());
111 if (ObjUtil.notEqual(conversation.getUserId(), userId)) { 144 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
112 throw exception(CHAT_CONVERSATION_NOT_EXISTS); 145 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
113 } 146 }
114 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); 147 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
115 // 1.2 校验模型 148 // 1.2 校验模型
116 - AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());  
117 - StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); 149 + AiModelDO model = modalService.validateModel(conversation.getModelId());
  150 + StreamingChatModel chatModel = modalService.getChatModel(model.getId());
118 151
119 - // 2. 插入 user 发送消息 152 + // 2. 知识库找回
  153 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(),
  154 + conversation);
  155 +
  156 + // 3. 插入 user 发送消息
120 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, 157 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
121 - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext()); 158 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext(),
  159 + null);
122 160
123 - // 3.1 插入 assistant 接收消息 161 + // 4.1 插入 assistant 接收消息
124 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, 162 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
125 - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());  
126 -  
127 -  
128 - // 3.2 召回段落  
129 - List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId()); 163 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext(),
  164 + knowledgeSegments);
130 165
131 - // 3.3 构建 Prompt,并进行调用  
132 - Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); 166 + // 4.2 构建 Prompt,并进行调用
  167 + Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO);
133 Flux<ChatResponse> streamResponse = chatModel.stream(prompt); 168 Flux<ChatResponse> streamResponse = chatModel.stream(prompt);
134 169
135 - // 3.4 流式返回  
136 - // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 170 + // 4.3 流式返回
137 StringBuffer contentBuffer = new StringBuffer(); 171 StringBuffer contentBuffer = new StringBuffer();
138 return streamResponse.map(chunk -> { 172 return streamResponse.map(chunk -> {
139 - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; 173 + // 处理知识库的返回,只有首次才有
  174 + List<AiChatMessageRespVO.KnowledgeSegment> segments = null;
  175 + if (StrUtil.isEmpty(contentBuffer)) {
  176 + segments = BeanUtils.toBean(knowledgeSegments, AiChatMessageRespVO.KnowledgeSegment.class,
  177 + segment -> TenantUtils.executeIgnore(() -> {
  178 + AiKnowledgeDocumentDO document = knowledgeDocumentService
  179 + .getKnowledgeDocument(segment.getDocumentId());
  180 + segment.setDocumentName(document != null ? document.getName() : null);
  181 + }));
  182 + }
  183 + // 响应结果
  184 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
140 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 185 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
141 contentBuffer.append(newContent); 186 contentBuffer.append(newContent);
142 - // 响应结果  
143 - return success(new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))  
144 - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent))); 187 + return success(new AiChatMessageSendRespVO()
  188 + .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  189 + .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class)
  190 + .setContent(newContent).setSegments(segments)));
145 }).doOnComplete(() -> { 191 }).doOnComplete(() -> {
146 // 忽略租户,因为 Flux 异步无法透传租户 192 // 忽略租户,因为 Flux 异步无法透传租户
147 - TenantUtils.executeIgnore(() ->  
148 - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId))  
149 - .setContent(contentBuffer.toString()))); 193 + TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(
  194 + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())));
150 }).doOnError(throwable -> { 195 }).doOnError(throwable -> {
151 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); 196 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
152 // 忽略租户,因为 Flux 异步无法透传租户 197 // 忽略租户,因为 Flux 异步无法透传租户
153 - TenantUtils.executeIgnore(() ->  
154 - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()))); 198 + TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(
  199 + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())));
155 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); 200 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
156 } 201 }
157 202
158 - private List<AiKnowledgeSegmentDO> recallSegment(String content, Long knowledgeId) {  
159 - if (Objects.isNull(knowledgeId)) { 203 + private List<AiKnowledgeSegmentSearchRespBO> recallKnowledgeSegment(String content,
  204 + AiChatConversationDO conversation) {
  205 + // 1. 查询聊天角色
  206 + if (conversation == null || conversation.getRoleId() == null) {
  207 + return Collections.emptyList();
  208 + }
  209 + AiChatRoleDO role = chatRoleService.getChatRole(conversation.getRoleId());
  210 + if (role == null || CollUtil.isEmpty(role.getKnowledgeIds())) {
160 return Collections.emptyList(); 211 return Collections.emptyList();
161 } 212 }
162 - return knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content));  
163 - }  
164 -  
165 - private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,List<AiKnowledgeSegmentDO> segmentList,  
166 - AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) {  
167 - // 1. 构建 Prompt Message 列表  
168 - List<Message> chatMessages = new ArrayList<>();  
169 213
170 - // 1.1 召回内容消息构建  
171 - if (CollUtil.isNotEmpty(segmentList)) {  
172 - PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage());  
173 - StringBuilder infoBuilder = StrUtil.builder();  
174 - segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent()));  
175 - Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString()));  
176 - chatMessages.add(message); 214 + // 2. 遍历找回
  215 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = new ArrayList<>();
  216 + for (Long knowledgeId : role.getKnowledgeIds()) {
  217 + knowledgeSegments.addAll(knowledgeSegmentService.searchKnowledgeSegment(new AiKnowledgeSegmentSearchReqBO()
  218 + .setKnowledgeId(knowledgeId).setContent(content)));
177 } 219 }
  220 + return knowledgeSegments;
  221 + }
178 222
179 - // 1.2 system context 角色设定 223 + private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,
  224 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments,
  225 + AiModelDO model, AiChatMessageSendReqVO sendReqVO) {
  226 + List<Message> chatMessages = new ArrayList<>();
  227 + // 1.1 System Context 角色设定
180 if (StrUtil.isNotBlank(conversation.getSystemMessage())) { 228 if (StrUtil.isNotBlank(conversation.getSystemMessage())) {
181 chatMessages.add(new SystemMessage(conversation.getSystemMessage())); 229 chatMessages.add(new SystemMessage(conversation.getSystemMessage()));
182 } 230 }
183 - // 1.3 history message 历史消息 231 +
  232 + // 1.2 历史 history message 历史消息
184 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO); 233 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO);
185 - contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));  
186 - // 1.4 user message 新发送消息 234 + contextMessages
  235 + .forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));
  236 +
  237 + // 1.3 当前 user message 新发送消息
187 chatMessages.add(new UserMessage(sendReqVO.getContent())); 238 chatMessages.add(new UserMessage(sendReqVO.getContent()));
188 239
189 - // 2. 构建 ChatOptions 对象 240 + // 1.4 知识库,通过 UserMessage 实现
  241 + if (CollUtil.isNotEmpty(knowledgeSegments)) {
  242 + String reference = knowledgeSegments.stream()
  243 + .map(segment -> "<Reference>" + segment.getContent() + "</Reference>")
  244 + .collect(Collectors.joining("\n\n"));
  245 + chatMessages.add(new UserMessage(String.format(KNOWLEDGE_USER_MESSAGE_TEMPLATE, reference)));
  246 + }
  247 +
  248 + // 2.1 查询 tool 工具
  249 + Set<String> toolNames = null;
  250 + if (conversation.getRoleId() != null) {
  251 + AiChatRoleDO chatRole = chatRoleService.getChatRole(conversation.getRoleId());
  252 + if (chatRole != null && CollUtil.isNotEmpty(chatRole.getToolIds())) {
  253 + toolNames = convertSet(toolService.getToolList(chatRole.getToolIds()), AiToolDO::getName);
  254 + }
  255 + }
  256 + // 2.2 构建 ChatOptions 对象
190 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 257 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
191 ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), 258 ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(),
192 - conversation.getTemperature(), conversation.getMaxTokens()); 259 + conversation.getTemperature(), conversation.getMaxTokens(), toolNames);
193 return new Prompt(chatMessages, chatOptions); 260 return new Prompt(chatMessages, chatOptions);
194 } 261 }
195 262
@@ -204,8 +271,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -204,8 +271,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
204 * @return 消息上下文 271 * @return 消息上下文
205 */ 272 */
206 private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, 273 private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages,
207 - AiChatConversationDO conversation,  
208 - AiChatMessageSendReqVO sendReqVO) { 274 + AiChatConversationDO conversation,
  275 + AiChatMessageSendReqVO sendReqVO) {
209 if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) { 276 if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) {
210 return Collections.emptyList(); 277 return Collections.emptyList();
211 } 278 }
@@ -216,7 +283,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -216,7 +283,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
216 continue; 283 continue;
217 } 284 }
218 AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); 285 AiChatMessageDO userMessage = CollUtil.get(messages, i - 1);
219 - if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) 286 + if (userMessage == null
  287 + || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId())
220 || StrUtil.isEmpty(assistantMessage.getContent())) { 288 || StrUtil.isEmpty(assistantMessage.getContent())) {
221 continue; 289 continue;
222 } 290 }
@@ -233,11 +301,13 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -233,11 +301,13 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
233 } 301 }
234 302
235 private AiChatMessageDO createChatMessage(Long conversationId, Long replyId, 303 private AiChatMessageDO createChatMessage(Long conversationId, Long replyId,
236 - AiChatModelDO model, Long userId, Long roleId,  
237 - MessageType messageType, String content, Boolean useContext) { 304 + AiModelDO model, Long userId, Long roleId,
  305 + MessageType messageType, String content, Boolean useContext,
  306 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments) {
238 AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId) 307 AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId)
239 .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId) 308 .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId)
240 - .setType(messageType.getValue()).setContent(content).setUseContext(useContext); 309 + .setType(messageType.getValue()).setContent(content).setUseContext(useContext)
  310 + .setSegmentIds(convertList(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getId));
241 message.setCreateTime(LocalDateTime.now()); 311 message.setCreateTime(LocalDateTime.now());
242 chatMessageMapper.insert(message); 312 chatMessageMapper.insert(message);
243 return message; 313 return message;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.service.image; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.service.image;
3 import cn.hutool.core.bean.BeanUtil; 3 import cn.hutool.core.bean.BeanUtil;
4 import cn.hutool.core.codec.Base64; 4 import cn.hutool.core.codec.Base64;
5 import cn.hutool.core.collection.CollUtil; 5 import cn.hutool.core.collection.CollUtil;
  6 +import cn.hutool.core.lang.Assert;
6 import cn.hutool.core.map.MapUtil; 7 import cn.hutool.core.map.MapUtil;
7 import cn.hutool.core.util.ObjUtil; 8 import cn.hutool.core.util.ObjUtil;
8 import cn.hutool.core.util.StrUtil; 9 import cn.hutool.core.util.StrUtil;
@@ -12,15 +13,19 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; @@ -12,15 +13,19 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
12 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 13 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
13 import cn.iocoder.yudao.framework.common.pojo.PageResult; 14 import cn.iocoder.yudao.framework.common.pojo.PageResult;
14 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 15 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
15 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*; 16 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
  17 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
  18 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePublicPageReqVO;
  19 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO;
16 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; 20 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
17 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; 21 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
18 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 22 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
  23 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
19 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; 24 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
20 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; 25 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
21 -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 26 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
22 import cn.iocoder.yudao.module.infra.api.file.FileApi; 27 import cn.iocoder.yudao.module.infra.api.file.FileApi;
23 -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesOptions; 28 +import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
24 import jakarta.annotation.Resource; 29 import jakarta.annotation.Resource;
25 import lombok.extern.slf4j.Slf4j; 30 import lombok.extern.slf4j.Slf4j;
26 import org.springframework.ai.image.ImageModel; 31 import org.springframework.ai.image.ImageModel;
@@ -55,13 +60,13 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; @@ -55,13 +60,13 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
55 public class AiImageServiceImpl implements AiImageService { 60 public class AiImageServiceImpl implements AiImageService {
56 61
57 @Resource 62 @Resource
58 - private AiImageMapper imageMapper; 63 + private AiModelService modelService;
59 64
60 @Resource 65 @Resource
61 - private FileApi fileApi; 66 + private AiImageMapper imageMapper;
62 67
63 @Resource 68 @Resource
64 - private AiApiKeyService apiKeyService; 69 + private FileApi fileApi;
65 70
66 @Override 71 @Override
67 public PageResult<AiImageDO> getImagePageMy(Long userId, AiImagePageReqVO pageReqVO) { 72 public PageResult<AiImageDO> getImagePageMy(Long userId, AiImagePageReqVO pageReqVO) {
@@ -88,23 +93,31 @@ public class AiImageServiceImpl implements AiImageService { @@ -88,23 +93,31 @@ public class AiImageServiceImpl implements AiImageService {
88 93
89 @Override 94 @Override
90 public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) { 95 public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) {
91 - // 1. 保存数据库  
92 - AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)  
93 - .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); 96 + // 1. 校验模型
  97 + AiModelDO model = modelService.validateModel(drawReqVO.getModelId());
  98 +
  99 + // 2. 保存数据库
  100 + AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId)
  101 + .setPlatform(model.getPlatform()).setModelId(model.getId()).setModel(model.getModel())
  102 + .setPublicStatus(false).setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
94 imageMapper.insert(image); 103 imageMapper.insert(image);
95 - // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果  
96 - getSelf().executeDrawImage(image, drawReqVO); 104 +
  105 + // 3. 异步绘制,后续前端通过返回的 id 进行轮询结果
  106 + getSelf().executeDrawImage(image, drawReqVO, model);
97 return image.getId(); 107 return image.getId();
98 } 108 }
99 109
100 @Async 110 @Async
101 - public void executeDrawImage(AiImageDO image, AiImageDrawReqVO req) { 111 + public void executeDrawImage(AiImageDO image, AiImageDrawReqVO reqVO, AiModelDO model) {
102 try { 112 try {
103 // 1.1 构建请求 113 // 1.1 构建请求
104 - ImageOptions request = buildImageOptions(req); 114 + ImageOptions request = buildImageOptions(reqVO, model);
105 // 1.2 执行请求 115 // 1.2 执行请求
106 - ImageModel imageModel = apiKeyService.getImageModel(AiPlatformEnum.validatePlatform(req.getPlatform()));  
107 - ImageResponse response = imageModel.call(new ImagePrompt(req.getPrompt(), request)); 116 + ImageModel imageModel = modelService.getImageModel(model.getId());
  117 + ImageResponse response = imageModel.call(new ImagePrompt(reqVO.getPrompt(), request));
  118 + if (response.getResult() == null) {
  119 + throw new IllegalArgumentException("生成结果为空");
  120 + }
108 121
109 // 2. 上传到文件服务 122 // 2. 上传到文件服务
110 String b64Json = response.getResult().getOutput().getB64Json(); 123 String b64Json = response.getResult().getOutput().getB64Json();
@@ -116,49 +129,49 @@ public class AiImageServiceImpl implements AiImageService { @@ -116,49 +129,49 @@ public class AiImageServiceImpl implements AiImageService {
116 imageMapper.updateById(new AiImageDO().setId(image.getId()).setStatus(AiImageStatusEnum.SUCCESS.getStatus()) 129 imageMapper.updateById(new AiImageDO().setId(image.getId()).setStatus(AiImageStatusEnum.SUCCESS.getStatus())
117 .setPicUrl(filePath).setFinishTime(LocalDateTime.now())); 130 .setPicUrl(filePath).setFinishTime(LocalDateTime.now()));
118 } catch (Exception ex) { 131 } catch (Exception ex) {
119 - log.error("[doDall][image({}) 生成异常]", image, ex); 132 + log.error("[executeDrawImage][image({}) 生成异常]", image, ex);
120 imageMapper.updateById(new AiImageDO().setId(image.getId()) 133 imageMapper.updateById(new AiImageDO().setId(image.getId())
121 .setStatus(AiImageStatusEnum.FAIL.getStatus()) 134 .setStatus(AiImageStatusEnum.FAIL.getStatus())
122 .setErrorMessage(ex.getMessage()).setFinishTime(LocalDateTime.now())); 135 .setErrorMessage(ex.getMessage()).setFinishTime(LocalDateTime.now()));
123 } 136 }
124 } 137 }
125 138
126 - private static ImageOptions buildImageOptions(AiImageDrawReqVO draw) {  
127 - if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) { 139 + private static ImageOptions buildImageOptions(AiImageDrawReqVO draw, AiModelDO model) {
  140 + if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) {
128 // https://platform.openai.com/docs/api-reference/images/create 141 // https://platform.openai.com/docs/api-reference/images/create
129 - return OpenAiImageOptions.builder().withModel(draw.getModel()) 142 + return OpenAiImageOptions.builder().withModel(model.getModel())
130 .withHeight(draw.getHeight()).withWidth(draw.getWidth()) 143 .withHeight(draw.getHeight()).withWidth(draw.getWidth())
131 .withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格 144 .withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格
132 .withResponseFormat("b64_json") 145 .withResponseFormat("b64_json")
133 .build(); 146 .build();
134 - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) { 147 + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) {
135 // https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage 148 // https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage
136 // https://platform.stability.ai/docs/api-reference#tag/Text-to-Image/operation/textToImage 149 // https://platform.stability.ai/docs/api-reference#tag/Text-to-Image/operation/textToImage
137 - return StabilityAiImageOptions.builder().withModel(draw.getModel())  
138 - .withHeight(draw.getHeight()).withWidth(draw.getWidth())  
139 - .withSeed(Long.valueOf(draw.getOptions().get("seed")))  
140 - .withCfgScale(Float.valueOf(draw.getOptions().get("scale")))  
141 - .withSteps(Integer.valueOf(draw.getOptions().get("steps")))  
142 - .withSampler(String.valueOf(draw.getOptions().get("sampler")))  
143 - .withStylePreset(String.valueOf(draw.getOptions().get("stylePreset")))  
144 - .withClipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset"))) 150 + return StabilityAiImageOptions.builder().model(model.getModel())
  151 + .height(draw.getHeight()).width(draw.getWidth())
  152 + .seed(Long.valueOf(draw.getOptions().get("seed")))
  153 + .cfgScale(Float.valueOf(draw.getOptions().get("scale")))
  154 + .steps(Integer.valueOf(draw.getOptions().get("steps")))
  155 + .sampler(String.valueOf(draw.getOptions().get("sampler")))
  156 + .stylePreset(String.valueOf(draw.getOptions().get("stylePreset")))
  157 + .clipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset")))
145 .build(); 158 .build();
146 - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) {  
147 - return TongYiImagesOptions.builder()  
148 - .withModel(draw.getModel()).withN(1) 159 + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) {
  160 + return DashScopeImageOptions.builder()
  161 + .withModel(model.getModel()).withN(1)
149 .withHeight(draw.getHeight()).withWidth(draw.getWidth()) 162 .withHeight(draw.getHeight()).withWidth(draw.getWidth())
150 .build(); 163 .build();
151 - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) { 164 + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
152 return QianFanImageOptions.builder() 165 return QianFanImageOptions.builder()
153 - .withModel(draw.getModel()).withN(1)  
154 - .withHeight(draw.getHeight()).withWidth(draw.getWidth()) 166 + .model(model.getModel()).N(1)
  167 + .height(draw.getHeight()).width(draw.getWidth())
155 .build(); 168 .build();
156 - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) { 169 + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) {
157 return ZhiPuAiImageOptions.builder() 170 return ZhiPuAiImageOptions.builder()
158 - .withModel(draw.getModel()) 171 + .model(model.getModel())
159 .build(); 172 .build();
160 } 173 }
161 - throw new IllegalArgumentException("不支持的 AI 平台:" + draw.getPlatform()); 174 + throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform());
162 } 175 }
163 176
164 @Override 177 @Override
@@ -205,52 +218,56 @@ public class AiImageServiceImpl implements AiImageService { @@ -205,52 +218,56 @@ public class AiImageServiceImpl implements AiImageService {
205 218
206 @Override 219 @Override
207 @Transactional(rollbackFor = Exception.class) 220 @Transactional(rollbackFor = Exception.class)
208 - public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO) {  
209 - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();  
210 - // 1. 保存数据库  
211 - AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false) 221 + public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO drawReqVO) {
  222 + // 1. 校验模型
  223 + AiModelDO model = modelService.validateModel(drawReqVO.getModelId());
  224 + Assert.equals(model.getPlatform(), AiPlatformEnum.MIDJOURNEY.getPlatform(), "平台不匹配");
  225 + MidjourneyApi midjourneyApi = modelService.getMidjourneyApi(model.getId());
  226 +
  227 + // 2. 保存数据库
  228 + AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)
212 .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()) 229 .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus())
213 - .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform()); 230 + .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform()).setModelId(model.getId()).setModel(model.getName());
214 imageMapper.insert(image); 231 imageMapper.insert(image);
215 232
216 - // 2. 调用 Midjourney Proxy 提交任务  
217 - List<String> base64Array = StrUtil.isBlank(reqVO.getReferImageUrl()) ? null :  
218 - Collections.singletonList("data:image/jpeg;base64,".concat(Base64.encode(HttpUtil.downloadBytes(reqVO.getReferImageUrl())))); 233 + // 3. 调用 Midjourney Proxy 提交任务
  234 + List<String> base64Array = StrUtil.isBlank(drawReqVO.getReferImageUrl()) ? null :
  235 + Collections.singletonList("data:image/jpeg;base64,".concat(Base64.encode(HttpUtil.downloadBytes(drawReqVO.getReferImageUrl()))));
219 MidjourneyApi.ImagineRequest imagineRequest = new MidjourneyApi.ImagineRequest( 236 MidjourneyApi.ImagineRequest imagineRequest = new MidjourneyApi.ImagineRequest(
220 - base64Array, reqVO.getPrompt(),null,  
221 - MidjourneyApi.ImagineRequest.buildState(reqVO.getWidth(),  
222 - reqVO.getHeight(), reqVO.getVersion(), reqVO.getModel())); 237 + base64Array, drawReqVO.getPrompt(),null,
  238 + MidjourneyApi.ImagineRequest.buildState(drawReqVO.getWidth(),
  239 + drawReqVO.getHeight(), drawReqVO.getVersion(), model.getModel()));
223 MidjourneyApi.SubmitResponse imagineResponse = midjourneyApi.imagine(imagineRequest); 240 MidjourneyApi.SubmitResponse imagineResponse = midjourneyApi.imagine(imagineRequest);
224 241
225 - // 3. 情况一【失败】:抛出业务异常 242 + // 4.1 情况一【失败】:抛出业务异常
226 if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) { 243 if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) {
227 String description = imagineResponse.description().contains("quota_not_enough") ? 244 String description = imagineResponse.description().contains("quota_not_enough") ?
228 "账户余额不足" : imagineResponse.description(); 245 "账户余额不足" : imagineResponse.description();
229 throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); 246 throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
230 } 247 }
231 248
232 - // 4. 情况二【成功】:更新 taskId 和参数 249 + // 4.2 情况二【成功】:更新 taskId 和参数
233 imageMapper.updateById(new AiImageDO().setId(image.getId()) 250 imageMapper.updateById(new AiImageDO().setId(image.getId())
234 - .setTaskId(imagineResponse.result()).setOptions(BeanUtil.beanToMap(reqVO))); 251 + .setTaskId(imagineResponse.result()).setOptions(BeanUtil.beanToMap(drawReqVO)));
235 return image.getId(); 252 return image.getId();
236 } 253 }
237 254
238 @Override 255 @Override
239 public Integer midjourneySync() { 256 public Integer midjourneySync() {
240 - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();  
241 // 1.1 获取 Midjourney 平台,状态在 “进行中” 的 image 257 // 1.1 获取 Midjourney 平台,状态在 “进行中” 的 image
242 - List<AiImageDO> imageList = imageMapper.selectListByStatusAndPlatform( 258 + List<AiImageDO> images = imageMapper.selectListByStatusAndPlatform(
243 AiImageStatusEnum.IN_PROGRESS.getStatus(), AiPlatformEnum.MIDJOURNEY.getPlatform()); 259 AiImageStatusEnum.IN_PROGRESS.getStatus(), AiPlatformEnum.MIDJOURNEY.getPlatform());
244 - if (CollUtil.isEmpty(imageList)) { 260 + if (CollUtil.isEmpty(images)) {
245 return 0; 261 return 0;
246 } 262 }
247 // 1.2 调用 Midjourney Proxy 获取任务进展 263 // 1.2 调用 Midjourney Proxy 获取任务进展
248 - List<MidjourneyApi.Notify> taskList = midjourneyApi.getTaskList(convertSet(imageList, AiImageDO::getTaskId)); 264 + MidjourneyApi midjourneyApi = modelService.getMidjourneyApi(images.get(0).getModelId());
  265 + List<MidjourneyApi.Notify> taskList = midjourneyApi.getTaskList(convertSet(images, AiImageDO::getTaskId));
249 Map<String, MidjourneyApi.Notify> taskMap = convertMap(taskList, MidjourneyApi.Notify::id); 266 Map<String, MidjourneyApi.Notify> taskMap = convertMap(taskList, MidjourneyApi.Notify::id);
250 267
251 // 2. 逐个处理,更新进展 268 // 2. 逐个处理,更新进展
252 int count = 0; 269 int count = 0;
253 - for (AiImageDO image : imageList) { 270 + for (AiImageDO image : images) {
254 MidjourneyApi.Notify notify = taskMap.get(image.getTaskId()); 271 MidjourneyApi.Notify notify = taskMap.get(image.getTaskId());
255 if (notify == null) { 272 if (notify == null) {
256 log.error("[midjourneySync][image({}) 查询不到进展]", image); 273 log.error("[midjourneySync][image({}) 查询不到进展]", image);
@@ -308,12 +325,12 @@ public class AiImageServiceImpl implements AiImageService { @@ -308,12 +325,12 @@ public class AiImageServiceImpl implements AiImageService {
308 325
309 @Override 326 @Override
310 public Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO) { 327 public Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO) {
311 - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();  
312 // 1.1 检查 image 328 // 1.1 检查 image
313 AiImageDO image = validateImageExists(reqVO.getId()); 329 AiImageDO image = validateImageExists(reqVO.getId());
314 if (ObjUtil.notEqual(userId, image.getUserId())) { 330 if (ObjUtil.notEqual(userId, image.getUserId())) {
315 throw exception(IMAGE_NOT_EXISTS); 331 throw exception(IMAGE_NOT_EXISTS);
316 } 332 }
  333 + MidjourneyApi midjourneyApi = modelService.getMidjourneyApi(image.getModelId());
317 // 1.2 检查 customId 334 // 1.2 检查 customId
318 MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(), 335 MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(),
319 buttonX -> buttonX.customId().equals(reqVO.getCustomId())); 336 buttonX -> buttonX.customId().equals(reqVO.getCustomId()));
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentService.java
1 package cn.iocoder.yudao.module.ai.service.knowledge; 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO;
4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO; 5 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
5 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
8 10
  11 +import java.util.Collection;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  16 +
9 /** 17 /**
10 - * AI 知识库-文档 Service 接口 18 + * AI 知识库文档 Service 接口
11 * 19 *
12 * @author xiaoxin 20 * @author xiaoxin
13 */ 21 */
14 public interface AiKnowledgeDocumentService { 22 public interface AiKnowledgeDocumentService {
15 23
16 /** 24 /**
17 - * 创建文档 25 + * 创建文档(单个)
18 * 26 *
19 * @param createReqVO 文档创建 Request VO 27 * @param createReqVO 文档创建 Request VO
20 * @return 文档编号 28 * @return 文档编号
21 */ 29 */
22 Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO); 30 Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO);
23 31
  32 + /**
  33 + * 创建文档(多个)
  34 + *
  35 + * @param createListReqVO 批量创建 Request VO
  36 + * @return 文档编号列表
  37 + */
  38 + List<Long> createKnowledgeDocumentList(AiKnowledgeDocumentCreateListReqVO createListReqVO);
24 39
25 /** 40 /**
26 * 获取文档分页 41 * 获取文档分页
@@ -31,9 +46,73 @@ public interface AiKnowledgeDocumentService { @@ -31,9 +46,73 @@ public interface AiKnowledgeDocumentService {
31 PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO); 46 PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO);
32 47
33 /** 48 /**
  49 + * 获取文档详情
  50 + *
  51 + * @param id 文档编号
  52 + * @return 文档详情
  53 + */
  54 + AiKnowledgeDocumentDO getKnowledgeDocument(Long id);
  55 +
  56 + /**
34 * 更新文档 57 * 更新文档
35 * 58 *
36 * @param reqVO 更新信息 59 * @param reqVO 更新信息
37 */ 60 */
38 void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO); 61 void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO);
  62 +
  63 + /**
  64 + * 更新文档状态
  65 + *
  66 + * @param reqVO 更新状态信息
  67 + */
  68 + void updateKnowledgeDocumentStatus(AiKnowledgeDocumentUpdateStatusReqVO reqVO);
  69 +
  70 + /**
  71 + * 更新文档检索次数(增加 +1)
  72 + *
  73 + * @param ids 文档编号列表
  74 + */
  75 + void updateKnowledgeDocumentRetrievalCountIncr(Collection<Long> ids);
  76 +
  77 + /**
  78 + * 删除文档
  79 + *
  80 + * @param id 文档编号
  81 + */
  82 + void deleteKnowledgeDocument(Long id);
  83 +
  84 + /**
  85 + * 校验文档是否存在
  86 + *
  87 + * @param id 文档编号
  88 + * @return 文档信息
  89 + */
  90 + AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id);
  91 +
  92 + /**
  93 + * 读取 URL 内容
  94 + *
  95 + * @param url URL
  96 + * @return 内容
  97 + */
  98 + String readUrl(String url);
  99 +
  100 + /**
  101 + * 获取文档列表
  102 + *
  103 + * @param ids 文档编号列表
  104 + * @return 文档列表
  105 + */
  106 + List<AiKnowledgeDocumentDO> getKnowledgeDocumentList(Collection<Long> ids);
  107 +
  108 + /**
  109 + * 获取文档 Map
  110 + *
  111 + * @param ids 文档编号列表
  112 + * @return 文档 Map
  113 + */
  114 + default Map<Long, AiKnowledgeDocumentDO> getKnowledgeDocumentMap(Collection<Long> ids) {
  115 + return convertMap(getKnowledgeDocumentList(ids), AiKnowledgeDocumentDO::getId);
  116 + }
  117 +
39 } 118 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.knowledge; 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.hutool.core.util.StrUtil;
4 import cn.hutool.http.HttpUtil; 6 import cn.hutool.http.HttpUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO;
9 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO; 11 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
10 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO; 12 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
  13 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO;
11 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
12 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
13 -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;  
14 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper; 16 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper;
15 -import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;  
16 -import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;  
17 import jakarta.annotation.Resource; 17 import jakarta.annotation.Resource;
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.ai.document.Document; 19 import org.springframework.ai.document.Document;
20 import org.springframework.ai.reader.tika.TikaDocumentReader; 20 import org.springframework.ai.reader.tika.TikaDocumentReader;
21 import org.springframework.ai.tokenizer.TokenCountEstimator; 21 import org.springframework.ai.tokenizer.TokenCountEstimator;
22 -import org.springframework.ai.transformer.splitter.TokenTextSplitter;  
23 -import org.springframework.ai.vectorstore.VectorStore; 22 +import org.springframework.context.annotation.Lazy;
24 import org.springframework.core.io.ByteArrayResource; 23 import org.springframework.core.io.ByteArrayResource;
25 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
26 import org.springframework.transaction.annotation.Transactional; 25 import org.springframework.transaction.annotation.Transactional;
27 26
  27 +import java.util.ArrayList;
  28 +import java.util.Collection;
  29 +import java.util.Collections;
28 import java.util.List; 30 import java.util.List;
29 31
30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
31 -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCUMENT_NOT_EXISTS; 33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  34 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
32 35
33 /** 36 /**
34 * AI 知识库文档 Service 实现类 37 * AI 知识库文档 Service 实现类
@@ -40,91 +43,172 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCU @@ -40,91 +43,172 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCU
40 public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentService { 43 public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentService {
41 44
42 @Resource 45 @Resource
43 - private AiKnowledgeDocumentMapper documentMapper;  
44 - @Resource  
45 - private AiKnowledgeSegmentMapper segmentMapper; 46 + private AiKnowledgeDocumentMapper knowledgeDocumentMapper;
46 47
47 @Resource 48 @Resource
48 private TokenCountEstimator tokenCountEstimator; 49 private TokenCountEstimator tokenCountEstimator;
  50 +
  51 + @Resource
  52 + private AiKnowledgeSegmentService knowledgeSegmentService;
49 @Resource 53 @Resource
  54 + @Lazy // 延迟加载,避免循环依赖
50 private AiKnowledgeService knowledgeService; 55 private AiKnowledgeService knowledgeService;
51 56
52 @Override 57 @Override
53 - @Transactional(rollbackFor = Exception.class)  
54 public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) { 58 public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
55 - // 0. 校验并获取向量存储实例  
56 - VectorStore vectorStore = knowledgeService.getVectorStoreById(createReqVO.getKnowledgeId()); 59 + // 1. 校验参数
  60 + knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
57 61
58 - // 1.1 下载文档  
59 - TikaDocumentReader loader = new TikaDocumentReader(downloadFile(createReqVO.getUrl()));  
60 - List<Document> documents = loader.get();  
61 - Document document = CollUtil.getFirst(documents);  
62 - // 1.2 文档记录入库  
63 - String content = document.getContent(); 62 + // 2. 下载文档
  63 + String content = readUrl(createReqVO.getUrl());
  64 +
  65 + // 3. 文档记录入库
64 AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class) 66 AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
65 - .setTokens(tokenCountEstimator.estimate(content)).setWordCount(content.length())  
66 - .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());  
67 - documentMapper.insert(documentDO);  
68 - Long documentId = documentDO.getId();  
69 - if (CollUtil.isEmpty(documents)) {  
70 - return documentId; 67 + .setContent(content).setContentLength(content.length()).setTokens(tokenCountEstimator.estimate(content))
  68 + .setStatus(CommonStatusEnum.ENABLE.getStatus());
  69 + knowledgeDocumentMapper.insert(documentDO);
  70 +
  71 + // 4. 文档切片入库(异步)
  72 + knowledgeSegmentService.createKnowledgeSegmentBySplitContentAsync(documentDO.getId(), content);
  73 + return documentDO.getId();
  74 + }
  75 +
  76 + @Override
  77 + public List<Long> createKnowledgeDocumentList(AiKnowledgeDocumentCreateListReqVO createListReqVO) {
  78 + // 1. 校验参数
  79 + knowledgeService.validateKnowledgeExists(createListReqVO.getKnowledgeId());
  80 +
  81 + // 2. 下载文档
  82 + List<String> contents = convertList(createListReqVO.getList(), document -> readUrl(document.getUrl()));
  83 +
  84 + // 3. 文档记录入库
  85 + List<AiKnowledgeDocumentDO> documentDOs = new ArrayList<>(createListReqVO.getList().size());
  86 + for (int i = 0; i < createListReqVO.getList().size(); i++) {
  87 + AiKnowledgeDocumentCreateListReqVO.Document documentVO = createListReqVO.getList().get(i);
  88 + String content = contents.get(i);
  89 + documentDOs.add(BeanUtils.toBean(documentVO, AiKnowledgeDocumentDO.class)
  90 + .setKnowledgeId(createListReqVO.getKnowledgeId())
  91 + .setContent(content).setContentLength(content.length())
  92 + .setTokens(tokenCountEstimator.estimate(content))
  93 + .setSegmentMaxTokens(createListReqVO.getSegmentMaxTokens())
  94 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
71 } 95 }
  96 + knowledgeDocumentMapper.insertBatch(documentDOs);
72 97
73 - // 2 构造文本分段器  
74 - TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultSegmentTokens(), createReqVO.getMinSegmentWordCount(), createReqVO.getMinChunkLengthToEmbed(),  
75 - createReqVO.getMaxNumSegments(), createReqVO.getKeepSeparator());  
76 - // 2.1 文档分段  
77 - List<Document> segments = tokenTextSplitter.apply(documents);  
78 - // 2.2 分段内容入库  
79 - List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,  
80 - segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId)  
81 - .setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())  
82 - .setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length())  
83 - .setStatus(CommonStatusEnum.ENABLE.getStatus()));  
84 - segmentMapper.insertBatch(segmentDOList);  
85 -  
86 - // 3. 向量化并存储  
87 - segments.forEach(segment -> segment.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId()));  
88 - vectorStore.add(segments);  
89 - return documentId; 98 + // 4. 批量创建文档切片(异步)
  99 + documentDOs.forEach(documentDO -> knowledgeSegmentService
  100 + .createKnowledgeSegmentBySplitContentAsync(documentDO.getId(), documentDO.getContent()));
  101 + return convertList(documentDOs, AiKnowledgeDocumentDO::getId);
90 } 102 }
91 103
92 @Override 104 @Override
93 public PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO) { 105 public PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO) {
94 - return documentMapper.selectPage(pageReqVO); 106 + return knowledgeDocumentMapper.selectPage(pageReqVO);
  107 + }
  108 +
  109 + @Override
  110 + public AiKnowledgeDocumentDO getKnowledgeDocument(Long id) {
  111 + return knowledgeDocumentMapper.selectById(id);
95 } 112 }
96 113
97 @Override 114 @Override
98 public void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO) { 115 public void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO) {
99 // 1. 校验文档是否存在 116 // 1. 校验文档是否存在
100 - validateKnowledgeDocumentExists(reqVO.getId()); 117 + AiKnowledgeDocumentDO oldDocument = validateKnowledgeDocumentExists(reqVO.getId());
  118 +
101 // 2. 更新文档 119 // 2. 更新文档
102 AiKnowledgeDocumentDO document = BeanUtils.toBean(reqVO, AiKnowledgeDocumentDO.class); 120 AiKnowledgeDocumentDO document = BeanUtils.toBean(reqVO, AiKnowledgeDocumentDO.class);
103 - documentMapper.updateById(document); 121 + knowledgeDocumentMapper.updateById(document);
  122 +
  123 + // 3. 如果处于开启状态,并且最大 tokens 发生变化,则 segment 需要重新索引
  124 + if (CommonStatusEnum.isEnable(oldDocument.getStatus())
  125 + && reqVO.getSegmentMaxTokens() != null
  126 + && ObjUtil.notEqual(reqVO.getSegmentMaxTokens(), oldDocument.getSegmentMaxTokens())) {
  127 + // 删除旧的文档切片
  128 + knowledgeSegmentService.deleteKnowledgeSegmentByDocumentId(reqVO.getId());
  129 + // 重新创建文档切片
  130 + knowledgeSegmentService.createKnowledgeSegmentBySplitContentAsync(reqVO.getId(), oldDocument.getContent());
  131 + }
104 } 132 }
105 133
106 - /**  
107 - * 校验文档是否存在  
108 - *  
109 - * @param id 文档编号  
110 - * @return 文档信息  
111 - */  
112 - private AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {  
113 - AiKnowledgeDocumentDO knowledgeDocument = documentMapper.selectById(id); 134 + @Override
  135 + public void updateKnowledgeDocumentStatus(AiKnowledgeDocumentUpdateStatusReqVO reqVO) {
  136 + // 1. 校验存在
  137 + AiKnowledgeDocumentDO document = validateKnowledgeDocumentExists(reqVO.getId());
  138 +
  139 + // 2. 更新状态
  140 + knowledgeDocumentMapper.updateById(new AiKnowledgeDocumentDO()
  141 + .setId(reqVO.getId()).setStatus(reqVO.getStatus()));
  142 +
  143 + // 3. 处理文档切片
  144 + if (CommonStatusEnum.isEnable(reqVO.getStatus())) {
  145 + knowledgeSegmentService.createKnowledgeSegmentBySplitContentAsync(reqVO.getId(), document.getContent());
  146 + } else {
  147 + knowledgeSegmentService.deleteKnowledgeSegmentByDocumentId(reqVO.getId());
  148 + }
  149 + }
  150 +
  151 + @Override
  152 + @Transactional(rollbackFor = Exception.class)
  153 + public void deleteKnowledgeDocument(Long id) {
  154 + // 1. 校验存在
  155 + validateKnowledgeDocumentExists(id);
  156 +
  157 + // 2. 删除
  158 + knowledgeDocumentMapper.deleteById(id);
  159 +
  160 + // 3. 删除对应的段落
  161 + knowledgeSegmentService.deleteKnowledgeSegmentByDocumentId(id);
  162 + }
  163 +
  164 + @Override
  165 + public void updateKnowledgeDocumentRetrievalCountIncr(Collection<Long> ids) {
  166 + if (CollUtil.isEmpty(ids)) {
  167 + return;
  168 + }
  169 + knowledgeDocumentMapper.updateRetrievalCountIncr(ids);
  170 + }
  171 +
  172 + @Override
  173 + public AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
  174 + AiKnowledgeDocumentDO knowledgeDocument = knowledgeDocumentMapper.selectById(id);
114 if (knowledgeDocument == null) { 175 if (knowledgeDocument == null) {
115 throw exception(KNOWLEDGE_DOCUMENT_NOT_EXISTS); 176 throw exception(KNOWLEDGE_DOCUMENT_NOT_EXISTS);
116 } 177 }
117 return knowledgeDocument; 178 return knowledgeDocument;
118 } 179 }
119 180
120 - private org.springframework.core.io.Resource downloadFile(String url) { 181 + @Override
  182 + public String readUrl(String url) {
  183 + // 下载文件
  184 + ByteArrayResource resource;
121 try { 185 try {
122 byte[] bytes = HttpUtil.downloadBytes(url); 186 byte[] bytes = HttpUtil.downloadBytes(url);
123 - return new ByteArrayResource(bytes); 187 + if (bytes.length == 0) {
  188 + throw exception(KNOWLEDGE_DOCUMENT_FILE_EMPTY);
  189 + }
  190 + resource = new ByteArrayResource(bytes);
124 } catch (Exception e) { 191 } catch (Exception e) {
125 - log.error("[downloadFile][url({}) 下载失败]", url, e);  
126 - throw new RuntimeException(e); 192 + log.error("[readUrl][url({}) 读取失败]", url, e);
  193 + throw exception(KNOWLEDGE_DOCUMENT_FILE_DOWNLOAD_FAIL);
  194 + }
  195 +
  196 + // 读取文件
  197 + TikaDocumentReader loader = new TikaDocumentReader(resource);
  198 + List<Document> documents = loader.get();
  199 + Document document = CollUtil.getFirst(documents);
  200 + if (document == null || StrUtil.isEmpty(document.getText())) {
  201 + throw exception(KNOWLEDGE_DOCUMENT_FILE_READ_FAIL);
  202 + }
  203 + return document.getText();
  204 + }
  205 +
  206 + @Override
  207 + public List<AiKnowledgeDocumentDO> getKnowledgeDocumentList(Collection<Long> ids) {
  208 + if (CollUtil.isEmpty(ids)) {
  209 + return Collections.emptyList();
127 } 210 }
  211 + return knowledgeDocumentMapper.selectBatchIds(ids);
128 } 212 }
129 213
130 } 214 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java
@@ -2,12 +2,19 @@ package cn.iocoder.yudao.module.ai.service.knowledge; @@ -2,12 +2,19 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; 4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
5 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;  
6 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; 5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSaveReqVO;
7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  9 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  10 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
  11 +import org.springframework.scheduling.annotation.Async;
9 12
  13 +import java.util.Collection;
10 import java.util.List; 14 import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
11 18
12 /** 19 /**
13 * AI 知识库段落 Service 接口 20 * AI 知识库段落 Service 接口
@@ -17,6 +24,32 @@ import java.util.List; @@ -17,6 +24,32 @@ import java.util.List;
17 public interface AiKnowledgeSegmentService { 24 public interface AiKnowledgeSegmentService {
18 25
19 /** 26 /**
  27 + * 获取知识库段落详情
  28 + *
  29 + * @param id 段落编号
  30 + * @return 段落详情
  31 + */
  32 + AiKnowledgeSegmentDO getKnowledgeSegment(Long id);
  33 +
  34 + /**
  35 + * 获取知识库段落列表
  36 + *
  37 + * @param ids 段落编号列表
  38 + * @return 段落列表
  39 + */
  40 + List<AiKnowledgeSegmentDO> getKnowledgeSegmentList(Collection<Long> ids);
  41 +
  42 + /**
  43 + * 获取知识库段落 Map
  44 + *
  45 + * @param ids 段落编号列表
  46 + * @return 段落 Map
  47 + */
  48 + default Map<Long, AiKnowledgeSegmentDO> getKnowledgeSegmentMap(Collection<Long> ids) {
  49 + return convertMap(getKnowledgeSegmentList(ids), AiKnowledgeSegmentDO::getId);
  50 + }
  51 +
  52 + /**
20 * 获取段落分页 53 * 获取段落分页
21 * 54 *
22 * @param pageReqVO 分页查询 55 * @param pageReqVO 分页查询
@@ -25,11 +58,38 @@ public interface AiKnowledgeSegmentService { @@ -25,11 +58,38 @@ public interface AiKnowledgeSegmentService {
25 PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO); 58 PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO);
26 59
27 /** 60 /**
  61 + * 基于 content 内容,切片创建多个段落
  62 + *
  63 + * @param documentId 知识库文档编号
  64 + * @param content 文档内容
  65 + */
  66 + void createKnowledgeSegmentBySplitContent(Long documentId, String content);
  67 +
  68 + /**
  69 + * 【异步】基于 content 内容,切片创建多个段落
  70 + *
  71 + * @param documentId 知识库文档编号
  72 + * @param content 文档内容
  73 + */
  74 + @Async
  75 + default void createKnowledgeSegmentBySplitContentAsync(Long documentId, String content) {
  76 + createKnowledgeSegmentBySplitContent(documentId, content);
  77 + }
  78 +
  79 + /**
  80 + * 创建知识库段落
  81 + *
  82 + * @param createReqVO 创建信息
  83 + * @return 段落编号
  84 + */
  85 + Long createKnowledgeSegment(AiKnowledgeSegmentSaveReqVO createReqVO);
  86 +
  87 + /**
28 * 更新段落的内容 88 * 更新段落的内容
29 * 89 *
30 * @param reqVO 更新内容 90 * @param reqVO 更新内容
31 */ 91 */
32 - void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO); 92 + void updateKnowledgeSegment(AiKnowledgeSegmentSaveReqVO reqVO);
33 93
34 /** 94 /**
35 * 更新段落的状态 95 * 更新段落的状态
@@ -39,11 +99,52 @@ public interface AiKnowledgeSegmentService { @@ -39,11 +99,52 @@ public interface AiKnowledgeSegmentService {
39 void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO); 99 void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
40 100
41 /** 101 /**
42 - * 召回段落 102 + * 重新索引知识库下的所有文档段落
  103 + *
  104 + * @param knowledgeId 知识库编号
  105 + */
  106 + void reindexKnowledgeSegmentByKnowledgeId(Long knowledgeId);
  107 +
  108 + /**
  109 + * 【异步】重新索引知识库下的所有文档段落
  110 + *
  111 + * @param knowledgeId 知识库编号
  112 + */
  113 + @Async
  114 + default void reindexByKnowledgeIdAsync(Long knowledgeId) {
  115 + reindexKnowledgeSegmentByKnowledgeId(knowledgeId);
  116 + }
  117 +
  118 + /**
  119 + * 根据文档编号删除段落
  120 + *
  121 + * @param documentId 文档编号
  122 + */
  123 + void deleteKnowledgeSegmentByDocumentId(Long documentId);
  124 +
  125 + /**
  126 + * 搜索知识库段落,并返回结果
  127 + *
  128 + * @param reqBO 搜索请求信息
  129 + * @return 搜索结果段落列表
  130 + */
  131 + List<AiKnowledgeSegmentSearchRespBO> searchKnowledgeSegment(AiKnowledgeSegmentSearchReqBO reqBO);
  132 +
  133 + /**
  134 + * 根据 URL 内容,切片创建多个段落
  135 + *
  136 + * @param url URL 地址
  137 + * @param segmentMaxTokens 段落最大 Token 数
  138 + * @return 切片后的段落列表
  139 + */
  140 + List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens);
  141 +
  142 + /**
  143 + * 获取文档处理进度(多个)
43 * 144 *
44 - * @param reqVO 召回请求信息  
45 - * @return 召回的段落 145 + * @param documentIds 文档编号列表
  146 + * @return 文档处理列表
46 */ 147 */
47 - List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO); 148 + List<AiKnowledgeSegmentProcessRespVO> getKnowledgeSegmentProcessList(List<Long> documentIds);
48 149
49 } 150 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
@@ -2,31 +2,39 @@ package cn.iocoder.yudao.module.ai.service.knowledge; @@ -2,31 +2,39 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.ListUtil; 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.util.ObjUtil;
  6 +import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; 10 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
9 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;  
10 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; 11 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
  12 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSaveReqVO;
11 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; 13 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
12 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; 14 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  15 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
13 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 16 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
14 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
15 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; 17 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
16 -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;  
17 -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 18 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  19 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
  20 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
18 import jakarta.annotation.Resource; 21 import jakarta.annotation.Resource;
19 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.ai.document.Document; 23 import org.springframework.ai.document.Document;
  24 +import org.springframework.ai.tokenizer.TokenCountEstimator;
  25 +import org.springframework.ai.transformer.splitter.TextSplitter;
  26 +import org.springframework.ai.transformer.splitter.TokenTextSplitter;
21 import org.springframework.ai.vectorstore.SearchRequest; 27 import org.springframework.ai.vectorstore.SearchRequest;
22 import org.springframework.ai.vectorstore.VectorStore; 28 import org.springframework.ai.vectorstore.VectorStore;
23 import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder; 29 import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
  30 +import org.springframework.context.annotation.Lazy;
24 import org.springframework.stereotype.Service; 31 import org.springframework.stereotype.Service;
25 32
26 -import java.util.List;  
27 -import java.util.Objects; 33 +import java.util.*;
28 34
29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  36 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  37 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG;
30 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_NOT_EXISTS; 38 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_NOT_EXISTS;
31 39
32 /** 40 /**
@@ -38,15 +46,28 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGM @@ -38,15 +46,28 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGM
38 @Slf4j 46 @Slf4j
39 public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService { 47 public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService {
40 48
  49 + private static final String VECTOR_STORE_METADATA_KNOWLEDGE_ID = "knowledgeId";
  50 + private static final String VECTOR_STORE_METADATA_DOCUMENT_ID = "documentId";
  51 + private static final String VECTOR_STORE_METADATA_SEGMENT_ID = "segmentId";
  52 +
  53 + private static final Map<String, Class<?>> VECTOR_STORE_METADATA_TYPES = Map.of(
  54 + VECTOR_STORE_METADATA_KNOWLEDGE_ID, String.class,
  55 + VECTOR_STORE_METADATA_DOCUMENT_ID, String.class,
  56 + VECTOR_STORE_METADATA_SEGMENT_ID, String.class);
  57 +
41 @Resource 58 @Resource
42 private AiKnowledgeSegmentMapper segmentMapper; 59 private AiKnowledgeSegmentMapper segmentMapper;
43 60
44 @Resource 61 @Resource
45 private AiKnowledgeService knowledgeService; 62 private AiKnowledgeService knowledgeService;
46 @Resource 63 @Resource
47 - private AiChatModelService chatModelService; 64 + @Lazy // 延迟加载,避免循环依赖
  65 + private AiKnowledgeDocumentService knowledgeDocumentService;
48 @Resource 66 @Resource
49 - private AiApiKeyService apiKeyService; 67 + private AiModelService modelService;
  68 +
  69 + @Resource
  70 + private TokenCountEstimator tokenCountEstimator;
50 71
51 @Override 72 @Override
52 public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) { 73 public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
@@ -54,67 +75,198 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @@ -54,67 +75,198 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
54 } 75 }
55 76
56 @Override 77 @Override
57 - public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) { 78 + public void createKnowledgeSegmentBySplitContent(Long documentId, String content) {
  79 + // 1. 校验
  80 + AiKnowledgeDocumentDO documentDO = knowledgeDocumentService.validateKnowledgeDocumentExists(documentId);
  81 + AiKnowledgeDO knowledgeDO = knowledgeService.validateKnowledgeExists(documentDO.getKnowledgeId());
  82 + VectorStore vectorStore = getVectorStoreById(knowledgeDO);
  83 +
  84 + // 2. 文档切片
  85 + List<Document> documentSegments = splitContentByToken(content, documentDO.getSegmentMaxTokens());
  86 +
  87 + // 3.1 存储切片
  88 + List<AiKnowledgeSegmentDO> segmentDOs = convertList(documentSegments, segment -> {
  89 + if (StrUtil.isEmpty(segment.getText())) {
  90 + return null;
  91 + }
  92 + return new AiKnowledgeSegmentDO().setKnowledgeId(documentDO.getKnowledgeId()).setDocumentId(documentId)
  93 + .setContent(segment.getText()).setContentLength(segment.getText().length())
  94 + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY)
  95 + .setTokens(tokenCountEstimator.estimate(segment.getText()))
  96 + .setStatus(CommonStatusEnum.ENABLE.getStatus());
  97 + });
  98 + segmentMapper.insertBatch(segmentDOs);
  99 + // 3.2 切片向量化
  100 + for (int i = 0; i < documentSegments.size(); i++) {
  101 + Document segment = documentSegments.get(i);
  102 + AiKnowledgeSegmentDO segmentDO = segmentDOs.get(i);
  103 + writeVectorStore(vectorStore, segmentDO, segment);
  104 + }
  105 + }
  106 +
  107 + @Override
  108 + public void updateKnowledgeSegment(AiKnowledgeSegmentSaveReqVO reqVO) {
58 // 1. 校验 109 // 1. 校验
59 - AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId()); 110 + AiKnowledgeSegmentDO oldSegment = validateKnowledgeSegmentExists(reqVO.getId());
  111 +
  112 + // 2. 删除向量
  113 + VectorStore vectorStore = getVectorStoreById(oldSegment.getKnowledgeId());
  114 + deleteVectorStore(vectorStore, oldSegment);
  115 +
  116 + // 3.1 更新切片
  117 + AiKnowledgeSegmentDO newSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
  118 + segmentMapper.updateById(newSegment);
  119 + // 3.2 重新向量化,必须开启状态
  120 + if (CommonStatusEnum.isEnable(oldSegment.getStatus())) {
  121 + newSegment.setKnowledgeId(oldSegment.getKnowledgeId()).setDocumentId(oldSegment.getDocumentId());
  122 + writeVectorStore(vectorStore, newSegment, new Document(newSegment.getContent()));
  123 + }
  124 + }
  125 +
  126 + @Override
  127 + public void deleteKnowledgeSegmentByDocumentId(Long documentId) {
  128 + // 1. 查询需要删除的段落
  129 + List<AiKnowledgeSegmentDO> segments = segmentMapper.selectListByDocumentId(documentId);
  130 + if (CollUtil.isEmpty(segments)) {
  131 + return;
  132 + }
60 133
61 - // 2.1 获取知识库向量实例  
62 - VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId());  
63 - // 2.2 删除原向量  
64 - vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId()));  
65 - // 2.3 重新向量化  
66 - Document document = new Document(reqVO.getContent());  
67 - document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId());  
68 - vectorStore.add(List.of(document)); 134 + // 2. 批量删除段落记录
  135 + segmentMapper.deleteByIds(convertList(segments, AiKnowledgeSegmentDO::getId));
69 136
70 - // 3. 更新段落内容  
71 - AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);  
72 - knowledgeSegment.setVectorId(document.getId());  
73 - segmentMapper.updateById(knowledgeSegment); 137 + // 3. 删除向量存储中的段落
  138 + VectorStore vectorStore = getVectorStoreById(segments.get(0).getKnowledgeId());
  139 + vectorStore.delete(convertList(segments, AiKnowledgeSegmentDO::getVectorId));
74 } 140 }
75 141
76 @Override 142 @Override
77 public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) { 143 public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
78 - // 0 校验  
79 - AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId());  
80 - // 1 获取知识库向量实例  
81 - VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId());  
82 - AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);  
83 -  
84 - if (Objects.equals(reqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {  
85 - // 2.1 启用重新向量化  
86 - Document document = new Document(oldKnowledgeSegment.getContent());  
87 - document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId());  
88 - vectorStore.add(List.of(document));  
89 - knowledgeSegment.setVectorId(document.getId()); 144 + // 1. 校验
  145 + AiKnowledgeSegmentDO segment = validateKnowledgeSegmentExists(reqVO.getId());
  146 +
  147 + // 2. 获取知识库向量实例
  148 + VectorStore vectorStore = getVectorStoreById(segment.getKnowledgeId());
  149 +
  150 + // 3. 更新状态
  151 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(reqVO.getId()).setStatus(reqVO.getStatus()));
  152 +
  153 + // 4. 更新向量
  154 + if (CommonStatusEnum.isEnable(reqVO.getStatus())) {
  155 + writeVectorStore(vectorStore, segment, new Document(segment.getContent()));
90 } else { 156 } else {
91 - // 2.2 禁用删除向量  
92 - vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId()));  
93 - knowledgeSegment.setVectorId(""); 157 + deleteVectorStore(vectorStore, segment);
  158 + }
  159 + }
  160 +
  161 + @Override
  162 + public void reindexKnowledgeSegmentByKnowledgeId(Long knowledgeId) {
  163 + // 1.1 校验知识库存在
  164 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(knowledgeId);
  165 + // 1.2 获取知识库向量实例
  166 + VectorStore vectorStore = getVectorStoreById(knowledge);
  167 +
  168 + // 2.1 查询知识库下的所有启用状态的段落
  169 + List<AiKnowledgeSegmentDO> segments = segmentMapper.selectListByKnowledgeIdAndStatus(
  170 + knowledgeId, CommonStatusEnum.ENABLE.getStatus());
  171 + if (CollUtil.isEmpty(segments)) {
  172 + return;
94 } 173 }
95 - // 3 更新段落状态  
96 - segmentMapper.updateById(knowledgeSegment); 174 + // 2.2 遍历所有段落,重新索引
  175 + for (AiKnowledgeSegmentDO segment : segments) {
  176 + // 删除旧的向量
  177 + deleteVectorStore(vectorStore, segment);
  178 + // 重新创建向量
  179 + writeVectorStore(vectorStore, segment, new Document(segment.getContent()));
  180 + }
  181 + log.info("[reindexKnowledgeSegmentByKnowledgeId][知识库({}) 重新索引完成,共处理 {} 个段落]",
  182 + knowledgeId, segments.size());
  183 + }
  184 +
  185 + private void writeVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO, Document segment) {
  186 + // 1. 向量存储
  187 + // 为什么要 toString 呢?因为部分 VectorStore 实现,不支持 Long 类型,例如说 QdrantVectorStore
  188 + segment.getMetadata().put(VECTOR_STORE_METADATA_KNOWLEDGE_ID, segmentDO.getKnowledgeId().toString());
  189 + segment.getMetadata().put(VECTOR_STORE_METADATA_DOCUMENT_ID, segmentDO.getDocumentId().toString());
  190 + segment.getMetadata().put(VECTOR_STORE_METADATA_SEGMENT_ID, segmentDO.getId().toString());
  191 + vectorStore.add(List.of(segment));
  192 +
  193 + // 2. 更新向量 ID
  194 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(segmentDO.getId()).setVectorId(segment.getId()));
  195 + }
  196 +
  197 + private void deleteVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO) {
  198 + // 1. 更新向量 ID
  199 + if (StrUtil.isEmpty(segmentDO.getVectorId())) {
  200 + return;
  201 + }
  202 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(segmentDO.getId())
  203 + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY));
  204 +
  205 + // 2. 删除向量
  206 + vectorStore.delete(List.of(segmentDO.getVectorId()));
97 } 207 }
98 208
99 @Override 209 @Override
100 - public List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO) { 210 + public List<AiKnowledgeSegmentSearchRespBO> searchKnowledgeSegment(AiKnowledgeSegmentSearchReqBO reqBO) {
101 // 1. 校验 211 // 1. 校验
102 - AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqVO.getKnowledgeId());  
103 - AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());  
104 -  
105 - // 2. 获取向量存储实例  
106 - VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());  
107 -  
108 - // 3.1 向量检索  
109 - List<Document> documentList = vectorStore.similaritySearch(SearchRequest.query(reqVO.getContent())  
110 - .withTopK(knowledge.getTopK())  
111 - .withSimilarityThreshold(knowledge.getSimilarityThreshold())  
112 - .withFilterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build()));  
113 - if (CollUtil.isEmpty(documentList)) { 212 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqBO.getKnowledgeId());
  213 +
  214 + // 2.1 向量检索
  215 + VectorStore vectorStore = getVectorStoreById(knowledge);
  216 + List<Document> documents = vectorStore.similaritySearch(SearchRequest.builder()
  217 + .query(reqBO.getContent())
  218 + .topK(ObjUtil.defaultIfNull(reqBO.getTopK(), knowledge.getTopK()))
  219 + .similarityThreshold(
  220 + ObjUtil.defaultIfNull(reqBO.getSimilarityThreshold(), knowledge.getSimilarityThreshold()))
  221 + .filterExpression(new FilterExpressionBuilder()
  222 + .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqBO.getKnowledgeId().toString())
  223 + .build())
  224 + .build());
  225 + if (CollUtil.isEmpty(documents)) {
  226 + return ListUtil.empty();
  227 + }
  228 + // 2.2 段落召回
  229 + List<AiKnowledgeSegmentDO> segments = segmentMapper
  230 + .selectListByVectorIds(convertList(documents, Document::getId));
  231 + if (CollUtil.isEmpty(segments)) {
114 return ListUtil.empty(); 232 return ListUtil.empty();
115 } 233 }
116 - // 3.2 段落召回  
117 - return segmentMapper.selectListByVectorIds(CollUtil.getFieldValues(documentList, "id", String.class)); 234 +
  235 + // 3. 增加召回次数
  236 + segmentMapper.updateRetrievalCountIncrByIds(convertList(segments, AiKnowledgeSegmentDO::getId));
  237 +
  238 + // 4. 构建结果
  239 + List<AiKnowledgeSegmentSearchRespBO> result = convertList(segments, segment -> {
  240 + Document document = CollUtil.findOne(documents, // 找到对应的文档
  241 + doc -> Objects.equals(doc.getId(), segment.getVectorId()));
  242 + if (document == null) {
  243 + return null;
  244 + }
  245 + return BeanUtils.toBean(segment, AiKnowledgeSegmentSearchRespBO.class)
  246 + .setScore(document.getScore());
  247 + });
  248 + result.sort((o1, o2) -> Double.compare(o2.getScore(), o1.getScore())); // 按照分数降序排序
  249 + return result;
  250 + }
  251 +
  252 + @Override
  253 + public List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens) {
  254 + // 1. 读取 URL 内容
  255 + String content = knowledgeDocumentService.readUrl(url);
  256 +
  257 + // 2. 文档切片
  258 + List<Document> documentSegments = splitContentByToken(content, segmentMaxTokens);
  259 +
  260 + // 3. 转换为段落对象
  261 + return convertList(documentSegments, segment -> {
  262 + if (StrUtil.isEmpty(segment.getText())) {
  263 + return null;
  264 + }
  265 + return new AiKnowledgeSegmentDO()
  266 + .setContent(segment.getText())
  267 + .setContentLength(segment.getText().length())
  268 + .setTokens(tokenCountEstimator.estimate(segment.getText()));
  269 + });
118 } 270 }
119 271
120 /** 272 /**
@@ -131,4 +283,75 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @@ -131,4 +283,75 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
131 return knowledgeSegment; 283 return knowledgeSegment;
132 } 284 }
133 285
  286 + private VectorStore getVectorStoreById(AiKnowledgeDO knowledge) {
  287 + return modelService.getOrCreateVectorStore(knowledge.getEmbeddingModelId(), VECTOR_STORE_METADATA_TYPES);
  288 + }
  289 +
  290 + private VectorStore getVectorStoreById(Long knowledgeId) {
  291 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(knowledgeId);
  292 + return getVectorStoreById(knowledge);
  293 + }
  294 +
  295 + private static List<Document> splitContentByToken(String content, Integer segmentMaxTokens) {
  296 + TextSplitter textSplitter = buildTokenTextSplitter(segmentMaxTokens);
  297 + return textSplitter.apply(Collections.singletonList(new Document(content)));
  298 + }
  299 +
  300 + private static TextSplitter buildTokenTextSplitter(Integer segmentMaxTokens) {
  301 + return TokenTextSplitter.builder()
  302 + .withChunkSize(segmentMaxTokens)
  303 + .withMinChunkSizeChars(Integer.MAX_VALUE) // 忽略字符的截断
  304 + .withMinChunkLengthToEmbed(1) // 允许的最小有效分段长度
  305 + .withMaxNumChunks(Integer.MAX_VALUE)
  306 + .withKeepSeparator(true) // 保留分隔符
  307 + .build();
  308 + }
  309 +
  310 + @Override
  311 + public List<AiKnowledgeSegmentProcessRespVO> getKnowledgeSegmentProcessList(List<Long> documentIds) {
  312 + if (CollUtil.isEmpty(documentIds)) {
  313 + return Collections.emptyList();
  314 + }
  315 + return segmentMapper.selectProcessList(documentIds);
  316 + }
  317 +
  318 + @Override
  319 + public Long createKnowledgeSegment(AiKnowledgeSegmentSaveReqVO createReqVO) {
  320 + // 1.1 校验文档是否存在
  321 + AiKnowledgeDocumentDO document = knowledgeDocumentService
  322 + .validateKnowledgeDocumentExists(createReqVO.getDocumentId());
  323 + // 1.2 获取知识库信息
  324 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(document.getKnowledgeId());
  325 + // 1.3 校验 token 熟练
  326 + Integer tokens = tokenCountEstimator.estimate(createReqVO.getContent());
  327 + if (tokens > document.getSegmentMaxTokens()) {
  328 + throw exception(KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG, tokens, document.getSegmentMaxTokens());
  329 + }
  330 +
  331 + // 2. 保存段落
  332 + AiKnowledgeSegmentDO segment = BeanUtils.toBean(createReqVO, AiKnowledgeSegmentDO.class)
  333 + .setKnowledgeId(knowledge.getId()).setDocumentId(document.getId())
  334 + .setContentLength(createReqVO.getContent().length()).setTokens(tokens)
  335 + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY)
  336 + .setRetrievalCount(0).setStatus(CommonStatusEnum.ENABLE.getStatus());
  337 + segmentMapper.insert(segment);
  338 +
  339 + // 3. 向量化
  340 + writeVectorStore(getVectorStoreById(knowledge), segment, new Document(segment.getContent()));
  341 + return segment.getId();
  342 + }
  343 +
  344 + @Override
  345 + public AiKnowledgeSegmentDO getKnowledgeSegment(Long id) {
  346 + return segmentMapper.selectById(id);
  347 + }
  348 +
  349 + @Override
  350 + public List<AiKnowledgeSegmentDO> getKnowledgeSegmentList(Collection<Long> ids) {
  351 + if (CollUtil.isEmpty(ids)) {
  352 + return Collections.emptyList();
  353 + }
  354 + return segmentMapper.selectBatchIds(ids);
  355 + }
  356 +
134 } 357 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java
1 package cn.iocoder.yudao.module.ai.service.knowledge; 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;  
5 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; 4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
6 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; 5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
8 -import org.springframework.ai.vectorstore.VectorStore; 7 +
  8 +import java.util.List;
9 9
10 /** 10 /**
11 * AI 知识库-基础信息 Service 接口 11 * AI 知识库-基础信息 Service 接口
@@ -18,18 +18,24 @@ public interface AiKnowledgeService { @@ -18,18 +18,24 @@ public interface AiKnowledgeService {
18 * 创建知识库 18 * 创建知识库
19 * 19 *
20 * @param createReqVO 创建信息 20 * @param createReqVO 创建信息
21 - * @param userId 用户编号  
22 * @return 编号 21 * @return 编号
23 */ 22 */
24 - Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId); 23 + Long createKnowledge(AiKnowledgeSaveReqVO createReqVO);
25 24
26 /** 25 /**
27 * 更新知识库 26 * 更新知识库
28 * 27 *
29 * @param updateReqVO 更新信息 28 * @param updateReqVO 更新信息
30 - * @param userId 用户编号  
31 */ 29 */
32 - void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId); 30 + void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO);
  31 +
  32 + /**
  33 + * 获得知识库
  34 + *
  35 + * @param id 编号
  36 + * @return 知识库
  37 + */
  38 + AiKnowledgeDO getKnowledge(Long id);
33 39
34 /** 40 /**
35 * 校验知识库是否存在 41 * 校验知识库是否存在
@@ -41,18 +47,17 @@ public interface AiKnowledgeService { @@ -41,18 +47,17 @@ public interface AiKnowledgeService {
41 /** 47 /**
42 * 获得知识库分页 48 * 获得知识库分页
43 * 49 *
44 - * @param userId 用户编号  
45 * @param pageReqVO 分页查询 50 * @param pageReqVO 分页查询
46 * @return 知识库分页 51 * @return 知识库分页
47 */ 52 */
48 - PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO); 53 + PageResult<AiKnowledgeDO> getKnowledgePage(AiKnowledgePageReqVO pageReqVO);
49 54
50 /** 55 /**
51 - * 根据知识库编号获取向量存储实例 56 + * 获得指定状态的知识库列表
52 * 57 *
53 - * @param id 知识库编号  
54 - * @return 向量存储实例 58 + * @param status 状态
  59 + * @return 知识库列表
55 */ 60 */
56 - VectorStore getVectorStoreById(Long id); 61 + List<AiKnowledgeDO> getKnowledgeSimpleListByStatus(Integer status);
57 62
58 } 63 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java
@@ -4,19 +4,19 @@ import cn.hutool.core.util.ObjUtil; @@ -4,19 +4,19 @@ import cn.hutool.core.util.ObjUtil;
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;  
8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
9 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; 8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
11 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 10 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  11 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
12 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper; 12 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper;
13 -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;  
14 -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 13 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
15 import jakarta.annotation.Resource; 14 import jakarta.annotation.Resource;
16 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
17 -import org.springframework.ai.vectorstore.VectorStore;  
18 import org.springframework.stereotype.Service; 16 import org.springframework.stereotype.Service;
19 17
  18 +import java.util.List;
  19 +
20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
21 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_EXISTS; 21 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_EXISTS;
22 22
@@ -33,36 +33,43 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { @@ -33,36 +33,43 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
33 private AiKnowledgeMapper knowledgeMapper; 33 private AiKnowledgeMapper knowledgeMapper;
34 34
35 @Resource 35 @Resource
36 - private AiChatModelService chatModelService; 36 + private AiModelService modelService;
37 @Resource 37 @Resource
38 - private AiApiKeyService apiKeyService; 38 + private AiKnowledgeSegmentService knowledgeSegmentService;
39 39
40 @Override 40 @Override
41 - public Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId) { 41 + public Long createKnowledge(AiKnowledgeSaveReqVO createReqVO) {
42 // 1. 校验模型配置 42 // 1. 校验模型配置
43 - AiChatModelDO model = chatModelService.validateChatModel(createReqVO.getModelId()); 43 + AiModelDO model = modelService.validateModel(createReqVO.getEmbeddingModelId());
44 44
45 // 2. 插入知识库 45 // 2. 插入知识库
46 - AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)  
47 - .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());  
48 - knowledgeMapper.insert(knowledgeBase);  
49 - return knowledgeBase.getId(); 46 + AiKnowledgeDO knowledge = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
  47 + .setEmbeddingModel(model.getModel());
  48 + knowledgeMapper.insert(knowledge);
  49 + return knowledge.getId();
50 } 50 }
51 51
52 @Override 52 @Override
53 - public void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId) { 53 + public void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO) {
54 // 1.1 校验知识库存在 54 // 1.1 校验知识库存在
55 - AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());  
56 - if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) {  
57 - throw exception(KNOWLEDGE_NOT_EXISTS);  
58 - } 55 + AiKnowledgeDO oldKnowledge = validateKnowledgeExists(updateReqVO.getId());
59 // 1.2 校验模型配置 56 // 1.2 校验模型配置
60 - AiChatModelDO model = chatModelService.validateChatModel(updateReqVO.getModelId()); 57 + AiModelDO model = modelService.validateModel(updateReqVO.getEmbeddingModelId());
61 58
62 // 2. 更新知识库 59 // 2. 更新知识库
63 - AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);  
64 - updateDO.setModel(model.getModel());  
65 - knowledgeMapper.updateById(updateDO); 60 + AiKnowledgeDO updateObj = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class)
  61 + .setEmbeddingModel(model.getModel());
  62 + knowledgeMapper.updateById(updateObj);
  63 +
  64 + // 3. 如果模型变化,需要 reindex 所有的文档
  65 + if (ObjUtil.notEqual(oldKnowledge.getEmbeddingModelId(), updateReqVO.getEmbeddingModelId())) {
  66 + knowledgeSegmentService.reindexByKnowledgeIdAsync(updateReqVO.getId());
  67 + }
  68 + }
  69 +
  70 + @Override
  71 + public AiKnowledgeDO getKnowledge(Long id) {
  72 + return knowledgeMapper.selectById(id);
66 } 73 }
67 74
68 @Override 75 @Override
@@ -75,16 +82,13 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { @@ -75,16 +82,13 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
75 } 82 }
76 83
77 @Override 84 @Override
78 - public PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO) {  
79 - return knowledgeMapper.selectPage(userId, pageReqVO); 85 + public PageResult<AiKnowledgeDO> getKnowledgePage(AiKnowledgePageReqVO pageReqVO) {
  86 + return knowledgeMapper.selectPage(pageReqVO);
80 } 87 }
81 88
82 @Override 89 @Override
83 - public VectorStore getVectorStoreById(Long id) {  
84 - AiKnowledgeDO knowledge = validateKnowledgeExists(id);  
85 - AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());  
86 - // 创建或获取 VectorStore 对象  
87 - return apiKeyService.getOrCreateVectorStore(model.getKeyId()); 90 + public List<AiKnowledgeDO> getKnowledgeSimpleListByStatus(Integer status) {
  91 + return knowledgeMapper.selectListByStatus(status);
88 } 92 }
89 93
90 } 94 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/bo/AiKnowledgeSegmentSearchReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import javax.validation.constraints.NotNull;
  6 +
  7 +import jakarta.validation.constraints.NotEmpty;
  8 +
  9 +/**
  10 + * AI 知识库段落搜索 Request BO
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +public class AiKnowledgeSegmentSearchReqBO {
  16 +
  17 + /**
  18 + * 知识库编号
  19 + */
  20 + @NotNull(message = "知识库编号不能为空")
  21 + private Long knowledgeId;
  22 +
  23 + /**
  24 + * 内容
  25 + */
  26 + @NotEmpty(message = "内容不能为空")
  27 + private String content;
  28 +
  29 + /**
  30 + * 最大返回数量
  31 + */
  32 + private Integer topK;
  33 +
  34 + /**
  35 + * 相似度阈值
  36 + */
  37 + private Double similarityThreshold;
  38 +
  39 +}
0 \ No newline at end of file 40 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/bo/AiKnowledgeSegmentSearchRespBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * AI 知识库段落搜索 Response BO
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +@Data
  11 +public class AiKnowledgeSegmentSearchRespBO {
  12 +
  13 + /**
  14 + * 段落编号
  15 + */
  16 + private Long id;
  17 + /**
  18 + * 文档编号
  19 + */
  20 + private Long documentId;
  21 + /**
  22 + * 知识库编号
  23 + */
  24 + private Long knowledgeId;
  25 +
  26 + /**
  27 + * 内容
  28 + */
  29 + private String content;
  30 + /**
  31 + * 内容长度
  32 + */
  33 + private Integer contentLength;
  34 +
  35 + /**
  36 + * Token 数量
  37 + */
  38 + private Integer tokens;
  39 +
  40 + /**
  41 + * 相似度分数
  42 + */
  43 + private Double score;
  44 +
  45 +}
0 \ No newline at end of file 46 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.mindmap; 1 package cn.iocoder.yudao.module.ai.service.mindmap;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.hutool.core.lang.Assert; 4 +import cn.hutool.core.util.ObjUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 7 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 import cn.iocoder.yudao.framework.ai.core.util.AiUtils; 8 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 9 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@@ -12,14 +13,13 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; @@ -12,14 +13,13 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; 13 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO;
14 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
15 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
16 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 16 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  17 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
17 import cn.iocoder.yudao.module.ai.dal.mysql.mindmap.AiMindMapMapper; 18 import cn.iocoder.yudao.module.ai.dal.mysql.mindmap.AiMindMapMapper;
18 import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum; 19 import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;
19 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; 20 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
20 -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;  
21 -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;  
22 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; 21 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  22 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
23 import jakarta.annotation.Resource; 23 import jakarta.annotation.Resource;
24 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
25 import org.springframework.ai.chat.messages.Message; 25 import org.springframework.ai.chat.messages.Message;
@@ -38,7 +38,7 @@ import java.util.List; @@ -38,7 +38,7 @@ import java.util.List;
38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; 39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
41 -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MIND_MAP_NOT_EXISTS; 41 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
42 42
43 /** 43 /**
44 * AI 思维导图 Service 实现类 44 * AI 思维导图 Service 实现类
@@ -50,9 +50,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MIND_MAP_NOT_E @@ -50,9 +50,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MIND_MAP_NOT_E
50 public class AiMindMapServiceImpl implements AiMindMapService { 50 public class AiMindMapServiceImpl implements AiMindMapService {
51 51
52 @Resource 52 @Resource
53 - private AiApiKeyService apiKeyService;  
54 - @Resource  
55 - private AiChatModelService chatModalService; 53 + private AiModelService modalService;
56 @Resource 54 @Resource
57 private AiChatRoleService chatRoleService; 55 private AiChatRoleService chatRoleService;
58 56
@@ -65,17 +63,17 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -65,17 +63,17 @@ public class AiMindMapServiceImpl implements AiMindMapService {
65 AiChatRoleDO role = CollUtil.getFirst( 63 AiChatRoleDO role = CollUtil.getFirst(
66 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName())); 64 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName()));
67 // 1.1 获取导图执行模型 65 // 1.1 获取导图执行模型
68 - AiChatModelDO model = getModel(role); 66 + AiModelDO model = getModel(role);
69 // 1.2 获取角色设定消息 67 // 1.2 获取角色设定消息
70 String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage()) 68 String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage())
71 ? role.getSystemMessage() : AiChatRoleEnum.AI_MIND_MAP_ROLE.getSystemMessage(); 69 ? role.getSystemMessage() : AiChatRoleEnum.AI_MIND_MAP_ROLE.getSystemMessage();
72 // 1.3 校验平台 70 // 1.3 校验平台
73 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 71 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
74 - ChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); 72 + ChatModel chatModel = modalService.getChatModel(model.getId());
75 73
76 // 2. 插入思维导图信息 74 // 2. 插入思维导图信息
77 - AiMindMapDO mindMapDO = BeanUtils.toBean(generateReqVO, AiMindMapDO.class,  
78 - mindMap -> mindMap.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform())); 75 + AiMindMapDO mindMapDO = BeanUtils.toBean(generateReqVO, AiMindMapDO.class, mindMap -> mindMap.setUserId(userId)
  76 + .setPlatform(platform.getPlatform()).setModelId(model.getId()).setModel(model.getModel()));
79 mindMapMapper.insert(mindMapDO); 77 mindMapMapper.insert(mindMapDO);
80 78
81 // 3.1 构建 Prompt,并进行调用 79 // 3.1 构建 Prompt,并进行调用
@@ -85,7 +83,7 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -85,7 +83,7 @@ public class AiMindMapServiceImpl implements AiMindMapService {
85 // 3.2 流式返回 83 // 3.2 流式返回
86 StringBuffer contentBuffer = new StringBuffer(); 84 StringBuffer contentBuffer = new StringBuffer();
87 return streamResponse.map(chunk -> { 85 return streamResponse.map(chunk -> {
88 - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; 86 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
89 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 87 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
90 contentBuffer.append(newContent); 88 contentBuffer.append(newContent);
91 // 响应结果 89 // 响应结果
@@ -103,7 +101,7 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -103,7 +101,7 @@ public class AiMindMapServiceImpl implements AiMindMapService {
103 101
104 } 102 }
105 103
106 - private Prompt buildPrompt(AiMindMapGenerateReqVO generateReqVO, AiChatModelDO model, String systemMessage) { 104 + private Prompt buildPrompt(AiMindMapGenerateReqVO generateReqVO, AiModelDO model, String systemMessage) {
107 // 1. 构建 message 列表 105 // 1. 构建 message 列表
108 List<Message> chatMessages = buildMessages(generateReqVO, systemMessage); 106 List<Message> chatMessages = buildMessages(generateReqVO, systemMessage);
109 // 2. 构建 options 对象 107 // 2. 构建 options 对象
@@ -123,15 +121,21 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -123,15 +121,21 @@ public class AiMindMapServiceImpl implements AiMindMapService {
123 return chatMessages; 121 return chatMessages;
124 } 122 }
125 123
126 - private AiChatModelDO getModel(AiChatRoleDO role) {  
127 - AiChatModelDO model = null; 124 + private AiModelDO getModel(AiChatRoleDO role) {
  125 + AiModelDO model = null;
128 if (role != null && role.getModelId() != null) { 126 if (role != null && role.getModelId() != null) {
129 - model = chatModalService.getChatModel(role.getModelId()); 127 + model = modalService.getModel(role.getModelId());
  128 + }
  129 + if (model == null) {
  130 + model = modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType());
130 } 131 }
  132 + // 校验模型存在、且合法
131 if (model == null) { 133 if (model == null) {
132 - model = chatModalService.getRequiredDefaultChatModel(); 134 + throw exception(MODEL_NOT_EXISTS);
  135 + }
  136 + if (ObjUtil.notEqual(model.getType(), AiModelTypeEnum.CHAT.getType())) {
  137 + throw exception(MODEL_USE_TYPE_ERROR);
133 } 138 }
134 - Assert.notNull(model, "[AI] 获取不到模型");  
135 return model; 139 return model;
136 } 140 }
137 141
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
1 package cn.iocoder.yudao.module.ai.service.model; 1 package cn.iocoder.yudao.module.ai.service.model;
2 2
3 -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;  
4 -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;  
5 -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;  
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; 4 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
8 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; 5 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
10 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
11 -import org.springframework.ai.chat.model.ChatModel;  
12 -import org.springframework.ai.embedding.EmbeddingModel;  
13 -import org.springframework.ai.image.ImageModel;  
14 -import org.springframework.ai.vectorstore.VectorStore;  
15 8
16 import java.util.List; 9 import java.util.List;
17 10
@@ -75,58 +68,13 @@ public interface AiApiKeyService { @@ -75,58 +68,13 @@ public interface AiApiKeyService {
75 */ 68 */
76 List<AiApiKeyDO> getApiKeyList(); 69 List<AiApiKeyDO> getApiKeyList();
77 70
78 - // ========== 与 spring-ai 集成 ==========  
79 -  
80 /** 71 /**
81 - * 获得 ChatModel 对象  
82 - *  
83 - * @param id 编号  
84 - * @return ChatModel 对象  
85 - */  
86 - ChatModel getChatModel(Long id);  
87 -  
88 - /**  
89 - * 获得 ImageModel 对象  
90 - *  
91 - * TODO 可优化点:目前默认获取 platform 对应的第一个开启的配置用于绘画;后续可以支持配置选择 72 + * 获得默认的 API 密钥
92 * 73 *
93 * @param platform 平台 74 * @param platform 平台
94 - * @return ImageModel 对象  
95 - */  
96 - ImageModel getImageModel(AiPlatformEnum platform);  
97 -  
98 - /**  
99 - * 获得 MidjourneyApi 对象  
100 - *  
101 - * TODO 可优化点:目前默认获取 Midjourney 对应的第一个开启的配置用于绘画;后续可以支持配置选择  
102 - *  
103 - * @return MidjourneyApi 对象  
104 - */  
105 - MidjourneyApi getMidjourneyApi();  
106 -  
107 - /**  
108 - * 获得 SunoApi 对象  
109 - *  
110 - * TODO 可优化点:目前默认获取 Suno 对应的第一个开启的配置用于音乐;后续可以支持配置选择  
111 - *  
112 - * @return SunoApi 对象  
113 - */  
114 - SunoApi getSunoApi();  
115 -  
116 - /**  
117 - * 获得 EmbeddingModel 对象  
118 - *  
119 - * @param id 编号  
120 - * @return EmbeddingModel 对象  
121 - */  
122 - EmbeddingModel getEmbeddingModel(Long id);  
123 -  
124 - /**  
125 - * 获得 VectorStore 对象  
126 - *  
127 - * @param id 编号  
128 - * @return VectorStore 对象 75 + * @param status 状态
  76 + * @return API 密钥
129 */ 77 */
130 - VectorStore getOrCreateVectorStore(Long id); 78 + AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status);
131 79
132 } 80 }
133 \ No newline at end of file 81 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.model; 1 package cn.iocoder.yudao.module.ai.service.model;
2 2
3 -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;  
4 -import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;  
5 -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;  
6 -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;  
7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
8 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -12,17 +8,14 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR @@ -12,17 +8,14 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
12 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; 8 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
13 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; 9 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
14 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
15 -import org.springframework.ai.chat.model.ChatModel;  
16 -import org.springframework.ai.embedding.EmbeddingModel;  
17 -import org.springframework.ai.image.ImageModel;  
18 -import org.springframework.ai.vectorstore.VectorStore;  
19 import org.springframework.stereotype.Service; 11 import org.springframework.stereotype.Service;
20 import org.springframework.validation.annotation.Validated; 12 import org.springframework.validation.annotation.Validated;
21 13
22 import java.util.List; 14 import java.util.List;
23 15
24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 16 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
25 -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; 17 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_KEY_DISABLE;
  18 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_KEY_NOT_EXISTS;
26 19
27 /** 20 /**
28 * AI API 密钥 Service 实现类 21 * AI API 密钥 Service 实现类
@@ -36,9 +29,6 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -36,9 +29,6 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
36 @Resource 29 @Resource
37 private AiApiKeyMapper apiKeyMapper; 30 private AiApiKeyMapper apiKeyMapper;
38 31
39 - @Resource  
40 - private AiModelFactory modelFactory;  
41 -  
42 @Override 32 @Override
43 public Long createApiKey(AiApiKeySaveReqVO createReqVO) { 33 public Long createApiKey(AiApiKeySaveReqVO createReqVO) {
44 // 插入 34 // 插入
@@ -97,57 +87,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -97,57 +87,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
97 return apiKeyMapper.selectList(); 87 return apiKeyMapper.selectList();
98 } 88 }
99 89
100 - // ========== 与 spring-ai 集成 ==========  
101 -  
102 - @Override  
103 - public ChatModel getChatModel(Long id) {  
104 - AiApiKeyDO apiKey = validateApiKey(id);  
105 - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());  
106 - return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl());  
107 - }  
108 -  
109 - @Override  
110 - public ImageModel getImageModel(AiPlatformEnum platform) {  
111 - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus());  
112 - if (apiKey == null) {  
113 - throw exception(API_KEY_IMAGE_NODE_FOUND, platform.getName());  
114 - }  
115 - return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl());  
116 - }  
117 -  
118 - @Override  
119 - public MidjourneyApi getMidjourneyApi() {  
120 - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(  
121 - AiPlatformEnum.MIDJOURNEY.getPlatform(), CommonStatusEnum.ENABLE.getStatus());  
122 - if (apiKey == null) {  
123 - throw exception(API_KEY_MIDJOURNEY_NOT_FOUND);  
124 - }  
125 - return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl());  
126 - }  
127 -  
128 @Override 90 @Override
129 - public SunoApi getSunoApi() {  
130 - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(  
131 - AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); 91 + public AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status) {
  92 + AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform, status);
132 if (apiKey == null) { 93 if (apiKey == null) {
133 - throw exception(API_KEY_SUNO_NOT_FOUND); 94 + throw exception(API_KEY_NOT_EXISTS);
134 } 95 }
135 - return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl());  
136 - }  
137 -  
138 - @Override  
139 - public EmbeddingModel getEmbeddingModel(Long id) {  
140 - AiApiKeyDO apiKey = validateApiKey(id);  
141 - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());  
142 - return modelFactory.getOrCreateEmbeddingModel(platform, apiKey.getApiKey(), apiKey.getUrl());  
143 - }  
144 -  
145 - @Override  
146 - public VectorStore getOrCreateVectorStore(Long id) {  
147 - AiApiKeyDO apiKey = validateApiKey(id);  
148 - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());  
149 - // 创建或获取 VectorStore 对象  
150 - return modelFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl()); 96 + return apiKey;
151 } 97 }
152 98
153 } 99 }
154 \ No newline at end of file 100 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java deleted
1 -package cn.iocoder.yudao.module.ai.service.model;  
2 -  
3 -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;  
4 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
5 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
6 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
7 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;  
8 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO;  
9 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
10 -import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatModelMapper;  
11 -import jakarta.annotation.Resource;  
12 -import org.springframework.stereotype.Service;  
13 -import org.springframework.validation.annotation.Validated;  
14 -  
15 -import java.util.Collection;  
16 -import java.util.List;  
17 -  
18 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
19 -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;  
20 -  
21 -/**  
22 - * AI 聊天模型 Service 实现类  
23 - *  
24 - * @author fansili  
25 - */  
26 -@Service  
27 -@Validated  
28 -public class AiChatModelServiceImpl implements AiChatModelService {  
29 -  
30 - @Resource  
31 - private AiApiKeyService apiKeyService;  
32 -  
33 - @Resource  
34 - private AiChatModelMapper chatModelMapper;  
35 -  
36 - @Override  
37 - public Long createChatModel(AiChatModelSaveReqVO createReqVO) {  
38 - // 1. 校验  
39 - AiPlatformEnum.validatePlatform(createReqVO.getPlatform());  
40 - apiKeyService.validateApiKey(createReqVO.getKeyId());  
41 -  
42 - // 2. 插入  
43 - AiChatModelDO chatModel = BeanUtils.toBean(createReqVO, AiChatModelDO.class);  
44 - chatModelMapper.insert(chatModel);  
45 - return chatModel.getId();  
46 - }  
47 -  
48 - @Override  
49 - public void updateChatModel(AiChatModelSaveReqVO updateReqVO) {  
50 - // 1. 校验  
51 - validateChatModelExists(updateReqVO.getId());  
52 - AiPlatformEnum.validatePlatform(updateReqVO.getPlatform());  
53 - apiKeyService.validateApiKey(updateReqVO.getKeyId());  
54 -  
55 - // 2. 更新  
56 - AiChatModelDO updateObj = BeanUtils.toBean(updateReqVO, AiChatModelDO.class);  
57 - chatModelMapper.updateById(updateObj);  
58 - }  
59 -  
60 - @Override  
61 - public void deleteChatModel(Long id) {  
62 - // 校验存在  
63 - validateChatModelExists(id);  
64 - // 删除  
65 - chatModelMapper.deleteById(id);  
66 - }  
67 -  
68 - private AiChatModelDO validateChatModelExists(Long id) {  
69 - AiChatModelDO model = chatModelMapper.selectById(id);  
70 - if (chatModelMapper.selectById(id) == null) {  
71 - throw exception(CHAT_MODEL_NOT_EXISTS);  
72 - }  
73 - return model;  
74 - }  
75 -  
76 - @Override  
77 - public AiChatModelDO getChatModel(Long id) {  
78 - return chatModelMapper.selectById(id);  
79 - }  
80 -  
81 - @Override  
82 - public AiChatModelDO getRequiredDefaultChatModel() {  
83 - AiChatModelDO model = chatModelMapper.selectFirstByStatus(CommonStatusEnum.ENABLE.getStatus());  
84 - if (model == null) {  
85 - throw exception(CHAT_MODEL_DEFAULT_NOT_EXISTS);  
86 - }  
87 - return model;  
88 - }  
89 -  
90 - @Override  
91 - public PageResult<AiChatModelDO> getChatModelPage(AiChatModelPageReqVO pageReqVO) {  
92 - return chatModelMapper.selectPage(pageReqVO);  
93 - }  
94 -  
95 - @Override  
96 - public AiChatModelDO validateChatModel(Long id) {  
97 - AiChatModelDO model = validateChatModelExists(id);  
98 - if (CommonStatusEnum.isDisable(model.getStatus())) {  
99 - throw exception(CHAT_MODEL_DISABLE);  
100 - }  
101 - return model;  
102 - }  
103 -  
104 - @Override  
105 - public List<AiChatModelDO> getChatModelListByStatus(Integer status) {  
106 - return chatModelMapper.selectList(status);  
107 - }  
108 -  
109 - @Override  
110 - public List<AiChatModelDO> getChatModelList(Collection<Long> ids) {  
111 - return chatModelMapper.selectBatchIds(ids);  
112 - }  
113 -  
114 -}  
115 \ No newline at end of file 0 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java
@@ -32,7 +32,7 @@ public interface AiChatRoleService { @@ -32,7 +32,7 @@ public interface AiChatRoleService {
32 * 创建【我的】聊天角色 32 * 创建【我的】聊天角色
33 * 33 *
34 * @param createReqVO 创建信息 34 * @param createReqVO 创建信息
35 - * @param userId 用户编号 35 + * @param userId 用户编号
36 * @return 编号 36 * @return 编号
37 */ 37 */
38 Long createChatRoleMy(AiChatRoleSaveMyReqVO createReqVO, Long userId); 38 Long createChatRoleMy(AiChatRoleSaveMyReqVO createReqVO, Long userId);
@@ -48,7 +48,7 @@ public interface AiChatRoleService { @@ -48,7 +48,7 @@ public interface AiChatRoleService {
48 * 创建【我的】聊天角色 48 * 创建【我的】聊天角色
49 * 49 *
50 * @param updateReqVO 更新信息 50 * @param updateReqVO 更新信息
51 - * @param userId 用户编号 51 + * @param userId 用户编号
52 */ 52 */
53 void updateChatRoleMy(AiChatRoleSaveMyReqVO updateReqVO, Long userId); 53 void updateChatRoleMy(AiChatRoleSaveMyReqVO updateReqVO, Long userId);
54 54
@@ -62,7 +62,7 @@ public interface AiChatRoleService { @@ -62,7 +62,7 @@ public interface AiChatRoleService {
62 /** 62 /**
63 * 删除【我的】聊天角色 63 * 删除【我的】聊天角色
64 * 64 *
65 - * @param id 编号 65 + * @param id 编号
66 * @param userId 用户编号 66 * @param userId 用户编号
67 */ 67 */
68 void deleteChatRoleMy(Long id, Long userId); 68 void deleteChatRoleMy(Long id, Long userId);
@@ -106,7 +106,7 @@ public interface AiChatRoleService { @@ -106,7 +106,7 @@ public interface AiChatRoleService {
106 * 获得【我的】聊天角色分页 106 * 获得【我的】聊天角色分页
107 * 107 *
108 * @param pageReqVO 分页查询 108 * @param pageReqVO 分页查询
109 - * @param userId 用户编号 109 + * @param userId 用户编号
110 * @return 聊天角色分页 110 * @return 聊天角色分页
111 */ 111 */
112 PageResult<AiChatRoleDO> getChatRoleMyPage(AiChatRolePageReqVO pageReqVO, Long userId); 112 PageResult<AiChatRoleDO> getChatRoleMyPage(AiChatRolePageReqVO pageReqVO, Long userId);
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleS @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleS
11 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO; 11 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
12 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 12 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
13 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatRoleMapper; 13 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatRoleMapper;
  14 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
14 import jakarta.annotation.Resource; 15 import jakarta.annotation.Resource;
15 import lombok.extern.slf4j.Slf4j; 16 import lombok.extern.slf4j.Slf4j;
16 import org.springframework.stereotype.Service; 17 import org.springframework.stereotype.Service;
@@ -21,7 +22,8 @@ import java.util.List; @@ -21,7 +22,8 @@ import java.util.List;
21 22
22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
23 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 24 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
24 -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; 25 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_ROLE_DISABLE;
  26 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_ROLE_NOT_EXISTS;
25 27
26 /** 28 /**
27 * AI 聊天角色 Service 实现类 29 * AI 聊天角色 Service 实现类
@@ -35,8 +37,19 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { @@ -35,8 +37,19 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
35 @Resource 37 @Resource
36 private AiChatRoleMapper chatRoleMapper; 38 private AiChatRoleMapper chatRoleMapper;
37 39
  40 + @Resource
  41 + private AiKnowledgeService knowledgeService;
  42 + @Resource
  43 + private AiToolService toolService;
  44 +
38 @Override 45 @Override
39 public Long createChatRole(AiChatRoleSaveReqVO createReqVO) { 46 public Long createChatRole(AiChatRoleSaveReqVO createReqVO) {
  47 + // 校验文档
  48 + validateDocuments(createReqVO.getKnowledgeIds());
  49 + // 校验工具
  50 + validateTools(createReqVO.getToolIds());
  51 +
  52 + // 保存角色
40 AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class); 53 AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class);
41 chatRoleMapper.insert(chatRole); 54 chatRoleMapper.insert(chatRole);
42 return chatRole.getId(); 55 return chatRole.getId();
@@ -44,6 +57,12 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { @@ -44,6 +57,12 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
44 57
45 @Override 58 @Override
46 public Long createChatRoleMy(AiChatRoleSaveMyReqVO createReqVO, Long userId) { 59 public Long createChatRoleMy(AiChatRoleSaveMyReqVO createReqVO, Long userId) {
  60 + // 校验文档
  61 + validateDocuments(createReqVO.getKnowledgeIds());
  62 + // 校验工具
  63 + validateTools(createReqVO.getToolIds());
  64 +
  65 + // 保存角色
47 AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class).setUserId(userId) 66 AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class).setUserId(userId)
48 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setPublicStatus(false); 67 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setPublicStatus(false);
49 chatRoleMapper.insert(chatRole); 68 chatRoleMapper.insert(chatRole);
@@ -54,7 +73,12 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { @@ -54,7 +73,12 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
54 public void updateChatRole(AiChatRoleSaveReqVO updateReqVO) { 73 public void updateChatRole(AiChatRoleSaveReqVO updateReqVO) {
55 // 校验存在 74 // 校验存在
56 validateChatRoleExists(updateReqVO.getId()); 75 validateChatRoleExists(updateReqVO.getId());
57 - // 更新 76 + // 校验文档
  77 + validateDocuments(updateReqVO.getKnowledgeIds());
  78 + // 校验工具
  79 + validateTools(updateReqVO.getToolIds());
  80 +
  81 + // 更新角色
58 AiChatRoleDO updateObj = BeanUtils.toBean(updateReqVO, AiChatRoleDO.class); 82 AiChatRoleDO updateObj = BeanUtils.toBean(updateReqVO, AiChatRoleDO.class);
59 chatRoleMapper.updateById(updateObj); 83 chatRoleMapper.updateById(updateObj);
60 } 84 }
@@ -66,12 +90,42 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { @@ -66,12 +90,42 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
66 if (ObjectUtil.notEqual(chatRole.getUserId(), userId)) { 90 if (ObjectUtil.notEqual(chatRole.getUserId(), userId)) {
67 throw exception(CHAT_ROLE_NOT_EXISTS); 91 throw exception(CHAT_ROLE_NOT_EXISTS);
68 } 92 }
  93 + // 校验文档
  94 + validateDocuments(updateReqVO.getKnowledgeIds());
  95 + // 校验工具
  96 + validateTools(updateReqVO.getToolIds());
69 97
70 // 更新 98 // 更新
71 AiChatRoleDO updateObj = BeanUtils.toBean(updateReqVO, AiChatRoleDO.class); 99 AiChatRoleDO updateObj = BeanUtils.toBean(updateReqVO, AiChatRoleDO.class);
72 chatRoleMapper.updateById(updateObj); 100 chatRoleMapper.updateById(updateObj);
73 } 101 }
74 102
  103 + /**
  104 + * 校验知识库是否存在
  105 + *
  106 + * @param knowledgeIds 知识库编号列表
  107 + */
  108 + private void validateDocuments(List<Long> knowledgeIds) {
  109 + if (CollUtil.isEmpty(knowledgeIds)) {
  110 + return;
  111 + }
  112 + // 校验文档是否存在
  113 + knowledgeIds.forEach(knowledgeService::validateKnowledgeExists);
  114 + }
  115 +
  116 + /**
  117 + * 校验工具是否存在
  118 + *
  119 + * @param toolIds 工具编号列表
  120 + */
  121 + private void validateTools(List<Long> toolIds) {
  122 + if (CollUtil.isEmpty(toolIds)) {
  123 + return;
  124 + }
  125 + // 遍历校验每个工具是否存在
  126 + toolIds.forEach(toolService::validateToolExists);
  127 + }
  128 +
75 @Override 129 @Override
76 public void deleteChatRole(Long id) { 130 public void deleteChatRole(Long id) {
77 // 校验存在 131 // 校验存在
@@ -134,7 +188,8 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { @@ -134,7 +188,8 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
134 @Override 188 @Override
135 public List<String> getChatRoleCategoryList() { 189 public List<String> getChatRoleCategoryList() {
136 List<AiChatRoleDO> list = chatRoleMapper.selectListGroupByCategory(CommonStatusEnum.ENABLE.getStatus()); 190 List<AiChatRoleDO> list = chatRoleMapper.selectListGroupByCategory(CommonStatusEnum.ENABLE.getStatus());
137 - return convertList(list, AiChatRoleDO::getCategory, role -> role != null && StrUtil.isNotBlank(role.getCategory())); 191 + return convertList(list, AiChatRoleDO::getCategory,
  192 + role -> role != null && StrUtil.isNotBlank(role.getCategory()));
138 } 193 }
139 194
140 @Override 195 @Override
@@ -143,4 +198,3 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { @@ -143,4 +198,3 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
143 } 198 }
144 199
145 } 200 }
146 -  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
  4 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
  8 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  9 +import jakarta.validation.Valid;
  10 +import org.springframework.ai.chat.model.ChatModel;
  11 +import org.springframework.ai.image.ImageModel;
  12 +import org.springframework.ai.vectorstore.VectorStore;
  13 +
  14 +import javax.annotation.Nullable;
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +
  18 +/**
  19 + * AI 模型 Service 接口
  20 + *
  21 + * @author fansili
  22 + * @since 2024/4/24 19:42
  23 + */
  24 +public interface AiModelService {
  25 +
  26 + /**
  27 + * 创建模型
  28 + *
  29 + * @param createReqVO 创建信息
  30 + * @return 编号
  31 + */
  32 + Long createModel(@Valid AiModelSaveReqVO createReqVO);
  33 +
  34 + /**
  35 + * 更新模型
  36 + *
  37 + * @param updateReqVO 更新信息
  38 + */
  39 + void updateModel(@Valid AiModelSaveReqVO updateReqVO);
  40 +
  41 + /**
  42 + * 删除模型
  43 + *
  44 + * @param id 编号
  45 + */
  46 + void deleteModel(Long id);
  47 +
  48 + /**
  49 + * 获得模型
  50 + *
  51 + * @param id 编号
  52 + * @return 模型
  53 + */
  54 + AiModelDO getModel(Long id);
  55 +
  56 + /**
  57 + * 获得默认的模型
  58 + *
  59 + * 如果获取不到,则抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 业务异常
  60 + *
  61 + * @return 模型
  62 + */
  63 + AiModelDO getRequiredDefaultModel(Integer type);
  64 +
  65 + /**
  66 + * 获得模型分页
  67 + *
  68 + * @param pageReqVO 分页查询
  69 + * @return 模型分页
  70 + */
  71 + PageResult<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO);
  72 +
  73 + /**
  74 + * 校验模型是否可使用
  75 + *
  76 + * @param id 编号
  77 + * @return 模型
  78 + */
  79 + AiModelDO validateModel(Long id);
  80 +
  81 + /**
  82 + * 获得模型列表
  83 + *
  84 + * @param status 状态
  85 + * @param type 类型
  86 + * @param platform 平台,允许空
  87 + * @return 模型列表
  88 + */
  89 + List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type,
  90 + @Nullable String platform);
  91 +
  92 + // ========== 与 Spring AI 集成 ==========
  93 +
  94 + /**
  95 + * 获得 ChatModel 对象
  96 + *
  97 + * @param id 编号
  98 + * @return ChatModel 对象
  99 + */
  100 + ChatModel getChatModel(Long id);
  101 +
  102 + /**
  103 + * 获得 ImageModel 对象
  104 + *
  105 + * @param id 编号
  106 + * @return ImageModel 对象
  107 + */
  108 + ImageModel getImageModel(Long id);
  109 +
  110 + /**
  111 + * 获得 MidjourneyApi 对象
  112 + *
  113 + * @param id 编号
  114 + * @return MidjourneyApi 对象
  115 + */
  116 + MidjourneyApi getMidjourneyApi(Long id);
  117 +
  118 + /**
  119 + * 获得 SunoApi 对象
  120 + *
  121 + * @return SunoApi 对象
  122 + */
  123 + SunoApi getSunoApi();
  124 +
  125 + /**
  126 + * 获得 VectorStore 对象
  127 + *
  128 + * @param id 编号
  129 + * @param metadataFields 元数据的定义
  130 + * @return VectorStore 对象
  131 + */
  132 + VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields);
  133 +
  134 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  4 +import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
  5 +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
  6 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
  7 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  8 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
  11 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
  12 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
  13 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  14 +import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatMapper;
  15 +import jakarta.annotation.Resource;
  16 +import org.springframework.ai.chat.model.ChatModel;
  17 +import org.springframework.ai.embedding.EmbeddingModel;
  18 +import org.springframework.ai.image.ImageModel;
  19 +import org.springframework.ai.vectorstore.SimpleVectorStore;
  20 +import org.springframework.ai.vectorstore.VectorStore;
  21 +import org.springframework.stereotype.Service;
  22 +import org.springframework.validation.annotation.Validated;
  23 +
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
  29 +
  30 +/**
  31 + * AI 模型 Service 实现类
  32 + *
  33 + * @author fansili
  34 + */
  35 +@Service
  36 +@Validated
  37 +public class AiModelServiceImpl implements AiModelService {
  38 +
  39 + @Resource
  40 + private AiApiKeyService apiKeyService;
  41 +
  42 + @Resource
  43 + private AiChatMapper modelMapper;
  44 +
  45 + @Resource
  46 + private AiModelFactory modelFactory;
  47 +
  48 + @Override
  49 + public Long createModel(AiModelSaveReqVO createReqVO) {
  50 + // 1. 校验
  51 + AiPlatformEnum.validatePlatform(createReqVO.getPlatform());
  52 + apiKeyService.validateApiKey(createReqVO.getKeyId());
  53 +
  54 + // 2. 插入
  55 + AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class);
  56 + modelMapper.insert(model);
  57 + return model.getId();
  58 + }
  59 +
  60 + @Override
  61 + public void updateModel(AiModelSaveReqVO updateReqVO) {
  62 + // 1. 校验
  63 + validateModelExists(updateReqVO.getId());
  64 + AiPlatformEnum.validatePlatform(updateReqVO.getPlatform());
  65 + apiKeyService.validateApiKey(updateReqVO.getKeyId());
  66 +
  67 + // 2. 更新
  68 + AiModelDO updateObj = BeanUtils.toBean(updateReqVO, AiModelDO.class);
  69 + modelMapper.updateById(updateObj);
  70 + }
  71 +
  72 + @Override
  73 + public void deleteModel(Long id) {
  74 + // 校验存在
  75 + validateModelExists(id);
  76 + // 删除
  77 + modelMapper.deleteById(id);
  78 + }
  79 +
  80 + private AiModelDO validateModelExists(Long id) {
  81 + AiModelDO model = modelMapper.selectById(id);
  82 + if (modelMapper.selectById(id) == null) {
  83 + throw exception(MODEL_NOT_EXISTS);
  84 + }
  85 + return model;
  86 + }
  87 +
  88 + @Override
  89 + public AiModelDO getModel(Long id) {
  90 + return modelMapper.selectById(id);
  91 + }
  92 +
  93 + @Override
  94 + public AiModelDO getRequiredDefaultModel(Integer type) {
  95 + AiModelDO model = modelMapper.selectFirstByStatus(type, CommonStatusEnum.ENABLE.getStatus());
  96 + if (model == null) {
  97 + throw exception(MODEL_DEFAULT_NOT_EXISTS);
  98 + }
  99 + return model;
  100 + }
  101 +
  102 + @Override
  103 + public PageResult<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO) {
  104 + return modelMapper.selectPage(pageReqVO);
  105 + }
  106 +
  107 + @Override
  108 + public AiModelDO validateModel(Long id) {
  109 + AiModelDO model = validateModelExists(id);
  110 + if (CommonStatusEnum.isDisable(model.getStatus())) {
  111 + throw exception(MODEL_DISABLE);
  112 + }
  113 + return model;
  114 + }
  115 +
  116 + @Override
  117 + public List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, String platform) {
  118 + return modelMapper.selectListByStatusAndType(status, type, platform);
  119 + }
  120 +
  121 + // ========== 与 Spring AI 集成 ==========
  122 +
  123 + @Override
  124 + public ChatModel getChatModel(Long id) {
  125 + AiModelDO model = validateModel(id);
  126 + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
  127 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  128 + return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl());
  129 + }
  130 +
  131 + @Override
  132 + public ImageModel getImageModel(Long id) {
  133 + AiModelDO model = validateModel(id);
  134 + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
  135 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  136 + return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl());
  137 + }
  138 +
  139 + @Override
  140 + public MidjourneyApi getMidjourneyApi(Long id) {
  141 + AiModelDO model = validateModel(id);
  142 + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
  143 + return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl());
  144 + }
  145 +
  146 + @Override
  147 + public SunoApi getSunoApi() {
  148 + AiApiKeyDO apiKey = apiKeyService.getRequiredDefaultApiKey(
  149 + AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
  150 + return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl());
  151 + }
  152 +
  153 + @Override
  154 + public VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields) {
  155 + // 获取模型 + 密钥
  156 + AiModelDO model = validateModel(id);
  157 + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
  158 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  159 +
  160 + // 创建或获取 EmbeddingModel 对象
  161 + EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel(
  162 + platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel());
  163 +
  164 + // 创建或获取 VectorStore 对象
  165 + return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields);
  166 +// return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields);
  167 +// return modelFactory.getOrCreateVectorStore(RedisVectorStore.class, embeddingModel, metadataFields);
  168 +// return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields);
  169 + }
  170 +
  171 +}
0 \ No newline at end of file 172 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelService.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiToolService.java
1 package cn.iocoder.yudao.module.ai.service.model; 1 package cn.iocoder.yudao.module.ai.service.model;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;  
5 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO;  
6 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 4 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolSaveReqVO;
  6 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
9 import java.util.Collection; 9 import java.util.Collection;
10 import java.util.List; 10 import java.util.List;
11 11
12 -import java.util.Set;  
13 -  
14 /** 12 /**
15 - * AI 聊天模型 Service 接口 13 + * AI 工具 Service 接口
16 * 14 *
17 - * @author fansili  
18 - * @since 2024/4/24 19:42 15 + * @author 芋道源码
19 */ 16 */
20 -public interface AiChatModelService { 17 +public interface AiToolService {
21 18
22 /** 19 /**
23 - * 创建聊天模型 20 + * 创建工具
24 * 21 *
25 * @param createReqVO 创建信息 22 * @param createReqVO 创建信息
26 * @return 编号 23 * @return 编号
27 */ 24 */
28 - Long createChatModel(@Valid AiChatModelSaveReqVO createReqVO); 25 + Long createTool(@Valid AiToolSaveReqVO createReqVO);
29 26
30 /** 27 /**
31 - * 更新聊天模型 28 + * 更新工具
32 * 29 *
33 * @param updateReqVO 更新信息 30 * @param updateReqVO 更新信息
34 */ 31 */
35 - void updateChatModel(@Valid AiChatModelSaveReqVO updateReqVO); 32 + void updateTool(@Valid AiToolSaveReqVO updateReqVO);
36 33
37 /** 34 /**
38 - * 删除聊天模型 35 + * 删除工具
39 * 36 *
40 * @param id 编号 37 * @param id 编号
41 */ 38 */
42 - void deleteChatModel(Long id); 39 + void deleteTool(Long id);
43 40
44 /** 41 /**
45 - * 获得聊天模型 42 + * 校验工具是否存在
46 * 43 *
47 * @param id 编号 44 * @param id 编号
48 - * @return 聊天模型  
49 */ 45 */
50 - AiChatModelDO getChatModel(Long id); 46 + void validateToolExists(Long id);
51 47
52 /** 48 /**
53 - * 获得默认的聊天模型  
54 - *  
55 - * 如果获取不到,则抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 业务异常 49 + * 获得工具
56 * 50 *
57 - * @return 聊天模型 51 + * @param id 编号
  52 + * @return 工具
58 */ 53 */
59 - AiChatModelDO getRequiredDefaultChatModel(); 54 + AiToolDO getTool(Long id);
60 55
61 /** 56 /**
62 - * 获得聊天模型分页 57 + * 获得工具列表
63 * 58 *
64 - * @param pageReqVO 分页查询  
65 - * @return 聊天模型分页 59 + * @param ids 编号列表
  60 + * @return 工具列表
66 */ 61 */
67 - PageResult<AiChatModelDO> getChatModelPage(AiChatModelPageReqVO pageReqVO); 62 + List<AiToolDO> getToolList(Collection<Long> ids);
68 63
69 /** 64 /**
70 - * 校验聊天模型 65 + * 获得工具分页
71 * 66 *
72 - * @param id 编号  
73 - * @return 聊天模型 67 + * @param pageReqVO 分页查询
  68 + * @return 工具分页
74 */ 69 */
75 - AiChatModelDO validateChatModel(Long id); 70 + PageResult<AiToolDO> getToolPage(AiToolPageReqVO pageReqVO);
76 71
77 /** 72 /**
78 - * 获得聊天模型列表 73 + * 获得工具列表
79 * 74 *
80 * @param status 状态 75 * @param status 状态
81 - * @return 聊天模型列表 76 + * @return 工具列表
82 */ 77 */
83 - List<AiChatModelDO> getChatModelListByStatus(Integer status); 78 + List<AiToolDO> getToolListByStatus(Integer status);
84 79
85 - /**  
86 - * 获得聊天模型列表  
87 - *  
88 - * @param ids 编号数组  
89 - * @return 模型列表  
90 - */  
91 - List<AiChatModelDO> getChatModelList(Collection<Long> ids);  
92 -} 80 +}
93 \ No newline at end of file 81 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiToolServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolSaveReqVO;
  8 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
  9 +import cn.iocoder.yudao.module.ai.dal.mysql.model.AiToolMapper;
  10 +import jakarta.annotation.Resource;
  11 +import org.springframework.beans.factory.NoSuchBeanDefinitionException;
  12 +import org.springframework.stereotype.Service;
  13 +import org.springframework.validation.annotation.Validated;
  14 +
  15 +import java.util.Collection;
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  19 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.TOOL_NAME_NOT_EXISTS;
  20 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.TOOL_NOT_EXISTS;
  21 +
  22 +/**
  23 + * AI 工具 Service 实现类
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Service
  28 +@Validated
  29 +public class AiToolServiceImpl implements AiToolService {
  30 +
  31 + @Resource
  32 + private AiToolMapper toolMapper;
  33 +
  34 + @Override
  35 + public Long createTool(AiToolSaveReqVO createReqVO) {
  36 + // 校验名称是否存在
  37 + validateToolNameExists(createReqVO.getName());
  38 +
  39 + // 插入
  40 + AiToolDO tool = BeanUtils.toBean(createReqVO, AiToolDO.class);
  41 + toolMapper.insert(tool);
  42 + return tool.getId();
  43 + }
  44 +
  45 + @Override
  46 + public void updateTool(AiToolSaveReqVO updateReqVO) {
  47 + // 1.1 校验存在
  48 + validateToolExists(updateReqVO.getId());
  49 + // 1.2 校验名称是否存在
  50 + validateToolNameExists(updateReqVO.getName());
  51 +
  52 + // 2. 更新
  53 + AiToolDO updateObj = BeanUtils.toBean(updateReqVO, AiToolDO.class);
  54 + toolMapper.updateById(updateObj);
  55 + }
  56 +
  57 + @Override
  58 + public void deleteTool(Long id) {
  59 + // 校验存在
  60 + validateToolExists(id);
  61 + // 删除
  62 + toolMapper.deleteById(id);
  63 + }
  64 +
  65 + @Override
  66 + public void validateToolExists(Long id) {
  67 + if (toolMapper.selectById(id) == null) {
  68 + throw exception(TOOL_NOT_EXISTS);
  69 + }
  70 + }
  71 +
  72 + private void validateToolNameExists(String name) {
  73 + try {
  74 + SpringUtil.getBean(name);
  75 + } catch (NoSuchBeanDefinitionException e) {
  76 + throw exception(TOOL_NAME_NOT_EXISTS, name);
  77 + }
  78 + }
  79 +
  80 + @Override
  81 + public AiToolDO getTool(Long id) {
  82 + return toolMapper.selectById(id);
  83 + }
  84 +
  85 + @Override
  86 + public List<AiToolDO> getToolList(Collection<Long> ids) {
  87 + return toolMapper.selectBatchIds(ids);
  88 + }
  89 +
  90 + @Override
  91 + public PageResult<AiToolDO> getToolPage(AiToolPageReqVO pageReqVO) {
  92 + return toolMapper.selectPage(pageReqVO);
  93 + }
  94 +
  95 + @Override
  96 + public List<AiToolDO> getToolListByStatus(Integer status) {
  97 + return toolMapper.selectListByStatus(status);
  98 + }
  99 +
  100 +}
0 \ No newline at end of file 101 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/DirectoryListToolFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model.tool;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.io.FileUtil;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import com.fasterxml.jackson.annotation.JsonClassDescription;
  8 +import com.fasterxml.jackson.annotation.JsonProperty;
  9 +import com.fasterxml.jackson.annotation.JsonPropertyDescription;
  10 +import lombok.AllArgsConstructor;
  11 +import lombok.Data;
  12 +import lombok.NoArgsConstructor;
  13 +import org.springframework.stereotype.Component;
  14 +
  15 +import java.io.File;
  16 +import java.util.Arrays;
  17 +import java.util.Collections;
  18 +import java.util.List;
  19 +import java.util.function.Function;
  20 +
  21 +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  23 +
  24 +/**
  25 + * 工具:列出指定目录的文件列表
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Component("directory_list")
  30 +public class DirectoryListToolFunction implements Function<DirectoryListToolFunction.Request, DirectoryListToolFunction.Response> {
  31 +
  32 + @Data
  33 + @JsonClassDescription("列出指定目录的文件列表")
  34 + public static class Request {
  35 +
  36 + /**
  37 + * 目录路径
  38 + */
  39 + @JsonProperty(required = true, value = "path")
  40 + @JsonPropertyDescription("目录路径,例如说:/Users/yunai")
  41 + private String path;
  42 +
  43 + }
  44 +
  45 + @Data
  46 + @AllArgsConstructor
  47 + @NoArgsConstructor
  48 + public static class Response {
  49 +
  50 + /**
  51 + * 文件列表
  52 + */
  53 + private List<File> files;
  54 +
  55 + @Data
  56 + public static class File {
  57 +
  58 + /**
  59 + * 是否为目录
  60 + */
  61 + private Boolean directory;
  62 +
  63 + /**
  64 + * 名称
  65 + */
  66 + private String name;
  67 +
  68 + /**
  69 + * 大小,仅对文件有效
  70 + */
  71 + private String size;
  72 +
  73 + /**
  74 + * 最后修改时间
  75 + */
  76 + private String lastModified;
  77 +
  78 + }
  79 +
  80 + }
  81 +
  82 + @Override
  83 + public Response apply(Request request) {
  84 + // 校验目录存在
  85 + String path = StrUtil.blankToDefault(request.getPath(), "/");
  86 + if (!FileUtil.exist(path) || !FileUtil.isDirectory(path)) {
  87 + return new Response(Collections.emptyList());
  88 + }
  89 + // 列出目录
  90 + File[] files = FileUtil.ls(path);
  91 + if (ArrayUtil.isEmpty(files)) {
  92 + return new Response(Collections.emptyList());
  93 + }
  94 + return new Response(convertList(Arrays.asList(files), file ->
  95 + new Response.File().setDirectory(file.isDirectory()).setName(file.getName())
  96 + .setLastModified(LocalDateTimeUtil.format(LocalDateTimeUtil.of(file.lastModified()), NORM_DATETIME_PATTERN))));
  97 + }
  98 +
  99 +}
0 \ No newline at end of file 100 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/WeatherQueryToolFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model.tool;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.util.RandomUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import com.fasterxml.jackson.annotation.JsonClassDescription;
  7 +import com.fasterxml.jackson.annotation.JsonProperty;
  8 +import com.fasterxml.jackson.annotation.JsonPropertyDescription;
  9 +import lombok.AllArgsConstructor;
  10 +import lombok.Data;
  11 +import lombok.NoArgsConstructor;
  12 +import org.springframework.stereotype.Component;
  13 +
  14 +import java.time.LocalDateTime;
  15 +import java.util.function.Function;
  16 +
  17 +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
  18 +
  19 +/**
  20 + * 工具:查询指定城市的天气信息
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +@Component("weather_query")
  25 +public class WeatherQueryToolFunction
  26 + implements Function<WeatherQueryToolFunction.Request, WeatherQueryToolFunction.Response> {
  27 +
  28 + private static final String[] WEATHER_CONDITIONS = { "晴朗", "多云", "阴天", "小雨", "大雨", "雷雨", "小雪", "大雪" };
  29 +
  30 + @Data
  31 + @JsonClassDescription("查询指定城市的天气信息")
  32 + public static class Request {
  33 +
  34 + /**
  35 + * 城市名称
  36 + */
  37 + @JsonProperty(required = true, value = "city")
  38 + @JsonPropertyDescription("城市名称,例如:北京、上海、广州")
  39 + private String city;
  40 +
  41 + }
  42 +
  43 + @Data
  44 + @AllArgsConstructor
  45 + @NoArgsConstructor
  46 + public static class Response {
  47 +
  48 + /**
  49 + * 城市名称
  50 + */
  51 + private String city;
  52 +
  53 + /**
  54 + * 天气信息
  55 + */
  56 + private WeatherInfo weatherInfo;
  57 +
  58 + @Data
  59 + @AllArgsConstructor
  60 + @NoArgsConstructor
  61 + public static class WeatherInfo {
  62 +
  63 + /**
  64 + * 温度(摄氏度)
  65 + */
  66 + private Integer temperature;
  67 +
  68 + /**
  69 + * 天气状况
  70 + */
  71 + private String condition;
  72 +
  73 + /**
  74 + * 湿度百分比
  75 + */
  76 + private Integer humidity;
  77 +
  78 + /**
  79 + * 风速(km/h)
  80 + */
  81 + private Integer windSpeed;
  82 +
  83 + /**
  84 + * 查询时间
  85 + */
  86 + private String queryTime;
  87 +
  88 + }
  89 +
  90 + }
  91 +
  92 + @Override
  93 + public Response apply(Request request) {
  94 + // 检查城市名称是否为空
  95 + if (StrUtil.isBlank(request.getCity())) {
  96 + return new Response("未知城市", null);
  97 + }
  98 +
  99 + // 获取天气数据
  100 + String city = request.getCity();
  101 + Response.WeatherInfo weatherInfo = generateMockWeatherInfo();
  102 + return new Response(city, weatherInfo);
  103 + }
  104 +
  105 + /**
  106 + * 生成模拟的天气数据
  107 + * 在实际应用中,应替换为真实 API 调用
  108 + */
  109 + private Response.WeatherInfo generateMockWeatherInfo() {
  110 + int temperature = RandomUtil.randomInt(-5, 30);
  111 + int humidity = RandomUtil.randomInt(1, 100);
  112 + int windSpeed = RandomUtil.randomInt(1, 30);
  113 + String condition = RandomUtil.randomEle(WEATHER_CONDITIONS);
  114 + return new Response.WeatherInfo(temperature, condition, humidity, windSpeed,
  115 + LocalDateTimeUtil.format(LocalDateTime.now(), NORM_DATETIME_PATTERN));
  116 + }
  117 +
  118 +}
0 \ No newline at end of file 119 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
@@ -16,7 +16,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; @@ -16,7 +16,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
16 import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; 16 import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper;
17 import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; 17 import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum;
18 import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; 18 import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum;
19 -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 19 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
20 import cn.iocoder.yudao.module.infra.api.file.FileApi; 20 import cn.iocoder.yudao.module.infra.api.file.FileApi;
21 import jakarta.annotation.Resource; 21 import jakarta.annotation.Resource;
22 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
@@ -41,7 +41,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXIS @@ -41,7 +41,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXIS
41 public class AiMusicServiceImpl implements AiMusicService { 41 public class AiMusicServiceImpl implements AiMusicService {
42 42
43 @Resource 43 @Resource
44 - private AiApiKeyService apiKeyService; 44 + private AiModelService modelService;
45 45
46 @Resource 46 @Resource
47 private AiMusicMapper musicMapper; 47 private AiMusicMapper musicMapper;
@@ -53,7 +53,7 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -53,7 +53,7 @@ public class AiMusicServiceImpl implements AiMusicService {
53 @Transactional(rollbackFor = Exception.class) 53 @Transactional(rollbackFor = Exception.class)
54 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { 54 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) {
55 // 1. 调用 Suno 生成音乐 55 // 1. 调用 Suno 生成音乐
56 - SunoApi sunoApi = apiKeyService.getSunoApi(); 56 + SunoApi sunoApi = modelService.getSunoApi();
57 List<SunoApi.MusicData> musicDataList; 57 List<SunoApi.MusicData> musicDataList;
58 if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { 58 if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) {
59 // 1.1 描述模式 59 // 1.1 描述模式
@@ -88,7 +88,7 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -88,7 +88,7 @@ public class AiMusicServiceImpl implements AiMusicService {
88 log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size()); 88 log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size());
89 89
90 // GET 请求,为避免参数过长,分批次处理 90 // GET 请求,为避免参数过长,分批次处理
91 - SunoApi sunoApi = apiKeyService.getSunoApi(); 91 + SunoApi sunoApi = modelService.getSunoApi();
92 CollUtil.split(streamingTask, 36).forEach(chunkList -> { 92 CollUtil.split(streamingTask, 36).forEach(chunkList -> {
93 Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId); 93 Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId);
94 List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet())); 94 List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet()));
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.write; 1 package cn.iocoder.yudao.module.ai.service.write;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.hutool.core.lang.Assert; 4 +import cn.hutool.core.util.ObjUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 7 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 import cn.iocoder.yudao.framework.ai.core.util.AiUtils; 8 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 9 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@@ -11,17 +12,16 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -11,17 +12,16 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 12 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; 13 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWritePageReqVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWritePageReqVO;
14 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
15 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  16 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
16 import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; 17 import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO;
17 import cn.iocoder.yudao.module.ai.dal.mysql.write.AiWriteMapper; 18 import cn.iocoder.yudao.module.ai.dal.mysql.write.AiWriteMapper;
18 import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum; 19 import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;
19 import cn.iocoder.yudao.module.ai.enums.DictTypeConstants; 20 import cn.iocoder.yudao.module.ai.enums.DictTypeConstants;
20 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; 21 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
21 import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum; 22 import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum;
22 -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;  
23 -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;  
24 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; 23 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  24 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
25 import cn.iocoder.yudao.module.system.api.dict.DictDataApi; 25 import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
26 import jakarta.annotation.Resource; 26 import jakarta.annotation.Resource;
27 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
@@ -42,7 +42,7 @@ import java.util.Objects; @@ -42,7 +42,7 @@ import java.util.Objects;
42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
43 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; 43 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
44 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 44 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
45 -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WRITE_NOT_EXISTS; 45 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
46 46
47 /** 47 /**
48 * AI 写作 Service 实现类 48 * AI 写作 Service 实现类
@@ -54,17 +54,15 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WRITE_NOT_EXIS @@ -54,17 +54,15 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WRITE_NOT_EXIS
54 public class AiWriteServiceImpl implements AiWriteService { 54 public class AiWriteServiceImpl implements AiWriteService {
55 55
56 @Resource 56 @Resource
57 - private AiApiKeyService apiKeyService;  
58 - @Resource  
59 - private AiChatModelService chatModalService; 57 + private AiModelService modalService;
60 @Resource 58 @Resource
61 private AiChatRoleService chatRoleService; 59 private AiChatRoleService chatRoleService;
62 60
63 @Resource 61 @Resource
64 - private DictDataApi dictDataApi; 62 + private AiWriteMapper writeMapper;
65 63
66 @Resource 64 @Resource
67 - private AiWriteMapper writeMapper; 65 + private DictDataApi dictDataApi;
68 66
69 @Override 67 @Override
70 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) { 68 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) {
@@ -72,17 +70,17 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -72,17 +70,17 @@ public class AiWriteServiceImpl implements AiWriteService {
72 AiChatRoleDO writeRole = CollUtil.getFirst( 70 AiChatRoleDO writeRole = CollUtil.getFirst(
73 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_WRITE_ROLE.getName())); 71 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_WRITE_ROLE.getName()));
74 // 1.1 获取写作执行模型 72 // 1.1 获取写作执行模型
75 - AiChatModelDO model = getModel(writeRole); 73 + AiModelDO model = getModel(writeRole);
76 // 1.2 获取角色设定消息 74 // 1.2 获取角色设定消息
77 String systemMessage = Objects.nonNull(writeRole) && StrUtil.isNotBlank(writeRole.getSystemMessage()) 75 String systemMessage = Objects.nonNull(writeRole) && StrUtil.isNotBlank(writeRole.getSystemMessage())
78 ? writeRole.getSystemMessage() : AiChatRoleEnum.AI_WRITE_ROLE.getSystemMessage(); 76 ? writeRole.getSystemMessage() : AiChatRoleEnum.AI_WRITE_ROLE.getSystemMessage();
79 // 1.3 校验平台 77 // 1.3 校验平台
80 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 78 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
81 - StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); 79 + StreamingChatModel chatModel = modalService.getChatModel(model.getKeyId());
82 80
83 // 2. 插入写作信息 81 // 2. 插入写作信息
84 - AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class,  
85 - write -> write.setUserId(userId).setPlatform(platform.getPlatform()).setModel(model.getModel())); 82 + AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class, write -> write.setUserId(userId)
  83 + .setPlatform(platform.getPlatform()).setModelId(model.getId()).setModel(model.getModel()));
86 writeMapper.insert(writeDO); 84 writeMapper.insert(writeDO);
87 85
88 // 3.1 构建 Prompt,并进行调用 86 // 3.1 构建 Prompt,并进行调用
@@ -92,7 +90,7 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -92,7 +90,7 @@ public class AiWriteServiceImpl implements AiWriteService {
92 // 3.2 流式返回 90 // 3.2 流式返回
93 StringBuffer contentBuffer = new StringBuffer(); 91 StringBuffer contentBuffer = new StringBuffer();
94 return streamResponse.map(chunk -> { 92 return streamResponse.map(chunk -> {
95 - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; 93 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
96 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 94 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
97 contentBuffer.append(newContent); 95 contentBuffer.append(newContent);
98 // 响应结果 96 // 响应结果
@@ -109,19 +107,25 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -109,19 +107,25 @@ public class AiWriteServiceImpl implements AiWriteService {
109 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); 107 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR)));
110 } 108 }
111 109
112 - private AiChatModelDO getModel(AiChatRoleDO writeRole) {  
113 - AiChatModelDO model = null; 110 + private AiModelDO getModel(AiChatRoleDO writeRole) {
  111 + AiModelDO model = null;
114 if (Objects.nonNull(writeRole) && Objects.nonNull(writeRole.getModelId())) { 112 if (Objects.nonNull(writeRole) && Objects.nonNull(writeRole.getModelId())) {
115 - model = chatModalService.getChatModel(writeRole.getModelId()); 113 + model = modalService.getModel(writeRole.getModelId());
  114 + }
  115 + if (model == null) {
  116 + model = modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType());
116 } 117 }
  118 + // 校验模型存在、且合法
117 if (model == null) { 119 if (model == null) {
118 - model = chatModalService.getRequiredDefaultChatModel(); 120 + throw exception(MODEL_NOT_EXISTS);
  121 + }
  122 + if (ObjUtil.notEqual(model.getType(), AiModelTypeEnum.CHAT.getType())) {
  123 + throw exception(MODEL_USE_TYPE_ERROR);
119 } 124 }
120 - Assert.notNull(model, "[AI] 获取不到模型");  
121 return model; 125 return model;
122 } 126 }
123 127
124 - private Prompt buildPrompt(AiWriteGenerateReqVO generateReqVO, AiChatModelDO model, String systemMessage) { 128 + private Prompt buildPrompt(AiWriteGenerateReqVO generateReqVO, AiModelDO model, String systemMessage) {
125 // 1. 构建 message 列表 129 // 1. 构建 message 列表
126 List<Message> chatMessages = buildMessages(generateReqVO, systemMessage); 130 List<Message> chatMessages = buildMessages(generateReqVO, systemMessage);
127 // 2. 构建 options 对象 131 // 2. 构建 options 对象
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
@@ -14,93 +14,105 @@ @@ -14,93 +14,105 @@
14 <name>${project.artifactId}</name> 14 <name>${project.artifactId}</name>
15 <description>AI 大模型拓展,接入国内外大模型</description> 15 <description>AI 大模型拓展,接入国内外大模型</description>
16 <properties> 16 <properties>
17 - <spring-ai.groupId>group.springframework.ai</spring-ai.groupId>  
18 - <spring-ai.version>1.1.0</spring-ai.version> 17 + <spring-ai.version>1.0.0-M6</spring-ai.version>
19 </properties> 18 </properties>
20 19
21 <dependencies> 20 <dependencies>
22 <dependency> 21 <dependency>
23 - <groupId>${spring-ai.groupId}</groupId>  
24 - <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>  
25 - <version>${spring-ai.version}</version> 22 + <groupId>cn.iocoder.boot</groupId>
  23 + <artifactId>yudao-common</artifactId>
26 </dependency> 24 </dependency>
  25 +
  26 + <!-- Spring AI Model 模型接入 -->
27 <dependency> 27 <dependency>
28 - <groupId>${spring-ai.groupId}</groupId> 28 + <groupId>org.springframework.ai</groupId>
29 <artifactId>spring-ai-openai-spring-boot-starter</artifactId> 29 <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
30 <version>${spring-ai.version}</version> 30 <version>${spring-ai.version}</version>
31 </dependency> 31 </dependency>
32 <dependency> 32 <dependency>
33 - <groupId>${spring-ai.groupId}</groupId> 33 + <groupId>org.springframework.ai</groupId>
34 <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId> 34 <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
35 <version>${spring-ai.version}</version> 35 <version>${spring-ai.version}</version>
36 </dependency> 36 </dependency>
37 <dependency> 37 <dependency>
38 - <groupId>${spring-ai.groupId}</groupId> 38 + <groupId>org.springframework.ai</groupId>
39 <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> 39 <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
40 <version>${spring-ai.version}</version> 40 <version>${spring-ai.version}</version>
41 </dependency> 41 </dependency>
42 <dependency> 42 <dependency>
43 - <groupId>${spring-ai.groupId}</groupId> 43 + <groupId>org.springframework.ai</groupId>
44 <artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId> 44 <artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId>
45 <version>${spring-ai.version}</version> 45 <version>${spring-ai.version}</version>
46 </dependency> 46 </dependency>
47 -  
48 - <!-- 向量化,基于 Redis 存储,Tika 解析内容 -->  
49 -  
50 - <!-- 暂不做经济型,先注释 TODO 经济型是啥呀? -->  
51 - <!-- <dependency>-->  
52 - <!-- <groupId>${spring-ai.groupId}</groupId>-->  
53 - <!-- <artifactId>spring-ai-transformers-spring-boot-starter</artifactId>-->  
54 - <!-- <version>${spring-ai.version}</version>-->  
55 - <!-- </dependency>-->  
56 - <dependency>  
57 - <groupId>${spring-ai.groupId}</groupId>  
58 - <artifactId>spring-ai-tika-document-reader</artifactId> 47 + <dependency>
  48 + <!-- 通义千问 -->
  49 + <groupId>com.alibaba.cloud.ai</groupId>
  50 + <artifactId>spring-ai-alibaba-starter</artifactId>
  51 + <version>${spring-ai.version}.1</version>
  52 + </dependency>
  53 + <dependency>
  54 + <!-- 文心一言 -->
  55 + <groupId>org.springframework.ai</groupId>
  56 + <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
59 <version>${spring-ai.version}</version> 57 <version>${spring-ai.version}</version>
60 - <!-- TODO 芋艿:boot 项目里,不引入 cloud 依赖!!!另外,这样也是为了解决启动报错的问题! -->  
61 - <exclusions>  
62 - <exclusion>  
63 - <artifactId>spring-cloud-function-context</artifactId>  
64 - <groupId>org.springframework.cloud</groupId>  
65 - </exclusion>  
66 - <exclusion>  
67 - <artifactId>spring-cloud-function-core</artifactId>  
68 - <groupId>org.springframework.cloud</groupId>  
69 - </exclusion>  
70 - </exclusions>  
71 </dependency> 58 </dependency>
72 <dependency> 59 <dependency>
73 - <groupId>${spring-ai.groupId}</groupId>  
74 - <artifactId>spring-ai-redis-store</artifactId> 60 + <!-- 智谱 GLM -->
  61 + <groupId>org.springframework.ai</groupId>
  62 + <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
  63 + <version>${spring-ai.version}</version>
  64 + </dependency>
  65 + <dependency>
  66 + <groupId>org.springframework.ai</groupId>
  67 + <artifactId>spring-ai-minimax-spring-boot-starter</artifactId>
  68 + <version>${spring-ai.version}</version>
  69 + </dependency>
  70 + <dependency>
  71 + <groupId>org.springframework.ai</groupId>
  72 + <artifactId>spring-ai-moonshot-spring-boot-starter</artifactId>
75 <version>${spring-ai.version}</version> 73 <version>${spring-ai.version}</version>
76 </dependency> 74 </dependency>
77 75
  76 + <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
78 <dependency> 77 <dependency>
79 - <groupId>cn.iocoder.boot</groupId>  
80 - <artifactId>yudao-spring-boot-starter-redis</artifactId> 78 + <!-- Qdrant:https://qdrant.tech/ -->
  79 + <groupId>org.springframework.ai</groupId>
  80 + <artifactId>spring-ai-qdrant-store</artifactId>
  81 + <version>${spring-ai.version}</version>
81 </dependency> 82 </dependency>
82 83
83 <dependency> 84 <dependency>
  85 + <!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ -->
  86 + <groupId>org.springframework.ai</groupId>
  87 + <artifactId>spring-ai-redis-store</artifactId>
  88 + <version>${spring-ai.version}</version>
  89 + </dependency>
  90 + <dependency>
84 <groupId>cn.iocoder.boot</groupId> 91 <groupId>cn.iocoder.boot</groupId>
85 - <artifactId>yudao-common</artifactId> 92 + <artifactId>yudao-spring-boot-starter-redis</artifactId>
86 </dependency> 93 </dependency>
87 94
88 <dependency> 95 <dependency>
89 - <groupId>${spring-ai.groupId}</groupId>  
90 - <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId> 96 + <!-- Milvus:https://milvus.io/ -->
  97 + <groupId>org.springframework.ai</groupId>
  98 + <artifactId>spring-ai-milvus-store</artifactId>
91 <version>${spring-ai.version}</version> 99 <version>${spring-ai.version}</version>
92 </dependency> 100 </dependency>
93 101
94 - <!-- 阿里云 通义千问 -->  
95 - <!-- TODO 芋艿:等 spring cloud alibaba ai 发布最新的时候,可以替换掉这个依赖,并且删除我们直接 cv 的代码 -->  
96 <dependency> 102 <dependency>
97 - <groupId>com.alibaba</groupId>  
98 - <artifactId>dashscope-sdk-java</artifactId>  
99 - <version>2.14.0</version> 103 + <!-- Tika:负责内容的解析 -->
  104 + <groupId>org.springframework.ai</groupId>
  105 + <artifactId>spring-ai-tika-document-reader</artifactId>
  106 + <version>${spring-ai.version}</version>
  107 + <!-- TODO 芋艿:boot 项目里,不引入 cloud 依赖!!!另外,这样也是为了解决启动报错的问题! -->
100 <exclusions> 108 <exclusions>
101 <exclusion> 109 <exclusion>
102 - <groupId>org.slf4j</groupId>  
103 - <artifactId>slf4j-simple</artifactId> 110 + <artifactId>spring-cloud-function-context</artifactId>
  111 + <groupId>org.springframework.cloud</groupId>
  112 + </exclusion>
  113 + <exclusion>
  114 + <artifactId>spring-cloud-function-core</artifactId>
  115 + <groupId>org.springframework.cloud</groupId>
104 </exclusion> 116 </exclusion>
105 </exclusions> 117 </exclusions>
106 </dependency> 118 </dependency>
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
1 package cn.iocoder.yudao.framework.ai.config; 1 package cn.iocoder.yudao.framework.ai.config;
2 2
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
3 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; 5 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; 6 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl;
5 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; 7 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
6 -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; 8 +import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
  9 +import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel;
7 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 10 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
  11 +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
8 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 12 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
9 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; 13 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
10 -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;  
11 -import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;  
12 import lombok.extern.slf4j.Slf4j; 14 import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;
  16 +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;
  17 +import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
  18 +import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
  19 +import org.springframework.ai.embedding.BatchingStrategy;
  20 +import org.springframework.ai.embedding.TokenCountBatchingStrategy;
  21 +import org.springframework.ai.model.tool.ToolCallingManager;
  22 +import org.springframework.ai.openai.OpenAiChatModel;
  23 +import org.springframework.ai.openai.OpenAiChatOptions;
  24 +import org.springframework.ai.openai.api.OpenAiApi;
13 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; 25 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
14 import org.springframework.ai.tokenizer.TokenCountEstimator; 26 import org.springframework.ai.tokenizer.TokenCountEstimator;
15 -import org.springframework.ai.transformer.splitter.TokenTextSplitter;  
16 import org.springframework.boot.autoconfigure.AutoConfiguration; 27 import org.springframework.boot.autoconfigure.AutoConfiguration;
17 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
18 import org.springframework.boot.context.properties.EnableConfigurationProperties; 29 import org.springframework.boot.context.properties.EnableConfigurationProperties;
19 import org.springframework.context.annotation.Bean; 30 import org.springframework.context.annotation.Bean;
20 -import org.springframework.context.annotation.Import;  
21 -import org.springframework.context.annotation.Lazy;  
22 31
23 /** 32 /**
24 * 芋道 AI 自动配置 33 * 芋道 AI 自动配置
@@ -26,9 +35,12 @@ import org.springframework.context.annotation.Lazy; @@ -26,9 +35,12 @@ import org.springframework.context.annotation.Lazy;
26 * @author fansili 35 * @author fansili
27 */ 36 */
28 @AutoConfiguration 37 @AutoConfiguration
29 -@EnableConfigurationProperties(YudaoAiProperties.class) 38 +@EnableConfigurationProperties({ YudaoAiProperties.class,
  39 + QdrantVectorStoreProperties.class, // 解析 Qdrant 配置
  40 + RedisVectorStoreProperties.class, // 解析 Redis 配置
  41 + MilvusVectorStoreProperties.class, MilvusServiceClientProperties.class // 解析 Milvus 配置
  42 +})
30 @Slf4j 43 @Slf4j
31 -@Import(TongYiAutoConfiguration.class)  
32 public class YudaoAiAutoConfiguration { 44 public class YudaoAiAutoConfiguration {
33 45
34 @Bean 46 @Bean
@@ -36,33 +48,148 @@ public class YudaoAiAutoConfiguration { @@ -36,33 +48,148 @@ public class YudaoAiAutoConfiguration {
36 return new AiModelFactoryImpl(); 48 return new AiModelFactoryImpl();
37 } 49 }
38 50
39 -  
40 // ========== 各种 AI Client 创建 ========== 51 // ========== 各种 AI Client 创建 ==========
41 52
42 @Bean 53 @Bean
43 @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true") 54 @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")
44 public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) { 55 public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {
45 - YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek();  
46 - DeepSeekChatOptions options = DeepSeekChatOptions.builder()  
47 - .model(properties.getModel())  
48 - .temperature(properties.getTemperature())  
49 - .maxTokens(properties.getMaxTokens())  
50 - .topP(properties.getTopP()) 56 + YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepseek();
  57 + return buildDeepSeekChatModel(properties);
  58 + }
  59 +
  60 + public DeepSeekChatModel buildDeepSeekChatModel(YudaoAiProperties.DeepSeekProperties properties) {
  61 + if (StrUtil.isEmpty(properties.getModel())) {
  62 + properties.setModel(DeepSeekChatModel.MODEL_DEFAULT);
  63 + }
  64 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  65 + .openAiApi(OpenAiApi.builder()
  66 + .baseUrl(DeepSeekChatModel.BASE_URL)
  67 + .apiKey(properties.getApiKey())
  68 + .build())
  69 + .defaultOptions(OpenAiChatOptions.builder()
  70 + .model(properties.getModel())
  71 + .temperature(properties.getTemperature())
  72 + .maxTokens(properties.getMaxTokens())
  73 + .topP(properties.getTopP())
  74 + .build())
  75 + .toolCallingManager(getToolCallingManager())
  76 + .build();
  77 + return new DeepSeekChatModel(openAiChatModel);
  78 + }
  79 +
  80 + @Bean
  81 + @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
  82 + public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
  83 + YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao();
  84 + return buildDouBaoChatClient(properties);
  85 + }
  86 +
  87 + public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBaoProperties properties) {
  88 + if (StrUtil.isEmpty(properties.getModel())) {
  89 + properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
  90 + }
  91 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  92 + .openAiApi(OpenAiApi.builder()
  93 + .baseUrl(DouBaoChatModel.BASE_URL)
  94 + .apiKey(properties.getApiKey())
  95 + .build())
  96 + .defaultOptions(OpenAiChatOptions.builder()
  97 + .model(properties.getModel())
  98 + .temperature(properties.getTemperature())
  99 + .maxTokens(properties.getMaxTokens())
  100 + .topP(properties.getTopP())
  101 + .build())
  102 + .toolCallingManager(getToolCallingManager())
  103 + .build();
  104 + return new DouBaoChatModel(openAiChatModel);
  105 + }
  106 +
  107 + @Bean
  108 + @ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true")
  109 + public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) {
  110 + YudaoAiProperties.SiliconFlowProperties properties = yudaoAiProperties.getSiliconflow();
  111 + return buildSiliconFlowChatClient(properties);
  112 + }
  113 +
  114 + public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlowProperties properties) {
  115 + if (StrUtil.isEmpty(properties.getModel())) {
  116 + properties.setModel(SiliconFlowChatModel.MODEL_DEFAULT);
  117 + }
  118 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  119 + .openAiApi(OpenAiApi.builder()
  120 + .baseUrl(SiliconFlowChatModel.BASE_URL)
  121 + .apiKey(properties.getApiKey())
  122 + .build())
  123 + .defaultOptions(OpenAiChatOptions.builder()
  124 + .model(properties.getModel())
  125 + .temperature(properties.getTemperature())
  126 + .maxTokens(properties.getMaxTokens())
  127 + .topP(properties.getTopP())
  128 + .build())
  129 + .toolCallingManager(getToolCallingManager())
51 .build(); 130 .build();
52 - return new DeepSeekChatModel(properties.getApiKey(), options); 131 + return new SiliconFlowChatModel(openAiChatModel);
  132 + }
  133 +
  134 + @Bean
  135 + @ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true")
  136 + public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) {
  137 + YudaoAiProperties.HunYuanProperties properties = yudaoAiProperties.getHunyuan();
  138 + return buildHunYuanChatClient(properties);
  139 + }
  140 +
  141 + public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuanProperties properties) {
  142 + if (StrUtil.isEmpty(properties.getModel())) {
  143 + properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
  144 + }
  145 + // 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL
  146 + if (StrUtil.isEmpty(properties.getBaseUrl())) {
  147 + properties.setBaseUrl(
  148 + StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL
  149 + : HunYuanChatModel.BASE_URL);
  150 + }
  151 + // 创建 OpenAiChatModel、HunYuanChatModel 对象
  152 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  153 + .openAiApi(OpenAiApi.builder()
  154 + .baseUrl(properties.getBaseUrl())
  155 + .apiKey(properties.getApiKey())
  156 + .build())
  157 + .defaultOptions(OpenAiChatOptions.builder()
  158 + .model(properties.getModel())
  159 + .temperature(properties.getTemperature())
  160 + .maxTokens(properties.getMaxTokens())
  161 + .topP(properties.getTopP())
  162 + .build())
  163 + .toolCallingManager(getToolCallingManager())
  164 + .build();
  165 + return new HunYuanChatModel(openAiChatModel);
53 } 166 }
54 167
55 @Bean 168 @Bean
56 @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") 169 @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
57 public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { 170 public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
58 YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo(); 171 YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo();
59 - XingHuoChatOptions options = XingHuoChatOptions.builder()  
60 - .model(properties.getModel())  
61 - .temperature(properties.getTemperature())  
62 - .maxTokens(properties.getMaxTokens())  
63 - .topK(properties.getTopK()) 172 + return buildXingHuoChatClient(properties);
  173 + }
  174 +
  175 + public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuoProperties properties) {
  176 + if (StrUtil.isEmpty(properties.getModel())) {
  177 + properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
  178 + }
  179 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  180 + .openAiApi(OpenAiApi.builder()
  181 + .baseUrl(XingHuoChatModel.BASE_URL)
  182 + .apiKey(properties.getAppKey() + ":" + properties.getSecretKey())
  183 + .build())
  184 + .defaultOptions(OpenAiChatOptions.builder()
  185 + .model(properties.getModel())
  186 + .temperature(properties.getTemperature())
  187 + .maxTokens(properties.getMaxTokens())
  188 + .topP(properties.getTopP())
  189 + .build())
  190 + .toolCallingManager(getToolCallingManager())
64 .build(); 191 .build();
65 - return new XingHuoChatModel(properties.getAppKey(), properties.getSecretKey(), options); 192 + return new XingHuoChatModel(openAiChatModel);
66 } 193 }
67 194
68 @Bean 195 @Bean
@@ -78,44 +205,20 @@ public class YudaoAiAutoConfiguration { @@ -78,44 +205,20 @@ public class YudaoAiAutoConfiguration {
78 return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); 205 return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl());
79 } 206 }
80 207
81 - // ========== rag 相关 ==========  
82 - // TODO @xin 免费版本  
83 -// @Bean  
84 -// @Lazy // TODO 芋艿:临时注释,避免无法启动」  
85 -// public TransformersEmbeddingModel transformersEmbeddingClient() {  
86 -// return new TransformersEmbeddingModel(MetadataMode.EMBED);  
87 -// }  
88 -  
89 - /**  
90 - * TODO @xin 默认版本先不弄,目前都先取对应的 EmbeddingModel  
91 - */  
92 -// @Bean  
93 -// @Lazy // TODO 芋艿:临时注释,避免无法启动  
94 -// public RedisVectorStore vectorStore(TransformersEmbeddingModel embeddingModel, RedisVectorStoreProperties properties,  
95 -// RedisProperties redisProperties) {  
96 -// var config = RedisVectorStore.RedisVectorStoreConfig.builder()  
97 -// .withIndexName(properties.getIndex())  
98 -// .withPrefix(properties.getPrefix())  
99 -// .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))  
100 -// .build();  
101 -//  
102 -// RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,  
103 -// new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),  
104 -// properties.isInitializeSchema());  
105 -// redisVectorStore.afterPropertiesSet();  
106 -// return redisVectorStore;  
107 -// }  
108 - @Bean  
109 - @Lazy // TODO 芋艿:临时注释,避免无法启动  
110 - public TokenTextSplitter tokenTextSplitter() {  
111 - //TODO @xin 配置提取  
112 - return new TokenTextSplitter(500, 100, 5, 10000, true);  
113 - } 208 + // ========== RAG 相关 ==========
114 209
115 @Bean 210 @Bean
116 - @Lazy // TODO 芋艿:临时注释,避免无法启动  
117 public TokenCountEstimator tokenCountEstimator() { 211 public TokenCountEstimator tokenCountEstimator() {
118 return new JTokkitTokenCountEstimator(); 212 return new JTokkitTokenCountEstimator();
119 } 213 }
120 214
  215 + @Bean
  216 + public BatchingStrategy batchingStrategy() {
  217 + return new TokenCountBatchingStrategy();
  218 + }
  219 +
  220 + private static ToolCallingManager getToolCallingManager() {
  221 + return SpringUtil.getBean(ToolCallingManager.class);
  222 + }
  223 +
121 } 224 }
122 \ No newline at end of file 225 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
@@ -16,11 +16,31 @@ public class YudaoAiProperties { @@ -16,11 +16,31 @@ public class YudaoAiProperties {
16 /** 16 /**
17 * DeepSeek 17 * DeepSeek
18 */ 18 */
19 - private DeepSeekProperties deepSeek; 19 + @SuppressWarnings("SpellCheckingInspection")
  20 + private DeepSeekProperties deepseek;
  21 +
  22 + /**
  23 + * 字节豆包
  24 + */
  25 + @SuppressWarnings("SpellCheckingInspection")
  26 + private DouBaoProperties doubao;
  27 +
  28 + /**
  29 + * 腾讯混元
  30 + */
  31 + @SuppressWarnings("SpellCheckingInspection")
  32 + private HunYuanProperties hunyuan;
  33 +
  34 + /**
  35 + * 硅基流动
  36 + */
  37 + @SuppressWarnings("SpellCheckingInspection")
  38 + private SiliconFlowProperties siliconflow;
20 39
21 /** 40 /**
22 * 讯飞星火 41 * 讯飞星火
23 */ 42 */
  43 + @SuppressWarnings("SpellCheckingInspection")
24 private XingHuoProperties xinghuo; 44 private XingHuoProperties xinghuo;
25 45
26 /** 46 /**
@@ -31,33 +51,74 @@ public class YudaoAiProperties { @@ -31,33 +51,74 @@ public class YudaoAiProperties {
31 /** 51 /**
32 * Suno 音乐 52 * Suno 音乐
33 */ 53 */
  54 + @SuppressWarnings("SpellCheckingInspection")
34 private SunoProperties suno; 55 private SunoProperties suno;
35 56
36 @Data 57 @Data
37 - public static class XingHuoProperties { 58 + public static class DeepSeekProperties {
38 59
39 private String enable; 60 private String enable;
40 - private String appId;  
41 - private String appKey;  
42 - private String secretKey; 61 + private String apiKey;
43 62
44 private String model; 63 private String model;
45 - private Float temperature; 64 + private Double temperature;
46 private Integer maxTokens; 65 private Integer maxTokens;
47 - private Integer topK; 66 + private Double topP;
48 67
49 } 68 }
50 69
51 @Data 70 @Data
52 - public static class DeepSeekProperties { 71 + public static class DouBaoProperties {
  72 +
  73 + private String enable;
  74 + private String apiKey;
  75 +
  76 + private String model;
  77 + private Double temperature;
  78 + private Integer maxTokens;
  79 + private Double topP;
  80 +
  81 + }
  82 +
  83 + @Data
  84 + public static class HunYuanProperties {
53 85
54 private String enable; 86 private String enable;
  87 + private String baseUrl;
55 private String apiKey; 88 private String apiKey;
56 89
57 private String model; 90 private String model;
58 - private Float temperature; 91 + private Double temperature;
  92 + private Integer maxTokens;
  93 + private Double topP;
  94 +
  95 + }
  96 +
  97 + @Data
  98 + public static class SiliconFlowProperties {
  99 +
  100 + private String enable;
  101 + private String apiKey;
  102 +
  103 + private String model;
  104 + private Double temperature;
  105 + private Integer maxTokens;
  106 + private Double topP;
  107 +
  108 + }
  109 +
  110 + @Data
  111 + public static class XingHuoProperties {
  112 +
  113 + private String enable;
  114 + private String appId;
  115 + private String appKey;
  116 + private String secretKey;
  117 +
  118 + private String model;
  119 + private Double temperature;
59 private Integer maxTokens; 120 private Integer maxTokens;
60 - private Float topP; 121 + private Double topP;
61 122
62 } 123 }
63 124