feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
import { mergeConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: config =>
mergeConfig(config, {
plugins: [
svgr({
svgrOptions: {
native: false,
},
}),
],
}),
};
export default config;

View File

@@ -0,0 +1,14 @@
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,3 @@
# @coze-data/knowledge-ide-base
## 知识库首页ide
### 地址:/space/{space_id}/knowledge/{knowledge_id}

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["dist"]
}
]
}

View File

@@ -0,0 +1,9 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {
'@typescript-eslint/naming-convention': 'off',
},
});

View File

@@ -0,0 +1,127 @@
{
"name": "@coze-data/knowledge-ide-base",
"version": "0.0.1",
"description": "Knowledge IDE text base components",
"license": "Apache-2.0",
"author": "haozhenfei@bytedance.com",
"maintainers": [],
"exports": {
".": "./src/index.tsx",
"./types": "./src/types/index.ts",
"./utils": "./src/utils/index.ts",
"./components/*": "./src/components/*/index.tsx",
"./features/*": "./src/features/*/index.tsx",
"./hooks/*": "./src/hooks/*/index.ts",
"./layout": "./src/layout/index.tsx",
"./layout/*": "./src/layout/*/index.tsx",
"./context/*": "./src/context/*/index.tsx"
},
"main": "src/index.tsx",
"typesVersions": {
"*": {
"types": [
"./src/types/index.ts"
],
"components/*": [
"./src/components/*/index.tsx"
],
"features/*": [
"./src/features/*/index.tsx"
],
"utils": [
"./src/utils/index.ts"
],
"hooks/*": [
"./src/hooks/*/index.ts"
],
"layout": [
"./src/layout/index.tsx"
],
"layout/*": [
"./src/layout/*/index.tsx"
],
"context/*": [
"./src/context/*/index.tsx"
]
}
},
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-error": "workspace:*",
"@coze-arch/bot-flags": "workspace:*",
"@coze-arch/bot-hooks": "workspace:*",
"@coze-arch/bot-icons": "workspace:*",
"@coze-arch/bot-monaco-editor": "workspace:*",
"@coze-arch/bot-semi": "workspace:*",
"@coze-arch/bot-studio-store": "workspace:*",
"@coze-arch/bot-tea": "workspace:*",
"@coze-arch/bot-utils": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/logger": "workspace:*",
"@coze-arch/report-events": "workspace:*",
"@coze-arch/report-tti": "workspace:*",
"@coze-common/chat-area-utils": "workspace:*",
"@coze-data/e2e": "workspace:*",
"@coze-data/feature-register": "workspace:*",
"@coze-data/knowledge-common-components": "workspace:*",
"@coze-data/knowledge-common-hooks": "workspace:*",
"@coze-data/knowledge-common-services": "workspace:*",
"@coze-data/knowledge-modal-adapter": "workspace:*",
"@coze-data/knowledge-modal-base": "workspace:*",
"@coze-data/knowledge-resource-processor-adapter": "workspace:*",
"@coze-data/knowledge-resource-processor-base": "workspace:*",
"@coze-data/knowledge-resource-processor-core": "workspace:*",
"@coze-data/knowledge-stores": "workspace:*",
"@coze-data/reporter": "workspace:*",
"@coze-data/utils": "workspace:*",
"@coze-foundation/local-storage": "workspace:*",
"@coze-studio/components": "workspace:*",
"@coze-studio/premium-components-adapter": "workspace:*",
"@coze-studio/premium-store-adapter": "workspace:*",
"@douyinfe/semi-illustrations": "^2.36.0",
"ahooks": "^3.7.8",
"classnames": "^2.3.2",
"dayjs": "^1.11.7",
"dompurify": "3.0.8",
"immer": "^10.0.3",
"lodash-es": "^4.17.21",
"nanoid": "^4.0.2",
"qs": "^6.11.2",
"react-router-dom": "^6.22.0",
"zustand": "^4.4.7"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@coze-common/table-view": "workspace:*",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/dompurify": "3.0.5",
"@types/lodash-es": "^4.17.10",
"@types/qs": "^6.9.7",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

View File

@@ -0,0 +1,13 @@
@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;
}
}
}

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.8619 1.52827C11.1223 1.78862 11.1223 2.21073 10.8619 2.47108L5.33333 7.99968L10.8619 13.5283C11.1223 13.7886 11.1223 14.2107 10.8619 14.4711C10.6016 14.7314 10.1795 14.7314 9.91912 14.4711L4.39052 8.94248C3.86983 8.42179 3.86982 7.57757 4.39052 7.05687L9.91912 1.52827C10.1795 1.26792 10.6016 1.26792 10.8619 1.52827Z"
fill="currentColor" style="fill-opacity:1;" />
</svg>

After

Width:  |  Height:  |  Size: 500 B

View File

@@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.437 6.33333H13.4176C12.7059 4.01711 10.5496 2.33333 8.00001 2.33333C5.45043 2.33333 3.29412 4.01711 2.5824 6.33333H1.19964C1.94734 3.27199 4.70835 1 8.00001 1C11.2917 1 14.0527 3.27199 14.8004 6.33333H15.8963C15.9536 6.33333 16 6.38181 16 6.44161C16 6.47033 15.9891 6.49787 15.9696 6.51818L14.2767 8.28576C14.2159 8.34919 14.1174 8.34919 14.0567 8.28576L12.3637 6.51818C12.3232 6.47589 12.3232 6.40733 12.3637 6.36505C12.3832 6.34474 12.4095 6.33333 12.437 6.33333Z"
fill="currentColor" style="fill:currentColor;fill-opacity:1;" />
<path
d="M3.56296 9.66667H2.5824C3.29411 11.9829 5.45042 13.6667 8 13.6667C10.5496 13.6667 12.7059 11.9829 13.4176 9.66667H14.8004C14.0527 12.728 11.2917 15 8 15C4.70834 15 1.94734 12.728 1.19963 9.66667H0.103709C0.076204 9.66667 0.0498254 9.65526 0.0303755 9.63495C-0.0101252 9.59267 -0.0101252 9.52411 0.0303755 9.48182L1.72333 7.71424C1.78408 7.65081 1.88258 7.65081 1.94333 7.71424L3.63629 9.48182C3.65574 9.50213 3.66667 9.52967 3.66667 9.55839C3.66667 9.61819 3.62024 9.66667 3.56296 9.66667Z"
fill="currentColor" style="fill:currentColor;fill-opacity:1;" />
<path
d="M8.67672 5H7.34015L5.00001 11H6.34407L7.02746 9.34409H8.93271L9.67654 11H11L8.67672 5ZM8.6202 8.00001H7.37163L8.00991 6.31981L8.6202 8.00001Z"
fill="currentColor" style="fill:currentColor;fill-opacity:1;" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1930_159006)">
<path d="M7.9974 13.9998C4.68369 13.9998 1.9974 11.3135 1.9974 7.99984C1.9974 4.68613 4.68369 1.99984 7.9974 1.99984C11.3111 1.99984 13.9974 4.68613 13.9974 7.99984C13.9974 11.3135 11.3111 13.9998 7.9974 13.9998ZM7.9974 15.3332C12.0475 15.3332 15.3307 12.0499 15.3307 7.99984C15.3307 3.94975 12.0475 0.666504 7.9974 0.666504C3.94731 0.666504 0.664062 3.94975 0.664062 7.99984C0.664062 12.0499 3.94731 15.3332 7.9974 15.3332Z" fill="#DB2E13" style="fill:#DB2E13;fill:color(display-p3 0.8588 0.1804 0.0745);fill-opacity:1;"/>
<path d="M7.9974 4.6665C7.62921 4.6665 7.33073 4.96498 7.33073 5.33317V8.6665C7.33073 9.03469 7.62921 9.33317 7.9974 9.33317C8.36559 9.33317 8.66406 9.03469 8.66406 8.6665V5.33317C8.66406 4.96498 8.36559 4.6665 7.9974 4.6665Z" fill="#DB2E13" style="fill:#DB2E13;fill:color(display-p3 0.8588 0.1804 0.0745);fill-opacity:1;"/>
<path d="M7.9974 9.99984C7.62921 9.99984 7.33073 10.2983 7.33073 10.6665C7.33073 11.0347 7.62921 11.3332 7.9974 11.3332C8.36559 11.3332 8.66406 11.0347 8.66406 10.6665C8.66406 10.2983 8.36559 9.99984 7.9974 9.99984Z" fill="#DB2E13" style="fill:#DB2E13;fill:color(display-p3 0.8588 0.1804 0.0745);fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_1930_159006">
<rect width="16" height="16" fill="white" style="fill:white;fill-opacity:1;"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,36 @@
.icon-with-suffix {
position: relative;
margin-right: 12px;
:global {
.icon-with-suffix-common {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
}
.icon {
top: 0;
left: 0;
width: 24px;
height: 24px;
border-radius: 4px;
}
.suffix {
position: absolute;
z-index: 1;
top: 16px;
left: 16px;
width: 12px;
height: 12px;
border-radius: 2.667px;
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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 React from 'react';
import classNames from 'classnames';
import { FormatType } from '@coze-arch/bot-api/memory';
import {
IconSvgFile,
IconSvgSheet,
IconSvgUnbound,
} from '@coze-arch/bot-icons';
import style from './index.module.less';
interface Props {
hasSuffix: boolean;
formatType?: FormatType;
className?: string;
}
export const IconMap = {
[FormatType.Table]: {
icon: <IconSvgSheet />,
bgColor: '#35C566',
suffixIcon: <IconSvgUnbound />,
suffixBgColor: 'rgba(255,150,0,1)',
},
[FormatType.Text]: {
icon: <IconSvgFile />,
bgColor: 'rgba(34, 136, 255, 1)',
suffixIcon: <IconSvgUnbound />,
suffixBgColor: 'rgba(255,150,0,1)',
},
};
export const IconWithSuffix = (props: Props) => {
const { formatType = FormatType.Text, hasSuffix, className } = props;
return (
<div className={classNames(style['icon-with-suffix'], className)}>
<div
className={classNames('icon-with-suffix-common', 'icon')}
style={{
backgroundColor: `${IconMap?.[formatType]?.bgColor}`,
}}
>
{IconMap?.[formatType]?.icon}
</div>
{hasSuffix ? (
<div
className={classNames('icon-with-suffix-common', 'suffix')}
style={{
backgroundColor: `${IconMap?.[formatType]?.suffixBgColor}`,
}}
>
{IconMap?.[formatType]?.suffixIcon}
</div>
) : null}
</div>
);
};

View File

@@ -0,0 +1,37 @@
// UI稿的 confirm modal 和 semi 默认的不一样,需要手动调整样式
.confirm-modal {
:global {
.semi-modal-header {
margin-bottom: 16px;
// icon 和 title 的间距
.semi-modal-icon-wrapper {
margin-right: 8px;
}
// title 颜色
.semi-modal-confirm-title-text {
color: rgba(29, 28, 35, 100%);
}
// 关闭 icon 的 hover 颜色
.semi-button:hover {
background-color: rgba(46, 46, 56, 8%);
}
}
.semi-modal-body {
margin: 0;
padding: 16px 0;
.semi-modal-confirm-content {
color: rgba(29, 28, 35, 100%)
}
}
.semi-modal-footer button {
min-width: 96px;
margin-left: 16px;
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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 React from 'react';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { UIIconButton, Icon, Tooltip, UIModal } from '@coze-arch/bot-semi';
import { IconCloseKnowledge, IconWarningSize24 } from '@coze-arch/bot-icons';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { ReactComponent as SvgTranslate } from '@/assets/icon_translate.svg';
import styles from './index.module.less';
export interface AutoGenerateButtonProps {
currentValue: string;
document_id: string;
disable: boolean;
onChange: (value: string) => void;
onProgress: (loading: boolean) => void;
}
export const AutoGenerateButton: React.FC<AutoGenerateButtonProps> = ({
currentValue,
document_id,
disable,
onChange,
onProgress,
}) => {
const handleGenerate = async () => {
const generateCaption = async () => {
onProgress(true);
try {
const res = await KnowledgeApi.ExtractPhotoCaption({
document_id,
});
if (res.caption) {
onChange(res.caption);
}
} catch (error) {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeGeneratePhotoCaption,
error: error as Error,
});
} finally {
onProgress(false);
}
};
// 如果没有 caption则不用confirm
if (!currentValue) {
await generateCaption();
return;
}
UIModal.warning({
// 必填参数,统一 confirm modal 的样式
className: styles['confirm-modal'],
closeIcon: <IconCloseKnowledge />,
// 自定义参数
title: I18n.t('knowledge_photo_021'),
content: I18n.t('knowledge_photo_022'),
icon: <IconWarningSize24 />,
cancelText: I18n.t('Cancel'),
okText: I18n.t('Confirm'),
okButtonProps: { theme: 'solid', type: 'warning' },
onOk: () => {
generateCaption();
},
});
};
return (
<Tooltip content={I18n.t('knowledge_photo_020')}>
<UIIconButton
icon={
<Icon
svg={
<SvgTranslate
color={disable ? 'rgba(28, 31, 35, 0.35)' : '#4D53E8'}
/>
}
/>
}
className="absolute !bottom-[8px] !left-[12px]"
onClick={handleGenerate}
disabled={disable}
/>
</Tooltip>
);
};

View File

@@ -0,0 +1,169 @@
/*
* 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 { useMemo } from 'react';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { formatBytes } from '@coze-arch/bot-utils';
import {
FormatType,
type Dataset,
type DocumentInfo,
DocumentSource,
DocumentStatus,
StorageLocation,
} from '@coze-arch/bot-api/knowledge';
import { Space, Tag } from '@coze-arch/coze-design';
import { type ProgressMap } from '@/types';
import { DOCUMENT_UPDATE_TYPE_MAP } from '@/constant';
import { getSourceName } from '../../utils';
export interface HeaderTagProps {
dataSetDetail: Dataset;
docInfo?: DocumentInfo;
progressMap?: ProgressMap;
}
// eslint-disable-next-line complexity
export const HeaderTags = ({
dataSetDetail,
docInfo,
progressMap = {},
}: HeaderTagProps) => {
const formatType = dataSetDetail?.format_type;
const updateFrequencyStr = useMemo(() => {
if (!docInfo) {
return '';
}
// @ts-expect-error -- linter-disable-autofix
let str: string = DOCUMENT_UPDATE_TYPE_MAP[docInfo?.update_type];
if (docInfo.update_interval) {
str = `${I18n.t('datasets_segment_tag_updateFrequency', {
num: docInfo.update_interval,
})}`;
}
return str;
}, [docInfo]);
return (
<div className="flex pb-[4px]" data-testid={KnowledgeE2e.UnitDetailTags}>
<Space spacing={4} className="[&_.semi-tag-content]:font-medium">
{/* file size */}
{dataSetDetail?.all_file_size ? (
<Tag size="mini" color="primary">
{formatBytes(parseInt(String(dataSetDetail.all_file_size)))}
</Tag>
) : null}
{/* table source type */}
{formatType === FormatType.Table && !!docInfo && (
<Tag size="mini" color="primary">
{getSourceName(docInfo)}
</Tag>
)}
{/* table api update type */}
{formatType === FormatType.Table &&
docInfo &&
docInfo?.source_type === DocumentSource.Web ? (
<Tag size="mini" color="primary">
{updateFrequencyStr}
</Tag>
) : null}
{/* doc count */}
{formatType === FormatType.Text && !!dataSetDetail?.doc_count && (
<Tag size="mini" color="primary">
{I18n.t('kl2_009', {
num: dataSetDetail?.doc_count ?? 0,
})}
</Tag>
)}
{/* 图片来源 */}
{formatType === FormatType.Image && (
<Tag size="mini" color="primary">
{I18n.t('dataset_detail_source_local')}
</Tag>
)}
{/* 图片数量 */}
{formatType === FormatType.Image && !!dataSetDetail?.doc_count && (
<Tag size="mini" color="primary">
{I18n.t('knowledge_photo_015', {
num: dataSetDetail?.doc_count || 0,
})}
</Tag>
)}
{/* 未添加文档时不展示 */}
{formatType !== FormatType.Image && !!dataSetDetail?.doc_count && (
<>
{/* slice count */}
<Tag size="mini" color="primary">
{I18n.t('datasets_segment_tag_segments', {
num: dataSetDetail?.slice_count ?? 0,
})}
</Tag>
{/* hit count */}
<Tag size="mini" color="primary">
{I18n.t('datasets_segment_card_hit', {
num: dataSetDetail?.hit_count,
})}
</Tag>
</>
)}
{dataSetDetail?.storage_location === StorageLocation.OpenSearch ? (
<Tag size="mini" color="cyan">
{I18n.t('knowledge_es_001')}
</Tag>
) : null}
{/* loading */}
{Boolean(dataSetDetail?.processing_file_id_list) &&
Boolean(dataSetDetail?.processing_file_id_list?.length) && (
<Tag
size="mini"
color="blue"
data-testid={KnowledgeE2e.UnitDetailTagsProcessing}
>
{I18n.t('datasets_segment_tag_processing')}
{docInfo?.format_type === FormatType.Table &&
// @ts-expect-error -- linter-disable-autofix
progressMap?.[docInfo?.document_id]
? // @ts-expect-error -- linter-disable-autofix
`${progressMap?.[docInfo?.document_id]?.progress}%`
: ''}
</Tag>
)}
{/** Table error */}
{formatType === FormatType.Table &&
docInfo?.status === DocumentStatus.Failed && (
<Tag
size="mini"
color="red"
data-testid={KnowledgeE2e.UnitDetailTagsFailed}
>
{docInfo?.status_descript || I18n.t('dataset_process_fail')}
</Tag>
)}
</Space>
</div>
);
};

View File

@@ -0,0 +1,23 @@
/*
* 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 { HeaderTags } from './header-tags';
export { RelatedBotsList } from './related-bots-list';
export { FailedTag } from './tag';
export { IconWithSuffix } from './Icon-with-suffix';
export { KnowledgePreviewNavigation } from './preview-navigation';
export { KnowledgeModalNavBar } from './knowledge-modal-nav-bar';
export { KnowledgeIDENavBar } from './knowledge-nav-bar';

View File

@@ -0,0 +1,61 @@
/* stylelint-disable declaration-no-important */
.table-config-menu-down {
padding: 4px;
font-size: 12px;
.dropdown-item-line {
width: 100%;
height: 1px;
margin: 4px 0;
}
:global {
.semi-dropdown-menu {
padding: 4px;
}
.semi-dropdown-item {
min-width: 126px;
height: 32px !important;
padding: 8px 16px !important;
}
}
}
.dropdown-item-link {
font-size: 12px;
color: var(--coz-fg-hglt) !important;
:global {
.semi-dropdown-item-icon {
margin-right: 4px;
}
}
}
.dropdown-item-link-icon {
svg {
width: 12px;
height: 12px;
}
:global {
.semi-dropdown-item-icon {
margin-right: 0;
}
}
}
.action-btn {
&-right {
padding: 4px;
}
:global {
.semi-button-content-left {
display: flex;
align-items: center;
margin-right: 4px;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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, type ReactNode } from 'react';
import { KnowledgeE2e } from '@coze-data/e2e';
import {
IconCozSetting,
IconCozArrowUp,
IconCozArrowDown,
} from '@coze-arch/coze-design/icons';
import { Button, Menu } from '@coze-arch/coze-design';
import styles from './index.module.less';
export interface KnowledgeConfigMenuProps {
children: ReactNode;
}
export const KnowledgeConfigMenu = (props: KnowledgeConfigMenuProps) => {
const { children } = props;
const [visible, setVisible] = useState(false);
return (
<Menu
clickToHide
keepDOM
trigger="click"
position="bottomRight"
onVisibleChange={setVisible}
render={
<Menu.SubMenu mode="menu" className={styles['table-config-menu-down']}>
{children}
</Menu.SubMenu>
}
>
<Button
className={styles['action-btn']}
data-testid={KnowledgeE2e.SegmentDetailSystemBtn}
icon={
visible ? (
<IconCozArrowUp className={'text-[12px]'} />
) : (
<IconCozArrowDown className={'text-[12px]'} />
)
}
iconPosition="right"
color="primary"
style={{
minWidth: '45px',
padding: '6px 6px 6px 8px',
}}
>
<IconCozSetting />
</Button>
</Menu>
);
};

View File

@@ -0,0 +1,24 @@
/*
* 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 DocumentInfo } from '@coze-arch/bot-api/knowledge';
export interface TableConfigButtonProps {
documentInfo: DocumentInfo;
onChangeDocList?: (docList: DocumentInfo[]) => void;
reload: () => void;
canEdit?: boolean;
}

View File

@@ -0,0 +1,19 @@
/*
* 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 interface TextConfigButtonProps {
reload: () => void;
}

View File

@@ -0,0 +1,34 @@
/* stylelint-disable declaration-no-important */
@import '../../assets/common.less';
.navbar {
.brief {
display: flex;
align-items: center;
color: var(--coz-fg-primary);
:global {
.semi-avatar {
margin-right: 8px;
background: transparent;
border: 0.5px solid var(--coz-stroke-primary);
border-radius: 8px;
}
}
}
.back-icon {
padding: 8px!important;
}
.toolbar {
display: flex;
gap: 12px;
align-items: center;
height: fit-content;
}
}
.doc-icon-note {
.common-svg-icon(36px, null);
}

View File

@@ -0,0 +1,121 @@
/*
* 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 React, { useEffect } from 'react';
import classNames from 'classnames';
import {
useKnowledgeParams,
useKnowledgeStore,
} from '@coze-data/knowledge-stores';
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import {
FormatType,
type Dataset,
type DocumentInfo,
} from '@coze-arch/bot-api/knowledge';
import { IconCozCross } from '@coze-arch/coze-design/icons';
import { IconButton, Avatar, Space } from '@coze-arch/coze-design';
import { getFormatTypeFromUnitType } from '@/utils';
import { RenderDocumentIcon } from '@/components/render-document-icon';
import { PhotoFilter } from '@/components/photo-filter';
import { HeaderTags } from '@/components/header-tags';
import styles from './index.module.less';
export interface KnowledgeModalNavBarProps {
title: string;
datasetDetail?: Dataset;
docInfo?: DocumentInfo;
importKnowledgeSourceButton?: React.ReactNode;
actionButtons?: React.ReactNode;
onBack?: () => void;
beforeBack?: () => void;
}
export const KnowledgeModalNavBar: React.FC<KnowledgeModalNavBarProps> = ({
title,
actionButtons,
datasetDetail,
docInfo,
onBack,
beforeBack,
importKnowledgeSourceButton,
}) => {
const setSearchValue = useKnowledgeStore(state => state.setSearchValue);
const dataSetDetail = useKnowledgeStore(state => state.dataSetDetail);
const canEdit = useKnowledgeStore(state => state.canEdit);
const params = useKnowledgeParams();
useEffect(
() => () => {
setSearchValue('');
},
[],
);
const isImageFormat = dataSetDetail?.format_type === FormatType.Image;
return (
<div
className={classNames(
'flex items-center justify-between shrink-0 h-[56px]',
styles.navbar,
)}
data-testid={KnowledgeE2e.KnowledgeAddContentNavBar}
>
<div className={styles.brief}>
<IconButton
color="secondary"
icon={<IconCozCross className="text-[16px]" />}
iconPosition="left"
className={`${styles['back-icon']} mr-[8px]`}
onClick={() => {
beforeBack?.();
onBack?.();
}}
></IconButton>
{datasetDetail?.icon_url ? (
<Avatar src={datasetDetail?.icon_url} shape="square" />
) : (
<RenderDocumentIcon
formatType={getFormatTypeFromUnitType(params.type ?? UnitType.TEXT)}
className={styles['doc-icon-note']}
iconSuffixClassName="icon-with-suffix-overlay"
/>
)}
<div className="ml-[12px]">
<p className="text-[18px] font-medium">{title}</p>
{!!datasetDetail && (
<HeaderTags dataSetDetail={datasetDetail} docInfo={docInfo} />
)}
</div>
</div>
<div className={styles.toolbar}>
<Space spacing={12}>
{isImageFormat ? <PhotoFilter /> : null}
{/* 导入按钮 */}
{canEdit ? importKnowledgeSourceButton : null}
{actionButtons}
</Space>
</div>
</div>
);
};

View File

@@ -0,0 +1,142 @@
/* stylelint-disable declaration-no-important */
@import '../../assets/common.less';
.brief {
.info {
display: flex;
align-items: center;
height: 40px;
}
.icon {
width: 36px;
height: 36px;
:global {
.semi-avatar {
width: 36px;
height: 36px;
background: transparent;
border: 0.5px solid var(--coz-stroke-primary);
border-radius: 9px;
}
}
}
.content {
overflow: hidden;
flex-grow: 1;
margin-left: 8px;
}
.bot-count {
display: flex;
align-items: center;
width: 220px;
font-size: 12px;
line-height: 16px;
color: rgba(29, 28, 36, 35%);
&-text {
margin-right: 4px;
}
&-icon {
cursor: pointer;
width: 14px;
height: 14px;
margin-top: 1px;
path {
fill: #C6CACD;
}
}
}
.action-right {
margin-left: auto;
}
.tags {
margin-top: 5px;
}
.title {
max-width: 800px;
font-size: 14px;
font-weight: 500;
line-height: 20px;
color: var(--coz-fg-plus);
}
.description {
overflow: hidden;
margin-top: 6px;
font-size: 14px;
font-weight: 700;
font-style: normal;
line-height: 22px;
color: var(--light-usage-text-color-text-1, rgb(28 29 35 / 80%));
text-overflow: ellipsis;
}
:global {
.semi-tag-blue-light {
color: var(--light-color-brand-brand-6, #304cdb);
background: var(--light-color-brand-brand-1, #d9e2ff);
}
.semi-button {
padding: 8px 12px;
}
.icon-with-suffix-overlay {
.icon {
width: 36px;
height: 36px;
[role="img"] {
.common-svg-icon(24px, null);
}
}
.suffix {
top: 27px;
left: 27px;
width: 18px;
height: 18px;
[role="img"] {
.common-svg-icon(12px, null);
}
}
}
}
}
.doc-icon-note {
border-radius: 8px;
.common-svg-icon(36px, null);
}
.bot-used-count {
display: flex;
:global {
.related-bots-circle {
margin-top: -5px;
margin-right: 4px;
font-size: 16px;
}
}
}

View File

@@ -0,0 +1,256 @@
/*
* 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 @coze-arch/max-line-per-function */
/* eslint-disable complexity */
import { useNavigate } from 'react-router-dom';
import { useMemo } from 'react';
import classNames from 'classnames';
import { useKnowledgeParams, useKnowledgeStore } from '@coze-data/knowledge-stores';
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { useEditKnowledgeModal } from '@coze-data/knowledge-modal-adapter';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { useFlags } from '@coze-arch/bot-flags';
import { FormatType, type Dataset } from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
import {
IconCozArrowLeft,
IconCozEdit,
IconCozInfoCircle,
} from '@coze-arch/coze-design/icons';
import {
Space,
IconButton,
Avatar,
Tooltip,
Typography,
} from '@coze-arch/coze-design';
import { getUnitType } from '@/utils';
import { type ProgressMap } from '@/types';
import { RenderDocumentIcon } from '@/components/render-document-icon';
import { PhotoFilter } from '@/components/photo-filter';
import { HeaderTags, RelatedBotsList } from '@/components';
import styles from './index.module.less';
export interface KnowledgeIDENavBarProps {
progressMap: ProgressMap;
hideBackButton?: boolean;
textConfigButton?: React.ReactNode;
tableConfigButton?: React.ReactNode;
importKnowledgeSourceButton?: React.ReactNode;
onChangeDataset: (dataset: Dataset) => void;
onBack?: () => void;
}
export const KnowledgeIDENavBar = ({
onChangeDataset,
progressMap,
hideBackButton,
textConfigButton,
tableConfigButton,
importKnowledgeSourceButton,
onBack,
}: KnowledgeIDENavBarProps) => {
const dataSetDetail = useKnowledgeStore(state => state.dataSetDetail);
const canEdit = useKnowledgeStore(state => state.canEdit);
const documentList = useKnowledgeStore(state => state.documentList);
const navigate = useNavigate();
const params = useKnowledgeParams();
const [FLAGS] = useFlags();
const { node: editKnowledgeModal, edit } = useEditKnowledgeModal({
onOk: async formValue => {
await KnowledgeApi.UpdateDataset({
dataset_id: formValue.id,
name: formValue.name,
icon_uri: formValue.icon_uri?.[0].uid,
description: formValue.description,
});
onChangeDataset({
...dataSetDetail,
name: formValue?.name || dataSetDetail?.name,
description: formValue?.description || dataSetDetail?.description,
icon_uri: formValue.icon_uri?.at(0)?.uid || dataSetDetail?.icon_uri,
icon_url: formValue.icon_uri?.at(0)?.url || dataSetDetail?.icon_url,
});
},
});
const isTableFormat = dataSetDetail?.format_type === FormatType.Table;
const isImageFormat = dataSetDetail?.format_type === FormatType.Image;
const isShowResegmentBtn =
canEdit &&
!isTableFormat &&
!!dataSetDetail?.doc_count &&
!dataSetDetail?.processing_file_id_list?.length &&
!isImageFormat;
const documentInfo = documentList?.[0];
const unitType = useMemo(() => {
if (documentInfo) {
return getUnitType({
format_type: FormatType.Table,
source_type: documentInfo?.source_type,
});
}
return UnitType.TABLE_API;
}, [documentInfo]);
const isShowLinkUrl = useMemo(
() =>
unitType &&
[
UnitType.TABLE_API,
UnitType.TABLE_GOOGLE_DRIVE,
UnitType.TABLE_FEISHU,
UnitType.TABLE_LARK,
].includes(unitType),
[unitType],
);
// link 或者 action 二选一存在时才展示
const showTableConfigButton =
(isShowLinkUrl || canEdit) && isTableFormat && documentList?.length;
const handleBack = () => {
onBack?.();
navigate(`/space/${params.spaceID}/library`);
};
const fromProject = params.biz === 'project';
return (
<div
className={classNames(
'flex items-center justify-between shrink-0',
fromProject ? 'px-[16px] py-[12px]' : 'h-[56px]',
styles.brief,
)}
>
<div className={styles.info}>
{hideBackButton ? null : (
<IconButton
color="secondary"
icon={<IconCozArrowLeft className="text-[16px]" />}
iconPosition="left"
onClick={handleBack}
className="mr-[4px]"
></IconButton>
)}
{/* icon */}
<div className={styles.icon}>
{dataSetDetail?.icon_url ? (
<Avatar
src={dataSetDetail?.icon_url}
shape="square"
size="default"
/>
) : (
<RenderDocumentIcon
formatType={dataSetDetail?.format_type || FormatType.Text}
className={styles['doc-icon-note']}
iconSuffixClassName="icon-with-suffix-overlay"
/>
)}
</div>
<div className={styles.content}>
<div className="flex items-center gap-[3px]">
<Typography.Text
data-testid={KnowledgeE2e.SegmentDetailTitle}
className={styles.title}
weight={500}
ellipsis={{
showTooltip: {
opts: { content: dataSetDetail?.name },
},
}}
>
{dataSetDetail?.name}
</Typography.Text>
{canEdit ? (
<Tooltip content={I18n.t('datasets_segment_edit')}>
<IconButton
data-testid={KnowledgeE2e.SegmentDetailTitleEditIcon}
size="mini"
color="secondary"
icon={<IconCozEdit className="text-[14px]" />}
iconPosition="left"
wrapperClass="text-[0] leading-none"
className="coz-fg-secondary"
onClick={() => {
edit({
id: dataSetDetail.dataset_id || '',
name: dataSetDetail?.name,
description: dataSetDetail?.description,
icon_uri: [
{
url: dataSetDetail?.icon_url || '',
uid: dataSetDetail?.icon_uri || '',
},
],
});
}}
/>
</Tooltip>
) : null}
{FLAGS['bot.data.knowledge_bots_count'] ? (
<div className={styles['bot-count']}>
<div className={styles['bot-count-text']}>
{I18n.t('knowledge_optimize_019', {
n: dataSetDetail?.bot_used_count ?? 0,
})}
</div>
{!!dataSetDetail?.bot_used_count && (
<Tooltip
autoAdjustOverflow={true}
position={
dataSetDetail?.bot_used_count > 2 ? 'bottom' : 'top'
}
content={
<RelatedBotsList
datasetId={dataSetDetail?.dataset_id ?? ''}
classNameItem={styles['bot-used-count']}
/>
}
>
<IconCozInfoCircle className={styles['bot-count-icon']} />
</Tooltip>
)}
</div>
) : null}
</div>
<HeaderTags
dataSetDetail={dataSetDetail}
docInfo={documentList?.[0]}
progressMap={progressMap}
/>
</div>
</div>
<div>
<Space spacing={10}>
{isImageFormat ? <PhotoFilter /> : null}
{isShowResegmentBtn ? textConfigButton : null}
{showTableConfigButton ? tableConfigButton : null}
{canEdit ? importKnowledgeSourceButton : null}
</Space>
</div>
{editKnowledgeModal}
</div>
);
};

View File

@@ -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 { Menu } from '@coze-arch/coze-design';
interface KnowledgeSourceMenuItemProps {
value?: string;
onClick?: (value: string) => void;
title: string;
icon?: React.ReactNode;
testId?: string;
}
export const KnowledgeSourceMenuItem = (
props: KnowledgeSourceMenuItemProps,
) => {
const { title, icon, testId, value, onClick } = props;
return (
<Menu.Item
key={value}
icon={icon ?? null}
onClick={() => {
if (value && onClick) {
onClick.call(this, value);
}
}}
data-testid={testId}
>
{title}
</Menu.Item>
);
};

View File

@@ -0,0 +1,12 @@
.hidden-tooltip {
display: none;
}
.create-opt-select-down {
:global {
.semi-dropdown-item {
min-width: 126px;
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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 { ReactNode } from 'react';
import { Menu } from '@coze-arch/coze-design';
import styles from './index.module.less';
export interface KnowledgeSourceMenuProps {
triggerComponent: ReactNode;
onVisibleChange?: (visible: boolean) => void;
children: ReactNode;
}
export const KnowledgeSourceMenu = (props: KnowledgeSourceMenuProps) => {
const { triggerComponent, onVisibleChange, children } = props;
return (
<Menu
clickToHide
trigger="click"
position="bottomRight"
onVisibleChange={onVisibleChange}
render={
<Menu.SubMenu mode="menu" className={styles['create-opt-select-down']}>
{children}
</Menu.SubMenu>
}
>
{triggerComponent}
</Menu>
);
};

View File

@@ -0,0 +1,88 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable selector-class-pattern */
.common-svg-icon(@size: 14px, @color: #3370ff) {
>svg {
width: @size;
height: @size;
>path {
fill: @color;
}
}
}
.radio-wrapper {
.radio-group {
display: flex;
gap: 8px;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.radio-item {
flex: 0 0 49%;
}
.displayNone {
display: none;
}
.form-line-wrapper {
position: relative;
}
.form-line {
margin-top: 24px;
}
.pt6 {
padding-top: 6px;
}
:global {
.semi-radioGroup-vertical {
row-gap: 8px;
}
.semi-radio-cardRadioGroup {
column-gap: 16px;
border: 1px solid var(--coz-stroke-plus);
&:hover {
background: var(--coz-mg-secondary-hovered);
}
&:active {
background: var(--coz-mg-secondary-pressed);
}
}
.semi-radio-cardRadioGroup_checked {
background: var(--coz-mg-hglt);
border: 1px solid var(--coz-stroke-hglt);
&:hover {
background: var(--coz-mg-hglt-hovered);
}
&:active {
background: var(--coz-mg-hglt-pressed);
}
}
}
.radio-icon {
display: flex;
align-items: center;
margin-right: 8px;
}
}
.icon-size-16 {
.common-svg-icon(16px, null);
}

View File

@@ -0,0 +1,49 @@
/*
* 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 { ReactNode } from 'react';
import { RadioGroup } from '@coze-arch/coze-design';
import type { RadioGroupProps } from '@coze-arch/coze-design';
import styles from './index.module.less';
export interface KnowledgeSourceRadioGroupProps {
value: RadioGroupProps['value'];
onChange: RadioGroupProps['onChange'];
children: ReactNode;
}
export const KnowledgeSourceRadioGroup = (
props: KnowledgeSourceRadioGroupProps,
) => {
const { value, onChange, children } = props;
return (
<div className={styles['radio-wrapper']}>
<RadioGroup
type="pureCard"
onChange={onChange}
value={value}
direction="horizontal"
name="format-type"
className={styles['radio-group']}
>
{children}
</RadioGroup>
</div>
);
};

View File

@@ -0,0 +1,54 @@
/*
* 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 { Radio, Typography } from '@coze-arch/coze-design';
interface KnowledgeSourceRadioProps {
title: string;
description: string;
icon?: React.ReactNode;
e2e?: string;
key?: string;
value?: string;
}
export const KnowledgeSourceRadio = (props: KnowledgeSourceRadioProps) => {
const { title, description, icon, e2e, key, value } = props;
return (
<Radio
key={key}
value={value}
extra={
<Typography.Text
type="tertiary"
ellipsis={{
showTooltip: {
opts: { content: description },
},
}}
style={{ lineHeight: '20px', width: 180 }}
>
{description}
</Typography.Text>
}
className="flex-[0_0_49%]"
data-testid={e2e}
>
{icon ? <div className="flex items-center mr-2">{icon}</div> : null}
{title}
</Radio>
);
};

View File

@@ -0,0 +1,243 @@
/* stylelint-disable declaration-no-important */
.photo-list {
overflow: auto;
display: flex;
flex-direction: column;
height: calc(100vh - 170px);
padding: 0 24px;
.photo-list-spin {
flex: 1
}
}
.footer {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 0;
.spin {
width: 100%;
height: 60px;
font-size: 14px;
}
}
.card-group {
margin-bottom: 25px;
}
// Card 整体
.card {
cursor: auto;
width: 222px;
height: 258px;
border-radius: 8px;
&:hover {
box-shadow: 0 2px 14px 0 rgba(0, 0, 0, 8%);
}
:global {
// 固定高度142超出高度的图片截取居中部分展示
.semi-card-cover {
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
height: 142px;
}
}
}
.card-disabled {
:global {
.semi-card-cover {
background: var(--Light-usage-fill---color-fill-0, rgba(46, 46, 57, 4%));
border-radius: var(--default, 8px) var(--default, 8px) 0 0;
svg {
width: 32px;
height: 32px;
}
}
}
}
// Card 封面
.card-cover {
cursor: pointer;
border-radius: 8px 8px 0 0;
// 设置最小高度142保证填满封面
img {
min-height: 142px;
}
}
.prohibit-cover {
overflow: hidden;
font-size: 12px;
line-height: 16px; /* 133.333% */
color: var(--Light-usage-text---color-text-3, rgba(29, 28, 36, 35%));
text-align: center;
text-overflow: ellipsis;
}
// Card 内容区 (title + description)
.card-content {
display: flex;
flex-direction: column;
gap: 8px;
.photo-name {
font-weight: 600;
line-height: 20px;
color: rgba(29, 28, 35, 100%);
}
.photo-description {
height: 32px;
font-size: 12px;
line-height: 16px;
.failed-tag {
padding: 0 6px;
color: rgba(219, 46, 19, 100%);
background-color: rgba(255, 224, 210, 100%);
border-radius: 4px;
}
.processing-tag {
padding: 0 6px;
color: #304cdb;
background-color: #d9e2ff;
border-radius: 4px;
}
}
}
// Card 底部栏(时间 + 操作按钮)
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
height: 24px;
.create-time {
font-size: 12px;
line-height: 16px;
color: rgba(28, 29, 35, 35%)
}
}
// UI稿的 confirm modal 和 semi 默认的不一样,需要手动调整样式
.confirm-modal {
:global {
.semi-modal-header {
margin-bottom: 16px;
// icon 和 title 的间距
.semi-modal-icon-wrapper {
margin-right: 8px;
}
// title 颜色
.semi-modal-confirm-title-text {
color: rgba(29, 28, 35, 100%);
}
// 关闭 icon 的 hover 颜色
.semi-button:hover {
background-color: rgba(46, 46, 56, 8%);
}
}
.semi-modal-body {
margin: 0;
padding: 16px 0;
.semi-modal-confirm-content {
color: rgba(29, 28, 35, 100%)
}
}
.semi-modal-footer button {
min-width: 96px;
margin-left: 16px;
}
}
}
// 编辑 photo 信息的弹窗
.modal-content {
display: flex;
flex-direction: column;
gap: 20px;
width: 100%;
// photo 大图展示
// 用 flex 解决宽高和内部 image 不一致的问题
.photo-large {
position: relative;
display: flex;
align-items: center;
justify-content: center;
.arrow-button {
position: absolute;
top: 126px;
width: 48px;
height: 48px;
padding: 12px;
color: #1D1C23;
background-color: rgba(255, 255, 255, 50%);
border: none;
border-radius: 26px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 20%), 0 0 1px 0 rgba(0, 0, 0, 20%);
transition: all 0.3s;
&:hover {
color: #4D53E8;
background-color: rgba(255, 255, 255, 80%);
}
}
}
// photo 描述输入框
.photo-caption-textarea {
position: relative;
:global {
.semi-input-textarea-counter {
height: 40px;
}
}
.ai-generate-button {
position: absolute;
bottom: 8px;
left: 12px;
}
// 解决 disabled button 样式错位的问题
>span {
display: inline !important;
}
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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 @coze-arch/max-line-per-function */
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from 'react';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import {
UIModal,
Image,
TextArea,
Spin,
IconButton,
Icon,
} from '@coze-arch/bot-semi';
import { DocumentStatus, type PhotoInfo } from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { type ProgressMap } from '@/types';
import { ReactComponent as SvgArrowLeft } from '@/assets/icon_arrow_left.svg';
import { AutoGenerateButton } from '../auto-generate-photo-detail-button';
import styles from './index.module.less';
export interface UsePhotoDetailModalParams {
photo: PhotoInfo | undefined;
photoList: PhotoInfo[] | undefined;
progressMap: ProgressMap;
canEdit: boolean;
setCurrentPhotoId: (v: string) => void;
reload: () => void;
onCancel?: () => void;
onSubmit?: () => void;
}
export const usePhotoDetailModal = (params: UsePhotoDetailModalParams) => {
const {
photo = {},
photoList = [],
canEdit = false,
setCurrentPhotoId,
reload,
progressMap,
onCancel,
onSubmit,
} = params;
const [visible, setVisible] = useState(false);
const [textAreaLoading, setTextAreaLoading] = useState(false);
const [saveButtonLoading, setSaveButtonLoading] = useState(false);
const [textAreaValue, setTextAreaValue] = useState('');
const { document_id, status: originStatus } = photo;
const currentIndex = photoList.findIndex(i => i.document_id === document_id);
const status = progressMap[document_id || '']?.status || originStatus;
// 处理中获处理失败的photo不允许更新 caption
const disableUpdate =
status === DocumentStatus.Processing ||
status === DocumentStatus.Failed ||
!canEdit;
useEffect(() => {
if (visible) {
// @ts-expect-error -- linter-disable-autofix
setTextAreaValue(photo.caption);
}
}, [
visible,
// 切换图片时需要更新初始状态
document_id,
]);
const handleSave = async () => {
if (disableUpdate) {
setVisible(false);
return;
}
onSubmit?.();
setSaveButtonLoading(true);
try {
await KnowledgeApi.UpdatePhotoCaption({
// @ts-expect-error -- linter-disable-autofix
document_id,
caption: textAreaValue,
});
reload();
setSaveButtonLoading(false);
setVisible(false);
} catch (error) {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeUpdatePhotoCaption,
error: error as Error,
});
setSaveButtonLoading(false);
}
};
return {
node: (
<UIModal
visible={visible}
onCancel={() => {
setVisible(false);
onCancel?.();
}}
centered
title={I18n.t('knowledge_photo_019')}
width={792}
onOk={handleSave}
okButtonProps={{
disabled: disableUpdate,
loading: saveButtonLoading,
}}
>
<div className={styles['modal-content']}>
<div className={styles['photo-large']}>
<Image
// 仅设置高度,宽度会按图片原比例自动缩放
height={300}
src={photo.url}
/>
{
// 未找到,或者为第一个,不展示 pre 按钮
currentIndex < 1 ? null : (
<IconButton
icon={<Icon svg={<SvgArrowLeft />} />}
className={styles['arrow-button']}
style={{
left: '24px',
}}
onClick={() => {
const { document_id: preId } = photoList[currentIndex - 1];
// @ts-expect-error -- linter-disable-autofix
setCurrentPhotoId(preId);
}}
/>
)
}
{
// 未找到,或者为最后一个,不展示 pre 按钮
currentIndex === -1 ||
currentIndex === photoList.length - 1 ? null : (
<IconButton
icon={<Icon rotate={180} svg={<SvgArrowLeft />} />}
className={styles['arrow-button']}
style={{
right: '24px',
}}
onClick={() => {
const { document_id: nextId } = photoList[currentIndex + 1];
// @ts-expect-error -- linter-disable-autofix
setCurrentPhotoId(nextId);
}}
/>
)
}
</div>
<div className={styles['photo-caption-textarea']}>
<Spin spinning={textAreaLoading}>
<TextArea
maxCount={2000}
maxLength={2000}
placeholder={I18n.t('knowledge_photo_026')}
value={textAreaValue}
onChange={v => setTextAreaValue(v)}
disabled={disableUpdate}
/>
</Spin>
<AutoGenerateButton
currentValue={textAreaValue}
document_id={document_id || ''}
disable={disableUpdate}
onChange={setTextAreaValue}
onProgress={setTextAreaLoading}
/>
</div>
</div>
</UIModal>
),
open: () => {
setVisible(true);
},
};
};

View File

@@ -0,0 +1,24 @@
// photo 类型的 Filter
.photo-filter-tab {
display: flex;
&-item {
cursor: pointer;
height: 14px;
font-weight: 600;
line-height: 20px;
color: #1D1C2399;
&-active {
cursor: pointer;
height: 14px;
font-weight: 600;
line-height: 20px;
color: #4D53E8;
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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 { useKnowledgeStore } from '@coze-data/knowledge-stores';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Divider } from '@coze-arch/coze-design';
import { FilterPhotoType } from '@/types';
import styles from './index.module.less';
export const PhotoFilter: FC = () => {
const photoFilterValue = useKnowledgeStore(state => state.photoFilterValue);
const setPhotoFilterValue = useKnowledgeStore(
state => state.setPhotoFilterValue,
);
return (
<div className={styles['photo-filter-tab']}>
<div
data-testid={KnowledgeE2e.ImageAnnotationAllTab}
key={FilterPhotoType.All}
onClick={() => setPhotoFilterValue(FilterPhotoType.All)}
className={
photoFilterValue === FilterPhotoType.All
? styles['photo-filter-tab-item-active']
: styles['photo-filter-tab-item']
}
>
{I18n.t('knowledge-dataset-type-all')}
</div>
<Divider layout="vertical" margin="12px" />
<div
data-testid={KnowledgeE2e.ImageAnnotationAnnotationedTab}
key={FilterPhotoType.HasCaption}
onClick={() => setPhotoFilterValue(FilterPhotoType.HasCaption)}
className={
photoFilterValue === FilterPhotoType.HasCaption
? styles['photo-filter-tab-item-active']
: styles['photo-filter-tab-item']
}
>
{I18n.t('knowledge_photo_013')}
</div>
<Divider layout="vertical" margin="12px" />
<div
data-testid={KnowledgeE2e.ImageAnnotationUnAnnotationTab}
key={FilterPhotoType.NoCaption}
onClick={() => setPhotoFilterValue(FilterPhotoType.NoCaption)}
className={
photoFilterValue === FilterPhotoType.NoCaption
? styles['photo-filter-tab-item-active']
: styles['photo-filter-tab-item']
}
>
{I18n.t('knowledge_photo_014')}
</div>
</div>
);
};

View File

@@ -0,0 +1,8 @@
.header {
padding-bottom: 32px;
}
.breadcrumb {
display: flex;
align-items: center;
}

View File

@@ -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 DataSetInfo } from '@coze-arch/bot-api/memory';
import { type Dataset } from '@coze-arch/bot-api/knowledge';
import { UIBreadcrumb } from '@coze-studio/components';
import { Layout } from '@coze-arch/coze-design';
import styles from './index.module.less';
export interface KnowledgePreviewNavigationProps {
datasetInfo: Dataset;
}
export const KnowledgePreviewNavigation = ({
datasetInfo,
}: KnowledgePreviewNavigationProps) => (
<Layout.Header
className={styles.header}
breadcrumb={
<UIBreadcrumb
showTooltip={{ width: '160px' }}
className={styles.breadcrumb}
datasetInfo={datasetInfo as unknown as DataSetInfo}
compact={false}
/>
}
></Layout.Header>
);

View File

@@ -0,0 +1,22 @@
.related-bots-list {
overflow: scroll;
min-width: 20px;
min-height: 20px;
max-height: 396px;
// color: var(--Fg-COZ-fg-primary, rgba(6, 7, 9, 0.80));
font-weight: 500;
line-height: 20px;
&-item {
display: flex;
}
:global {
.related-bots-circle {
margin-top: -4px;
margin-right: 4px;
font-size: 18px
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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 { useEffect } from 'react';
import { useRequest } from 'ahooks';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { Loading } from '@coze-arch/coze-design';
import styles from './index.module.less';
interface RelatedBotsListProps {
datasetId: string;
classNameItem?: string;
style?: Record<string, string>;
}
export const RelatedBotsList = (props: RelatedBotsListProps) => {
const { datasetId, style, classNameItem } = props;
const {
loading,
data,
run,
error: requestError,
} = useRequest(
async () => {
const resq = await KnowledgeApi.GetDatasetRefBots({
dataset_id: datasetId,
});
return resq.ref_bots;
},
{
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeGetDatasetRefDetail,
error,
});
},
manual: true,
},
);
useEffect(() => {
run();
}, [datasetId]);
if (requestError) {
return <div>{I18n.t('knowledge_optimize_020')}</div>;
}
if (loading) {
return <Loading loading={loading}></Loading>;
}
return (
<div className={styles['related-bots-list']}>
{data?.map(item => (
<div
className={
classNameItem ? classNameItem : styles['related-bots-list-item']
}
style={style}
key={item.name}
>
<div className="related-bots-circle">.</div>
<div>{item.name}</div>
</div>
))}
</div>
);
};

View File

@@ -0,0 +1,36 @@
.icon-with-suffix {
position: relative;
margin-right: 12px;
:global {
.icon-with-suffix-common {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
}
.icon {
top: 0;
left: 0;
width: 24px;
height: 24px;
border-radius: 4px;
}
.suffix {
position: absolute;
z-index: 1;
top: 16px;
left: 16px;
width: 12px;
height: 12px;
border-radius: 2.667px;
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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 { DocumentSource, FormatType } from '@coze-arch/bot-api/knowledge';
import { isFeishuOrLarkDocumentSource } from '@coze-data/utils';
import { IconUnitsTable, IconUnitsFile } from '@coze-arch/bot-icons';
import { IconWithSuffix } from './suffix';
// 获取 icon
export const RenderDocumentIcon = ({
formatType,
sourceType,
isDisconnect,
className,
iconSuffixClassName,
}: {
formatType?: FormatType;
sourceType?: DocumentSource;
isDisconnect?: boolean;
className?: string;
iconSuffixClassName?: string;
}) => {
if (
sourceType &&
([DocumentSource.Notion, DocumentSource.GoogleDrive].includes(sourceType) ||
isFeishuOrLarkDocumentSource(sourceType))
) {
return (
<IconWithSuffix
hasSuffix={!!isDisconnect}
formatType={formatType}
className={iconSuffixClassName}
/>
);
} else {
return formatType === FormatType.Table ? (
<IconUnitsTable className={className} />
) : (
<IconUnitsFile className={className} />
);
}
};

View File

@@ -0,0 +1,75 @@
/*
* 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 React from 'react';
import classNames from 'classnames';
import { FormatType } from '@coze-arch/bot-api/knowledge';
import {
IconSvgFile,
IconSvgSheet,
IconSvgUnbound,
} from '@coze-arch/bot-icons';
import style from './index.module.less';
interface Props {
hasSuffix: boolean;
formatType?: FormatType;
className?: string;
}
export const IconMap = {
[FormatType.Table]: {
icon: <IconSvgSheet />,
bgColor: '#35C566',
suffixIcon: <IconSvgUnbound />,
suffixBgColor: 'rgba(255,150,0,1)',
},
[FormatType.Text]: {
icon: <IconSvgFile />,
bgColor: 'rgba(34, 136, 255, 1)',
suffixIcon: <IconSvgUnbound />,
suffixBgColor: 'rgba(255,150,0,1)',
},
};
export const IconWithSuffix = (props: Props) => {
const { formatType = FormatType.Text, hasSuffix, className } = props;
return (
<div className={classNames(style['icon-with-suffix'], className)}>
<div
className={classNames('icon-with-suffix-common', 'icon')}
style={{
backgroundColor: `${IconMap?.[formatType]?.bgColor}`,
}}
>
{IconMap?.[formatType]?.icon}
</div>
{hasSuffix ? (
<div
className={classNames('icon-with-suffix-common', 'suffix')}
style={{
backgroundColor: `${IconMap?.[formatType]?.suffixBgColor}`,
}}
>
{IconMap?.[formatType]?.suffixIcon}
</div>
) : null}
</div>
);
};

View File

@@ -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 { FailedTag } from './tag';

View File

@@ -0,0 +1,33 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { Tag, Tooltip } from '@coze-arch/coze-design';
interface FailedTagProps {
statusDescription: string | undefined;
}
export const FailedTag: FC<FailedTagProps> = ({ statusDescription }) => (
<Tooltip content={statusDescription}>
<span>
<Tag color="red" size="small">
{I18n.t('bot_publish_columns_status_failed')}
</Tag>
</span>
</Tooltip>
);

View File

@@ -0,0 +1,68 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { DocumentUpdateType, FormatType } from '@coze-arch/bot-api/memory';
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
export const MAX_SEGMENT_TOTAL = 10000;
export const CREATE_UNIT_DISABLE_UNIT_TYPES = [
UnitType.TABLE_GOOGLE_DRIVE,
UnitType.TABLE_FEISHU,
UnitType.TEXT_FEISHU,
UnitType.TEXT_LARK,
UnitType.TABLE_LARK,
UnitType.TEXT_GOOGLE_DRIVE,
UnitType.TEXT_DOC,
UnitType.TEXT_URL,
UnitType.TEXT_NOTION,
UnitType.TEXT_CUSTOM,
];
export const CREATE_UNIT_DISABLE_FORMAT_TYPES = [FormatType.Text];
export enum ViewMode {
ContentView,
SegmentView,
}
export const VIEW_MODE_OPTIONS = [
{
value: ViewMode.ContentView,
label: I18n.t('content_view_001'),
},
{
value: ViewMode.SegmentView,
label: I18n.t('content_view_002'),
},
];
export enum SegmentOptSelect {
RENAME = 0,
UPDATE_CONTENT = 1,
UPDATE_FREQUENCY = 2,
DELETE = 4,
CONFIGURATION_TABLE_STRUCTURE = 5,
FETCH_SLICE = 6,
UPDATE_FREQUENCY_BATCH = 7,
APPEND_FREQUENCY = 8,
OPEN_SEARCH_CONFIG = 9,
}
export const DOCUMENT_UPDATE_TYPE_MAP = {
[DocumentUpdateType.NoUpdate]: I18n.t('datasets_segment_tag_updateNo'),
[DocumentUpdateType.Cover]: I18n.t('datasets_segment_tag_overwrite'),
[DocumentUpdateType.Append]: I18n.t('datasets_segment_tag_overwriteNo'),
} as const;
export { POLLING_TIME } from './polling';

View File

@@ -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 const POLLING_TIME = 5000; //ms

View File

@@ -0,0 +1,30 @@
/*
* 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 { createContext, useContext } from 'react';
import { type ImportKnowledgeMenuSourceRegistry } from '@/features/import-knowledge-sources/menu/registry';
export interface KnowledgeIDERegistry {
importKnowledgeMenuSourceFeatureRegistry?: ImportKnowledgeMenuSourceRegistry;
}
export const KnowledgeIDERegistryContext = createContext<KnowledgeIDERegistry>({
importKnowledgeMenuSourceFeatureRegistry: undefined,
});
export const useKnowledgeIDERegistry = () =>
useContext(KnowledgeIDERegistryContext);

View File

@@ -0,0 +1,243 @@
/* stylelint-disable declaration-no-important */
.photo-list {
overflow: auto;
display: flex;
flex-direction: column;
height: calc(100vh - 170px);
padding: 0 24px;
.photo-list-spin {
flex: 1
}
}
.footer {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 0;
.spin {
width: 100%;
height: 60px;
font-size: 14px;
}
}
.card-group {
margin-bottom: 25px;
}
// Card 整体
.card {
cursor: auto;
width: 222px;
height: 258px;
border-radius: 8px;
&:hover {
box-shadow: 0 2px 14px 0 rgba(0, 0, 0, 8%);
}
:global {
// 固定高度142超出高度的图片截取居中部分展示
.semi-card-cover {
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
height: 142px;
}
}
}
.card-disabled {
:global {
.semi-card-cover {
background: var(--Light-usage-fill---color-fill-0, rgba(46, 46, 57, 4%));
border-radius: var(--default, 8px) var(--default, 8px) 0 0;
svg {
width: 32px;
height: 32px;
}
}
}
}
// Card 封面
.card-cover {
cursor: pointer;
border-radius: 8px 8px 0 0;
// 设置最小高度142保证填满封面
img {
min-height: 142px;
}
}
.prohibit-cover {
overflow: hidden;
font-size: 12px;
line-height: 16px; /* 133.333% */
color: var(--Light-usage-text---color-text-3, rgba(29, 28, 36, 35%));
text-align: center;
text-overflow: ellipsis;
}
// Card 内容区 (title + description)
.card-content {
display: flex;
flex-direction: column;
gap: 8px;
.photo-name {
font-weight: 600;
line-height: 20px;
color: rgba(29, 28, 35, 100%);
}
.photo-description {
height: 32px;
font-size: 12px;
line-height: 16px;
.failed-tag {
padding: 0 6px;
color: rgba(219, 46, 19, 100%);
background-color: rgba(255, 224, 210, 100%);
border-radius: 4px;
}
.processing-tag {
padding: 0 6px;
color: #304cdb;
background-color: #d9e2ff;
border-radius: 4px;
}
}
}
// Card 底部栏(时间 + 操作按钮)
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
height: 24px;
.create-time {
font-size: 12px;
line-height: 16px;
color: rgba(28, 29, 35, 35%)
}
}
// UI稿的 confirm modal 和 semi 默认的不一样,需要手动调整样式
.confirm-modal {
:global {
.semi-modal-header {
margin-bottom: 16px;
// icon 和 title 的间距
.semi-modal-icon-wrapper {
margin-right: 8px;
}
// title 颜色
.semi-modal-confirm-title-text {
color: rgba(29, 28, 35, 100%);
}
// 关闭 icon 的 hover 颜色
.semi-button:hover {
background-color: rgba(46, 46, 56, 8%);
}
}
.semi-modal-body {
margin: 0;
padding: 16px 0;
.semi-modal-confirm-content {
color: rgba(29, 28, 35, 100%)
}
}
.semi-modal-footer button {
min-width: 96px;
margin-left: 16px;
}
}
}
// 编辑 photo 信息的弹窗
.modal-content {
display: flex;
flex-direction: column;
gap: 20px;
width: 100%;
// photo 大图展示
// 用 flex 解决宽高和内部 image 不一致的问题
.photo-large {
position: relative;
display: flex;
align-items: center;
justify-content: center;
.arrow-button {
position: absolute;
top: 126px;
width: 48px;
height: 48px;
padding: 12px;
color: #1D1C23;
background-color: rgba(255, 255, 255, 50%);
border: none;
border-radius: 26px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 20%), 0 0 1px 0 rgba(0, 0, 0, 20%);
transition: all 0.3s;
&:hover {
color: #4D53E8;
background-color: rgba(255, 255, 255, 80%);
}
}
}
// photo 描述输入框
.photo-caption-textarea {
position: relative;
:global {
.semi-input-textarea-counter {
height: 40px;
}
}
.ai-generate-button {
position: absolute;
bottom: 8px;
left: 12px;
}
// 解决 disabled button 样式错位的问题
>span {
display: inline !important;
}
}
}

View File

@@ -0,0 +1,386 @@
/*
* 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 @coze-arch/max-line-per-function */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable max-lines-per-function */
import { useSearchParams } from 'react-router-dom';
import { useEffect, useState, type FC, useRef } from 'react';
import dayjs from 'dayjs';
import classNames from 'classnames';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import {
useKnowledgeParams,
useKnowledgeStore,
} from '@coze-data/knowledge-stores';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import {
Card,
CardGroup,
Image,
Space,
Spin,
Tooltip,
Typography,
UIIconButton,
UIModal,
UIEmpty,
} from '@coze-arch/bot-semi';
import {
IconCloseKnowledge,
IconDeleteOutline,
IconEdit,
IconWaringRed,
IconSegmentEmpty,
IconImageFailOutlined,
} from '@coze-arch/bot-icons';
import { DocumentStatus, type PhotoInfo } from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { type ProgressItem, type ProgressMap } from '@/types';
import { usePhotoDetailModal } from '@/components/photo-detail-modal';
import { usePhotoList } from './use-photo-list';
import styles from './index.module.less';
export interface ImageKnowledgeWorkspaceProps {
progressMap: ProgressMap;
}
export const ImageKnowledgeWorkspace: FC<
ImageKnowledgeWorkspaceProps
> = props => {
const { progressMap } = props;
const [_, setSearchParams] = useSearchParams();
const params = useKnowledgeParams();
const firstAutoOpenEditDocumentId = params.first_auto_open_edit_document_id;
const [currentPhotoId, setCurrentPhotoId] = useState<string>('');
const [currentHoverCardId, setCurrentHoverCardId] = useState<string>('');
const dataSetDetail = useKnowledgeStore(state => state.dataSetDetail);
const canEdit = useKnowledgeStore(state => state.canEdit);
const searchValue = useKnowledgeStore(state => state.searchValue);
const photoFilterValue = useKnowledgeStore(state => state.photoFilterValue);
const ref = useRef<HTMLDivElement>(null);
const { data, loading, loadingMore, reloadAsync, noMore } = usePhotoList(
{
// @ts-expect-error -- linter-disable-autofix
datasetID: dataSetDetail?.dataset_id,
filterPhotoType: photoFilterValue,
searchValue,
},
{
// @ts-expect-error -- linter-disable-autofix
isNoMore: d => d?.list.length >= d?.total,
target: ref,
},
);
const photoList = data?.list;
const shouldAutoOpenDetailModal = photoList?.find(
i => i.document_id === firstAutoOpenEditDocumentId,
);
const previewPhotoList = photoList?.filter(
photo => photo.status !== DocumentStatus.AuditFailed,
);
const curPhoto = previewPhotoList?.find(
i => i.document_id === currentPhotoId,
);
const resetUrlQueryParams = () => {
setSearchParams((state: URLSearchParams) => {
state.delete('action_type');
return state;
});
};
const { node, open } = usePhotoDetailModal({
photo: curPhoto,
progressMap,
photoList: previewPhotoList,
canEdit: !!canEdit,
setCurrentPhotoId,
reload: reloadAsync,
onCancel: () => {
// 重置url参数
resetUrlQueryParams();
},
onSubmit: () => {
resetUrlQueryParams();
},
});
// 手动控制 data 加载时机
useEffect(() => {
if (dataSetDetail?.dataset_id) {
reloadAsync();
// 重新加载时,回到最顶部
ref.current?.scrollTo?.({
top: 0,
behavior: 'smooth',
});
}
}, [searchValue, photoFilterValue, dataSetDetail?.dataset_id]);
// 自动打开编辑弹窗
useEffect(() => {
if (shouldAutoOpenDetailModal) {
if (firstAutoOpenEditDocumentId) {
setCurrentPhotoId(firstAutoOpenEditDocumentId);
}
open();
}
}, [firstAutoOpenEditDocumentId, shouldAutoOpenDetailModal]);
return (
<>
<div className={styles['photo-list']} ref={ref}>
<Spin
spinning={loading}
wrapperClassName={styles['photo-list-spin']}
childStyle={{
height: '100%',
width: '100%',
}}
>
{/* @ts-expect-error -- linter-disable-autofix */}
{photoList?.length <= 0 ? (
<UIEmpty
empty={{
icon: <IconSegmentEmpty />,
title: I18n.t('query_data_empty'),
}}
/>
) : (
<CardGroup spacing={12} className={styles['card-group']}>
{photoList?.map(item => {
const {
url,
document_id,
name,
update_time,
caption: originCaption,
status: originStatus,
} = item;
// 此处使用 progressMap 可以保持不断刷新直至完成
// @ts-expect-error -- linter-disable-autofix
const status = progressMap[document_id]?.status || originStatus;
// 使用 progressMap 获取最新的caption
const caption =
// @ts-expect-error -- linter-disable-autofix
(progressMap[document_id] as ProgressItem & PhotoInfo)
?.caption || originCaption;
const hasCaption =
typeof caption === 'string' && Boolean(caption);
const handleEdit = () => {
// @ts-expect-error -- linter-disable-autofix
setCurrentPhotoId(document_id);
open();
};
const handleDelete = () => {
UIModal.error({
// 必填参数,统一 confirm modal 的样式
className: styles['confirm-modal'],
closeIcon: <IconCloseKnowledge />,
// 自定义参数
title: I18n.t('kl2_007'),
content: I18n.t(
'dataset_detail_table_deleteModel_description',
),
icon: <IconWaringRed />,
cancelText: I18n.t('Cancel'),
okText: I18n.t('Delete'),
okButtonProps: {
type: 'danger',
},
onOk: async () => {
try {
await KnowledgeApi.DeleteDocument({
// @ts-expect-error -- linter-disable-autofix
document_ids: [document_id],
});
await reloadAsync();
} catch (error) {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeDeleteDocument,
error: error as Error,
});
}
},
});
};
const onMouseEnter = () => {
// @ts-expect-error -- linter-disable-autofix
setCurrentHoverCardId(document_id);
};
const onMouseLeave = () => {
setCurrentHoverCardId('');
};
const isHover = currentHoverCardId === document_id;
const isAudiFailed =
originStatus === DocumentStatus.AuditFailed;
const getCaption = () => {
// 违规图片
if (isAudiFailed) {
return (
<span>
{I18n.t('knowledge_content_illegal_error_msg')}
</span>
);
}
// 处理失败
if (status === DocumentStatus.Failed) {
return (
<span className={styles['failed-tag']}>
{I18n.t('dataset_process_fail')}
</span>
);
}
// 处理中
if (status === DocumentStatus.Processing) {
return (
<span className={styles['processing-tag']}>
{I18n.t('datasets_segment_tag_processing')}
</span>
);
}
// 已标注
if (hasCaption) {
return caption;
}
// 未标注
return I18n.t('knowledge_photo_016');
};
return (
<Card
key={document_id}
cover={
isAudiFailed ? (
<div className={styles['prohibit-cover']}>
<IconImageFailOutlined size={'extra-small'} />
<p>{I18n.t('knowledge_photo_illegal_error_msg')}</p>
</div>
) : (
<Image
src={url}
// 仅设置宽度,高度会按图片原比例自动缩放
width={222}
preview={false}
onClick={handleEdit}
className={styles['card-cover']}
/>
)
}
headerLine={false}
bodyStyle={{
padding: '12px 16px',
}}
className={classNames(
styles.card,
isAudiFailed ? styles['card-disabled'] : '',
)}
>
<div
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={styles['card-content']}
>
<Typography.Text
className={styles['photo-name']}
ellipsis={{
showTooltip: true,
}}
>
{name}
</Typography.Text>
<Typography.Paragraph
className={styles['photo-description']}
ellipsis={{
showTooltip: true,
rows: 2,
}}
style={{
color: hasCaption
? 'rgba(29, 28, 35, 0.6)'
: 'rgba(255, 178, 51, 1)',
}}
>
{getCaption()}
</Typography.Paragraph>
<div className={styles['card-footer']}>
<Typography.Text className={styles['create-time']}>
{/* @ts-expect-error -- linter-disable-autofix */}
{dayjs.unix(update_time).format('YYYY-MM-DD HH:mm')}
</Typography.Text>
{isHover && canEdit ? (
<Space spacing={12}>
<Tooltip content={I18n.t('Edit')}>
<UIIconButton
icon={<IconEdit />}
disabled={isAudiFailed}
onClick={handleEdit}
/>
</Tooltip>
<Tooltip content={I18n.t('Delete')}>
<UIIconButton
icon={<IconDeleteOutline />}
onClick={handleDelete}
/>
</Tooltip>
</Space>
) : null}
</div>
</div>
</Card>
);
})}
</CardGroup>
)}
</Spin>
<div className={styles.footer}>
{!noMore && (
<Spin
spinning={loadingMore}
tip={I18n.t('loading')}
wrapperClassName={styles.spin}
/>
)}
</div>
</div>
{node}
</>
);
};

View File

@@ -0,0 +1,89 @@
/*
* 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 InfiniteScrollOptions } from 'ahooks/lib/useInfiniteScroll/types';
import { useInfiniteScroll } from 'ahooks';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { KNOWLEDGE_MAX_DOC_SIZE } from '@coze-data/knowledge-modal-base';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { type PhotoInfo } from '@coze-arch/bot-api/knowledge';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { FilterPhotoType } from '@/types';
export interface UsePhotoListParams {
datasetID: string;
searchValue?: string;
filterPhotoType?: FilterPhotoType;
}
interface Result {
list: PhotoInfo[];
total: number;
}
const PAGE_SIZE = 20;
export const usePhotoList = (
params: UsePhotoListParams,
options: InfiniteScrollOptions<Result>,
) => {
const { datasetID, searchValue, filterPhotoType } = params;
const fetchPhotoList = async (
page: number,
pageSize: number,
// @ts-expect-error -- linter-disable-autofix
): Promise<Result> => {
try {
const res = await KnowledgeApi.ListPhoto({
page: page ?? 1,
size: pageSize ?? KNOWLEDGE_MAX_DOC_SIZE,
dataset_id: datasetID,
filter: {
keyword: searchValue,
has_caption:
filterPhotoType === FilterPhotoType.HasCaption
? true
: filterPhotoType === FilterPhotoType.NoCaption
? false
: // 传 undefined 代表返回全部
undefined,
},
});
return {
list: res.photo_infos || [],
total: res.total || 0,
};
} catch (error) {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgePhotoList,
error: error as Error,
});
}
};
return useInfiniteScroll<Result>(
async d => {
const p = d ? Math.ceil(d.list.length / PAGE_SIZE) + 1 : 1;
return fetchPhotoList(p, PAGE_SIZE);
},
{
manual: true,
...options,
},
);
};

View File

@@ -0,0 +1,117 @@
/*
* 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 {
useDataNavigate,
useKnowledgeStore,
} from '@coze-data/knowledge-stores';
import { OptType } from '@coze-data/knowledge-resource-processor-core';
import { getKnowledgeIDEQuery } from '@coze-data/knowledge-common-services/use-case';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozArrowDown } from '@coze-arch/bot-icons';
import { FormatType } from '@coze-arch/bot-api/knowledge';
import { IconCozArrowUp } from '@coze-arch/coze-design/icons';
import { Button, Tooltip } from '@coze-arch/coze-design';
import { ImportKnowledgeSourceMenu } from '@/features/import-knowledge-source-menu';
import { type ImportKnowledgeSourceButtonProps } from '../module';
import {
createBtnDisableToolTip,
getTableFormatTooltip,
getDefaultFormatTooltip,
} from './services/use-case/disabled-tooltip';
export const ImportKnowledgeSourceButton = ({
disabledTooltip: disabledTooltipProp,
onSourceChange,
}: ImportKnowledgeSourceButtonProps) => {
const documentList = useKnowledgeStore(state => state.documentList);
const dataSetDetail = useKnowledgeStore(state => state.dataSetDetail);
const [visible, setVisible] = useState<boolean>(false);
const resourceNavigate = useDataNavigate();
const disabledTooltip =
disabledTooltipProp ?? createBtnDisableToolTip(dataSetDetail, documentList);
const query = getKnowledgeIDEQuery() as Record<string, string>;
if (disabledTooltip) {
return (
<Tooltip
content={disabledTooltip}
arrowPointAtCenter={false}
position="top"
>
<Button
data-testid={KnowledgeE2e.SegmentDetailAddBtn}
color="hgltplus"
disabled
iconPosition="right"
icon={<IconCozArrowDown className={'text-[12px]'} />}
>
{I18n.t('knowledg_unit_add_segments')}
</Button>
</Tooltip>
);
}
return (
<ImportKnowledgeSourceMenu
onVisibleChange={dropVisible => {
setVisible(dropVisible);
}}
onChange={unitType => {
if (onSourceChange) {
onSourceChange(unitType);
return;
}
/** 默认跳转到upload */
const formatType = dataSetDetail?.format_type;
const docID = documentList?.[0]?.document_id;
const params: Record<string, string> = {
type: unitType,
...query,
};
if (formatType === FormatType.Table && docID) {
params.opt = OptType.INCREMENTAL;
params.doc_id = docID;
}
resourceNavigate.upload?.(params);
}}
triggerComponent={
<Button
data-testid={KnowledgeE2e.SegmentDetailAddBtn}
iconPosition="right"
icon={
visible ? (
<IconCozArrowUp className={'text-[12px]'} />
) : (
<IconCozArrowDown className={'text-[12px]'} />
)
}
>
{I18n.t('knowledg_unit_add_segments')}
</Button>
}
/>
);
};
export {
getTableFormatTooltip,
getDefaultFormatTooltip,
createBtnDisableToolTip,
};

View File

@@ -0,0 +1,85 @@
/*
* 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 {
KNOWLEDGE_MAX_DOC_SIZE,
KNOWLEDGE_MAX_SLICE_COUNT,
} from '@coze-data/knowledge-modal-base';
import { I18n } from '@coze-arch/i18n';
import { type Dataset, type DocumentInfo } from '@coze-arch/bot-api/knowledge';
import { DocumentStatus, FormatType } from '@coze-arch/bot-api/knowledge';
/**
* 处理表格类型数据集的禁用提示
*/
export const getTableFormatTooltip = (documentList: DocumentInfo[]): string => {
const docInfo = documentList?.[0];
if (!docInfo) {
return '';
}
if (docInfo.status === DocumentStatus.Processing) {
return I18n.t('knowledge_add_content_processing_tips');
}
// @ts-expect-error -- linter-disable-autofix
if (docInfo?.slice_count >= KNOWLEDGE_MAX_SLICE_COUNT) {
return I18n.t('kl2_002');
}
return '';
};
/**
* 处理默认类型数据集的禁用提示
*/
export const getDefaultFormatTooltip = (dataSetDetail: Dataset): string => {
// @ts-expect-error -- linter-disable-autofix
if (dataSetDetail?.doc_count >= KNOWLEDGE_MAX_DOC_SIZE) {
return I18n.t('kl2_003');
}
if (dataSetDetail?.processing_file_id_list?.length) {
return I18n.t('knowledge_add_content_processing_tips');
}
return '';
};
/**
* 创建按钮禁用时的提示文本
* @param dataSetDetail - 数据集详情
* @param documentList - 文档列表
* @returns 提示文本
*/
export const createBtnDisableToolTip = (
dataSetDetail: Dataset,
documentList: DocumentInfo[],
): string => {
const formatType = dataSetDetail?.format_type;
const tooltipHandlers: Record<string, () => string> = {
[FormatType.Table]: () => getTableFormatTooltip(documentList),
default: () => getDefaultFormatTooltip(dataSetDetail),
};
if (!formatType) {
return tooltipHandlers.default();
}
const handler = tooltipHandlers[formatType] || tooltipHandlers.default;
return handler();
};

View File

@@ -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 {
getTableFormatTooltip,
getDefaultFormatTooltip,
createBtnDisableToolTip,
} from './disabled-tooltip';

View File

@@ -0,0 +1,64 @@
/*
* 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 { useShallow } from 'zustand/react/shallow';
import { useKnowledgeParams, useKnowledgeStore } from '@coze-data/knowledge-stores';
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
import { useKnowledgeNavigate } from '@coze-data/knowledge-common-hooks/use-case';
import { useSpaceStore } from '@coze-arch/bot-studio-store';
import { type FormatType } from '@coze-arch/bot-api/knowledge';
import { getAddContentUrl } from '@/utils';
import { ActionType } from '@/types';
import { type ImportKnowledgeSourceButtonProps } from '../module';
import { ImportKnowledgeSourceButton } from '../base';
export type BizAgentIdeImportKnowledgeSourceButtonProps =
ImportKnowledgeSourceButtonProps;
export const BizAgentIdeImportKnowledgeSourceButton = ({
disabledTooltip,
}: BizAgentIdeImportKnowledgeSourceButtonProps) => {
const navigate = useKnowledgeNavigate();
const { documentList, dataSetDetail } = useKnowledgeStore(
useShallow(state => ({
documentList: state.documentList,
dataSetDetail: state.dataSetDetail,
})),
);
const params = useKnowledgeParams();
const spaceId = useSpaceStore(item => item.space.id);
return (
<ImportKnowledgeSourceButton
disabledTooltip={disabledTooltip}
onSourceChange={unitType => {
navigate(
getAddContentUrl({
spaceID: spaceId as string,
datasetID: dataSetDetail?.dataset_id as string,
docID: documentList?.[0]?.document_id,
formatType: dataSetDetail?.format_type as FormatType,
type: unitType as UnitType,
pageMode: 'modal',
botId: params.botID,
actionType: ActionType.ADD,
}),
);
}}
/>
);
};

View File

@@ -0,0 +1,20 @@
/*
* 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 interface ImportKnowledgeSourceButtonProps {
disabledTooltip?: string;
onSourceChange?: (source: string) => void;
}

View File

@@ -0,0 +1,49 @@
/*
* 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 { ReactNode } from 'react';
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
import { useKnowledgeIDERegistry } from '../../context/knowledge-ide-registry-context';
import { KnowledgeSourceMenu as KnowledgeSourceMenuComponent } from '../../components/knowledge-source-menu';
export interface ImportKnowledgeSourceMenuProps {
triggerComponent?: ReactNode;
onVisibleChange?: (visible: boolean) => void;
onChange?: (val: UnitType) => void;
}
export const ImportKnowledgeSourceMenu = (
props: ImportKnowledgeSourceMenuProps,
) => {
const { triggerComponent, onVisibleChange, onChange } = props;
const { importKnowledgeMenuSourceFeatureRegistry } =
useKnowledgeIDERegistry();
return (
<KnowledgeSourceMenuComponent
triggerComponent={triggerComponent}
onVisibleChange={onVisibleChange}
>
{importKnowledgeMenuSourceFeatureRegistry
?.entries()
.map(([key, { Component }]) => (
<Component key={key} onClick={value => onChange?.(value)} />
))}
</KnowledgeSourceMenuComponent>
);
};

View File

@@ -0,0 +1,36 @@
/*
* 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 { ImageLocalModule } from '@/features/import-knowledge-sources/radio/image-local';
import {
createImportKnowledgeSourceRadioFeatureRegistry,
type ImportKnowledgeRadioSourceFeatureRegistry,
} from '@/features/import-knowledge-sources/radio';
export const importImageKnowledgeSourceRadioGroupContributes: ImportKnowledgeRadioSourceFeatureRegistry =
(() => {
const importKnowledgeRadioSourceFeatureRegistry =
createImportKnowledgeSourceRadioFeatureRegistry(
'import-knowledge-source-image-radio-group',
);
importKnowledgeRadioSourceFeatureRegistry.registerSome([
{
type: 'image-local',
module: ImageLocalModule,
},
]);
return importKnowledgeRadioSourceFeatureRegistry;
})();

View File

@@ -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 { TableLocalModule } from '@coze-data/knowledge-ide-base/features/import-knowledge-sources/radio/table-local';
import {
createImportKnowledgeSourceRadioFeatureRegistry,
type ImportKnowledgeRadioSourceFeatureRegistry,
} from '@coze-data/knowledge-ide-base/features/import-knowledge-sources/radio';
import { TableCustomModule } from '@coze-data/knowledge-ide-base/features/import-knowledge-sources/radio';
export const importTableKnowledgeSourceRadioGroupContributes: ImportKnowledgeRadioSourceFeatureRegistry =
(() => {
const importKnowledgeRadioSourceFeatureRegistry =
createImportKnowledgeSourceRadioFeatureRegistry(
'import-knowledge-source-table-radio-group',
);
importKnowledgeRadioSourceFeatureRegistry.registerSome([
{
type: 'table-local',
module: TableLocalModule,
},
{
type: 'table-custom',
module: TableCustomModule,
},
]);
return importKnowledgeRadioSourceFeatureRegistry;
})();

View File

@@ -0,0 +1,43 @@
/*
* 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 {
createImportKnowledgeSourceRadioFeatureRegistry,
type ImportKnowledgeRadioSourceFeatureRegistry,
} from '@coze-data/knowledge-ide-base/features/import-knowledge-sources/radio';
import {
TextCustomModule,
TextLocalModule,
} from '@coze-data/knowledge-ide-base/features/import-knowledge-sources/radio';
export const importTextKnowledgeSourceRadioGroupContributes: ImportKnowledgeRadioSourceFeatureRegistry =
(() => {
const importKnowledgeRadioSourceFeatureRegistry =
createImportKnowledgeSourceRadioFeatureRegistry(
'import-knowledge-source-text-radio-group',
);
importKnowledgeRadioSourceFeatureRegistry.registerSome([
{
type: 'text-local',
module: TextLocalModule,
},
{
type: 'text-custom',
module: TextCustomModule,
},
]);
return importKnowledgeRadioSourceFeatureRegistry;
})();

View File

@@ -0,0 +1,66 @@
/*
* 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 { useMemo } from 'react';
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
import { FormatType } from '@coze-arch/bot-api/knowledge';
import { type ImportKnowledgeRadioSourceFeatureRegistry } from '../import-knowledge-sources/radio/registry';
import { KnowledgeSourceRadioGroup as KnowledgeSourceRadioGroupComponent } from '../../components/knowledge-source-radio-group';
import { importTextKnowledgeSourceRadioGroupContributes } from './import-text-knowledge-source-contributes';
import { importTableKnowledgeSourceRadioGroupContributes } from './import-table-knowledge-source-contributes';
import { importImageKnowledgeSourceRadioGroupContributes } from './import-image-knowledge-source-contributes';
export interface ImportKnowledgeSourceRadioGroupProps {
formatType: FormatType;
value?: UnitType;
importKnowledgeSourceRegistry: ImportKnowledgeRadioSourceFeatureRegistry;
onChange?: (val: UnitType) => void;
}
export const ImportKnowledgeSourceRadioGroup = (
props: ImportKnowledgeSourceRadioGroupProps,
) => {
const { value, onChange, formatType } = props;
const importKnowledgeSourceRegistry = useMemo(() => {
if (formatType === FormatType.Text) {
return importTextKnowledgeSourceRadioGroupContributes;
}
if (formatType === FormatType.Table) {
return importTableKnowledgeSourceRadioGroupContributes;
}
if (formatType === FormatType.Image) {
return importImageKnowledgeSourceRadioGroupContributes;
}
}, [formatType]);
if (!importKnowledgeSourceRegistry) {
return null;
}
return (
<KnowledgeSourceRadioGroupComponent
value={value}
onChange={e => {
onChange?.(e.target.value);
}}
>
{importKnowledgeSourceRegistry.entries().map(([key, { Component }]) => (
<Component key={key} />
))}
</KnowledgeSourceRadioGroupComponent>
);
};

View File

@@ -0,0 +1,44 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { KnowledgeSourceMenuItem } from '@/components/knowledge-source-menu-item';
import {
type ImportKnowledgeMenuSourceModule,
type ImportKnowledgeMenuSourceModuleProps,
} from '../module';
export const ImageLocal = (props: ImportKnowledgeMenuSourceModuleProps) => {
const { onClick } = props;
return (
<KnowledgeSourceMenuItem
title={I18n.t('knowledge_photo_002')}
icon={<IconCozDocument className="w-4 h-4" />}
testId={`${KnowledgeE2e.SegmentDetailDropdownItem}.${UnitType.IMAGE_FILE}`}
value={UnitType.IMAGE_FILE}
onClick={() => onClick(UnitType.IMAGE_FILE)}
/>
);
};
export const ImageLocalModule: ImportKnowledgeMenuSourceModule = {
Component: ImageLocal,
};

View File

@@ -0,0 +1,30 @@
/*
* 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 { TableCustomModule } from './table-custom';
export { TableLocalModule } from './table-local';
export { TextCustomModule } from './text-custom';
export { TextLocalModule } from './text-local';
export { ImageLocalModule } from './image-local';
export type {
ImportKnowledgeMenuSourceModuleProps,
ImportKnowledgeMenuSourceModule,
} from './module';
export {
createImportKnowledgeMenuSourceFeatureRegistry,
type ImportKnowledgeMenuSourceFeatureType,
type ImportKnowledgeMenuSourceRegistry,
} from './registry';

View File

@@ -0,0 +1,25 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
export interface ImportKnowledgeMenuSourceModuleProps {
onClick: (item: UnitType) => void;
}
export interface ImportKnowledgeMenuSourceModule {
Component: React.ComponentType<ImportKnowledgeMenuSourceModuleProps>;
}

View File

@@ -0,0 +1,42 @@
/*
* 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 { FeatureRegistry } from '@coze-data/feature-register';
import { type ImportKnowledgeMenuSourceModule } from './module';
export type ImportKnowledgeMenuSourceFeatureType =
| 'text-local'
| 'text-custom'
| 'table-custom'
| 'table-local'
| 'image-local'
| 'image-custom';
export type ImportKnowledgeMenuSourceRegistry = FeatureRegistry<
ImportKnowledgeMenuSourceFeatureType,
ImportKnowledgeMenuSourceModule
>;
export const createImportKnowledgeMenuSourceFeatureRegistry = (
name: string,
): ImportKnowledgeMenuSourceRegistry =>
new FeatureRegistry<
ImportKnowledgeMenuSourceFeatureType,
ImportKnowledgeMenuSourceModule
>({
name,
});

View File

@@ -0,0 +1,44 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozPencilPaper } from '@coze-arch/coze-design/icons';
import { KnowledgeSourceMenuItem } from '@/components/knowledge-source-menu-item';
import {
type ImportKnowledgeMenuSourceModule,
type ImportKnowledgeMenuSourceModuleProps,
} from '../module';
export const TableCustom = (props: ImportKnowledgeMenuSourceModuleProps) => {
const { onClick } = props;
return (
<KnowledgeSourceMenuItem
title={I18n.t('datasets_createFileModel_step1_TabCustomTitle')}
icon={<IconCozPencilPaper className="w-4 h-4" />}
testId={`${KnowledgeE2e.SegmentDetailDropdownItem}.${UnitType.TABLE_CUSTOM}`}
value={UnitType.TABLE_CUSTOM}
onClick={() => onClick(UnitType.TABLE_CUSTOM)}
/>
);
};
export const TableCustomModule: ImportKnowledgeMenuSourceModule = {
Component: TableCustom,
};

View File

@@ -0,0 +1,44 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { KnowledgeSourceMenuItem } from '@/components/knowledge-source-menu-item';
import {
type ImportKnowledgeMenuSourceModule,
type ImportKnowledgeMenuSourceModuleProps,
} from '../module';
export const TableLocal = (props: ImportKnowledgeMenuSourceModuleProps) => {
const { onClick } = props;
return (
<KnowledgeSourceMenuItem
title={I18n.t('datasets_createFileModel_step1_TabLocalTitle')}
icon={<IconCozDocument className="w-4 h-4" />}
testId={`${KnowledgeE2e.SegmentDetailDropdownItem}.${UnitType.TABLE_DOC}`}
value={UnitType.TABLE_DOC}
onClick={() => onClick(UnitType.TABLE_DOC)}
/>
);
};
export const TableLocalModule: ImportKnowledgeMenuSourceModule = {
Component: TableLocal,
};

View File

@@ -0,0 +1,44 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozPencilPaper } from '@coze-arch/coze-design/icons';
import { KnowledgeSourceMenuItem } from '@/components/knowledge-source-menu-item';
import {
type ImportKnowledgeMenuSourceModule,
type ImportKnowledgeMenuSourceModuleProps,
} from '../module';
export const TextCustom = (props: ImportKnowledgeMenuSourceModuleProps) => {
const { onClick } = props;
return (
<KnowledgeSourceMenuItem
title={I18n.t('datasets_createFileModel_step1_CustomTitle')}
icon={<IconCozPencilPaper className="w-4 h-4" />}
testId={`${KnowledgeE2e.SegmentDetailDropdownItem}.${UnitType.TEXT_CUSTOM}`}
value={UnitType.TEXT_CUSTOM}
onClick={() => onClick(UnitType.TEXT_CUSTOM)}
/>
);
};
export const TextCustomModule: ImportKnowledgeMenuSourceModule = {
Component: TextCustom,
};

View File

@@ -0,0 +1,44 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { KnowledgeSourceMenuItem } from '@/components/knowledge-source-menu-item';
import {
type ImportKnowledgeMenuSourceModule,
type ImportKnowledgeMenuSourceModuleProps,
} from '../module';
export const TextLocal = (props: ImportKnowledgeMenuSourceModuleProps) => {
const { onClick } = props;
return (
<KnowledgeSourceMenuItem
title={I18n.t('datasets_createFileModel_step1_LocalTitle')}
icon={<IconCozDocument className="w-4 h-4" />}
testId={`${KnowledgeE2e.SegmentDetailDropdownItem}.${UnitType.TEXT_DOC}`}
value={UnitType.TEXT_DOC}
onClick={() => onClick(UnitType.TEXT_DOC)}
/>
);
};
export const TextLocalModule: ImportKnowledgeMenuSourceModule = {
Component: TextLocal,
};

View File

@@ -0,0 +1,39 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { KnowledgeSourceRadio } from '@/components/knowledge-source-radio';
import { type ImportKnowledgeRadioSourceModule } from '../module';
export const ImageLocal: ImportKnowledgeRadioSourceModule['Component'] = () => (
<KnowledgeSourceRadio
title={I18n.t('knowledge_photo_002')}
description={I18n.t('knowledge_photo_003')}
icon={<IconCozDocument className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalPhotoImgRadio}
key={UnitType.IMAGE_FILE}
value={UnitType.IMAGE_FILE}
/>
);
export const ImageLocalModule: ImportKnowledgeRadioSourceModule = {
Component: ImageLocal,
};

View File

@@ -0,0 +1,27 @@
/*
* 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 { TableCustomModule } from './table-custom';
export { TableLocalModule } from './table-local';
export { TextCustomModule } from './text-custom';
export { TextLocalModule } from './text-local';
export { ImageLocalModule } from './image-local';
export type { ImportKnowledgeRadioSourceModule } from './module';
export {
createImportKnowledgeSourceRadioFeatureRegistry,
type ImportKnowledgeRadioSourceFeatureType,
ImportKnowledgeRadioSourceFeatureRegistry,
} from './registry';

View File

@@ -0,0 +1,25 @@
/*
* 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 { ComponentProps, ReactElement } from 'react';
import type { KnowledgeSourceRadio } from '@/components/knowledge-source-radio';
export interface ImportKnowledgeRadioSourceModule {
Component: () => ReactElement<
ComponentProps<typeof KnowledgeSourceRadio>
> | null;
}

View File

@@ -0,0 +1,47 @@
/*
* 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 { FeatureRegistry } from '@coze-data/feature-register';
import { TextLocalModule } from './text-local';
import { type ImportKnowledgeRadioSourceModule } from './module';
export type ImportKnowledgeRadioSourceFeatureType =
| 'text-local'
| 'text-custom'
| 'table-custom'
| 'table-local'
| 'image-local'
| 'image-custom';
export type ImportKnowledgeRadioSourceFeatureRegistry = FeatureRegistry<
ImportKnowledgeRadioSourceFeatureType,
ImportKnowledgeRadioSourceModule
>;
export const createImportKnowledgeSourceRadioFeatureRegistry = (
name: string,
): ImportKnowledgeRadioSourceFeatureRegistry =>
new FeatureRegistry<
ImportKnowledgeRadioSourceFeatureType,
ImportKnowledgeRadioSourceModule
>({
name,
defaultFeature: {
type: 'text-local',
module: TextLocalModule,
},
});

View File

@@ -0,0 +1,42 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozPencilPaper } from '@coze-arch/coze-design/icons';
import { KnowledgeSourceRadio } from '@/components/knowledge-source-radio';
import { type ImportKnowledgeRadioSourceModule } from '../module';
export const TableCustom: ImportKnowledgeRadioSourceModule['Component'] =
() => (
<KnowledgeSourceRadio
title={I18n.t('datasets_createFileModel_step1_TabCustomTitle')}
description={I18n.t(
'datasets_createFileModel_step1_TabCustomDescription',
)}
icon={<IconCozPencilPaper className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalTableCustomRadio}
key={UnitType.TABLE_CUSTOM}
value={UnitType.TABLE_CUSTOM}
/>
);
export const TableCustomModule: ImportKnowledgeRadioSourceModule = {
Component: TableCustom,
};

View File

@@ -0,0 +1,38 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { KnowledgeSourceRadio } from '@/components/knowledge-source-radio';
import { type ImportKnowledgeRadioSourceModule } from '../module';
export const TableLocal: ImportKnowledgeRadioSourceModule['Component'] = () => (
<KnowledgeSourceRadio
title={I18n.t('datasets_createFileModel_step1_TabLocalTitle')}
description={I18n.t('datasets_createFileModel_step1_TabLocalDescription')}
icon={<IconCozDocument className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalTableLocalRadio}
key={UnitType.TABLE_DOC}
value={UnitType.TABLE_DOC}
/>
);
export const TableLocalModule: ImportKnowledgeRadioSourceModule = {
Component: TableLocal,
};

View File

@@ -0,0 +1,38 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { IconCozPencilPaper } from '@coze-arch/coze-design/icons';
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { type ImportKnowledgeRadioSourceModule } from '../module';
import { KnowledgeSourceRadio } from '@/components/knowledge-source-radio';
export const TextCustom: ImportKnowledgeRadioSourceModule['Component'] = () => (
<KnowledgeSourceRadio
title={I18n.t('datasets_createFileModel_step1_CustomTitle')}
description={I18n.t('datasets_createFileModel_step1_CustomDescription')}
icon={<IconCozPencilPaper className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalTextCustomRadio}
key={UnitType.TEXT_CUSTOM}
value={UnitType.TEXT_CUSTOM}
/>
);
export const TextCustomModule: ImportKnowledgeRadioSourceModule = {
Component: TextCustom,
};

View File

@@ -0,0 +1,38 @@
/*
* 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 { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { IconCozDocument } from '@coze-arch/coze-design/icons';
import { KnowledgeSourceRadio } from '@/components/knowledge-source-radio';
import { type ImportKnowledgeRadioSourceModule } from '../module';
export const TextLocal: ImportKnowledgeRadioSourceModule['Component'] = () => (
<KnowledgeSourceRadio
title={I18n.t('datasets_createFileModel_step1_LocalTitle')}
description={I18n.t('datasets_createFileModel_step1_LocalDescription')}
icon={<IconCozDocument className="w-4 h-4" />}
e2e={KnowledgeE2e.CreateKnowledgeModalTextLocalRadio}
key={UnitType.TEXT_DOC}
value={UnitType.TEXT_DOC}
/>
);
export const TextLocalModule: ImportKnowledgeRadioSourceModule = {
Component: TextLocal,
};

View File

@@ -0,0 +1,57 @@
/*
* 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 { useDataNavigate } from '@coze-data/knowledge-stores';
import { OptType } from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import { UpdateType } from '@coze-arch/bot-api/knowledge';
import { Menu } from '@coze-arch/coze-design';
import {
type TableConfigMenuModule,
type TableConfigMenuModuleProps,
} from '../module';
export const ConfigurationTableStructure = (
props: TableConfigMenuModuleProps,
) => {
const { documentInfo } = props;
const resourceNavigate = useDataNavigate();
if (
documentInfo.update_type !== undefined &&
documentInfo.update_type !== UpdateType.NoUpdate
) {
return null;
}
const handleClick = () => {
resourceNavigate.upload?.({
type: 'table',
opt: OptType.RESEGMENT,
doc_id: documentInfo?.document_id ?? '',
});
};
return (
<Menu.Item onClick={handleClick}>
{I18n.t('knowledge_segment_config_table')}
</Menu.Item>
);
};
export const ConfigurationTableStructureModule: TableConfigMenuModule = {
Component: ConfigurationTableStructure,
};

View File

@@ -0,0 +1,26 @@
/*
* 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 {
createTableConfigMenuRegistry,
type TableConfigMenuRegistry,
type TableConfigMenuFeatureType,
} from './registry';
export {
type TableConfigMenuModule,
type TableConfigMenuModuleProps,
} from './module';
export { ConfigurationTableStructureModule } from './configuration-table-structure';

View File

@@ -0,0 +1,26 @@
/*
* 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 { DocumentInfo } from '@coze-arch/bot-api/knowledge';
export interface TableConfigMenuModuleProps {
documentInfo: DocumentInfo;
reload?: () => void;
onChangeDocList?: (docList: DocumentInfo[]) => void;
}
export interface TableConfigMenuModule {
Component: React.ComponentType<TableConfigMenuModuleProps>;
}

View File

@@ -0,0 +1,37 @@
/*
* 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 { FeatureRegistry } from '@coze-data/feature-register';
import { type TableConfigMenuModule } from './module';
export type TableConfigMenuFeatureType =
| 'configuration-table-structure'
| 'update-frequency'
| 'fetch-slice'
| 'view-source';
export type TableConfigMenuRegistry = FeatureRegistry<
TableConfigMenuFeatureType,
TableConfigMenuModule
>;
export const createTableConfigMenuRegistry = (
name: string,
): TableConfigMenuRegistry =>
new FeatureRegistry<TableConfigMenuFeatureType, TableConfigMenuModule>({
name,
});

View File

@@ -0,0 +1,57 @@
/*
* 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 { useKnowledgeStore } from '@coze-data/knowledge-stores';
import { type DocumentInfo } from '@coze-arch/bot-api/knowledge';
import { useReloadKnowledgeIDE } from '@/hooks/use-case/use-reload-knowledge-ide';
import { type TableConfigMenuRegistry } from '../../knowledge-ide-table-config-menus';
import { KnowledgeConfigMenu as KnowledgeConfigMenuComponent } from '../../../components/knowledge-config-menu';
export interface TableConfigButtonProps {
knowledgeTableConfigMenuContributes?: TableConfigMenuRegistry;
onChangeDocList?: (docList: DocumentInfo[]) => void;
}
export const TableConfigButton = (props: TableConfigButtonProps) => {
const { knowledgeTableConfigMenuContributes, onChangeDocList } = props;
const documentList = useKnowledgeStore(state => state.documentList);
const documentInfo = documentList?.[0];
const canEdit = useKnowledgeStore(state => state.canEdit);
const { reload } = useReloadKnowledgeIDE();
if (!knowledgeTableConfigMenuContributes) {
return null;
}
return (
<KnowledgeConfigMenuComponent>
{canEdit
? knowledgeTableConfigMenuContributes
?.entries()
.map(([key, { Component }]) => (
<Component
key={key}
documentInfo={documentInfo}
onChangeDocList={onChangeDocList}
reload={reload}
/>
))
: null}
</KnowledgeConfigMenuComponent>
);
};

View File

@@ -0,0 +1,46 @@
/*
* 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 { useMemo } from 'react';
import { useKnowledgeStore } from '@coze-data/knowledge-stores';
import { UnitType } from '@coze-data/knowledge-resource-processor-core';
import { FormatType } from '@coze-arch/bot-api/knowledge';
import { getUnitType } from '@/utils';
import { TableLocalTableConfigButton } from './table-local';
import { TableCustomTableConfigButton } from './table-custom';
import { type TableConfigButtonProps } from './base';
export const KnowledgeIDETableConfig = (props: TableConfigButtonProps) => {
const documentInfo = useKnowledgeStore(state => state.documentList?.[0]);
const unitType = useMemo(() => {
if (documentInfo) {
return getUnitType({
format_type: FormatType.Table,
source_type: documentInfo?.source_type,
});
}
return UnitType.TABLE_API;
}, [documentInfo]);
if (unitType === UnitType.TABLE_CUSTOM) {
return <TableCustomTableConfigButton {...props} />;
}
if (unitType === UnitType.TABLE_DOC) {
return <TableLocalTableConfigButton {...props} />;
}
return null;
};

View File

@@ -0,0 +1,24 @@
/*
* 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 { TableConfigButton, type TableConfigButtonProps } from '../base';
import { knowledgeTableConfigMenuContributes } from './knowledge-ide-table-config-menu-contributes';
export const TableCustomTableConfigButton = (props: TableConfigButtonProps) => (
<TableConfigButton
{...props}
knowledgeTableConfigMenuContributes={knowledgeTableConfigMenuContributes}
/>
);

View File

@@ -0,0 +1,35 @@
/*
* 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 {
ConfigurationTableStructureModule,
createTableConfigMenuRegistry,
type TableConfigMenuRegistry,
} from '@/features/knowledge-ide-table-config-menus';
export const knowledgeTableConfigMenuContributes: TableConfigMenuRegistry =
(() => {
const knowledgeTableConfigMenuRegistry = createTableConfigMenuRegistry(
'knowledge-ide-table-custom-config-menu',
);
knowledgeTableConfigMenuRegistry.registerSome([
{
type: 'configuration-table-structure',
module: ConfigurationTableStructureModule,
},
]);
return knowledgeTableConfigMenuRegistry;
})();

View File

@@ -0,0 +1,26 @@
/*
* 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 { TableConfigButton, type TableConfigButtonProps } from '../base';
import { knowledgeTableLocalConfigMenuContributes } from './knowledge-ide-table-config-menu-contributes';
export const TableLocalTableConfigButton = (props: TableConfigButtonProps) => (
<TableConfigButton
{...props}
knowledgeTableConfigMenuContributes={
knowledgeTableLocalConfigMenuContributes
}
/>
);

View File

@@ -0,0 +1,35 @@
/*
* 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 {
ConfigurationTableStructureModule,
createTableConfigMenuRegistry,
type TableConfigMenuRegistry,
} from '@/features/knowledge-ide-table-config-menus';
export const knowledgeTableLocalConfigMenuContributes: TableConfigMenuRegistry =
(() => {
const knowledgeTableConfigMenuRegistry = createTableConfigMenuRegistry(
'knowledge-ide-table-local-config-menu',
);
knowledgeTableConfigMenuRegistry.registerSome([
{
type: 'configuration-table-structure',
module: ConfigurationTableStructureModule,
},
]);
return knowledgeTableConfigMenuRegistry;
})();

View File

@@ -0,0 +1,275 @@
/*
* 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 { useEffect, useState } from 'react';
import { useKnowledgeParams } from '@coze-data/knowledge-stores';
import { I18n } from '@coze-arch/i18n';
import { SceneType, usePageJumpService } from '@coze-arch/bot-hooks';
import {
type Knowledge,
type GetDraftBotInfoAgwData,
type KnowledgeInfo,
ReferenceUpdateType,
BotMode,
} from '@coze-arch/bot-api/playground_api';
import {
type Dataset,
DatasetStatus,
StorageLocation,
} from '@coze-arch/bot-api/knowledge';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { Toast, Button } from '@coze-arch/coze-design';
import { ActionType } from '@/types';
type BotDataset =
| Knowledge
| {
dataset: KnowledgeInfo[];
};
interface UpdateDatasetForBot {
botId: string;
agentId: string;
dataset: BotDataset;
updatedDatasetList?: KnowledgeInfo[];
spaceId: string;
dataSetDetail?: Dataset;
}
export const useFetchBotInfo = (spaceId, botId) => {
const [botInfo, setBotInfo] = useState<GetDraftBotInfoAgwData>();
const fetchBotInfo = async () => {
try {
const { data } = await PlaygroundApi.GetDraftBotInfoAgw({
bot_id: botId,
});
setBotInfo(data ?? {});
} catch (error) {
console.error(error);
}
};
useEffect(() => {
if (spaceId && botId) {
fetchBotInfo();
}
}, [spaceId, botId]);
return botInfo;
};
const updateDatasetForBot = async ({
botId,
agentId,
dataset,
botInfo,
updatedDatasetList,
spaceId,
}: UpdateDatasetForBot & {
botInfo?: GetDraftBotInfoAgwData;
}) => {
if (botInfo?.bot_info.bot_mode === BotMode.SingleMode) {
await PlaygroundApi.UpdateDraftBotInfoAgw({
bot_info: {
bot_id: botId,
knowledge: {
...dataset,
knowledge_info: updatedDatasetList,
},
},
});
} else {
const currentAgent = botInfo?.bot_info?.agents?.find(
agent => agent.agent_id === agentId,
);
if (currentAgent?.agent_id) {
await PlaygroundApi.UpdateAgentV2({
...currentAgent,
current_version:
currentAgent.update_type === ReferenceUpdateType.AutoUpdate
? '0'
: currentAgent.current_version,
id: currentAgent?.agent_id,
space_id: spaceId,
bot_id: botId,
knowledge: {
...dataset,
knowledge_info: updatedDatasetList,
},
});
}
}
};
export const getUpdatedDataset = (
dataset: BotDataset,
actionType: ActionType,
dataSetDetail: Dataset,
): KnowledgeInfo[] => {
// 更新后的bot知识库
let updatedDatasetList: KnowledgeInfo[] = [];
// 原本的bot知识库内容
let originDataset: KnowledgeInfo[] = [];
// 兼容json版本的datasetFG全量后删除
if ('dataset' in dataset) {
originDataset = dataset?.dataset ?? [];
} else {
originDataset = dataset?.knowledge_info ?? [];
}
if (actionType === ActionType.REMOVE) {
updatedDatasetList = originDataset?.filter(
item => item.id !== dataSetDetail.dataset_id,
);
} else {
updatedDatasetList = [
...originDataset,
{ name: dataSetDetail.name, id: dataSetDetail.dataset_id },
];
}
return updatedDatasetList;
};
// 更新bot知识库逻辑
export const handleDatasetUpdate = async ({
botInfo,
botId,
agentId,
dataSetDetail = {},
dataset,
actionType,
spaceId,
updateSuccess,
}: UpdateDatasetForBot & {
botInfo?: GetDraftBotInfoAgwData;
updateSuccess: () => void;
actionType: ActionType;
}) => {
const updatedDatasetList = getUpdatedDataset(
dataset,
actionType,
dataSetDetail,
);
const updateBotParams = {
spaceId,
botId,
agentId,
updatedDatasetList,
dataset,
};
await updateDatasetForBot({ ...updateBotParams, botInfo });
updateSuccess();
};
// 根据不同botInfo信息 获取不同的bot原有的dataset
export const getDatasetInfo = (
botInfo: GetDraftBotInfoAgwData | undefined,
agentId: string,
): BotDataset => {
if (agentId) {
return (
botInfo?.bot_info?.agents?.find(item => item.agent_id === agentId)
?.knowledge ?? {}
);
}
return botInfo?.bot_info?.knowledge ?? {};
};
export const NavBarActionButton = ({
dataSetDetail,
}: {
dataSetDetail: Dataset;
}) => {
const [loading, setLoading] = useState(false);
const { jump } = usePageJumpService();
const params = useKnowledgeParams();
const { spaceID, botID, agentID, actionType } = params;
const botInfo = useFetchBotInfo(spaceID, botID);
const dataset = getDatasetInfo(botInfo, agentID ?? '');
const updateSuccess = () => {
Toast.success(
I18n.t(
actionType === ActionType.REMOVE
? 'bot_edit_dataset_removed_toast'
: 'bot_edit_dataset_added_toast',
{ dataset_name: dataSetDetail.name },
),
);
jump(SceneType.KNOWLEDGE__BACK__BOT, {
spaceID,
botID,
mode:
dataSetDetail.storage_location === StorageLocation.Douyin
? 'douyin'
: 'bot',
});
};
const handleActionClick = async () => {
setLoading(true);
try {
await handleDatasetUpdate({
botInfo,
botId: botID ?? '',
agentId: agentID ?? '',
dataSetDetail,
dataset,
actionType: actionType ?? ActionType.ADD,
spaceId: spaceID ?? '',
updateSuccess,
});
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
return (
<Button
loading={loading}
disabled={dataSetDetail?.status === DatasetStatus.DatasetForbid}
onClick={handleActionClick}
>
{actionType === ActionType.REMOVE &&
dataSetDetail?.storage_location === StorageLocation.Douyin
? I18n.t('dy_avatar_resource_delete')
: null}
{actionType === ActionType.REMOVE &&
dataSetDetail?.storage_location !== StorageLocation.Douyin
? I18n.t('kl2_014')
: null}
{actionType !== ActionType.REMOVE &&
dataSetDetail?.storage_location === StorageLocation.Douyin
? I18n.t('dy_avatar_resource_add')
: null}
{actionType !== ActionType.REMOVE &&
dataSetDetail?.storage_location !== StorageLocation.Douyin
? I18n.t('kl2_013')
: null}
</Button>
);
};

View File

@@ -0,0 +1,43 @@
/*
* 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 { useShallow } from 'zustand/react/shallow';
import { useKnowledgeStore } from '@coze-data/knowledge-stores';
import { ImportKnowledgeSourceButton } from '@/features/import-knowledge-source-button/base';
import { KnowledgeIDENavBar as KnowledgeIDENavBarComponent } from '@/components/knowledge-nav-bar';
import { type KnowledgeIDENavBarProps } from '../module';
export const BaseKnowledgeIDENavBar = (props: KnowledgeIDENavBarProps) => {
const { progressMap, hideBackButton, importKnowledgeSourceButton } = props;
const { setDataSetDetail } = useKnowledgeStore(
useShallow(state => ({
setDataSetDetail: state.setDataSetDetail,
})),
);
return (
<KnowledgeIDENavBarComponent
{...props}
importKnowledgeSourceButton={
importKnowledgeSourceButton ?? <ImportKnowledgeSourceButton />
}
onChangeDataset={setDataSetDetail}
progressMap={progressMap}
hideBackButton={hideBackButton}
/>
);
};

View File

@@ -0,0 +1,143 @@
/*
* 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 { useMemo, useState } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { useKnowledgeParams, useKnowledgeStore } from '@coze-data/knowledge-stores';
import { I18n } from '@coze-arch/i18n';
import { SceneType, usePageJumpService } from '@coze-arch/bot-hooks';
import { StorageLocation } from '@coze-arch/bot-api/knowledge';
import { Modal, Toast } from '@coze-arch/coze-design';
import { ActionType } from '@/types';
import {
useFetchBotInfo,
getDatasetInfo,
handleDatasetUpdate,
} from '@/features/nav-bar-action-button';
export const useBeforeKnowledgeIDEClose = ({
onBack,
}: {
onBack?: () => void;
}) => {
const [loading, setLoading] = useState(false);
const {
spaceID: spaceId,
agentID: agentId,
botID: botId,
actionType,
} = useKnowledgeParams();
const { dataSetDetail } = useKnowledgeStore(
useShallow(state => ({
dataSetDetail: state.dataSetDetail,
})),
);
const { jump } = usePageJumpService();
const botInfo = useFetchBotInfo(spaceId, botId);
const dataset = getDatasetInfo(botInfo, agentId ?? '');
const hasAddDataset = useMemo(() => {
let datasetIds: string[] = [];
if ('dataset' in dataset) {
datasetIds = (dataset?.dataset || []).map(item => item.id ?? '');
}
if ('knowledge_info' in dataset) {
datasetIds = (dataset?.knowledge_info || []).map(item => item.id ?? '');
}
return !datasetIds.includes(dataSetDetail?.dataset_id || '');
}, [dataset, dataSetDetail?.dataset_id]);
const updateSuccessJump = () => {
jump(SceneType.KNOWLEDGE__BACK__BOT, {
spaceID: spaceId,
botID: botId,
mode:
dataSetDetail?.storage_location === StorageLocation.Douyin
? 'douyin'
: 'bot',
});
};
const handleFullModalBack = () => {
if (onBack) {
onBack?.();
} else {
updateSuccessJump();
}
};
const updateSuccess = () => {
Toast.success(
I18n.t(
actionType === ActionType.REMOVE
? 'bot_edit_dataset_removed_toast'
: 'bot_edit_dataset_added_toast',
{ dataset_name: dataSetDetail?.name },
),
);
updateSuccessJump();
};
const handleBotIdeBack = () => {
// Bot IDE检查是否有绑定knowledge如果有绑定知识库正常关闭没有绑定确认提示处理
if (hasAddDataset) {
Modal.confirm({
title: I18n.t('bot_ide_knowledge_confirm_title'),
content:
dataSetDetail?.storage_location === StorageLocation.Douyin
? I18n.t('dy_avatar_resource_add_tip')
: I18n.t('bot_ide_knowledge_confirm_content'),
okText: I18n.t('bot_ide_knowledge_confirm_ok'),
cancelText: I18n.t('bot_ide_knowledge_confirm_cancel'),
confirmLoading: loading,
onOk: async () => {
setLoading(true);
try {
await handleDatasetUpdate({
botInfo,
botId: botId ?? '',
agentId: agentId ?? '',
dataSetDetail,
dataset,
actionType: actionType ?? ActionType.ADD,
spaceId: spaceId ?? '',
updateSuccess,
});
} catch (error) {
console.error(error);
} finally {
setLoading(false);
// 无论成功无论都跳转一次
handleFullModalBack();
}
},
onCancel: () => {
// 取消,正常跳转
handleFullModalBack();
},
});
} else {
// 正常绑定不做弹窗拦截
handleFullModalBack();
}
};
return handleBotIdeBack;
};

View File

@@ -0,0 +1,62 @@
/*
* 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 { useShallow } from 'zustand/react/shallow';
import { useKnowledgeStore } from '@coze-data/knowledge-stores';
import { NavBarActionButton } from '@/features/nav-bar-action-button';
import { BizAgentIdeImportKnowledgeSourceButton } from '@/features/import-knowledge-source-button/biz-agent-ide';
import { KnowledgeModalNavBar as KnowledgeModalNavBarComponent } from '@/components/knowledge-modal-nav-bar';
import { type KnowledgeIDENavBarProps } from '../module';
import { useBeforeKnowledgeIDEClose } from './hooks/use-case/use-before-knowledgeide-close';
export type BizAgentIdeKnowledgeIDENavBarProps = KnowledgeIDENavBarProps;
export const BizAgentIdeKnowledgeIDENavBar = (
props: BizAgentIdeKnowledgeIDENavBarProps,
) => {
const { onBack, importKnowledgeSourceButton } = props;
const { dataSetDetail, documentList } = useKnowledgeStore(
useShallow(state => ({
dataSetDetail: state.dataSetDetail,
documentList: state.documentList,
})),
);
const handleBotIdeBack = useBeforeKnowledgeIDEClose({
onBack,
});
return (
<KnowledgeModalNavBarComponent
title={dataSetDetail?.name as string}
onBack={onBack}
datasetDetail={dataSetDetail}
docInfo={documentList?.[0]}
actionButtons={
<NavBarActionButton
key={dataSetDetail?.dataset_id}
dataSetDetail={dataSetDetail}
/>
}
importKnowledgeSourceButton={
importKnowledgeSourceButton ?? (
<BizAgentIdeImportKnowledgeSourceButton />
)
}
beforeBack={handleBotIdeBack}
/>
);
};

View File

@@ -0,0 +1,24 @@
/*
* 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 KnowledgeIDENavBarProps } from '../module';
import { BaseKnowledgeIDENavBar } from '../base';
export type BizLibraryKnowledgeIDENavBarProps = KnowledgeIDENavBarProps;
export const BizLibraryKnowledgeIDENavBar = (
props: BizLibraryKnowledgeIDENavBarProps,
) => <BaseKnowledgeIDENavBar {...props} />;

View File

@@ -0,0 +1,24 @@
/*
* 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 KnowledgeIDENavBarProps } from '../module';
import { BaseKnowledgeIDENavBar } from '../base';
export type BizProjectKnowledgeIDENavBarProps = KnowledgeIDENavBarProps;
export const BizProjectKnowledgeIDENavBar = (
props: BizProjectKnowledgeIDENavBarProps,
) => <BaseKnowledgeIDENavBar {...props} hideBackButton />;

View File

@@ -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 { useShallow } from 'zustand/react/shallow';
import { useKnowledgeStore } from '@coze-data/knowledge-stores';
import { KnowledgeModalNavBar as KnowledgeModalNavBarComponent } from '@/components/knowledge-modal-nav-bar';
import { type KnowledgeIDENavBarProps } from '../module';
export type BizWorkflowKnowledgeIDENavBarProps = KnowledgeIDENavBarProps;
export const BizWorkflowKnowledgeIDENavBar = (
props: BizWorkflowKnowledgeIDENavBarProps,
) => {
const { onBack, actionButtons } = props;
const { dataSetDetail, documentList } = useKnowledgeStore(
useShallow(state => ({
dataSetDetail: state.dataSetDetail,
documentList: state.documentList,
})),
);
return (
<KnowledgeModalNavBarComponent
title={dataSetDetail?.name as string}
onBack={onBack}
datasetDetail={dataSetDetail}
docInfo={documentList?.[0]}
actionButtons={actionButtons}
/>
);
};

View File

@@ -0,0 +1,27 @@
/*
* 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 ProgressMap } from '@/types';
export interface KnowledgeIDENavBarProps {
progressMap: ProgressMap;
hideBackButton?: boolean;
textConfigButton?: React.ReactNode;
tableConfigButton?: React.ReactNode;
importKnowledgeSourceButton?: React.ReactNode;
actionButtons?: React.ReactNode;
onBack?: () => void;
}

View File

@@ -0,0 +1,164 @@
/*
* 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 { useMemo, useRef, useState } from 'react';
import classnames from 'classnames';
import { useKnowledgeStore } from '@coze-data/knowledge-stores';
import { type TableViewMethods } from '@coze-common/table-view';
import { I18n } from '@coze-arch/i18n';
import { EmptyState, Spin, Layout } from '@coze-arch/coze-design';
import { IconSegmentEmpty } from '@coze-arch/bot-icons';
import { type DocumentInfo } from '@coze-arch/bot-api/knowledge';
import styles from '../styles/index.module.less';
import { useGetSliceListData } from '../hooks/inner/use-get-slice-list-data';
import { TableUIContext } from '../context/table-ui-context';
import { TableDataContext } from '../context/table-data-context';
import { TableActionsContext } from '../context/table-actions-context';
import { TableDataView } from './table-data-view';
const MAX_TOTAL = 1000;
export interface TableKnowledgeWorkspaceProps {
onChangeDocList?: (docList: DocumentInfo[]) => void;
reload?: () => void;
isReloading: boolean;
}
export const TableKnowledgeWorkspace = ({
isReloading,
}: TableKnowledgeWorkspaceProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const contentWrapperRef = useRef<HTMLDivElement>(null);
const tableViewRef = useRef<TableViewMethods>(null);
const canEdit = useKnowledgeStore(state => state.canEdit);
const documentList = useKnowledgeStore(state => state.documentList)?.filter(
doc => doc.document_id,
) as { document_id: string }[];
const [curIndex, setCurIndex] = useState(0);
const [curSliceId, setCurSliceId] = useState('');
const [delSliceIds, setDelSliceIds] = useState<string[]>([]);
const {
sliceListData,
mutateSliceListData,
loadMoreSliceList,
isLoadingSliceList,
isLoadingMoreSliceList,
} = useGetSliceListData();
const slices = sliceListData?.list;
const isShowAddBtn = useMemo(
() =>
Boolean(
canEdit &&
!sliceListData?.hasMore &&
sliceListData?.total &&
sliceListData?.total < MAX_TOTAL,
),
[canEdit, sliceListData],
);
const hasShowEmptyContent = useMemo(() => {
if (!documentList?.length && !isReloading) {
return true;
}
return (
sliceListData?.ready &&
!slices?.length &&
!(isLoadingMoreSliceList || isLoadingSliceList)
);
}, [
sliceListData,
documentList,
isReloading,
isLoadingMoreSliceList,
isLoadingSliceList,
slices,
]);
// 创建 UI Context 值
const uiContextValue = {
tableViewRef,
isLoadingMoreSliceList,
isLoadingSliceList,
isShowAddBtn,
};
// 创建数据 Context 值
const dataContextValue = {
sliceListData: sliceListData || { list: [], total: 0 },
curIndex,
curSliceId,
delSliceIds,
};
// 创建操作 Context 值
const actionsContextValue = {
setCurIndex,
setCurSliceId,
setDelSliceIds,
loadMoreSliceList,
mutateSliceListData,
};
return (
<TableUIContext.Provider value={uiContextValue}>
<TableDataContext.Provider value={dataContextValue}>
<TableActionsContext.Provider value={actionsContextValue}>
<Layout.Content
ref={containerRef}
className={classnames(
styles['slice-list-ui-content'],
'knowledge-ide-base-slice-list-ui-content',
)}
>
<Spin
spinning={isLoadingSliceList}
wrapperClassName={styles.spin}
size="large"
style={{ width: '100%', height: '100%' }}
>
{slices?.length ? (
<div
ref={contentWrapperRef}
className={styles['slice-list-table']}
>
<TableDataView />
</div>
) : null}
{hasShowEmptyContent && !isLoadingSliceList ? (
<div className={styles['empty-content']}>
<EmptyState
size="large"
icon={
<IconSegmentEmpty
style={{ width: 150, height: '100%' }}
/>
}
title={I18n.t('dataset_segment_empty_desc')}
/>
</div>
) : null}
</Spin>
</Layout.Content>
</TableActionsContext.Provider>
</TableDataContext.Provider>
</TableUIContext.Provider>
);
};

View File

@@ -0,0 +1,183 @@
/*
* 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 {
useKnowledgeParamsStore,
useKnowledgeStore,
} from '@coze-data/knowledge-stores';
import { KnowledgeE2e } from '@coze-data/e2e';
import { TableView } from '@coze-common/table-view';
import { I18n } from '@coze-arch/i18n';
import { IconCozPlus } from '@coze-arch/coze-design/icons';
import { Button } from '@coze-arch/coze-design';
import { DocumentStatus } from '@coze-arch/bot-api/knowledge';
import styles from '../styles/index.module.less';
import { getTableRenderColumnsData } from '../service/use-case/get-table-render-columns-data';
import { useTableSliceOperations } from '../hooks/use-case/use-table-slice-operations';
import { useTableSegmentModal } from '../hooks/use-case/use-table-segment-modal';
import { useDeleteSliceModal } from '../hooks/use-case/use-delete-slice-modal';
import { useAddRow } from '../hooks/use-case/use-add-row';
import { useTableHeight } from '../hooks/inner/use-table-height';
import { useScroll } from '../hooks/inner/use-scroll';
import { useTableUI } from '../context/table-ui-context';
import { useTableData } from '../context/table-data-context';
import { useTableActions } from '../context/table-actions-context';
// 表格内容组件
const TableContent = () => {
const knowledgeIDEBiz = useKnowledgeParamsStore(state => state.params.biz);
const documentList = useKnowledgeStore(state => state.documentList);
const curDoc = documentList?.[0];
const { tableViewRef, isLoadingMoreSliceList, isLoadingSliceList } =
useTableUI();
const { sliceListData } = useTableData();
const slices = sliceListData?.list;
const { loadMoreSliceList } = useTableActions();
const canEdit = Boolean(useKnowledgeStore(state => state.canEdit));
// 删除切片弹窗
const { deleteSliceModalNode, openDeleteSliceModal } = useDeleteSliceModal();
// 编辑slice弹窗
const { tableSegmentModalNode, openTableSegmentModal } =
useTableSegmentModal();
// 获取表格操作方法
const { deleteSlice, rowUpdateSliceData, modalEditSlice } =
useTableSliceOperations({
openDeleteSliceModal,
openTableSegmentModal,
});
const { tableH } = useTableHeight();
// 如果没有数据,直接返回空
if (!slices?.length) {
return null;
}
const tableKey = curDoc?.document_id;
const { data: dataSource, columns } = getTableRenderColumnsData({
sliceList: slices,
metaData: curDoc?.table_meta,
onEdit: modalEditSlice,
onDelete: deleteSlice,
onUpdate: rowUpdateSliceData,
canEdit,
tableKey: tableKey || '',
});
return (
<div
className={classnames(
styles['table-view-container-box'],
'table-view-box',
)}
style={{ height: tableH }}
>
<TableView
tableKey={tableKey}
ref={tableViewRef}
className={classnames(
`${styles['unit-table-view']} ${
isLoadingMoreSliceList ? styles['table-view-loading'] : ''
}`,
knowledgeIDEBiz === 'project'
? styles['table-preview-max']
: styles['table-preview-secondary'],
)}
resizable
dataSource={dataSource}
loading={isLoadingSliceList}
columns={columns}
rowSelect={canEdit}
isVirtualized
rowOperation={canEdit}
scrollToBottom={() => {
if (!isLoadingSliceList && !isLoadingMoreSliceList) {
loadMoreSliceList();
}
}}
editProps={{
onDelete: indexs => deleteSlice(indexs as number[]),
onEdit: (record, index) => {
modalEditSlice(record, index as number);
},
}}
/>
{deleteSliceModalNode}
{tableSegmentModalNode}
</div>
);
};
// 添加行按钮组件
const AddRowButton = () => {
const { isShowAddBtn } = useTableUI();
const documentList = useKnowledgeStore(state => state.documentList);
const curDoc = documentList?.[0];
const { increaseTableHeight } = useTableHeight();
const { scrollTableBodyToBottom } = useScroll();
const { handleAddRow } = useAddRow({
increaseTableHeight,
scrollTableBodyToBottom,
});
if (!isShowAddBtn) {
return null;
}
return (
<div className={styles['add-row-btn']}>
<Button
disabled={curDoc?.status === DocumentStatus.Processing}
data-testid={KnowledgeE2e.SegmentDetailContentAddRowBtn}
color="primary"
size="default"
icon={<IconCozPlus />}
onClick={handleAddRow}
>
{I18n.t('knowledge_optimize_0010')}
</Button>
</div>
);
};
// 主组件
export const TableDataView = () => {
const { sliceListData } = useTableData();
const slices = sliceListData?.list;
// 如果没有数据,只显示添加按钮
if (!slices?.length) {
return <AddRowButton />;
}
return (
<>
<TableContent />
<AddRowButton />
</>
);
};

View File

@@ -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 { createContext, useContext } from 'react';
import { type DatasetDataScrollList } from '@/service';
// 表格操作相关的 Context
interface TableActionsContextType {
setCurIndex: (index: number | ((prev: number) => number)) => void;
setCurSliceId: (id: string | ((prev: string) => string)) => void;
setDelSliceIds: (ids: string[] | ((prev: string[]) => string[])) => void;
loadMoreSliceList: () => void;
mutateSliceListData: (data: DatasetDataScrollList) => void;
}
export const TableActionsContext =
createContext<TableActionsContextType | null>(null);
export const useTableActions = () => {
const context = useContext(TableActionsContext);
if (!context) {
throw new Error(
'useTableActions must be used within a TableActionsProvider',
);
}
return context;
};

View File

@@ -0,0 +1,39 @@
/*
* 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 { createContext, useContext } from 'react';
import { type DatasetDataScrollList } from '@/service';
// 表格数据相关的 Context
interface TableDataContextType {
sliceListData: DatasetDataScrollList;
curIndex: number;
curSliceId: string;
delSliceIds: string[];
}
export const TableDataContext = createContext<TableDataContextType | null>(
null,
);
export const useTableData = () => {
const context = useContext(TableDataContext);
if (!context) {
throw new Error('useTableData must be used within a TableDataProvider');
}
return context;
};

View File

@@ -0,0 +1,38 @@
/*
* 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 { createContext, useContext } from 'react';
import { type MutableRefObject } from 'react';
import { type TableViewMethods } from '@coze-common/table-view';
// 表格 UI 相关的 Context
interface TableUIContextType {
tableViewRef: MutableRefObject<TableViewMethods | null>;
isLoadingMoreSliceList: boolean;
isLoadingSliceList: boolean;
isShowAddBtn: boolean;
}
export const TableUIContext = createContext<TableUIContextType | null>(null);
export const useTableUI = () => {
const context = useContext(TableUIContext);
if (!context) {
throw new Error('useTableUI must be used within a TableUIProvider');
}
return context;
};

Some files were not shown because too many files have changed in this diff Show More