feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.dataset-header {
|
||||
padding-right: 12px !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.modal.upgrade-level {
|
||||
:global {
|
||||
.semi-modal-body {
|
||||
height: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.semi-modal-content {
|
||||
@apply bg-white-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { FilterKnowledgeType } from '@coze-data/utils';
|
||||
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
type UIModalProps,
|
||||
UICompositionModal,
|
||||
UICompositionModalSider,
|
||||
UICompositionModalMain,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { type Dataset } from '@coze-arch/bot-api/knowledge';
|
||||
|
||||
import { DATA_REFACTOR_CLASS_NAME } from '@/constant';
|
||||
|
||||
import {
|
||||
useKnowledgeListModalContent,
|
||||
KnowledgeListModalContent,
|
||||
} from './use-content';
|
||||
import SiderCategory from './sider-category';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface UseKnowledgeListModalParams {
|
||||
datasetList: Dataset[];
|
||||
onDatasetListChange: (list: Dataset[]) => void;
|
||||
onClickAddKnowledge?: (
|
||||
datasetId: string,
|
||||
type: UnitType,
|
||||
shouldUpload?: boolean,
|
||||
) => void;
|
||||
beforeCreate?: (shouldUpload: boolean) => void;
|
||||
onClickKnowledgeDetail?: (knowledgeID: string) => void;
|
||||
modalProps?: UIModalProps;
|
||||
canCreate?: boolean;
|
||||
defaultType?: FilterKnowledgeType;
|
||||
knowledgeTypeConfigList?: FilterKnowledgeType[];
|
||||
|
||||
projectID?: string;
|
||||
hideCreate?: boolean;
|
||||
createKnowledgeModal?: {
|
||||
modal: React.ReactNode;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseKnowledgeListReturnValue {
|
||||
node: JSX.Element;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export const useKnowledgeListModal = ({
|
||||
datasetList,
|
||||
onDatasetListChange,
|
||||
onClickAddKnowledge,
|
||||
beforeCreate,
|
||||
onClickKnowledgeDetail,
|
||||
modalProps,
|
||||
canCreate = true,
|
||||
defaultType,
|
||||
knowledgeTypeConfigList,
|
||||
projectID,
|
||||
hideCreate,
|
||||
createKnowledgeModal,
|
||||
}: UseKnowledgeListModalParams): UseKnowledgeListReturnValue => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const [category, setCategory] = useState<'library' | 'project'>(
|
||||
projectID ? 'project' : 'library',
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const { renderContent, renderSearch, renderCreateBtn, renderFilters } =
|
||||
useKnowledgeListModalContent({
|
||||
hideHeader: true,
|
||||
showFilters: ['scope-type', 'search-type'],
|
||||
datasetList,
|
||||
onDatasetListChange,
|
||||
onClickAddKnowledge,
|
||||
beforeCreate,
|
||||
onClickKnowledgeDetail,
|
||||
canCreate,
|
||||
defaultType,
|
||||
knowledgeTypeConfigList,
|
||||
// 需要优化属性选择方式
|
||||
projectID: category === 'project' ? projectID : '',
|
||||
createKnowledgeModal,
|
||||
});
|
||||
|
||||
return {
|
||||
node: (
|
||||
<UICompositionModal
|
||||
type="base-composition"
|
||||
header={I18n.t('dataset_set_title')}
|
||||
visible={visible}
|
||||
className={classNames(
|
||||
styles.modal,
|
||||
styles['upgrade-level'],
|
||||
DATA_REFACTOR_CLASS_NAME,
|
||||
)}
|
||||
centered
|
||||
onCancel={handleClose}
|
||||
filter={
|
||||
<div className="flex justify-between gap-[24px]">
|
||||
{renderFilters()}
|
||||
</div>
|
||||
}
|
||||
sider={
|
||||
<UICompositionModalSider className="!pt-[16px]">
|
||||
<UICompositionModalSider.Header className="flex flex-col gap-[16px]">
|
||||
{renderSearch()}
|
||||
{hideCreate ? null : renderCreateBtn()}
|
||||
</UICompositionModalSider.Header>
|
||||
<UICompositionModalSider.Content className="flex flex-col gap-[4px] mt-[16px]">
|
||||
<SiderCategory
|
||||
label={I18n.t('project_resource_modal_library_resources', {
|
||||
resource: I18n.t('resource_type_knowledge'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setCategory('library');
|
||||
}}
|
||||
selected={category === 'library'}
|
||||
/>
|
||||
{projectID ? (
|
||||
<SiderCategory
|
||||
label={I18n.t('project_resource_modal_project_resources', {
|
||||
resource: I18n.t('resource_type_knowledge'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setCategory('project');
|
||||
}}
|
||||
selected={category === 'project'}
|
||||
/>
|
||||
) : null}
|
||||
</UICompositionModalSider.Content>
|
||||
</UICompositionModalSider>
|
||||
}
|
||||
content={
|
||||
<UICompositionModalMain className="px-[12px]">
|
||||
{renderContent()}
|
||||
</UICompositionModalMain>
|
||||
}
|
||||
{...modalProps}
|
||||
></UICompositionModal>
|
||||
),
|
||||
close: handleClose,
|
||||
open: handleOpen,
|
||||
};
|
||||
};
|
||||
|
||||
export { KnowledgeCard } from './knowledge-card';
|
||||
export {
|
||||
KnowledgeListModalContent,
|
||||
useKnowledgeListModalContent,
|
||||
FilterKnowledgeType,
|
||||
};
|
||||
|
||||
export { KnowledgeCardListVertical } from './knowledge-card-list';
|
||||
@@ -0,0 +1,9 @@
|
||||
.popover {
|
||||
padding: 16px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: #2e3238;
|
||||
white-space: pre-line;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, type PropsWithChildren } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Popover } from '@coze-arch/bot-semi';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const FilePopover: FC<
|
||||
PropsWithChildren<{
|
||||
fileNames: string[];
|
||||
showTitle?: boolean;
|
||||
}>
|
||||
> = ({ fileNames = [], showTitle = true, children }) => (
|
||||
<Popover
|
||||
className={styles.popover}
|
||||
content={
|
||||
<div>
|
||||
{showTitle ? <p>{I18n.t('datasets_processing_notice')}</p> : null}
|
||||
<p>{fileNames.join('\n')}</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { FilePopover } from './file-popover';
|
||||
@@ -0,0 +1,220 @@
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
cursor: pointer;
|
||||
background: var(--light-usage-fill-color-fill-0, rgb(46 47 56 / 5%));
|
||||
border-bottom: 1px solid transparent;
|
||||
border-radius: var(--spacing-tight, 8px);
|
||||
}
|
||||
|
||||
.item:hover::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
border-top: 1px solid rgb(245 247 250);
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: 10px 8px;
|
||||
|
||||
border-bottom: 1px solid rgba(29, 28, 35, 8%);
|
||||
|
||||
.left {
|
||||
box-sizing: border-box;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
background-color: #fff;
|
||||
border: 1px solid rgb(237 237 238);
|
||||
border-radius: 6px;
|
||||
|
||||
&>img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
width: 0;
|
||||
height: 92px;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0, #1c1d23);
|
||||
}
|
||||
|
||||
.description {
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.tags-wapper {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
.file-list {
|
||||
color: var(--light-color-teal-teal-6, #00a794);
|
||||
background-color: #e4e6e9;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tag-square {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.creator {
|
||||
padding-left: 4px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-3, rgb(28 29 35 / 35%));
|
||||
}
|
||||
|
||||
.border-right {
|
||||
width: 1px;
|
||||
height: 8px;
|
||||
margin: 0 4px 0 8px;
|
||||
background-color: rgb(28 29 35 / 12%);
|
||||
}
|
||||
}
|
||||
|
||||
button.button {
|
||||
flex-shrink: 0;
|
||||
width: 80px;
|
||||
|
||||
&.added {
|
||||
color: var(--light-usage-primary-color-primary-disabled, #b4baf6);
|
||||
background: var(--light-usage-bg-color-bg-0, #fff);
|
||||
border: 1px solid var(--light-usage-disabled-color-disabled-border, #f0f0f5);
|
||||
}
|
||||
|
||||
&.addedMouseIn {
|
||||
color: var(--light-color-red-red-5, #ff441e);
|
||||
background: #fff;
|
||||
border: 1px solid var(--light-usage-border-color-border-1, rgb(29 28 35 / 12%));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.file-list-details {
|
||||
max-width: 335px;
|
||||
|
||||
.dataset-name {
|
||||
padding: 9px 12px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0,
|
||||
var(--light-usage-text-color-text-0, #1c1f23));
|
||||
}
|
||||
|
||||
.file-info {
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
padding: 9px 10px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0,
|
||||
var(--light-usage-text-color-text-0, #1c1f23));
|
||||
|
||||
.icon-note {
|
||||
margin-right: 8px;
|
||||
|
||||
>svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
>path {
|
||||
fill: #3370ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
padding: 12px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: #2e3238;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.no-more {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
grid-column: 1 / -1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
padding: 13px 0;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: var(--light-usage-text-color-text-2,
|
||||
var(--light-usage-text-color-text-2, rgb(28 31 35 / 60%)));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
KnowledgeCardListVertical,
|
||||
type DatasetCardListVerticalOperations,
|
||||
type DatasetCardListVerticalProps,
|
||||
} from './vertical';
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC } from 'react';
|
||||
|
||||
import { unix } from 'dayjs';
|
||||
import cs from 'classnames';
|
||||
import { useBoolean } from 'ahooks';
|
||||
import { IconSpin } from '@douyinfe/semi-icons';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { type ButtonProps } from '@coze-arch/bot-semi/Button';
|
||||
import {
|
||||
UITag,
|
||||
UIButton,
|
||||
Typography,
|
||||
Space,
|
||||
Avatar,
|
||||
Popover,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconNote } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
OrderField,
|
||||
type Dataset,
|
||||
DatasetStatus,
|
||||
StorageLocation,
|
||||
} from '@coze-arch/bot-api/knowledge';
|
||||
import { SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { getEllipsisCount, formatBytes } from '../../utils';
|
||||
import { FilePopover } from './components';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export interface DatasetCardListVerticalOperations {
|
||||
onAdd: (dataset: Dataset) => void | Promise<void>;
|
||||
onRemove: (dataset: Dataset) => void | Promise<void>;
|
||||
isAdded: (id: string) => boolean;
|
||||
}
|
||||
|
||||
function AddedButton(buttonProps: ButtonProps) {
|
||||
const [isMouseIn, { setFalse, setTrue }] = useBoolean(false);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
setTrue();
|
||||
};
|
||||
const onMouseLeave = () => {
|
||||
setFalse();
|
||||
};
|
||||
|
||||
return (
|
||||
<UIButton
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
{...buttonProps}
|
||||
className={cs({
|
||||
[buttonProps.className || '']: Boolean(buttonProps.className),
|
||||
[styles.addedMouseIn]: isMouseIn,
|
||||
})}
|
||||
>
|
||||
{isMouseIn ? I18n.t('Remove') : I18n.t('Added')}
|
||||
</UIButton>
|
||||
);
|
||||
}
|
||||
|
||||
export type DatasetCardListVerticalProps = DatasetCardListVerticalOperations & {
|
||||
list: Dataset[];
|
||||
loading: boolean;
|
||||
noMore: boolean;
|
||||
searchType: OrderField;
|
||||
onClickKnowledgeDetail?: (knowledgeID: string) => void;
|
||||
};
|
||||
|
||||
const DEFAULT_BOT_NUM = 99;
|
||||
|
||||
const SpaceTags = (item: Dataset) => (
|
||||
<Space className={styles.tags} wrap>
|
||||
{item.processing_file_list?.length ? (
|
||||
<FilePopover fileNames={item.processing_file_list || []}>
|
||||
<UITag color="teal" className={styles['file-list']}>
|
||||
{I18n.t('dataset_data_processing_tag', {
|
||||
num: item.processing_file_list?.length || 0,
|
||||
})}
|
||||
</UITag>
|
||||
</FilePopover>
|
||||
) : null}
|
||||
<UITag color="grey">
|
||||
{formatBytes(parseInt(String(item.all_file_size)))}
|
||||
</UITag>
|
||||
{item.file_list?.length ? (
|
||||
<Popover
|
||||
trigger="hover"
|
||||
showArrow
|
||||
content={
|
||||
<div className={styles['file-list-details']}>
|
||||
<div className={styles['dataset-name']}>{item.name || ''}</div>
|
||||
<div className={styles['file-info']}>
|
||||
{item.file_list?.map(fileInfo => (
|
||||
<div className={styles['file-info-item']} key={fileInfo}>
|
||||
<IconNote className={styles['icon-note']} />
|
||||
{fileInfo}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<UITag color="grey">
|
||||
{I18n.t('dataset_bot_count_tag', {
|
||||
num: getEllipsisCount(item.file_list?.length || 0, DEFAULT_BOT_NUM),
|
||||
})}
|
||||
</UITag>
|
||||
</Popover>
|
||||
) : (
|
||||
<UITag color="grey">
|
||||
{I18n.t('dataset_bot_count_tag', {
|
||||
num: getEllipsisCount(item.file_list?.length || 0, DEFAULT_BOT_NUM),
|
||||
})}
|
||||
</UITag>
|
||||
)}
|
||||
{item.storage_location === StorageLocation.OpenSearch ? (
|
||||
<UITag color="cyan">{I18n.t('knowledge_es_001')}</UITag>
|
||||
) : null}
|
||||
</Space>
|
||||
);
|
||||
|
||||
export const KnowledgeCardListVertical: FC<DatasetCardListVerticalProps> = ({
|
||||
list,
|
||||
loading,
|
||||
noMore,
|
||||
onAdd,
|
||||
onRemove,
|
||||
isAdded,
|
||||
searchType,
|
||||
onClickKnowledgeDetail,
|
||||
}) => {
|
||||
const { id: spaceId, space_type } = useSpaceStore(s => s.space);
|
||||
|
||||
const isPersonal = space_type === SpaceType.Personal;
|
||||
|
||||
const handleRow = (e: { stopPropagation: () => void }, id: string) => {
|
||||
e.stopPropagation();
|
||||
if (onClickKnowledgeDetail) {
|
||||
onClickKnowledgeDetail(id);
|
||||
} else {
|
||||
window.open(`/space/${spaceId}/knowledge/${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{list.map(item => (
|
||||
<div
|
||||
className={styles.item}
|
||||
key={item.dataset_id || ''}
|
||||
onClick={e => handleRow(e, item?.dataset_id || '')}
|
||||
>
|
||||
<Avatar shape="square" src={item.icon_url} className={styles.left} />
|
||||
|
||||
<div
|
||||
className={styles.content}
|
||||
data-testid={`${BotE2e.BotKnowledgeSelectListModalName}.${item.name}`}
|
||||
data-dtestid={`${BotE2e.BotKnowledgeSelectListModalName}.${item.name}`}
|
||||
>
|
||||
<Text className={styles.title} ellipsis={{ showTooltip: true }}>
|
||||
{item.name || ''}
|
||||
</Text>
|
||||
|
||||
{item.description ? (
|
||||
<Typography.Text
|
||||
className={styles.description}
|
||||
ellipsis={{ rows: 1 }}
|
||||
>
|
||||
{item.description}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{!item.description && !!item.file_list?.length && (
|
||||
<Typography.Text
|
||||
className={styles.description}
|
||||
ellipsis={{ rows: 1 }}
|
||||
>
|
||||
{item.file_list?.join('、')}
|
||||
</Typography.Text>
|
||||
)}
|
||||
|
||||
<div className={styles['tags-wapper']}>
|
||||
<SpaceTags {...item}></SpaceTags>
|
||||
|
||||
<div className={styles.info}>
|
||||
{!isPersonal && (
|
||||
<>
|
||||
<Avatar
|
||||
src={item.avatar_url}
|
||||
style={{ width: 14, height: 14 }}
|
||||
/>
|
||||
<Text
|
||||
className={cs(styles.creator)}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
>
|
||||
{item.creator_name || ''}
|
||||
</Text>
|
||||
<span className={styles['border-right']}></span>
|
||||
</>
|
||||
)}
|
||||
{searchType === OrderField.CreateTime ? (
|
||||
<span className={styles.creator}>
|
||||
{I18n.t('dataset_bot_create_time_knowledge', {
|
||||
time: unix(item.create_time || 0).format(
|
||||
'YYYY-MM-DD HH:mm',
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
) : (
|
||||
<span className={styles.creator}>
|
||||
{I18n.t('dataset_bot_update_time_knowledge', {
|
||||
time: unix(item.update_time || 0).format(
|
||||
'YYYY-MM-DD HH:mm',
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles.right}
|
||||
onClick={e => e.stopPropagation()}
|
||||
data-testid={`${BotE2e.BotKnowledgeSelectListModalAddBtn}.${item.name}`}
|
||||
>
|
||||
{isAdded(item.dataset_id || '') ? (
|
||||
<AddedButton
|
||||
className={cs(styles.button, styles.added)}
|
||||
onClick={() => onRemove(item)}
|
||||
>
|
||||
{I18n.t('Added')}
|
||||
</AddedButton>
|
||||
) : (
|
||||
<UIButton
|
||||
disabled={item.status === DatasetStatus.DatasetForbid}
|
||||
className={styles.button}
|
||||
onClick={() => onAdd(item)}
|
||||
data-testid="bot.database.add.modal.add.button"
|
||||
>
|
||||
{I18n.t('Add_2')}
|
||||
</UIButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{loading ? (
|
||||
<div className={styles['loading-more']}>
|
||||
<IconSpin spin style={{ marginRight: '4px' }} />
|
||||
<div>{I18n.t('Loading')}</div>
|
||||
</div>
|
||||
) : null}
|
||||
{noMore ? (
|
||||
<div className={styles['no-more']}>
|
||||
<div>{I18n.t('No_more')}</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,266 @@
|
||||
@common-box-shadow: 0px 2px 8px 0px rgba(31, 35, 41, 0.02),
|
||||
0px 2px 4px 0px rgba(31, 35, 41, 0.02), 0px 2px 2px 0px rgba(31, 35, 41, 0.02);
|
||||
|
||||
.common-svg-icon(@size: 14px, @color: #3370ff) {
|
||||
>svg {
|
||||
width: @size;
|
||||
height: @size;
|
||||
|
||||
>path {
|
||||
fill: @color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data-set-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
margin-bottom: 4px;
|
||||
padding: 8px;
|
||||
|
||||
background: rgba(6, 7, 9, 2%);
|
||||
border-radius: var(--default, 8px);
|
||||
|
||||
.data-set-item-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(6, 7, 9, 14%);
|
||||
|
||||
.data-set-item-right {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data-set-item-left {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: calc(100% - 60px);
|
||||
margin-right: 20px;
|
||||
|
||||
.minus {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.data-set-name {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
|
||||
/* 142.857% */
|
||||
padding-left: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: rgba(6, 7, 9, 80%);
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.data-set-desc {
|
||||
overflow: hidden;
|
||||
|
||||
padding-left: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: rgba(6, 7, 9, 50%);
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.icon-note {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
width: 24px !important;
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
height: 24px !important;
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: calc(100% - 24px);
|
||||
}
|
||||
|
||||
.icon-no {
|
||||
.common-svg-icon(14px, rgba(107, 109, 117, 1));
|
||||
|
||||
&:hover {
|
||||
background-color: var(--semi-color-fill-0);
|
||||
}
|
||||
}
|
||||
|
||||
.between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-copy {
|
||||
.common-svg-icon(14px, rgba(107, 109, 117, 1));
|
||||
|
||||
&:hover {
|
||||
background-color: var(--semi-color-fill-0);
|
||||
}
|
||||
}
|
||||
|
||||
.data-set-content {
|
||||
.dataset-setting-tip {
|
||||
margin: 8px 0 20px;
|
||||
padding: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
|
||||
|
||||
background: var(--light-usage-fill-color-fill-0, rgb(46 46 56 / 4%));
|
||||
border-radius: 8px;
|
||||
|
||||
.copy-trigger {
|
||||
cursor: pointer;
|
||||
|
||||
margin: 0 4px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: var(--light-color-brand-brand-5, #4d53e8);
|
||||
|
||||
.icon-copy {
|
||||
.common-svg-icon(14px, var(--light-color-brand-brand-5, #4d53e8));
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tag-grey-light {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
background: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.failed-tag,
|
||||
.processing-tag {
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.processing-tag {
|
||||
color: var(--light-color-green-green-6, #32A247);
|
||||
background: var(--light-color-green-green-1, #D2F3D5);
|
||||
}
|
||||
|
||||
.failed-tag {
|
||||
color: var(--light-color-red-red-6, #DB2E13);
|
||||
background: var(--light-color-red-red-1, #FFE0D2);
|
||||
}
|
||||
|
||||
// .default-text {
|
||||
// .tip-text;
|
||||
// }
|
||||
|
||||
.setting-trigger {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
margin-left: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--light-color-brand-brand-5, #4d53e8);
|
||||
|
||||
&-icon {
|
||||
svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-button-content-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-content-popover {
|
||||
background: #f7f7fa;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.setting {
|
||||
overflow-y: auto;
|
||||
|
||||
height: 454px;
|
||||
padding: 24px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--light-usage-text-color-text-0, #1f2329);
|
||||
|
||||
.setting-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: self-start;
|
||||
margin-top: 16px;
|
||||
|
||||
.setting-item-copy {
|
||||
cursor: pointer;
|
||||
|
||||
margin: 0 4px;
|
||||
padding: 2px 4px 2px 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: var(--light-color-brand-brand-5, #4d53e8);
|
||||
|
||||
border-radius: 6px;
|
||||
|
||||
.icon-copy {
|
||||
.common-svg-icon(14px, var(--light-color-brand-brand-5, #4d53e8));
|
||||
|
||||
margin: 0 0 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tag-grey-light {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
background: var(--light-color-brand-brand-1, #d9dcfa) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useDataNavigate } from '@coze-data/knowledge-stores';
|
||||
import { REPORT_EVENTS as ReportEventNames } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIIconButton, Typography, Toast, Avatar } from '@coze-arch/bot-semi';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { type Dataset } from '@coze-arch/bot-api/knowledge';
|
||||
import { IconCozCopy, IconCozMinusCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface DataSetItemProps {
|
||||
dataSet: Dataset;
|
||||
isReadonly?: boolean;
|
||||
onRemove: () => void;
|
||||
onClick?: (datasetID: string) => void;
|
||||
}
|
||||
|
||||
export const KnowledgeCard: React.FC<DataSetItemProps> = ({
|
||||
dataSet,
|
||||
isReadonly,
|
||||
onRemove,
|
||||
onClick,
|
||||
}) => {
|
||||
const { name, description, icon_url, dataset_id: id } = dataSet;
|
||||
|
||||
const resourceNavigate = useDataNavigate();
|
||||
|
||||
const navigateToKnowledgePage = (): void => {
|
||||
resourceNavigate.toResource?.('knowledge', id);
|
||||
};
|
||||
|
||||
const onCopy = (text: string) => {
|
||||
const res = copy(text);
|
||||
if (!res) {
|
||||
throw new CustomError(ReportEventNames.parmasValidation, 'empty copy');
|
||||
}
|
||||
Toast.success({
|
||||
content: I18n.t('copy_success'),
|
||||
showClose: false,
|
||||
id: 'dataset_copy_id',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['data-set-item']}>
|
||||
<div
|
||||
className={styles['data-set-item-left']}
|
||||
onClick={() => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
onClick ? onClick(id) : navigateToKnowledgePage();
|
||||
}}
|
||||
>
|
||||
<Avatar shape="square" src={icon_url} className={styles['icon-note']} />
|
||||
<div className={styles['card-content']}>
|
||||
<Typography.Text
|
||||
className={styles['data-set-name']}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
>
|
||||
{name}
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
className={styles['data-set-desc']}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
>
|
||||
{description}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['data-set-item-right']}>
|
||||
{!isReadonly && (
|
||||
<Tooltip content={I18n.t('Copy_name')}>
|
||||
<UIIconButton
|
||||
// wrapperClass={commonStyles['icon-button-16']}
|
||||
iconSize="small"
|
||||
icon={<IconCozCopy className={styles['icon-copy']} />}
|
||||
onClick={() => name && onCopy(name)}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!isReadonly && (
|
||||
<Tooltip content={I18n.t('remove_dataset')}>
|
||||
<UIIconButton
|
||||
// wrapperClass={commonStyles['icon-button-16']}
|
||||
iconSize="small"
|
||||
icon={<IconCozMinusCircle className={styles['icon-no']} />}
|
||||
onClick={onRemove}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozKnowledgeFill } from '@coze-arch/coze-design/icons';
|
||||
|
||||
interface SiderCategoryProps {
|
||||
label: string;
|
||||
selected: boolean;
|
||||
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const SiderCategory = ({ label, onClick, selected }: SiderCategoryProps) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={classNames([
|
||||
'flex items-center gap-[8px] px-[12px]',
|
||||
'px-[12px] py-[6px] rounded-[8px]',
|
||||
'cursor-pointer',
|
||||
'hover:text-[var(--light-usage-text-color-text-0,#1c1f23)]',
|
||||
'hover:bg-[var(--light-usage-fill-color-fill-0,rgba(46,50,56,5%))]',
|
||||
selected &&
|
||||
'text-[var(--light-usage-text-color-text-0,#1c1d23)] bg-[var(--light-usage-fill-color-fill-0,rgba(46,47,56,5%))]',
|
||||
])}
|
||||
>
|
||||
<IconCozKnowledgeFill />
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SiderCategory;
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { FC, ReactNode } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { type FilterKnowledgeType } from '@coze-data/utils';
|
||||
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { type Dataset } from '@coze-arch/bot-api/knowledge';
|
||||
|
||||
import { DATA_REFACTOR_CLASS_NAME } from '@/constant';
|
||||
|
||||
import {
|
||||
useKnowledgeFilter,
|
||||
Scene,
|
||||
type DatasetFilterType,
|
||||
} from './use-knowledge-filter';
|
||||
import { KnowledgeCardListVertical } from './knowledge-card-list';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface DataSetModalContentProps {
|
||||
datasetList: Dataset[];
|
||||
onDatasetListChange: (list: Dataset[]) => void;
|
||||
onClickAddKnowledge?: (
|
||||
datasetId: string,
|
||||
type: UnitType,
|
||||
shouldUpload?: boolean,
|
||||
) => void;
|
||||
beforeCreate?: (shouldUpload: boolean) => void;
|
||||
onClickKnowledgeDetail?: (knowledgeID: string) => void;
|
||||
canCreate?: boolean;
|
||||
defaultType?: FilterKnowledgeType;
|
||||
knowledgeTypeConfigList?: FilterKnowledgeType[];
|
||||
|
||||
projectID?: string;
|
||||
showFilters?: DatasetFilterType[];
|
||||
hideHeader?: boolean;
|
||||
createKnowledgeModal?: {
|
||||
modal: ReactNode;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
const useKnowledgeListModalContent = ({
|
||||
datasetList,
|
||||
onDatasetListChange,
|
||||
onClickAddKnowledge,
|
||||
beforeCreate,
|
||||
onClickKnowledgeDetail,
|
||||
canCreate = true,
|
||||
defaultType,
|
||||
knowledgeTypeConfigList,
|
||||
projectID,
|
||||
showFilters = ['scope-type', 'search-type', 'query-input'],
|
||||
hideHeader,
|
||||
createKnowledgeModal,
|
||||
}: DataSetModalContentProps) => {
|
||||
const botId = useBotInfoStore(state => state.botId);
|
||||
|
||||
const { renderContentFilter, renderSearch, renderCreateBtn, renderFilters } =
|
||||
useKnowledgeFilter({
|
||||
hideHeader,
|
||||
showFilters,
|
||||
scene: Scene.MODAL,
|
||||
headerClassName: classNames(
|
||||
s['dataset-header'],
|
||||
DATA_REFACTOR_CLASS_NAME,
|
||||
),
|
||||
onClickAddKnowledge,
|
||||
beforeCreate,
|
||||
canCreate,
|
||||
defaultType,
|
||||
knowledgeTypeConfigList,
|
||||
projectID,
|
||||
createKnowledgeModal,
|
||||
children: ({ list, loading, noMore, searchType }) => (
|
||||
<KnowledgeCardListVertical
|
||||
searchType={searchType}
|
||||
noMore={noMore}
|
||||
list={list}
|
||||
loading={loading}
|
||||
onAdd={async dataset => {
|
||||
await onDatasetListChange([...datasetList, dataset]);
|
||||
sendTeaEvent(EVENT_NAMES.click_database_select, {
|
||||
operation: 'add',
|
||||
bot_id: botId,
|
||||
});
|
||||
// Toast.success({
|
||||
// showClose: false,
|
||||
// content: I18n.t('bot_edit_dataset_added_toast', {
|
||||
// dataset_name: dataset.name || '',
|
||||
// }),
|
||||
// style: {
|
||||
// wordWrap: 'break-word',
|
||||
// },
|
||||
// });
|
||||
}}
|
||||
onRemove={dataset => {
|
||||
onDatasetListChange(
|
||||
datasetList.filter(
|
||||
item => item.dataset_id !== dataset.dataset_id,
|
||||
),
|
||||
);
|
||||
sendTeaEvent(EVENT_NAMES.click_database_select, {
|
||||
operation: 'remove',
|
||||
bot_id: botId,
|
||||
});
|
||||
// Toast.success({
|
||||
// showClose: false,
|
||||
// content: I18n.t('bot_edit_dataset_removed_toast', {
|
||||
// dataset_name: dataset.name || '',
|
||||
// }),
|
||||
// style: {
|
||||
// wordWrap: 'break-word',
|
||||
// },
|
||||
// });
|
||||
}}
|
||||
isAdded={id => datasetList.some(dataset => dataset.dataset_id === id)}
|
||||
onClickKnowledgeDetail={onClickKnowledgeDetail}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
renderContent: renderContentFilter,
|
||||
renderSearch,
|
||||
renderCreateBtn,
|
||||
renderFilters,
|
||||
};
|
||||
};
|
||||
|
||||
const KnowledgeListModalContent: FC<DataSetModalContentProps> = ({
|
||||
datasetList,
|
||||
onDatasetListChange,
|
||||
onClickAddKnowledge,
|
||||
beforeCreate,
|
||||
onClickKnowledgeDetail,
|
||||
canCreate = true,
|
||||
defaultType,
|
||||
knowledgeTypeConfigList,
|
||||
projectID,
|
||||
createKnowledgeModal,
|
||||
}) => {
|
||||
const { renderContent } = useKnowledgeListModalContent({
|
||||
datasetList,
|
||||
onDatasetListChange,
|
||||
onClickAddKnowledge,
|
||||
beforeCreate,
|
||||
onClickKnowledgeDetail,
|
||||
canCreate,
|
||||
defaultType,
|
||||
knowledgeTypeConfigList,
|
||||
projectID,
|
||||
createKnowledgeModal,
|
||||
});
|
||||
|
||||
return <>{renderContent()}</>;
|
||||
};
|
||||
|
||||
export { useKnowledgeListModalContent, KnowledgeListModalContent };
|
||||
@@ -0,0 +1,120 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.spin {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
|
||||
:global {
|
||||
.semi-spin-children {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
&-image {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&>* {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.new-filter-header {
|
||||
justify-content: space-between !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-end;
|
||||
|
||||
height: fit-content;
|
||||
padding: 0 36px 8px;
|
||||
|
||||
.select {
|
||||
width: 160px;
|
||||
|
||||
:global {
|
||||
.semi-select-selection-text {
|
||||
color: rgba(28, 31, 35, 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 260px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.tab-select {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
|
||||
&.scrollable {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&.centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-end;
|
||||
|
||||
height: fit-content;
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-type-tab {
|
||||
display: flex;
|
||||
margin-left: 8px;
|
||||
padding: 6px 0;
|
||||
|
||||
&-item {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: #1D1C2399;
|
||||
|
||||
&-active {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: #4D53E8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-lines-per-function */
|
||||
/* eslint-disable max-lines -- 待拆分 */
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import {
|
||||
type FC,
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
type ReactNode,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import { isFunction, uniq, debounce } from 'lodash-es';
|
||||
import cs from 'classnames';
|
||||
import {
|
||||
useInfiniteScroll,
|
||||
useUpdateEffect,
|
||||
useDocumentVisibility,
|
||||
} from 'ahooks';
|
||||
import { FilterKnowledgeType } from '@coze-data/utils';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import {
|
||||
UIButton,
|
||||
UIEmpty,
|
||||
UISelect,
|
||||
Spin,
|
||||
UISearch,
|
||||
Divider,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import {
|
||||
OrderField,
|
||||
type Dataset,
|
||||
DatasetScopeType,
|
||||
FormatType,
|
||||
} from '@coze-arch/bot-api/knowledge';
|
||||
import { SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
import { KnowledgeApi } from '@coze-arch/bot-api';
|
||||
import { Input } from '@coze-arch/coze-design';
|
||||
|
||||
import { DATA_REFACTOR_CLASS_NAME } from '../../constant';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface GetDatasetListData {
|
||||
list: Dataset[];
|
||||
nextPageIndex: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
const getDatasetList = async (
|
||||
props: {
|
||||
query?: string;
|
||||
search_type?: OrderField;
|
||||
space_id: string;
|
||||
scope_type?: DatasetScopeType;
|
||||
format_type?: FormatType;
|
||||
projectID?: string;
|
||||
},
|
||||
pageIndex = 1,
|
||||
) => {
|
||||
const { query, search_type, space_id, scope_type, format_type, projectID } =
|
||||
props;
|
||||
const resp = await KnowledgeApi.ListDataset({
|
||||
space_id,
|
||||
page: pageIndex,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
filter: {
|
||||
name: query,
|
||||
scope_type,
|
||||
format_type,
|
||||
},
|
||||
order_field: search_type,
|
||||
project_id: projectID,
|
||||
});
|
||||
|
||||
return {
|
||||
list: resp?.dataset_list || [],
|
||||
nextPageIndex: pageIndex + 1,
|
||||
total: Number(resp?.total),
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_SEARCH_TYPE = OrderField.CreateTime;
|
||||
|
||||
interface CreateKnowledgeModalProps {
|
||||
modal: ReactNode;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
const EmptyToCreate: FC<{
|
||||
onAdd: () => void;
|
||||
scene: Scene;
|
||||
canCreate: boolean;
|
||||
createKnowledgeModal?: CreateKnowledgeModalProps;
|
||||
}> = ({ onAdd, scene, canCreate, createKnowledgeModal }) => {
|
||||
const handleAdd = () => {
|
||||
if (scene === Scene.MODAL) {
|
||||
onAdd();
|
||||
return;
|
||||
}
|
||||
createKnowledgeModal?.open();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className={cs(styles.content, styles.centered)}>
|
||||
<UIEmpty
|
||||
className={styles.empty}
|
||||
empty={{
|
||||
...(canCreate
|
||||
? {
|
||||
btnText: I18n.t('datasets_create_btn'),
|
||||
btnOnClick: handleAdd,
|
||||
}
|
||||
: {}),
|
||||
title: I18n.t('datasets_empty_title'),
|
||||
description: I18n.t('datasets_empty_description'),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{createKnowledgeModal?.modal}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface DatasetFilterAction {
|
||||
list: Dataset[];
|
||||
size: number;
|
||||
query: string | undefined;
|
||||
searchType: OrderField;
|
||||
loading: boolean;
|
||||
noMore: boolean;
|
||||
resetFilter: () => void;
|
||||
refresh: () => void;
|
||||
createDataset?: (name: string, source_type: number) => Promise<void>;
|
||||
deleteDataset?: (id: string) => Promise<void>;
|
||||
updateDataset?: (id: string, name: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export type DatasetFilterType = 'scope-type' | 'search-type' | 'query-input';
|
||||
|
||||
export interface DatasetFilterProps {
|
||||
hideHeader?: boolean;
|
||||
children:
|
||||
| ((action: DatasetFilterAction) => React.ReactNode)
|
||||
| React.ReactNode;
|
||||
showFilters?: DatasetFilterType[];
|
||||
headerClassName?: string;
|
||||
scene?: Scene;
|
||||
onClickAddKnowledge?: (
|
||||
datasetId: string,
|
||||
type: UnitType,
|
||||
shouldUpload?: boolean,
|
||||
) => void;
|
||||
beforeCreate?: (shouldUpload: boolean) => void;
|
||||
canCreate: boolean;
|
||||
defaultType?: FilterKnowledgeType;
|
||||
knowledgeTypeConfigList?: FilterKnowledgeType[];
|
||||
projectID?: string;
|
||||
createKnowledgeModal?: CreateKnowledgeModalProps;
|
||||
}
|
||||
|
||||
export enum Scene {
|
||||
PAGE = 'page',
|
||||
MODAL = 'modal',
|
||||
}
|
||||
|
||||
const defaultKnowledgeTypeFallback = (param: FilterKnowledgeType[]) => {
|
||||
if (param.includes(FilterKnowledgeType.ALL)) {
|
||||
return FilterKnowledgeType.ALL;
|
||||
}
|
||||
return param.at(0) ?? FilterKnowledgeType.ALL;
|
||||
};
|
||||
|
||||
const useKnowledgeFilter = ({
|
||||
hideHeader,
|
||||
children,
|
||||
showFilters,
|
||||
headerClassName,
|
||||
scene = Scene.PAGE,
|
||||
onClickAddKnowledge,
|
||||
canCreate,
|
||||
defaultType,
|
||||
knowledgeTypeConfigList = [
|
||||
FilterKnowledgeType.ALL,
|
||||
FilterKnowledgeType.TEXT,
|
||||
FilterKnowledgeType.TABLE,
|
||||
FilterKnowledgeType.IMAGE,
|
||||
],
|
||||
projectID,
|
||||
beforeCreate,
|
||||
createKnowledgeModal,
|
||||
}: DatasetFilterProps) => {
|
||||
const uniqKnowledgeTypeConfigList = uniq(knowledgeTypeConfigList);
|
||||
const [currentKnowledgeType, setCurrentKnowledgeType] = useState(
|
||||
defaultType || defaultKnowledgeTypeFallback(uniqKnowledgeTypeConfigList),
|
||||
);
|
||||
const [query, setQuery] = useState<string>();
|
||||
const [searchType, setSearchType] = useState<OrderField>(DEFAULT_SEARCH_TYPE);
|
||||
const [scopeType, setScopeType] = useState<DatasetScopeType>(
|
||||
projectID ? DatasetScopeType.ScopeSelf : DatasetScopeType.ScopeAll,
|
||||
);
|
||||
|
||||
const scopeOptions = [
|
||||
{
|
||||
label: I18n.t('scope_all'),
|
||||
value: DatasetScopeType.ScopeAll,
|
||||
},
|
||||
{
|
||||
label: I18n.t('scope_self'),
|
||||
value: DatasetScopeType.ScopeSelf,
|
||||
},
|
||||
];
|
||||
const { id, space_type } = useSpaceStore(s => s.space);
|
||||
|
||||
const isPersonal = space_type === SpaceType.Personal;
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { loading, data, loadingMore, noMore, reload } =
|
||||
useInfiniteScroll<GetDatasetListData>(
|
||||
(newData?: GetDatasetListData): Promise<GetDatasetListData> => {
|
||||
if (!newData || newData.nextPageIndex === 1) {
|
||||
containerRef.current?.scroll(0, 0);
|
||||
}
|
||||
return getDatasetList(
|
||||
{
|
||||
space_id: id || '',
|
||||
query,
|
||||
search_type: searchType,
|
||||
scope_type: isPersonal ? DatasetScopeType.ScopeSelf : scopeType,
|
||||
format_type:
|
||||
currentKnowledgeType === FilterKnowledgeType.ALL
|
||||
? undefined
|
||||
: {
|
||||
[FilterKnowledgeType.TABLE]: FormatType.Table,
|
||||
[FilterKnowledgeType.TEXT]: FormatType.Text,
|
||||
[FilterKnowledgeType.IMAGE]: FormatType.Image,
|
||||
}[currentKnowledgeType],
|
||||
projectID,
|
||||
},
|
||||
newData?.nextPageIndex,
|
||||
);
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
isNoMore: newData =>
|
||||
Boolean(
|
||||
!newData?.total ||
|
||||
(newData.nextPageIndex - 1) * DEFAULT_PAGE_SIZE >= newData.total,
|
||||
),
|
||||
onError: error => {
|
||||
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
|
||||
eventName: REPORT_EVENTS.KnowledgeGetDataSetList,
|
||||
error,
|
||||
});
|
||||
},
|
||||
target: containerRef,
|
||||
reloadDeps: [query, searchType, scopeType, projectID],
|
||||
},
|
||||
);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
handleResetFilter();
|
||||
}, [id]);
|
||||
|
||||
const documentVisibility = useDocumentVisibility();
|
||||
useEffect(() => {
|
||||
if (documentVisibility === 'visible') {
|
||||
reload();
|
||||
}
|
||||
}, [documentVisibility]);
|
||||
|
||||
const handleResetFilter = () => {
|
||||
setQuery(undefined);
|
||||
setSearchType(DEFAULT_SEARCH_TYPE);
|
||||
};
|
||||
|
||||
const handleSearchTypeChange = (value: OrderField) => {
|
||||
setSearchType(value);
|
||||
};
|
||||
|
||||
const handleQueryChange = (value = '') => {
|
||||
setQuery(value);
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
createKnowledgeModal?.open();
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
/** 有数据则展示列表 */
|
||||
if (data?.total) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cs(styles.content, styles.scrollable)}
|
||||
ref={containerRef}
|
||||
>
|
||||
{isFunction(children)
|
||||
? children({
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
query,
|
||||
searchType,
|
||||
loading: loadingMore,
|
||||
list: data.list,
|
||||
noMore,
|
||||
resetFilter: handleResetFilter,
|
||||
refresh: reload,
|
||||
})
|
||||
: children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
/** 无数据且未在加载则展示空状态 */
|
||||
if (!loading) {
|
||||
return (
|
||||
<EmptyToCreate
|
||||
scene={scene}
|
||||
onAdd={() => {
|
||||
handleAdd();
|
||||
}}
|
||||
canCreate={canCreate}
|
||||
createKnowledgeModal={createKnowledgeModal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
/** 无数据且加载中则不展示 */
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderSearch = useMemo(
|
||||
() => () => (
|
||||
<Input
|
||||
autoFocus
|
||||
key="query-input"
|
||||
placeholder={I18n.t('db2_014')}
|
||||
onChange={debounce(handleQueryChange, 500)}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const renderCreateBtn = useMemo(
|
||||
() => () => (
|
||||
<UIButton
|
||||
theme="solid"
|
||||
onClick={handleAdd}
|
||||
data-testid={BotE2e.BotKnowledgeSelectListModalCreateBtn}
|
||||
>
|
||||
{I18n.t('datasets_create_btn')}
|
||||
</UIButton>
|
||||
),
|
||||
[handleAdd],
|
||||
);
|
||||
|
||||
const renderFilters = useMemo(
|
||||
() => () => (
|
||||
<>
|
||||
<div className={styles['file-type-tab']}>
|
||||
{uniqKnowledgeTypeConfigList.reduce<ReactNode[]>(
|
||||
(
|
||||
accumulator: ReactNode[],
|
||||
currentValue: FilterKnowledgeType,
|
||||
currentIndex: number,
|
||||
) => {
|
||||
const reactNode = renderKnowledgeTypeConfigNode(currentValue);
|
||||
if (currentIndex !== 0) {
|
||||
return accumulator.concat([
|
||||
<Divider layout="vertical" margin="12px" />,
|
||||
reactNode,
|
||||
]);
|
||||
}
|
||||
return accumulator.concat([reactNode]);
|
||||
},
|
||||
[],
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={'flex'}>
|
||||
{uniq(showFilters).map((filterType: DatasetFilterType) => {
|
||||
if (filterType === 'scope-type') {
|
||||
return !isPersonal ? (
|
||||
<UISelect
|
||||
label={I18n.t('Creator')}
|
||||
showClear={false}
|
||||
value={scopeType}
|
||||
optionList={scopeOptions}
|
||||
onChange={v => {
|
||||
setScopeType(v as DatasetScopeType);
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
} else if (filterType === 'search-type') {
|
||||
return (
|
||||
<UISelect
|
||||
data-testid={
|
||||
BotE2e.BotKnowledgeSelectListModalCreateDateSelect
|
||||
}
|
||||
label={I18n.t('Sort')}
|
||||
showClear={false}
|
||||
value={searchType}
|
||||
optionList={[
|
||||
{
|
||||
label: I18n.t('Create_time'),
|
||||
value: OrderField.CreateTime,
|
||||
},
|
||||
{
|
||||
label: I18n.t('Update_time'),
|
||||
value: OrderField.UpdateTime,
|
||||
},
|
||||
]}
|
||||
onChange={v => {
|
||||
handleSearchTypeChange(v as OrderField);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
[
|
||||
headerClassName,
|
||||
handleSearchTypeChange,
|
||||
scopeType,
|
||||
scopeOptions,
|
||||
isPersonal,
|
||||
showFilters,
|
||||
uniqKnowledgeTypeConfigList,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
reload();
|
||||
}, [currentKnowledgeType]);
|
||||
|
||||
const renderKnowledgeTypeConfigNode = (type: FilterKnowledgeType) => {
|
||||
if (type === FilterKnowledgeType.ALL) {
|
||||
return (
|
||||
<div
|
||||
data-testid={BotE2e.BotKnowledgeSelectListModalAllTab}
|
||||
key={FilterKnowledgeType.ALL}
|
||||
onClick={() => setCurrentKnowledgeType(FilterKnowledgeType.ALL)}
|
||||
className={
|
||||
currentKnowledgeType === FilterKnowledgeType.ALL
|
||||
? styles['file-type-tab-item-active']
|
||||
: styles['file-type-tab-item']
|
||||
}
|
||||
>
|
||||
{I18n.t('kl2_010')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (type === FilterKnowledgeType.TEXT) {
|
||||
return (
|
||||
<div
|
||||
data-testid={BotE2e.BotKnowledgeSelectListModalTextTab}
|
||||
key={FilterKnowledgeType.TEXT}
|
||||
onClick={() => setCurrentKnowledgeType(FilterKnowledgeType.TEXT)}
|
||||
className={
|
||||
currentKnowledgeType === FilterKnowledgeType.TEXT
|
||||
? styles['file-type-tab-item-active']
|
||||
: styles['file-type-tab-item']
|
||||
}
|
||||
>
|
||||
{I18n.t('kl2_011')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (type === FilterKnowledgeType.TABLE) {
|
||||
return (
|
||||
<div
|
||||
data-testid={BotE2e.BotKnowledgeSelectListModalTableTab}
|
||||
key={FilterKnowledgeType.TABLE}
|
||||
onClick={() => setCurrentKnowledgeType(FilterKnowledgeType.TABLE)}
|
||||
className={
|
||||
currentKnowledgeType === FilterKnowledgeType.TABLE
|
||||
? styles['file-type-tab-item-active']
|
||||
: styles['file-type-tab-item']
|
||||
}
|
||||
>
|
||||
{I18n.t('kl2_012')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (type === FilterKnowledgeType.IMAGE) {
|
||||
return (
|
||||
<div
|
||||
data-testid={BotE2e.BotKnowledgeSelectListModalPhotoTab}
|
||||
key={FilterKnowledgeType.IMAGE}
|
||||
onClick={() => setCurrentKnowledgeType(FilterKnowledgeType.IMAGE)}
|
||||
className={
|
||||
currentKnowledgeType === FilterKnowledgeType.IMAGE
|
||||
? styles['file-type-tab-item-active']
|
||||
: styles['file-type-tab-item']
|
||||
}
|
||||
>
|
||||
{I18n.t('knowledge_photo_025')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderContentFilter = () => (
|
||||
<Spin spinning={loading} wrapperClassName={styles.spin}>
|
||||
<div className={cs(styles.container, DATA_REFACTOR_CLASS_NAME)}>
|
||||
{!hideHeader && showFilters?.length ? (
|
||||
<div
|
||||
className={cs(
|
||||
styles.header,
|
||||
headerClassName,
|
||||
styles['new-filter-header'],
|
||||
)}
|
||||
>
|
||||
<div className={styles['file-type-tab']}>
|
||||
{uniqKnowledgeTypeConfigList.reduce<ReactNode[]>(
|
||||
(
|
||||
accumulator: ReactNode[],
|
||||
currentValue: FilterKnowledgeType,
|
||||
currentIndex: number,
|
||||
) => {
|
||||
const reactNode = renderKnowledgeTypeConfigNode(currentValue);
|
||||
if (currentIndex !== 0) {
|
||||
return accumulator.concat([
|
||||
<Divider layout="vertical" margin="12px" />,
|
||||
reactNode,
|
||||
]);
|
||||
}
|
||||
return accumulator.concat([reactNode]);
|
||||
},
|
||||
[],
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-[8px]">
|
||||
{uniq(showFilters).map((filterType: DatasetFilterType) => {
|
||||
if (filterType === 'scope-type') {
|
||||
return !isPersonal ? (
|
||||
<UISelect
|
||||
label={I18n.t('Creator')}
|
||||
showClear={false}
|
||||
value={scopeType}
|
||||
optionList={scopeOptions}
|
||||
onChange={v => {
|
||||
setScopeType(v as DatasetScopeType);
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
} else if (filterType === 'search-type') {
|
||||
return (
|
||||
<UISelect
|
||||
data-testid={
|
||||
BotE2e.BotKnowledgeSelectListModalCreateDateSelect
|
||||
}
|
||||
label={I18n.t('Sort')}
|
||||
showClear={false}
|
||||
value={searchType}
|
||||
optionList={[
|
||||
{
|
||||
label: I18n.t('Create_time'),
|
||||
value: OrderField.CreateTime,
|
||||
},
|
||||
{
|
||||
label: I18n.t('Update_time'),
|
||||
value: OrderField.UpdateTime,
|
||||
},
|
||||
]}
|
||||
onChange={v => {
|
||||
handleSearchTypeChange(v as OrderField);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (filterType === 'query-input') {
|
||||
return (
|
||||
<UISearch
|
||||
key="filterType"
|
||||
loading={loading}
|
||||
onSearch={handleQueryChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
{scene === Scene.MODAL && canCreate ? (
|
||||
<UIButton
|
||||
theme="solid"
|
||||
onClick={handleAdd}
|
||||
data-testid={BotE2e.BotKnowledgeSelectListModalCreateBtn}
|
||||
>
|
||||
{I18n.t('datasets_create_btn')}
|
||||
</UIButton>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{renderContent()}
|
||||
</div>
|
||||
{createKnowledgeModal?.modal}
|
||||
</Spin>
|
||||
);
|
||||
|
||||
return { renderContentFilter, renderSearch, renderCreateBtn, renderFilters };
|
||||
};
|
||||
|
||||
export { useKnowledgeFilter };
|
||||
Reference in New Issue
Block a user