feat:替换 Dify 为自建 RAG去实现

1、修复了若干无权限时的失败提示语
2、新增了一个生成后续建议问题的功能
3、重构了知识问答部分的权限管理模块
4、修复了若干渲染不恰当的样式渲染
This commit is contained in:
PingChuan
2026-04-10 16:20:32 +08:00
parent f525707358
commit 5bee9288b9
31 changed files with 407 additions and 304 deletions
@@ -7,7 +7,7 @@
* @version 1.0.0
*/
import { useState, useEffect } from 'react';
import { useEffect } from 'react';
import {
Card,
Table,
@@ -24,7 +24,6 @@ import {
Flex,
Typography,
Popconfirm,
Spin,
Tooltip,
} from 'antd';
import {
@@ -35,8 +34,7 @@ import {
CheckCircleOutlined,
} from '@ant-design/icons';
import { useAreaDatasetConfig } from '~/hooks/use-area-dataset-config';
import { fetchDatasets } from '~/api/dify-dataset/api/datasetApi';
import type { Dataset as DifyDataset } from '~/api/dify-dataset/type';
import { usePermission } from '~/hooks/usePermission';
import type { AreaDataset } from '~/api/v3/dify/area-datasets';
const { Title, Text } = Typography;
@@ -90,13 +88,11 @@ export default function AreaDatasetConfig() {
canManageDataset,
} = useAreaDatasetConfig();
const { userRole: rawUserRole, userArea: rawUserArea } = usePermission();
const isProvincialAdmin = rawUserRole === 'provincial_admin';
// 内部状态
const [form] = Form.useForm();
const [difyDatasets, setDifyDatasets] = useState<DifyDataset[]>([]);
const [difyDatasetsLoading, setDifyDatasetsLoading] = useState<boolean>(false);
const [difyDatasetsTotal, setDifyDatasetsTotal] = useState<number>(0);
const [difyDatasetsPage, setDifyDatasetsPage] = useState<number>(1);
const [isLoadingDifyDatasets, setIsLoadingDifyDatasets] = useState<boolean>(false);
// ==================== Effects ====================
@@ -107,7 +103,6 @@ export default function AreaDatasetConfig() {
if (record) {
form.setFieldsValue({
area: record.area,
dataset_id: record.dataset_id,
dataset_name: record.dataset_name,
dataset_description: record.dataset_description,
is_public: record.is_public,
@@ -116,51 +111,14 @@ export default function AreaDatasetConfig() {
});
}
} else if (!editingId && modalVisible) {
// 新增时重置表单
form.resetFields();
loadDifyDatasets(); // 加载Dify知识库列表
// 非省级管理员自动填充地区
if (!isProvincialAdmin && rawUserArea) {
form.setFieldValue('area', rawUserArea);
}
}
}, [editingId, modalVisible, datasets, form]);
// ==================== Dify Datasets Loading ====================
/**
* 从Dify API加载知识库列表
*/
const loadDifyDatasets = async (pageNum: number = 1) => {
if (isLoadingDifyDatasets) return;
setIsLoadingDifyDatasets(true);
try {
const response = await fetchDatasets(pageNum, 20);
setDifyDatasets(response.data);
setDifyDatasetsTotal(response.total);
setDifyDatasetsPage(pageNum);
setDifyDatasetsLoading(false);
} catch (error: any) {
console.error('加载Dify知识库列表失败:', error);
message.error('加载Dify知识库列表失败');
setDifyDatasetsLoading(false);
} finally {
setIsLoadingDifyDatasets(false);
}
};
/**
* Dify数据集选择器滚动加载
*/
const handleDatasetSelectScroll = (e: React.UIEvent<HTMLDivElement>) => {
const { target } = e;
const { scrollTop, scrollHeight, clientHeight } = target as HTMLDivElement;
// 滚动到底部且还有更多数据时加载下一页
if (scrollHeight - scrollTop === clientHeight &&
difyDatasets.length < difyDatasetsTotal &&
!difyDatasetsLoading) {
loadDifyDatasets(difyDatasetsPage + 1);
}
};
// ==================== Event Handlers ====================
/**
@@ -168,13 +126,12 @@ export default function AreaDatasetConfig() {
*/
const handleCreateClick = () => {
if (!canManageDataset) {
message.error('您没有创建知识库绑定的权限');
message.error('您没有创建知识库的权限');
return;
}
setEditingId(null);
setModalVisible(true);
form.resetFields();
loadDifyDatasets();
};
/**
@@ -208,6 +165,24 @@ export default function AreaDatasetConfig() {
* 处理表单提交
*/
const handleFormSubmit = async (values: any) => {
// 编辑时检查 is_default 是否从 false 变为 true
if (editingId && values.is_default) {
const record = datasets.find((item) => item.id === editingId);
if (record && !record.is_default) {
Modal.confirm({
title: '切换默认知识库',
content: '确认将此知识库设为默认?该地区的对话助手将自动绑定此知识库进行问答。',
okText: '确认',
cancelText: '取消',
onOk: () => doSubmit(values),
});
return;
}
}
await doSubmit(values);
};
const doSubmit = async (values: any) => {
let success = false;
if (editingId) {
@@ -269,6 +244,7 @@ export default function AreaDatasetConfig() {
key: 'dataset_name',
width: 200,
ellipsis: true,
align: 'center',
render: (text: string) => (
<Tooltip title={text}>
<Text style={{ color: colors.text }} strong>
@@ -277,25 +253,26 @@ export default function AreaDatasetConfig() {
</Tooltip>
),
},
{
title: '知识库ID',
dataIndex: 'dataset_id',
key: 'dataset_id',
width: 200,
ellipsis: true,
render: (text: string) => (
<Tooltip title={text}>
<Text type="secondary" style={{ fontSize: '12px' }}>
{text.substring(0, 8)}...{text.substring(text.length - 4)}
</Text>
</Tooltip>
),
},
// {
// title: '知识库ID',
// dataIndex: 'dataset_id',
// key: 'dataset_id',
// width: 200,
// ellipsis: true,
// render: (text: string) => (
// <Tooltip title={text}>
// <Text type="secondary" style={{ fontSize: '12px' }}>
// {text.substring(0, 8)}...{text.substring(text.length - 4)}
// </Text>
// </Tooltip>
// ),
// },
{
title: '描述',
dataIndex: 'dataset_description',
key: 'dataset_description',
ellipsis: true,
align: 'center',
render: (text: string) =>
text ? (
<Tooltip title={text}>
@@ -319,7 +296,7 @@ export default function AreaDatasetConfig() {
</Tag>
)}
{record.is_default && (
<Tag color={colors.primary} style={{ color: '#fff' }}>
<Tag color={colors.primary} style={{ color: '#00684a' }}>
</Tag>
)}
@@ -330,7 +307,7 @@ export default function AreaDatasetConfig() {
title: '排序',
dataIndex: 'sort_order',
key: 'sort_order',
width: 70,
width: 170,
align: 'center' as const,
sorter: (a: AreaDataset, b: AreaDataset) => a.sort_order - b.sort_order,
},
@@ -364,29 +341,37 @@ export default function AreaDatasetConfig() {
key: 'actions',
width: 120,
fixed: 'right' as const,
render: (_: any, record: AreaDataset) => (
<Space size="small">
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEditClick(record)}
>
</Button>
<Popconfirm
title="确定删除?"
description="删除后该地区的用户将无法访问此知识库"
onConfirm={() => handleDeleteClick(record.id)}
okText="确定"
cancelText="取消"
>
<Button type="link" danger size="small" icon={<DeleteOutlined />}>
</Button>
</Popconfirm>
</Space>
),
render: (_: any, record: AreaDataset) => {
// 市级管理员只能编辑自己地区的知识库
const canEdit = isProvincialAdmin || record.area === rawUserArea;
return (
<Space size="small">
{canEdit && (
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEditClick(record)}
>
</Button>
)}
{isProvincialAdmin && (
<Popconfirm
title="确定删除?"
description="删除后该地区的用户将无法访问此知识库"
onConfirm={() => handleDeleteClick(record.id)}
okText="确定"
cancelText="取消"
>
<Button type="link" danger size="small" icon={<DeleteOutlined />}>
</Button>
</Popconfirm>
)}
</Space>
);
},
},
]
: []),
@@ -432,7 +417,7 @@ export default function AreaDatasetConfig() {
icon={<PlusOutlined />}
onClick={handleCreateClick}
>
</Button>
)}
</Flex>
@@ -503,7 +488,7 @@ export default function AreaDatasetConfig() {
{/* 新增/编辑对话框 */}
<Modal
title={editingId ? '编辑知识库绑定' : '新增知识库绑定'}
title={editingId ? '编辑知识库' : '新增知识库'}
open={modalVisible}
onOk={() => form.submit()}
onCancel={handleFormCancel}
@@ -529,55 +514,12 @@ export default function AreaDatasetConfig() {
>
<Select
placeholder="请选择地区"
disabled={!!editingId} // 编辑时禁用
options={Array.isArray(areas) ? areas.map((area) => ({
label: area,
value: area,
})) : []}
/>
</Form.Item>
{/* Dify知识库选择(仅新增时可选) */}
<Form.Item
name="dataset_id"
label="Dify知识库"
rules={[{ required: true, message: '请选择Dify知识库' }]}
>
<Select
placeholder="请选择或输入知识库ID"
disabled={!!editingId}
loading={difyDatasetsLoading}
onPopupScroll={handleDatasetSelectScroll}
dropdownRender={(menu) => (
<div>
{menu}
{difyDatasets.length < difyDatasetsTotal && (
<div style={{ textAlign: 'center', padding: '8px' }}>
<Spin size="small" />
<Text style={{ marginLeft: '8px' }} type="secondary">
...
</Text>
</div>
)}
</div>
)}
onDropdownVisibleChange={(open) => {
if (open && !editingId) {
loadDifyDatasets();
}
}}
options={difyDatasets.map((ds) => ({
label: (
<Flex vertical>
<Text strong>{ds.name}</Text>
<Text type="secondary" style={{ fontSize: '12px' }}>
ID: {ds.id}
</Text>
</Flex>
),
value: ds.id,
}))}
styles={{ popup: { root: { maxHeight: '300px' } } }}
options={
isProvincialAdmin
? (Array.isArray(areas) ? areas.map((area) => ({ label: area, value: area })) : [])
: (rawUserArea ? [{ label: rawUserArea, value: rawUserArea }] : [])
}
/>
</Form.Item>
@@ -594,7 +536,7 @@ export default function AreaDatasetConfig() {
</Form.Item>
{/* 知识库描述 */}
{/* <Form.Item
<Form.Item
name="dataset_description"
label="知识库描述"
>
@@ -603,36 +545,33 @@ export default function AreaDatasetConfig() {
rows={3}
maxLength={500}
/>
</Form.Item> */}
</Form.Item>
{/* 高级设置折叠面板 */}
{/* 高级设置 */}
<div style={{ marginTop: '24px' }}>
<Text strong style={{ color: colors.text, display: 'block', marginBottom: '16px' }}>
</Text>
<Flex gap="24px">
{/* 是否公开 */}
<Form.Item
name="is_public"
label="公开知识库"
valuePropName="checked"
tooltip="公开后所有地区的用户可以访问此知识库"
tooltip="公开后,其他地区的用户可以在对话中选择此知识库的问答助手"
>
<Switch />
</Form.Item>
{/* 是否默认 */}
<Form.Item
name="is_default"
label="默认知识库"
valuePropName="checked"
tooltip="设置为该地区的默认知识库,用户优先使用"
tooltip="设为默认后,该地区的对话助手将自动切换为使用此知识库"
>
<Switch />
</Form.Item>
{/* 排序顺序 */}
<Form.Item
name="sort_order"
label="排序顺序"