chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:24:55 +08:00
commit 7baeb17845
15305 changed files with 2077301 additions and 0 deletions
@@ -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;
@@ -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;
@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});
@@ -0,0 +1,3 @@
# @coze-data/knowledge-ide-base
## 知识库首页ide
### 地址:/space/{space_id}/knowledge/{knowledge_id}
@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["dist"]
}
]
}
@@ -0,0 +1,9 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {
'@typescript-eslint/naming-convention': 'off',
},
});
@@ -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"
}
}
@@ -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;
}
}
}
@@ -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

@@ -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

@@ -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

@@ -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;
}
}
}
@@ -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>
);
};
@@ -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;
}
}
}
@@ -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);
}
};
// If there is no caption, do not confirm
if (!currentValue) {
await generateCaption();
return;
}
UIModal.warning({
// Required parameters to confirm modal style
className: styles['confirm-modal'],
closeIcon: <IconCloseKnowledge />,
// custom parameters
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>
);
};
@@ -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>
)}
{/* Image source */}
{formatType === FormatType.Image && (
<Tag size="mini" color="primary">
{I18n.t('dataset_detail_source_local')}
</Tag>
)}
{/* number of pictures */}
{formatType === FormatType.Image && !!dataSetDetail?.doc_count && (
<Tag size="mini" color="primary">
{I18n.t('knowledge_photo_015', {
num: dataSetDetail?.doc_count || 0,
})}
</Tag>
)}
{/* Do not display when no document is added */}
{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>
);
};
@@ -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';
@@ -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;
}
}
}
@@ -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>
);
};
@@ -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;
}
@@ -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;
}
@@ -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);
}
@@ -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}
{/* import button */}
{canEdit ? importKnowledgeSourceButton : null}
{actionButtons}
</Space>
</div>
</div>
);
};
@@ -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;
}
}
}
@@ -0,0 +1,259 @@
/*
* 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 or action will only be displayed when one exists
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>
);
};
@@ -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>
);
};
@@ -0,0 +1,12 @@
.hidden-tooltip {
display: none;
}
.create-opt-select-down {
:global {
.semi-dropdown-item {
min-width: 126px;
}
}
}
@@ -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>
);
};
@@ -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);
}
@@ -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>
);
};
@@ -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>
);
};
@@ -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;
}
}
}
@@ -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;
// The photo that failed to be processed is not allowed to update the caption.
const disableUpdate =
status === DocumentStatus.Processing ||
status === DocumentStatus.Failed ||
!canEdit;
useEffect(() => {
if (visible) {
// @ts-expect-error -- linter-disable-autofix
setTextAreaValue(photo.caption);
}
}, [
visible,
// The initial state needs to be updated when switching pictures.
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
// Only set the height, and the width will be automatically scaled according to the original scale of the picture.
height={300}
src={photo.url}
/>
{
// Not found, or for the first one, the pre button is not displayed
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);
}}
/>
)
}
{
// Not found, or for the last one, the pre button is not displayed
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);
},
};
};
@@ -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;
}
}
}
@@ -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>
);
};
@@ -0,0 +1,8 @@
.header {
padding-bottom: 32px;
}
.breadcrumb {
display: flex;
align-items: center;
}
@@ -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>
);
@@ -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
}
}
}
@@ -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>
);
};
@@ -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;
}
}
}
@@ -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';
// Get 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} />
);
}
};
@@ -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>
);
};
@@ -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';
@@ -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>
);
@@ -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';
@@ -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
@@ -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);
@@ -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;
}
}
}
@@ -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: () => {
// Reset url parameters
resetUrlQueryParams();
},
onSubmit: () => {
resetUrlQueryParams();
},
});
// Manually control data loading timing
useEffect(() => {
if (dataSetDetail?.dataset_id) {
reloadAsync();
// When reloading, return to the top
ref.current?.scrollTo?.({
top: 0,
behavior: 'smooth',
});
}
}, [searchValue, photoFilterValue, dataSetDetail?.dataset_id]);
// Automatically open the editing pop-up window
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;
// Use progressMap here to keep refreshing until completion
// @ts-expect-error -- linter-disable-autofix
const status = progressMap[document_id]?.status || originStatus;
// Use progressMap to get the latest 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({
// Required parameters to confirm modal style
className: styles['confirm-modal'],
closeIcon: <IconCloseKnowledge />,
// custom parameters
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 = () => {
// Illegal pictures
if (isAudiFailed) {
return (
<span>
{I18n.t('knowledge_content_illegal_error_msg')}
</span>
);
}
// Processing failed
if (status === DocumentStatus.Failed) {
return (
<span className={styles['failed-tag']}>
{I18n.t('dataset_process_fail')}
</span>
);
}
// Processing
if (status === DocumentStatus.Processing) {
return (
<span className={styles['processing-tag']}>
{I18n.t('datasets_segment_tag_processing')}
</span>
);
}
// marked
if (hasCaption) {
return caption;
}
// unmarked
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}
// Only set the width, and the height will be automatically scaled according to the original scale of the picture.
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}
</>
);
};
@@ -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
: // Pass undefined to return all
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,
},
);
};
@@ -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;
}
/** Default jump to 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,
};
@@ -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';
/**
* Disable hints for working with table-type datasets
*/
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 '';
};
/**
* Disable hints for handling default type datasets
*/
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 '';
};
/**
* Create prompt text when button is disabled
* @param dataSetDetail - Dataset Details
* @param documentList - Document List
* @Returns prompt text
*/
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();
};
@@ -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';
@@ -0,0 +1,67 @@
/*
* 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,
}),
);
}}
/>
);
};
@@ -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;
}
@@ -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>
);
};
@@ -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;
})();
@@ -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;
})();
@@ -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;
})();
@@ -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>
);
};
@@ -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,
};
@@ -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';
@@ -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>;
}
@@ -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,
});
@@ -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,
};
@@ -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,
};
@@ -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,
};
@@ -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,
};
@@ -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,
};
@@ -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';
@@ -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;
}
@@ -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,
},
});
@@ -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,
};
@@ -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,
};
@@ -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,
};
@@ -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,
};
@@ -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,
};
@@ -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';
@@ -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>;
}
@@ -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,
});
@@ -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>
);
};
@@ -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;
};
@@ -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}
/>
);
@@ -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;
})();
@@ -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
}
/>
);
@@ -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;
})();
@@ -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[] => {
// Updated bot knowledge base
let updatedDatasetList: KnowledgeInfo[] = [];
// The original bot knowledge base content
let originDataset: KnowledgeInfo[] = [];
// Compatible with the json version of dataset, delete it after FG is full
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;
};
// Update bot knowledge base logic
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();
};
// Get the original dataset of different bots according to different botInfo information
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>
);
};
@@ -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}
/>
);
};
@@ -0,0 +1,146 @@
/*
* 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 checks whether there is binding knowledge. If there is binding knowledge base, it will be closed normally, and there is no binding confirmation prompt.
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);
// Jump once regardless of success
handleFullModalBack();
}
},
onCancel: () => {
// Cancel, jump normally
handleFullModalBack();
},
});
} else {
// Normal binding does not do pop-up interception
handleFullModalBack();
}
};
return handleBotIdeBack;
};
@@ -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}
/>
);
};
@@ -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} />;
@@ -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 />;
@@ -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}
/>
);
};
@@ -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;
}
@@ -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,
]);
// Creating UI Context Values
const uiContextValue = {
tableViewRef,
isLoadingMoreSliceList,
isLoadingSliceList,
isShowAddBtn,
};
// Creating Data Context Values
const dataContextValue = {
sliceListData: sliceListData || { list: [], total: 0 },
curIndex,
curSliceId,
delSliceIds,
};
// Create Action Context Value
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>
);
};
@@ -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';
// Table Content Component
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));
// Delete slice pop-up
const { deleteSliceModalNode, openDeleteSliceModal } = useDeleteSliceModal();
// Edit slice pop-up
const { tableSegmentModalNode, openTableSegmentModal } =
useTableSegmentModal();
// Get table manipulation method
const { deleteSlice, rowUpdateSliceData, modalEditSlice } =
useTableSliceOperations({
openDeleteSliceModal,
openTableSegmentModal,
});
const { tableH } = useTableHeight();
// If there is no data, return to empty directly
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>
);
};
// Add line button component
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>
);
};
// main component
export const TableDataView = () => {
const { sliceListData } = useTableData();
const slices = sliceListData?.list;
// If there is no data, only the add button is displayed.
if (!slices?.length) {
return <AddRowButton />;
}
return (
<>
<TableContent />
<AddRowButton />
</>
);
};
@@ -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 related to table manipulation
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;
};
@@ -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 related to tabular data
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;
};
@@ -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';
// Context related to table UI
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