feat: manually mirror opencoze's code from bytedance

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
# @coze-data/knowledge-resource-processor-base
> Project template for react component with storybook.
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

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

View File

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

View File

@@ -0,0 +1,150 @@
{
"name": "@coze-data/knowledge-resource-processor-base",
"version": "0.0.1",
"description": "知识库资源处理,包含导入,切片流程",
"license": "Apache-2.0",
"author": "haozhenfei@bytedance.com",
"maintainers": [],
"exports": {
".": "./src/index.tsx",
"./types": "./src/types/index.ts",
"./types/*": "./src/types/*/index.ts",
"./constants": "./src/constants/index.ts",
"./constants/*": "./src/constants/*/index.ts",
"./utils": "./src/utils/index.ts",
"./utils/*": "./src/utils/*/index.ts",
"./hooks": "./src/hooks/index.ts",
"./hooks/*": "./src/hooks/*/index.ts",
"./services": "./src/services/index.ts",
"./services/*": "./src/services/*/index.ts",
"./components": "./src/components/index.tsx",
"./components/*": "./src/components/*/index.tsx",
"./features/*": "./src/features/*/index.tsx",
"./layout/*": "./src/layout/*/index.tsx"
},
"main": "src/index.tsx",
"typesVersions": {
"*": {
"types": [
"./src/types/index.ts"
],
"types/*": [
"./src/types/*/index.ts"
],
"constants": [
"./src/constants/index.ts"
],
"constants/*": [
"./src/constants/*/index.ts"
],
"hooks": [
"./src/hooks/index.ts"
],
"hooks/*": [
"./src/hooks/*/index.ts"
],
"services": [
"./src/services/index.ts"
],
"services/*": [
"./src/services/*/index.ts"
],
"utils": [
"./src/utils/index.ts"
],
"utils/*": [
"./src/utils/*/index.ts"
],
"components": [
"./src/components/index.tsx"
],
"components/*": [
"./src/components/*/index.tsx"
],
"features/*": [
"./src/features/*/index.tsx"
],
"layout/*": [
"./src/layout/*/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-http": "workspace:*",
"@coze-arch/bot-icons": "workspace:*",
"@coze-arch/bot-md-box-adapter": "workspace:*",
"@coze-arch/bot-semi": "workspace:*",
"@coze-arch/bot-studio-store": "workspace:*",
"@coze-arch/bot-utils": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/foundation-sdk": "workspace:*",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/idl": "workspace:*",
"@coze-arch/pdfjs-shadow": "workspace:*",
"@coze-arch/report-events": "workspace:*",
"@coze-arch/report-tti": "workspace:*",
"@coze-arch/utils": "workspace:*",
"@coze-common/table-view": "workspace:*",
"@coze-common/virtual-list": "workspace:*",
"@coze-data/e2e": "workspace:*",
"@coze-data/knowledge-common-components": "workspace:*",
"@coze-data/knowledge-common-hooks": "workspace:*",
"@coze-data/knowledge-common-services": "workspace:*",
"@coze-data/knowledge-modal-base": "workspace:*",
"@coze-data/knowledge-resource-processor-core": "workspace:*",
"@coze-data/knowledge-stores": "workspace:*",
"@coze-data/reporter": "workspace:*",
"@coze-data/utils": "workspace:*",
"@coze-studio/components": "workspace:*",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@douyinfe/semi-icons": "^2.36.0",
"@douyinfe/semi-illustrations": "^2.36.0",
"ahooks": "^3.7.8",
"classnames": "^2.3.2",
"cropperjs": "^1.5.12",
"immer": "^10.0.3",
"lodash-es": "^4.17.21",
"nanoid": "^4.0.2",
"react-arborist": "^3.4.0",
"react-pdf": "9.1.1",
"semver": "^7.3.7",
"usehooks-ts": "^3.1.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:*",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

View File

@@ -0,0 +1,10 @@
.common-svg-icon(@size: 14px, @color: #3370ff) {
>svg {
width: @size;
height: @size;
>path {
fill: @color;
}
}
}

View File

@@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M3.40426 10.334L3.75781 10.6875C3.95307 10.8828 4.26966 10.8828 4.46492 10.6875L8.35401 6.79845C8.74453 6.40792 8.74453 5.77476 8.35401 5.38424L4.46492 1.49515C4.26966 1.29988 3.95307 1.29988 3.75781 1.49515L3.40426 1.8487C3.209 2.04396 3.209 2.36054 3.40426 2.55581L6.93979 6.09134L3.40426 9.62688C3.209 9.82214 3.209 10.1387 3.40426 10.334Z"
fill="#888892" />
</svg>

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,181 @@
/*
* 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 { nanoid } from 'nanoid';
import { transSliceContentOutput } from '@coze-data/knowledge-modal-base';
import {
DocumentEditor,
useInitEditor,
EditorToolbar,
type Chunk,
} from '@coze-data/knowledge-common-components/text-knowledge-editor';
import { I18n } from '@coze-arch/i18n';
import { Form, Input, Modal, Toast } from '@coze-arch/coze-design';
import { MemoryApi } from '@coze-arch/bot-api';
import { useGetWebInfo } from './hooks/get-web-info';
import { editorToolbarActionRegistry } from './editor-toolbar-actions-contributes';
import { editorContextActionRegistry } from './editor-context-actions-contributes';
import styles from './index.module.less';
const MAX_DOC_NAME_LEN = 100;
export interface UseBrowseDetailModalReturnValue {
open: () => void;
node: JSX.Element;
}
export interface ViewOnlinePageDetailProps {
id?: string;
url?: string;
content?: string;
title?: string;
}
export interface BrowseUrlModalProps {
name: string;
webID?: string;
onSubmit: (name: string, content?: string) => void;
onCancel?: () => void;
}
export const BrowseUrlModal = ({
name,
webID,
onSubmit,
onCancel,
}: BrowseUrlModalProps) => {
const [docName, setDocName] = useState<string>(name);
const { data: pageList, runAsync, mutate } = useGetWebInfo();
const [initChunk, setInitChunk] = useState<Chunk>({
text_knowledge_editor_chunk_uuid: nanoid(),
content: '',
});
const { editor } = useInitEditor({
chunk: initChunk,
editorProps: {
attributes: {
class: 'h-[360px] overflow-y-auto',
},
},
onChange: v => {
mutate(
([] as ViewOnlinePageDetailProps[]).concat({
...pageList?.[0],
content: v.content ?? '',
}),
);
},
});
useEffect(() => {
setDocName(name);
}, [name]);
useEffect(() => {
if (webID) {
runAsync(webID).then(data => {
setInitChunk({
text_knowledge_editor_chunk_uuid: nanoid(),
content: data[0].content ?? '',
});
});
}
}, [webID, runAsync, editor]);
return (
<Modal
title={I18n.t('knowledge_insert_img_001')}
width={792}
visible
cancelText={I18n.t('Cancel')}
okText={I18n.t('datasets_segment_detailModel_save')}
maskClosable={false}
onOk={async () => {
const pageInfo = pageList?.[0];
const content = transSliceContentOutput(pageInfo.content as string);
await MemoryApi.SubmitWebContentV2({
web_id: pageInfo.id,
content,
});
Toast.success({
content: I18n.t('datasets_url_saveSuccess'),
showClose: false,
});
onSubmit?.(docName, content);
onCancel?.();
}}
onCancel={() => {
onCancel?.();
}}
>
<Form<Record<string, unknown>> layout="vertical" showValidateIcon={false}>
<Form.Slot
label={{ text: I18n.t('knowledge_upload_text_custom_doc_name') }}
>
<Input
className={styles['doc-name-input']}
value={docName}
onChange={(v: string) => setDocName(v)}
maxLength={MAX_DOC_NAME_LEN}
placeholder={I18n.t('knowledge_upload_text_custom_doc_name_tips')}
/>
</Form.Slot>
<Form.Slot
className={styles['form-segment-content']}
label={{ text: I18n.t('knowledge_upload_text_custom_doc_content') }}
>
<DocumentEditor
editor={editor}
placeholder={I18n.t(
'knowledge_upload_text_custom_doc_content_tips',
)}
editorContextMenuItemsRegistry={editorContextActionRegistry}
editorBottomSlot={
<EditorToolbar
editor={editor}
actionRegistry={editorToolbarActionRegistry}
/>
}
/>
{pageList?.[0]?.url ? (
<div className={styles['browse-source-url']}>
{I18n.t('knowledge_insert_img_003', {
url: pageList[0]?.url,
})}
</div>
) : null}
</Form.Slot>
</Form>
</Modal>
);
};
export const useBrowseUrlModal = (props: BrowseUrlModalProps) => {
const [visible, setVisible] = useState(false);
return {
open: () => setVisible(true),
close: () => setVisible(false),
node: visible ? (
<BrowseUrlModal {...props} onCancel={() => setVisible(false)} />
) : null,
};
};

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UploadImageMenu } from '@coze-data/knowledge-common-components/text-knowledge-editor/features/editor-actions/upload-image';
import {
createEditorActionFeatureRegistry,
type EditorActionRegistry,
} from '@coze-data/knowledge-common-components/text-knowledge-editor/features/editor-actions';
export const editorContextActionRegistry: EditorActionRegistry = (() => {
const editorContextActionFeatureRegistry = createEditorActionFeatureRegistry(
'editor-context-actions',
);
editorContextActionFeatureRegistry.registerSome([
{
type: 'upload-image',
module: {
Component: UploadImageMenu,
},
},
]);
return editorContextActionFeatureRegistry;
})();

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UploadImageButton } from '@coze-data/knowledge-common-components/text-knowledge-editor/features/editor-actions/upload-image';
import {
createEditorActionFeatureRegistry,
type EditorActionRegistry,
} from '@coze-data/knowledge-common-components/text-knowledge-editor/features/editor-actions';
export const editorToolbarActionRegistry: EditorActionRegistry = (() => {
const editorToolbarActionFeatureRegistry = createEditorActionFeatureRegistry(
'editor-toolbar-actions',
);
editorToolbarActionFeatureRegistry.registerSome([
{
type: 'upload-image',
module: {
Component: UploadImageButton,
},
},
]);
return editorToolbarActionFeatureRegistry;
})();

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useRequest } from 'ahooks';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { type ViewOnlinePageDetailProps } from '@/types';
/**
* 将API返回的网页信息转换为视图数据
*/
const transformWebInfoToViewData = (webInfo: {
id?: string;
url?: string;
title?: string;
content?: string;
}): ViewOnlinePageDetailProps => ({
id: webInfo?.id,
url: webInfo?.url,
title: webInfo?.title,
content: webInfo?.content,
});
export const useGetWebInfo = (): {
data: ViewOnlinePageDetailProps[];
loading: boolean;
runAsync: (webID: string) => Promise<ViewOnlinePageDetailProps[]>;
mutate: (data: ViewOnlinePageDetailProps[]) => void;
} => {
const { data, mutate, loading, runAsync } = useRequest(
async (webID: string) => {
const { data: responseData } = await KnowledgeApi.GetWebInfo({
web_ids: [webID],
include_content: true,
});
// 如果没有数据,返回空数组
if (!responseData?.[webID]?.web_info) {
return [] as ViewOnlinePageDetailProps[];
}
const webInfo = responseData[webID].web_info;
const mainPageData = transformWebInfoToViewData(webInfo);
const result = [mainPageData];
// 处理子页面数据
if (webInfo?.subpages?.length) {
const subpagesData = webInfo.subpages.map(transformWebInfoToViewData);
result.push(...subpagesData);
}
return result;
},
);
return {
data: data || [],
loading,
runAsync,
mutate,
};
};

View File

@@ -0,0 +1,106 @@
/* stylelint-disable declaration-no-important */
.browse-detail-modal {
padding-bottom: 40px;
&-main {
display: flex;
align-items: center;
margin-bottom: 16px;
font-size: 14px;
font-weight: 600;
line-height: 20px;
color: rgb(28 31 35 / 100%);
&-image {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
margin-right: 7px;
background-color: rgb(152 205 253 / 100%);
border-radius: 2px;
}
&-title {
max-width: 40%;
}
&-url {
max-width: 40%;
margin-left: 7px;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: rgb(28 31 35 / 35%);
}
}
&-collapse {
overflow-y: auto;
max-height: 400px;
background-color: #f5f5f5;
border-radius: 4px;
:global {
.semi-collapse-item {
border-color: #efefef !important;
}
.semi-input-textarea-wrapper {
background-color: rgb(0 0 0 / 0%) !important;
border: none !important;
}
}
}
}
.browse-modal-header {
display: flex;
flex-wrap: nowrap;
align-items: center;
padding: 24px 0;
&-left {
display: flex;
align-items: center;
&-icon {
margin-right: 8px;
}
}
}
.modal.upgrade-level {
:global {
.semi-modal-body {
height: 0;
padding-bottom: 0;
}
.semi-modal-content {
background-color: var(--semi-color-tertiary-light-default);
}
}
}
.browse-detail-tooltip {
width: 200px;
word-wrap: break-word;
}
.browse-source-url {
margin-top: 12px;
font-size: 12px;
font-weight: 400;
font-style: normal;
line-height: 16px;
color: rgb(29 28 36 / 35%)
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { useBrowseUrlModal } from './browser-url-modal';

View File

@@ -0,0 +1,37 @@
.card-radio-group {
gap: 4px;
:global {
.semi-radio {
padding: 12px;
border-radius: 8px;
&:hover {
background-color: var(--coz-mg-secondary-hovered);
}
&:active {
background-color: var(--coz-mg-secondary-pressed);
}
&.semi-radio-checked {
padding: 11.5px;
background-color: var(--coz-mg-card);
border-width: 1.5px;
}
}
.semi-radio-content {
flex-grow: 1;
.semi-radio-addon {
font-weight: 500;
}
.semi-radio-extra {
font-size: 12px;
line-height: 16px;
}
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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 PropsWithChildren } from 'react';
import classNames from 'classnames';
import { RadioGroup, type RadioGroupProps } from '@coze-arch/coze-design';
import styles from './index.module.less';
export type CardRadioGroupProps<T = unknown> = PropsWithChildren<
Pick<RadioGroupProps, 'value' | 'className'>
> & {
onChange?: (value: T) => void;
};
/**
* 始终使用卡片风格,并符合 UI 设计样式的 {@link RadioGroup}
*/
export function CardRadioGroup<T = unknown>({
value,
onChange,
className,
children,
}: CardRadioGroupProps<T>) {
return (
<RadioGroup
type="pureCard"
direction="vertical"
value={value}
onChange={e => {
onChange?.(e.target.value as T);
}}
className={classNames(styles['card-radio-group'], className)}
>
{children}
</RadioGroup>
);
}

View File

@@ -0,0 +1,65 @@
/*
* 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 PropsWithChildren } from 'react';
import classNames from 'classnames';
import { IconCozArrowRight } from '@coze-arch/coze-design/icons';
import { Collapsible, Typography } from '@coze-arch/coze-design';
export interface CollapsePanelProps extends PropsWithChildren {
header: React.ReactNode;
keepDOM?: boolean;
}
/**
* 用 Collapsible 封装的更符合 UI 设计的折叠面板
*/
export function CollapsePanel({
header,
keepDOM,
children,
}: CollapsePanelProps) {
const [open, setOpen] = useState(true);
return (
<div className="mb-[4px]">
<div
className={classNames(
'h-[40px] flex items-center gap-[4px] shrink-0 rounded',
'cursor-pointer hover:coz-mg-secondary-hovered active:coz-mg-secondary-pressed',
)}
onClick={() => setOpen(!open)}
>
<IconCozArrowRight
className={classNames('coz-fg-secondary text-[14px] m-[4px]', {
'rotate-90': open,
})}
/>
<Typography.Text fontSize="14px" weight={400}>
{header}
</Typography.Text>
</div>
<Collapsible
className="ml-[26px] [&>div]:pt-[4px]"
isOpen={open}
keepDOM={keepDOM}
>
{children}
</Collapsible>
</div>
);
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { I18n } from '@coze-arch/i18n';
import { Banner } from '@coze-arch/coze-design';
export const ConfigurationBanner = () => (
<Banner
style={{ marginTop: '10px' }}
type="warning"
description={I18n.t('knowledge_limit_20')}
/>
);

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { IllustrationNoResult } from '@douyinfe/semi-illustrations';
import { UIEmpty } from '@coze-arch/bot-semi';
import styles from './index.module.less';
interface ConfigurationErrorProps {
fetchTableInfo: () => void;
}
export const ConfigurationError = (props: ConfigurationErrorProps) => {
const { fetchTableInfo } = props;
return (
<UIEmpty
className={styles['load-failure']}
empty={{
title: 'Read failure',
description: '',
icon: <IllustrationNoResult></IllustrationNoResult>,
btnText: 'Retry',
btnOnClick: fetchTableInfo,
}}
/>
);
};

View File

@@ -0,0 +1,24 @@
/* stylelint-disable font-family-no-missing-generic-family-keyword */
.load-failure {
min-height: 400px;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 400px;
&-content {
margin-left: 8px;
font-family: "SF Pro Display";
font-size: 14px;
font-weight: 400;
font-style: normal;
line-height: 20px;
color: rgb(29 28 35 / 35%);
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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 { Spin } from '@coze-arch/coze-design';
import styles from './index.module.less';
export const ConfigurationLoading = () => (
<div className={styles.loading}>
<Spin spinning></Spin>
<div className={styles['loading-content']}>
{I18n.t('knowledge_1221_03')}
</div>
</div>
);

View File

@@ -0,0 +1,53 @@
/*
* 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 cls from 'classnames';
import { Typography } from '@coze-arch/coze-design';
interface IDocumentItemProps {
id: string;
onClick: (id: string) => void;
selected: boolean;
title: string;
status?: 'pending' | 'finished' | 'failed';
addonAfter?: ReactNode;
}
export const DocumentItem: React.FC<IDocumentItemProps> = props => {
const { id, onClick, title, selected, addonAfter } = props;
return (
<div
className={cls(
'w-full h-8 px-2 py-[6px] rounded-[8px] hover:coz-mg-primary cursor-pointer flex flex-nowrap',
selected && 'coz-mg-primary',
)}
onClick={() => onClick(id)}
>
<Typography.Text
className="w-full coz-fg-primary text-[14px] leading-[20px] grow truncate"
ellipsis
>
{title}
</Typography.Text>
{addonAfter}
</div>
);
};
export default DocumentItem;

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import classNames from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { Tag, Tooltip } from '@coze-arch/coze-design';
import {
getFilterPagesString,
getSortedFilterPages,
} from '../../utils/render-document-filter-value';
import { DocumentItem } from './document-item';
interface FilterPageConfig {
pageIndex: number;
isFilter: boolean;
}
interface Document {
// TODO: 扩充
id: string;
title: string;
/** 是否存在过滤内容 */
filterPageConfigList: FilterPageConfig[];
}
interface IDocumentListProps {
documents: Document[];
value: string;
onChange: (value: string) => void;
className?: string;
}
export const DocumentList = (props: IDocumentListProps) => (
<div className={classNames('flex flex-col gap-1 h-full', props.className)}>
<div className="w-full pl-2 h-6 items-center flex">
<div className="coz-fg-secondary text-[12px] font-[400] leading-4 shrink-0">
{I18n.t('kl_write_105')}
</div>
</div>
<div className="flex flex-col gap-1 h-[150px] !overflow-scroll shrink-0">
{props.documents.map(document => {
if (!document.id) {
return null;
}
const filterPages = getSortedFilterPages(document.filterPageConfigList);
const isFiltered = Boolean(filterPages.length);
const filterPagesString = getFilterPagesString(filterPages);
return (
<DocumentItem
id={document.id}
selected={document.id === props.value}
onClick={id => {
props.onChange(id);
}}
title={document.title}
addonAfter={
isFiltered ? (
<Tooltip
content={I18n.t('data_filter_values', {
filterPages: filterPagesString,
})}
>
<Tag color="primary" className="flex-shrink-0">
{I18n.t('knowledge_new_002')}
</Tag>
</Tooltip>
) : null
}
/>
);
})}
</div>
</div>
);

View File

@@ -0,0 +1,41 @@
.auth-empty-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: calc(100% - 112px);
.auth-empty {
display: flex;
flex-direction: column;
align-items: center;
}
.auth-empty-image {
display: flex;
align-items: center;
justify-content: center;
width: 140px;
height: 140px;
}
.auth-empty-description {
margin-top: 16px;
font-size: 16px;
font-weight: 600;
line-height: 22px;
color: var(--coz-fg-primary);
}
.auth-empty-second-desc {
margin-top: 4px;
font-size: 14px;
line-height: 20px;
color: var(--coz-fg-secondary);
}
.auth-empty-button {
margin-top: 21px;
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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 { Button } from '@coze-arch/coze-design';
import style from './index.module.less';
export interface CommonEmptyAuthProps {
illustrationIcon: JSX.Element;
description: string;
authUrl?: string;
btnText?: string;
className?: string;
onClick?: () => void;
secondDesc?: string;
}
export const AuthEmpty = (props: CommonEmptyAuthProps) => {
const {
description,
authUrl,
onClick,
btnText,
illustrationIcon,
className,
secondDesc,
} = props;
const handleClick = () => {
if (authUrl) {
window.open(authUrl, '_self');
} else {
onClick?.();
}
};
return (
<div className={classNames(style['auth-empty-wrapper'], className)}>
<div className={style['auth-empty']}>
<div className={style['auth-empty-image']}>{illustrationIcon}</div>
<div className={style['auth-empty-description']}>{description}</div>
{secondDesc ? (
<div className={style['auth-empty-second-desc']}>{secondDesc}</div>
) : null}
{btnText ? (
<Button
color="highlight"
className={style['auth-empty-button']}
onClick={handleClick}
>
{btnText}
</Button>
) : null}
</div>
</div>
);
};

View File

@@ -0,0 +1,18 @@
/*
* 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 { AuthEmpty } from './common-empty-auth';
export type { CommonEmptyAuthProps } from './common-empty-auth';

View File

@@ -0,0 +1,41 @@
.auth-empty-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: calc(100% - 112px);
.auth-empty {
display: flex;
flex-direction: column;
align-items: center;
}
.auth-empty-image {
display: flex;
align-items: center;
justify-content: center;
width: 140px;
height: 140px;
}
.auth-empty-description {
margin-top: 16px;
font-size: 16px;
font-weight: 600;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1D1C23);
}
.auth-empty-second-desc {
margin-top: 4px;
font-size: 14px;
line-height: 20px;
color: var(--Light-usage-text---color-text-2, rgba(29, 28, 35, 60%));
}
.auth-empty-button {
margin-top: 21px;
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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 { UIButton } from '@coze-arch/bot-semi';
import style from './index.module.less';
interface Props {
illustrationIcon: JSX.Element;
description: string;
btnText?: string;
className?: string;
onClick?: () => void;
secondDesc?: string;
}
export const Empty = (props: Props) => {
const {
description,
onClick,
btnText,
illustrationIcon,
className,
secondDesc,
} = props;
return (
<div className={classNames(style['auth-empty-wrapper'], className)}>
<div className={style['auth-empty']}>
<div className={style['auth-empty-image']}>{illustrationIcon}</div>
<div className={style['auth-empty-description']}>{description}</div>
{secondDesc ? (
<div className={style['auth-empty-second-desc']}>{secondDesc}</div>
) : null}
{btnText ? (
<UIButton
type="tertiary"
className={style['auth-empty-button']}
onClick={onClick}
>
{btnText}
</UIButton>
) : null}
</div>
</div>
);
};

View File

@@ -0,0 +1,17 @@
.frequency-form-item {
.title {
display: block;
flex-shrink: 0;
box-sizing: border-box;
margin-bottom: 8px;
font-size: 12px;
line-height: 16px;
color: var(--coz-fg-secondary);
}
.content {
flex: 1;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC } from 'react';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Select, Typography } from '@coze-arch/coze-design';
import { getFrequencyMap } from '../../utils';
import { FrequencyDay } from '../../constants';
import styles from './index.module.less';
interface Props {
value: number;
onChange: (v: number) => void;
}
export const FrequencyFormItem: FC<Props> = ({ value, onChange }) => (
<div
className={styles['frequency-form-item']}
data-testid={KnowledgeE2e.OnlineUploadModalFrequencySelect}
>
<Typography.Text className={styles.title}>
{I18n.t('datasets_frequencyModal_frequency')}
</Typography.Text>
<div className={styles.content}>
<Select
placeholder={I18n.t('datasets_frequencyModal_frequency')}
style={{ width: '100%' }}
value={value}
onChange={v => onChange(v as number)}
>
{[
FrequencyDay.ZERO,
FrequencyDay.ONE,
FrequencyDay.THREE,
FrequencyDay.SEVEN,
FrequencyDay.THIRTY,
].map(frequency => (
<Select.Option key={frequency} value={frequency}>
{getFrequencyMap(frequency)}
</Select.Option>
))}
</Select>
</div>
</div>
);

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { UploadFooter } from './upload-footer';
export { useBrowseUrlModal } from './browser-url-modal';
export {
TableSettingBar,
type TableSettingBarProps,
type TablePreviewProps,
TablePreview,
type TableStructureProps,
TableStructure,
TableStructureTitle,
} from './table-format';
export { UnitProgress } from './unit-progress';
export { UploadUnitFile } from './upload-unit-file';
export { UploadUnitTable } from './upload-unit-table';
export { ConfigurationError } from './configuration/error';
export { ConfigurationLoading } from './configuration/loading';
export { ConfigurationBanner } from './configuration/banner';
export { AuthEmpty } from './empty-auth';
export { Empty } from './empty';
export { FrequencyFormItem } from './frequency-form-item';
export { CollapsePanel } from './collapse-panel';
export { CardRadioGroup } from './card-radio-group';
export { getProcessStatus } from './upload-unit-table/utils';
export {
type ColumnInfo,
type UploadUnitTableProps,
type RenderColumnsProps,
} from './upload-unit-table/types';
export {
ActionRenderByDelete,
ActionRenderByEditName,
ActionRenderByRetry,
} from './upload-unit-table';

View File

@@ -0,0 +1,117 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable no-duplicate-selectors */
.progress-wrap {
overflow: hidden;
display: flex;
gap: 10px;
align-items: center;
align-self: stretch;
padding: 8px 10px;
background: var(--coz-mg-card);
border:1px solid var(--coz-stroke-primary);
border-radius: var(--default, 8px);
.content {
z-index: 1;
}
.progress {
position: absolute;
top: 0;
bottom: 0;
left: 0;
background: linear-gradient(180deg, rgba(148, 152, 247, 44%), rgba(255, 255, 255, 0%));
}
.info {
.main-text {
font-size: 14px;
line-height: 20px;
color: var(--coz-fg-primary);
}
.sub-text {
font-size: 12px;
line-height: 16px;
color: var(--coz-fg-secondary);
}
.desc {
display: block;
}
.tip-desc {
display: none;
}
:global {
.semi-image {
margin-right: 0!important;
}
}
}
&:hover {
.info {
.desc {
display: none;
}
.tip-desc {
display: block;
}
}
}
&.processing-failed {
border: 1px solid var(--coz-stroke-hglt-red);
.sub-text {
color: var(--coz-fg-hglt-red)!important;
}
}
&.processing {
&:hover {
.info {
.desc {
display: block;
}
.tip-desc {
display: none;
}
}
}
}
.right {
z-index: 2;
}
.percent {
font-size: 14px;
font-weight: 400;
font-style: normal;
line-height: 20px;
color: var(--coz-fg-primary);
text-align: right;
text-overflow: ellipsis;
}
.actions {
display: none;
}
&:hover {
.actions {
display: block;
}
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ProcessProgressItem } from './process-progress-item';

View File

@@ -0,0 +1,120 @@
/*
* 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 { Fragment } from 'react';
import cls from 'classnames';
import { KnowledgeE2e } from '@coze-data/e2e';
import { Typography, Space } from '@coze-arch/coze-design';
import { ProcessStatus, type ProcessProgressItemProps } from '../../types';
import styles from './index.module.less';
export const ProcessProgressItem: React.FC<ProcessProgressItemProps> = ({
className,
style,
mainText,
subText,
percent = 10,
percentFormat,
avatar,
status,
actions,
tipText = '',
}) => {
const renderProgress = () => {
if (status === ProcessStatus.Processing) {
return (
<div className={styles.progress} style={{ width: `${percent}%` }}></div>
);
}
return null;
};
const renderActions = () => {
if (status === ProcessStatus.Processing) {
return (
<span className={styles.percent}>
{percentFormat ? percentFormat : `${percent}%`}
</span>
);
}
return (
<div className={cls(styles.actions, 'process-progress-item-actions')}>
{Array.isArray(actions) ? (
<Space spacing="tight">
{actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment>
))}
</Space>
) : null}
</div>
);
};
return (
<div
key={mainText}
className={cls(
styles['progress-wrap'],
'flex justify-between relative mb-[8px]',
status === ProcessStatus.Failed ? styles['processing-failed'] : '',
status === ProcessStatus.Processing ? styles.processing : '',
className,
)}
style={style}
>
<div
className={cls(
styles.content,
'process-progress-item-content',
'max-w-[calc(100%-100px)]',
)}
>
<div className={cls('flex items-center', styles.info)}>
{avatar}
<div className={cls('pl-[10px] max-w-full')}>
<div className={styles['main-text']}>
<Typography.Text
data-dtestid={`${KnowledgeE2e.CreateUnitListProgressName}.${mainText}`}
className={'coz-fg-primary text-14px'}
ellipsis={{
showTooltip: {
opts: { content: mainText },
},
}}
>
{mainText}
</Typography.Text>
</div>
<div className={styles['sub-text']}>
<div className={styles.desc}>{subText}</div>
{tipText ? (
<div className={styles['tip-desc']}>{tipText}</div>
) : null}
</div>
</div>
</div>
</div>
<div className={cls(styles.right, 'process-progress-item-right')}>
{renderActions()}
</div>
{renderProgress()}
</div>
);
};

View File

@@ -0,0 +1,164 @@
/* stylelint-disable no-descending-specificity */
/* stylelint-disable max-nesting-depth */
/* stylelint-disable font-family-no-missing-generic-family-keyword */
/* stylelint-disable declaration-no-important */
.table-preview {
overflow: hidden;
display: flex;
flex: 1;
flex-direction: column;
&-title {
display: flex;
align-items: center;
height: 32px;
margin-bottom: 5px;
font-size: 14px;
font-weight: 600;
font-style: normal;
line-height: 20px;
color: var(--coz-fg-plus);
}
// .table-view-wrapper {
// border: 1px solid #1D1C231F;
// border-radius: 8px;
// :global {
// .semi-table-wrapper {
// margin-top: 0;
// }
// }
// }
.semantic-tag,
.column-type {
margin-left: 6px;
}
.preview-tips {
margin-top: 8px;
font-size: 12px;
font-weight: 400;
line-height: 20px;
color: var(--coz-fg-dim);
text-align: left;
letter-spacing: 0;
}
.no-result {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 448px;
&-tips {
margin-top: 16px;
font-size: 16px;
font-weight: 600;
font-style: normal;
line-height: 22px;
color: var(--coz-fg-plus);
}
}
&-content {
// flex: 1;
overflow: auto;
:global {
th,tr {
background-color: transparent !important;
}
.semi-table-wrapper {
height: 100%;
margin-top: 0;
.coz-tag {
font-weight: 400;
}
}
.coz-table-wrapper {
.coz-table-list-hover .semi-table-row:hover>.semi-table-row-cell::before {
background-color: transparent
}
.coz-table-list-hover .semi-table-row:hover>.semi-table-row-cell:first-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.coz-table-list {
.semi-table-thead>.semi-table-row>.semi-table-row-head {
font-size: 12px;
font-weight: 500;
color: var(--coz-fg-secondary);
}
.semi-table-tbody>.semi-table-row>.semi-table-row-cell {
height: 56px;
text-align: unset;
.semi-typography {
color: var(--coz-fg-secondary);
}
}
.semi-table-container>.semi-table-body {
padding-top: 0;
}
.semi-table-tbody>.semi-table-row:last-child>.semi-table-row-cell {
border-bottom: 1px solid var(--coz-stroke-primary);
}
}
}
.semi-table-header {
position: sticky;
z-index: 99;
overflow-y: hidden !important;
// border-top-left-radius: 8px;
// border-top-right-radius: 8px;
}
.semi-table-body {
// max-height: calc(100% - 40px) !important;
// max-height: 460px !important;
// border-bottom-right-radius: 8px;
// border-bottom-left-radius: 8px;
}
.semi-table-colgroup .semi-table-col {
min-width: 200px;
}
.semi-table-tbody>.semi-table-row>.semi-table-row-cell {
min-height: 40px;
padding: 9px 16px !important;
}
/** 去掉hover行样式 */
.semi-table-tbody .semi-table-row:hover>.semi-table-row-cell {
background-color: transparent !important;
background-image: none !important;
border-bottom: 1px solid var(--coz-stroke-primary);
}
}
}
}
.td-title {
display: flex;
align-items: center;
}

View File

@@ -0,0 +1,180 @@
/*
* 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, { useMemo } from 'react';
import { IllustrationNoResult } from '@douyinfe/semi-illustrations';
import { getDataTypeText } from '@coze-data/utils';
import { KnowledgeE2e } from '@coze-data/e2e';
import { ImageRender, type TableViewColumns } from '@coze-common/table-view';
import { ColumnType } from '@coze-arch/idl/knowledge';
import { I18n } from '@coze-arch/i18n';
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
import { Table, Tag, Typography } from '@coze-arch/coze-design';
import { getSrcFromImg } from '@/utils/table';
import { type TableInfo, type TableSettings } from '@/types';
import { TableSettingFormFields } from '@/constants';
import styles from './index.module.less';
export interface TablePreviewProps {
data: TableInfo;
settings: TableSettings;
}
interface PreviewColumn {
[key: string]: string;
}
const ColumnTypeComp = (props: { columnType: ColumnType }) => (
<Tag color="primary" className={styles['column-type']} size="mini">
{getDataTypeText(props.columnType)}
</Tag>
);
const baseClassName = 'table-preview';
const maxDataLen = 10;
export const TablePreview: React.FC<TablePreviewProps> = ({
data,
settings,
}) => {
const { sheet_list = [], table_meta = {}, preview_data = {} } = data;
const startRow = Number(settings[TableSettingFormFields.DATA_START_ROW]) || 0;
// 选中的表id
const sheetId = useMemo(
() => settings[TableSettingFormFields.SHEET] || 0,
[settings],
);
// 选中的表名
const sheetName = useMemo(
() => (sheet_list || []).find(sheet => sheet?.id === sheetId)?.sheet_name,
[sheet_list, sheetId],
);
// 选中的表的数据量
const total = useMemo(
() =>
Number(
(sheet_list || []).find(sheet => sheet?.id === sheetId)?.total_row || 0,
) - startRow || 0,
[sheet_list, sheetId],
);
const newColumns: ColumnProps<PreviewColumn | TableViewColumns>[] = (
table_meta[sheetId] || []
).map(meta => {
const {
sequence,
column_name,
is_semantic,
column_type = ColumnType.Unknown,
} = meta;
if (column_type === ColumnType.Image) {
return {
title: (
<div className={styles['td-title']}>
<div>{column_name}</div>
<ColumnTypeComp columnType={column_type}></ColumnTypeComp>
</div>
),
dataIndex: sequence,
render: (text, _record) => {
const srcList = getSrcFromImg(text);
return <ImageRender srcList={srcList} editable={false} />;
},
};
}
return {
title: is_semantic ? (
<div className={styles['td-title']}>
{column_name}
{is_semantic ? (
<Tag
size="mini"
color="green"
className={styles['semantic-tag']}
data-testid={KnowledgeE2e.TableLocalPreviewSemantic}
>
{I18n.t('knowledge_1226_001')}
</Tag>
) : null}
<ColumnTypeComp columnType={column_type}></ColumnTypeComp>
</div>
) : (
<div className={styles['td-title']}>
{column_name}
<ColumnTypeComp columnType={column_type}></ColumnTypeComp>
</div>
),
width: 180,
dataIndex: sequence,
ellipsis: { showTitle: false },
render: text => (
<Typography.Text ellipsis={{ showTooltip: true }}>
{text}
</Typography.Text>
),
};
});
const dataList = useMemo(() => {
const previewData = preview_data[sheetId] || [];
return previewData
.slice(0, maxDataLen)
.sort((a, b) =>
(JSON.stringify(a) as unknown as number) >
(JSON.stringify(b) as unknown as number)
? 1
: -1,
);
}, [preview_data, sheetId]);
return (
<div className={styles[baseClassName]}>
{dataList.length ? (
<>
<div
className={styles[`${baseClassName}-title`]}
data-testid={KnowledgeE2e.TableLocalPreviewTitle}
>
{sheetName}
</div>
<div className={styles[`${baseClassName}-content`]}>
<Table
tableProps={{
dataSource: dataList,
columns: newColumns,
}}
/>
</div>
<div
className={styles['preview-tips']}
data-testid={KnowledgeE2e.TableLocalPreviewFooterTotal}
>
{I18n.t('datasets_unit_tableformat_tips1', {
TotalRows: total,
ShowRows: Number(total) > maxDataLen ? maxDataLen : total,
})}
</div>
</>
) : (
<div className={styles['no-result']}>
<IllustrationNoResult></IllustrationNoResult>
<div className={styles['no-result-tips']}>
{I18n.t('knowledge_1221_02')}
</div>
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,37 @@
/* stylelint-disable font-family-no-missing-generic-family-keyword */
.table-setting-bar {
width: 100%;
margin-bottom: 30px;
:global {
.semi-form-horizontal .semi-form-field {
flex: 1;
.semi-select {
width: 100%;
border-radius: 8px;
}
}
.semi-form-field-label-text {
font-size: 14px;
font-weight: 500;
font-style: normal;
line-height: 24px;
color: var(--coz-fg-plus);
}
.semi-form-field-label {
display: flex;
align-items: center;
height: 24px;
margin-bottom: 4px;
}
.semi-form-horizontal .semi-form-field:last-child {
margin-right: 0;
padding-right: 0;
}
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useMemo, useRef } from 'react';
import { get, isNumber } from 'lodash-es';
import classNames from 'classnames';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { type FormApi } from '@coze-arch/bot-semi/Form';
import { Form } from '@coze-arch/bot-semi';
import { type GetDocumentTableInfoResponse } from '@coze-arch/bot-api/memory';
import { FormSelect } from '@coze-arch/coze-design';
import { type TableSettings } from '@/types';
import { TableSettingFormFields } from '@/constants';
import { initIndexOptions } from './utils';
import styles from './index.module.less';
export interface TableSettingBarProps {
className?: string;
data: GetDocumentTableInfoResponse;
tableSettings: TableSettings;
setTableSettings: (values: TableSettings) => void;
}
const minOptLen = 2;
export const TableSettingBar: React.FC<TableSettingBarProps> = ({
className = '',
data = {},
tableSettings,
setTableSettings,
}) => {
const { preview_data, sheet_list } = data;
if (!preview_data || !sheet_list || !sheet_list.length) {
return <></>;
}
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
const formApi = useRef<FormApi<TableSettings>>();
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
const sheet = useMemo(
() => get(sheet_list, tableSettings[TableSettingFormFields.SHEET]),
[sheet_list, tableSettings],
);
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
const initValues = useMemo(() => tableSettings, [sheet_list]);
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
const settings = useMemo(() => {
const options =
!sheet.id && sheet.id !== 0
? []
: initIndexOptions(
Number(sheet?.total_row) > 1 ? Number(sheet.total_row) : minOptLen,
0,
);
return [
{
e2e: KnowledgeE2e.TableLocalTableConfigurationDataSheet,
field: TableSettingFormFields.SHEET,
label: I18n.t('datasets_createFileModel_tab_DataSheet'),
options: sheet_list.map(s => ({
value: s.id,
label: s.sheet_name,
})),
},
{
e2e: KnowledgeE2e.TableLocalTableConfigurationSheetHeader,
field: TableSettingFormFields.KEY_START_ROW,
label: I18n.t('datasets_createFileModel_tab_header'),
options: options.slice(0, options.length - 1),
},
{
e2e: KnowledgeE2e.TableLocalTableConfigurationStarRow,
field: TableSettingFormFields.DATA_START_ROW,
label: I18n.t('datasets_createFileModel_tab_dataStarRow'),
// 数据起始行需大于表头行
options: options.slice(
Number(tableSettings[TableSettingFormFields.KEY_START_ROW]) + 1,
),
},
];
}, [data, sheet]);
const handleFormChange = (
values: TableSettings,
changedValue: Partial<TableSettings>,
) => {
if (setTableSettings) {
setTableSettings({ ...values });
}
const curSheet = get(changedValue, TableSettingFormFields.SHEET);
const curKeyStartRow = get(
changedValue,
TableSettingFormFields.KEY_START_ROW,
);
if (isNumber(curSheet) && formApi.current) {
// 修改sheet,初始化表头行和数据行
formApi.current.setValue(TableSettingFormFields.KEY_START_ROW, 0);
formApi.current.setValue(TableSettingFormFields.DATA_START_ROW, 1);
}
if (curKeyStartRow && formApi.current) {
const dataStartRow = get(values, TableSettingFormFields.DATA_START_ROW);
if (!(curKeyStartRow < dataStartRow)) {
formApi.current.setValue(
TableSettingFormFields.DATA_START_ROW,
curKeyStartRow + 1,
);
}
}
};
return (
<div className={classNames(styles['table-setting-bar'], className)}>
<Form<typeof initValues>
layout="horizontal"
initValues={initValues}
getFormApi={api => (formApi.current = api)}
onValueChange={(values, changedValue) => {
handleFormChange(values, changedValue);
}}
>
{settings.map(setting => {
const { options, ...selectProps } = setting;
return (
<FormSelect
data-testid={setting.e2e}
key={setting.field}
optionList={options}
{...selectProps}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChange={(v?: string | number | any[] | Record<string, any>) => {
if (!v) {
return;
}
handleFormChange(
{ ...tableSettings, [setting.field]: v as unknown as number },
{ [setting.field]: v as unknown as number },
);
}}
/>
);
})}
</Form>
</div>
);
};

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { I18n } from '@coze-arch/i18n';
export const initIndexOptions = (length: number, start: number) => {
const MAX_VALUE = 50;
const limit = length > MAX_VALUE ? MAX_VALUE : length;
const res: Array<{
label: string;
value: number;
}> = [];
for (let i = start; i < limit; i++) {
res.push({
label: I18n.t('datasets_createFileModel_tab_dataStarRow_value', {
LineNumber: i + 1,
}),
value: i,
});
}
return res;
};

View File

@@ -0,0 +1,234 @@
/* stylelint-disable no-descending-specificity */
/* stylelint-disable no-duplicate-selectors */
/* stylelint-disable declaration-no-important */
/* stylelint-disable max-nesting-depth */
.table-structure-wrapper {
overflow: hidden;
flex: 1;
height: 100%;
}
.structure-wrapper {
overflow: hidden;
display: flex;
flex-direction: column;
}
.drag-table {
:global {
.semi-table-container {
.semi-table-tbody {
.semi-table-row {
.semi-table-row-cell {
&:first-child {
padding-left: 16px !important;
}
}
/** 暂时注释,后续还要用,待讨论 */
// &:hover {
// background: var(--coz-mg-secondary-hovered) !important;
// .semi-table-row-cell:first-child {
// border-top-left-radius: 8px !important;
// border-bottom-left-radius: 8px !important;
// }
// }
}
}
}
}
}
.table-structure {
overflow: hidden;
margin-top: 4px !important;
border-radius: 8px;
:global {
.semi-spin, .semi-spin-children, .semi-table-fixed-header,.semi-table-container {
height: 100%;
}
}
&-required-container {
display: flex;
align-items: center;
}
&-col-required {
margin-top: 4px;
font-size: 12px;
font-weight: 600;
font-style: normal;
line-height: 16px;
color: var(--coz-fg-hglt-red);
text-overflow: ellipsis;
}
.input-suffix {
display: inline-block;
margin-right: 5px;
font-size: 12px;
// color: rgb(28 29 35 / 35%);
}
.input-error-msg {
position: relative;
z-index: 100;
margin-top: 4px;
margin-bottom: -20px;
font-size: 12px;
font-weight: 400;
font-style: normal;
line-height: 16px;
color: var(--coz-fg-hglt-red);
}
.semantic-radio {
position: relative;
display: flex;
align-items: center;
height: 100%;
// padding-top: 3px;
padding-left: 2px;
}
.column-item-action {
display: flex;
align-items: center;
height: 100%;
padding-top: 3px;
padding-left: 7px;
&-delete {
cursor: pointer;
font-size: 14px;
}
}
.column-item {
display: flex;
flex-direction: column;
height: 100%;
}
.column-item-value {
font-size: 14px;
line-height: 32px;
color: var(--coz-fg-secondary);
text-overflow: ellipsis;
}
:global {
.structure-table-drag-icon {
position: absolute;
left: -16px;
opacity: 0;
}
.semi-table-row {
&:hover,
&:focus {
.structure-table-drag-icon {
opacity: 1;
}
}
}
.semi-table-thead>.semi-table-row>.semi-table-row-head {
font-size: 12px !important;
color: var(--coz-fg-secondary) !important;
}
.semi-table-container {
.semi-table-body {
height: calc(100% - 39px) !important;
padding-bottom: 12px;
}
}
.select-error-text {
padding-top: 0 !important;
}
.singleline-select-error-content {
height: 0;
}
.semi-table-thead>.semi-table-row>.semi-table-row-head {
&:first-child {
padding-left: 8px;
}
}
/** table去掉背景色 */
.semi-table-thead>.semi-table-row>.semi-table-row-head, .semi-table-tbody>.semi-table-row, .semi-table-tbody>.semi-table-row>.semi-table-cell-fixed-left,.semi-table-thead>.semi-table-row>.semi-table-row-head.semi-table-cell-fixed-left::before {
background-color: transparent !important;
}
.semi-table-tbody {
padding: 5px 0;
.semi-table-row {
.semi-table-row-cell {
height: 56px !important;
padding: 12px 8px !important;
border-bottom: 0;
// 此处不需要 UITable 默认的 border-radius
&:first-child {
padding-right: 28px !important;
padding-left: 8px !important;
border-radius: 0 !important;
}
}
&:hover {
cursor: auto;
background: var(--coz-mg-secondary-hovered);
&>.semi-table-row-cell {
background: transparent !important;
border-bottom: 0 !important;
}
}
}
}
}
}
.table-header-tooltip {
display: flex;
align-items: center;
}
.table-structure-bar-title {
display: flex;
align-items: center;
height: 32px;
font-size: 14px;
font-weight: 600;
font-style: normal;
color: var(--coz-fg-plus);
.icon {
margin-top: 3px;
margin-left: 4px;
}
}

View File

@@ -0,0 +1,630 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable max-lines */
/* eslint-disable max-lines-per-function */
import React, {
useCallback,
useMemo,
useState,
type FC,
type CSSProperties,
} from 'react';
import classNames from 'classnames';
import { CSS as cssDndKit } from '@dnd-kit/utilities';
import {
SortableContext,
arrayMove,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
DndContext,
PointerSensor,
useSensors,
useSensor,
type DragEndEvent,
} from '@dnd-kit/core';
import {
DataTypeSelect,
getDataTypeOptions,
getDataTypeText,
} from '@coze-data/utils';
import { OptType } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import {
IconCozInfoCircle,
IconCozTrashCan,
} from '@coze-arch/coze-design/icons';
import {
Input,
Table,
Tooltip,
Checkbox,
type CheckboxProps,
} from '@coze-arch/coze-design';
import { type ColumnProps, type TableProps } from '@coze-arch/bot-semi/Table';
import { UIButton, Typography } from '@coze-arch/bot-semi';
import { IconDragOutlined } from '@coze-arch/bot-icons';
import { type DocTableColumn } from '@coze-arch/bot-api/memory';
import { ColumnType } from '@coze-arch/bot-api/knowledge';
import { type IValidateRes, validateField, useOptFromQuery } from '@/utils';
import { type TableItem } from '@/types/table';
import { type SemanticValidateItem } from '@/types';
import styles from './index.module.less';
type Align = 'center' | 'left' | 'right' | undefined;
enum ValidateStatus {
Error = 'error',
Default = 'default',
}
export interface TableStructureProps extends TableProps {
data: Array<
DocTableColumn & {
key?: string;
autofocus?: boolean;
errMsg?: string;
}
>;
setData: (v: Array<DocTableColumn>) => void;
initValid?: boolean;
isBlurValid?: boolean;
isPreview?: boolean;
verifyMap?: SemanticValidateItem;
baseKey?: string;
showTitle?: boolean;
tipsNode?: React.ReactNode;
isDragTable?: boolean;
}
const NAME_MAX_STR_LEN = 30;
const DESC_MAX_STR_LEN = 2000;
const baseClassName = 'table-structure';
const RequiredColRender = ({
children,
tooltip,
dataTestId,
}: {
children: React.ReactNode;
tooltip?: string | React.ReactNode;
dataTestId?: string;
}) => (
<div
className={styles[`${baseClassName}-required-container`]}
data-testid={dataTestId}
>
{children}
<span className={styles[`${baseClassName}-col-required`]}> *</span>
{tooltip}
</div>
);
interface InputRenderProps {
onChange: (v: string) => void;
validate?: (v: string, error: string) => IValidateRes;
record: TableItem & { errMsg?: string };
value: string;
autofocus: boolean;
initValid: boolean;
isBlurValid: boolean;
errorMsg?: string;
isPreview?: boolean;
placeholder?: string;
maxStrLen: number;
}
const InputRender = ({
onChange,
record,
value,
autofocus = false,
isBlurValid = false,
initValid,
validate,
isPreview,
placeholder,
maxStrLen,
}: InputRenderProps) => {
const ValidateResult = (v: string) => {
const validRes = validate?.(
v,
I18n.t('datasets_segment_tableStructure_field_errEmpty'),
);
return {
valid: initValid ? !!(validate ? validRes?.valid : v && v !== '') : true,
errorMsg:
validRes?.errorMsg ||
I18n.t('datasets_segment_tableStructure_field_errEmpty'),
};
};
const [inputValue, setInputValue] = useState(value);
const [validObj, setValidObj] = useState(() => {
const res = ValidateResult(value);
return {
valid: isBlurValid ? true : res.valid,
errorMsg: res.errorMsg,
};
});
const validateValue = (v: string) => {
const validRes = ValidateResult(v);
setValidObj(validRes);
};
const hasDisable = isPreview;
const apiErrorMsg = record?.errMsg || '';
const validateStatus = useMemo(() => {
if (apiErrorMsg) {
return ValidateStatus.Error;
}
return !validObj.valid ? ValidateStatus.Error : ValidateStatus.Default;
}, [validObj, apiErrorMsg]);
const renderErrorMsg = useCallback(() => {
if (apiErrorMsg) {
return <div className={styles['input-error-msg']}>{apiErrorMsg}</div>;
}
return (
<>
{!validObj.valid && (
<div className={styles['input-error-msg']}>{validObj.errorMsg}</div>
)}
</>
);
}, [apiErrorMsg, validObj]);
return (
<>
<Input
autoFocus={autofocus}
value={inputValue}
maxLength={maxStrLen}
onChange={v => {
setInputValue(v.substring(0, maxStrLen));
!isBlurValid && validateValue(v);
}}
disabled={hasDisable}
validateStatus={validateStatus}
suffix={
<span className={styles['input-suffix']}>
{(inputValue || '').length}/{maxStrLen}
</span>
}
onBlur={() => {
onChange(inputValue?.substring(0, maxStrLen) || '');
validateValue(inputValue);
}}
placeholder={placeholder}
/>
{renderErrorMsg()}
</>
);
};
// TODO 待解
// eslint-disable-next-line @coze-arch/max-line-per-function
export const TableStructure: React.FC<TableStructureProps> = ({
data = [],
setData,
verifyMap = {},
initValid = false,
isBlurValid = false,
isPreview = false,
baseKey,
showTitle = false,
children: childrenNode,
tipsNode,
isDragTable = false,
...tableProps
}) => {
const opt = useOptFromQuery();
const isResegment = opt === OptType.RESEGMENT;
const columns: ColumnProps<TableItem>[] = [
{
title: () => (
<RequiredColRender
dataTestId={KnowledgeE2e.TableLocalTableConfigurationIndex}
tooltip={
<Tooltip
className="whitespace-pre-line"
content={I18n.t('knowledge_multi_index')}
>
<UIButton
size="small"
theme="borderless"
type="tertiary"
style={{
marginLeft: 4,
}}
icon={<IconCozInfoCircle className="coz-fg-secondary" />}
/>
</Tooltip>
}
>
<div
className={styles['table-header-tooltip']}
data-testid={KnowledgeE2e.TableLocalTableConfigurationIndex}
>
<span>{I18n.t('knowledge_table_structure_semantic')}</span>
</div>
</RequiredColRender>
),
dataIndex: 'is_semantic',
width: 90,
align: 'left' as Align,
render: (value, record, index: number) => {
const onChange: CheckboxProps['onChange'] = e => {
const newData = [...data];
newData[index].is_semantic = Boolean(e.target.checked);
setData(newData);
};
const { sequence } = record;
function hasFormItemDisable() {
const disabled = Object.keys(verifyMap).length
? !verifyMap[sequence || index]?.valid
: false;
return disabled || isPreview;
}
const hasDisable = hasFormItemDisable();
const Wrapper = ({ children }: { children: JSX.Element }) => {
if (hasDisable && verifyMap[sequence || index]?.msg) {
return (
<Tooltip
trigger="hover"
content={verifyMap[sequence || index]?.msg}
>
{children}
</Tooltip>
);
}
return children;
};
return (
<div className={styles['semantic-radio']}>
{isDragTable ? (
<IconDragOutlined className={'structure-table-drag-icon'} />
) : null}
<Wrapper>
<Checkbox
checked={value}
disabled={hasDisable}
onChange={onChange}
data-testid={KnowledgeE2e.TableStructureIndexCheckbox}
/>
</Wrapper>
</div>
);
},
},
{
title: () => (
<RequiredColRender
dataTestId={KnowledgeE2e.TableLocalTableConfigurationColumnName}
>
{I18n.t('knowledge_table_structure_column_name')}
</RequiredColRender>
),
dataIndex: 'column_name',
align: 'left' as Align,
render: (value, record, index) => {
const { autofocus = false } = record;
const onChange = (v: string) => {
const newData = [...data];
newData[index].column_name = v;
setData(newData);
};
const validateFn = (v: string, emptyMsg: string) => {
if (data?.filter(i => i.column_name === v).length >= 2) {
return {
valid: false,
errorMsg: I18n.t('Manual_crawling_040'),
};
}
return validateField(v, emptyMsg);
};
return (
<div className={styles['column-item']}>
<InputRender
initValid={initValid}
isBlurValid={isBlurValid}
key={`${baseKey}${record?.sequence}`}
onChange={onChange}
record={record}
value={value}
validate={validateFn}
autofocus={autofocus}
isPreview={isPreview}
maxStrLen={NAME_MAX_STR_LEN}
/>
</div>
);
},
},
{
title: () => (
<div data-testid={KnowledgeE2e.TableLocalTableConfigurationDesc}>
{I18n.t('knowledge_table_structure_desc')}
</div>
),
dataIndex: 'desc',
align: 'left' as Align,
render: (value, record, index) => {
const onChange = (v: string) => {
const newData = [...data];
newData[index].desc = v;
setData(newData);
};
return (
<div className={styles['column-item']}>
<InputRender
initValid={false}
isBlurValid={false}
key={`column-desc.${baseKey}${record?.sequence}`}
placeholder={I18n.t('knowledge_variable_description_placeholder')}
onChange={onChange}
record={record}
value={value}
autofocus={false}
isPreview={isPreview}
maxStrLen={DESC_MAX_STR_LEN}
/>
</div>
);
},
},
{
title: () => (
<RequiredColRender
dataTestId={KnowledgeE2e.TableLocalTableConfigurationType}
>
{I18n.t('knowledge_table_structure_data_type')}
</RequiredColRender>
),
dataIndex: 'column_type',
align: 'left' as Align,
render: (value, record, index) => {
const valid = isBlurValid ? true : !!value;
return (
<div
className={`pr-[16px] ${styles['column-item']}`}
key={record.sequence}
>
{isPreview ? (
<Typography.Text className={styles['column-item-value']}>
{getDataTypeText(value)}
</Typography.Text>
) : (
<DataTypeSelect
value={value || ''}
selectProps={{
disabled: !record.is_new_column && isResegment,
optionList: getDataTypeOptions().map(option => {
if (option.value === ColumnType.Image) {
return {
...option,
disabled: record.is_semantic,
};
}
return option;
}),
placeholder: I18n.t('db_table_save_exception_fieldtype'),
}}
errorMsg={
valid
? undefined
: I18n.t(
'datasets_segment_tableStructure_field_type_errEmpty',
)
}
handleChange={v => {
const newData = [...data];
newData[index].column_type = v as ColumnType;
setData(newData);
}}
/>
)}
</div>
);
},
},
{
title: (
<div data-testid={KnowledgeE2e.TableLocalTableConfigurationAction}>
{I18n.t('datasets_unit_upload_field_action')}
</div>
),
dataIndex: 'operate',
width: 82,
align: 'left' as Align,
render: (_text, record, index) => (
<div
className={styles['column-item-action']}
onClick={() => {
setData(data.filter((_, i) => index !== i));
}}
>
<Tooltip
content={I18n.t(
record.is_semantic
? 'datasets_segment_tableStructure_delTips'
: 'datasets_table_title_actions_delete',
)}
>
<IconCozTrashCan
aria-disabled={Boolean(record.is_semantic)}
className={styles['column-item-action-delete']}
/>
</Tooltip>
</div>
),
},
];
// 预览场景下,不需要操作列
if (isPreview) {
columns.pop();
}
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: { distance: 1 },
}),
);
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (active && over && active.id !== over.id) {
const items = Array.from(data);
const activeIndex = items.findIndex(item => item.key === active.id);
const overIndex = items.findIndex(item => item.key === over.id);
setData(arrayMove(items, activeIndex, overIndex));
}
};
const SortableRow: FC<{
className: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
'data-row-key': string;
style: CSSProperties;
}> = props => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id: props['data-row-key'],
});
const style: CSSProperties = {
...props.style,
transform: cssDndKit.Transform.toString(transform),
transition,
cursor: isDragging ? 'grabbing' : 'grab',
...(isDragging
? {
zIndex: 999,
position: 'relative',
background: 'rgba(217, 220, 250, 1)',
}
: {}),
};
return (
<tr
{...props}
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
></tr>
);
};
const renderTableContent = () => {
if (isDragTable) {
return (
<DndContext
// https://docs.dndkit.com/api-documentation/context-provider#autoscroll
autoScroll={true}
sensors={sensors}
modifiers={[restrictToVerticalAxis]}
onDragEnd={handleDragEnd}
>
<SortableContext
items={data.map(item => item.key || '')}
strategy={verticalListSortingStrategy}
>
<Table
wrapperClassName={classNames(
styles[`${baseClassName}-wrapper`],
styles['drag-table'],
)}
key={baseKey}
tableProps={{
sticky: true,
dataSource: data,
columns,
pagination: false,
className: styles[baseClassName],
components: {
body: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
row: SortableRow as any,
},
},
...tableProps,
}}
/>
</SortableContext>
</DndContext>
);
}
return (
<Table
wrapperClassName={styles[`${baseClassName}-wrapper`]}
key={baseKey}
tableProps={{
sticky: true,
dataSource: data,
columns,
pagination: false,
className: styles[baseClassName],
...tableProps,
}}
/>
);
};
return (
<div className={styles['structure-wrapper']}>
{showTitle ? <TableStructureTitle /> : null}
{tipsNode ? tipsNode : null}
{renderTableContent()}
{childrenNode}
</div>
);
};
export const TableStructureTitle = () => (
<div
className={styles['table-structure-bar-title']}
data-testid={KnowledgeE2e.TableLocalTableStructureTitle}
>
<span>{I18n.t('datasets_segment_tableStructure_title')}</span>
<Tooltip content={I18n.t('knowledge_table_structure_column_tooltip')}>
<IconCozInfoCircle
className={classNames(styles.icon, 'coz-fg-secondary')}
/>
</Tooltip>
</div>
);

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 组件相关
export {
TableSettingBar,
type TableSettingBarProps,
} from './components/table-setting-bar';
export {
type TablePreviewProps,
TablePreview,
} from './components/table-preview';
export {
type TableStructureProps,
TableStructure,
TableStructureTitle,
} from './components/table-structure';

View File

@@ -0,0 +1,58 @@
@import '../../assets/common.less';
.embed-progress {
flex: 1;
.progress-info {
width: 100%;
.text {
display: flex;
align-items: center;
height: 32px;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
font-style: normal;
line-height: 20px;
color: var(--coz-fg-primary);
}
.progress-list {
overflow-y: auto;
max-height: 532px;
}
}
.banner {
width: 100%;
}
.progress-success-icon {
.common-svg-icon(16px, var(--semi-color-primary));
}
}
.data-processing {
.finish-text {
overflow: hidden;
font-size: 14px;
font-weight: 400;
font-style: normal;
line-height: 20px; /* 142.857% */
color: var(--coz-fg-primary);
text-align: right;
text-overflow: ellipsis;
}
:global {
.process-progress-item-actions {
/* stylelint-disable-next-line declaration-no-important */
display: block !important;
}
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { UnitProgress } from './unit-progress';

View File

@@ -0,0 +1,227 @@
/*
* 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 { getFormatTypeFromUnitType } from '@coze-data/utils';
import { useKnowledgeParams } from '@coze-data/knowledge-stores';
import {
type ProgressItem,
CreateUnitStatus,
UnitType,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { DocumentStatus } from '@coze-arch/idl/knowledge';
import { I18n } from '@coze-arch/i18n';
import { formatBytes } from '@coze-arch/bot-utils';
import { getTypeIcon } from '../upload-unit-table/utils';
import { ProcessProgressItem } from '../process-progress-item';
import { getFrequencyMap } from '../../utils';
import { ProcessStatus } from '../../types';
const INIT_PERCENT = 10;
import styles from './index.module.less';
interface UnitProgressProps {
progressList: ProgressItem[];
createStatus: CreateUnitStatus;
}
const OneHundred = 100;
function hoursToDays(hours: number | string) {
const curHours = typeof hours === 'string' ? parseInt(hours) : hours;
if (isNaN(curHours)) {
return 0;
}
return curHours / 24;
}
function formatRemainingTime(remainingSeconds: number) {
const minutes = Math.floor(remainingSeconds / 60);
const seconds = remainingSeconds % 60;
return {
minutes,
seconds,
};
}
const renderSubText = (
status: ProcessStatus,
item: ProgressItem,
hasURLImport?: boolean,
) => {
const statusDesc = item?.statusDesc || I18n.t('datasets_unit_upload_fail');
if (status === ProcessStatus.Failed) {
return (
<div
data-dtestid={`${KnowledgeE2e.CreateUnitListProgressName}.${'subText'}`}
className={'text-12px'}
>
{statusDesc}
</div>
);
}
let subDesc = '';
if (hasURLImport) {
// 更新频率
const updateInterval = hoursToDays(item?.update_interval || 0);
subDesc = getFrequencyMap(updateInterval);
} else {
subDesc = formatBytes((item?.size || 0) as number);
}
return (
<div
data-dtestid={`${KnowledgeE2e.CreateUnitListProgressName}.${item?.name}`}
className={'coz-fg-secondary text-12px'}
>
{subDesc}
</div>
);
};
export const UnitProgress: React.FC<UnitProgressProps> = ({
progressList,
createStatus,
}) => {
const params = useKnowledgeParams();
const headerTitle = useMemo(() => {
let msg: string = I18n.t('datasets_createFileModel_step4_processing');
if (createStatus === CreateUnitStatus.TASK_FINISH) {
const allFailed = progressList.every(
status => status.status === DocumentStatus.Failed,
);
if (allFailed) {
msg = I18n.t('datasets_createFileModel_step4_failed');
} else {
msg = I18n.t('datasets_createFileModel_step4_Finish');
}
}
return msg;
}, [createStatus, progressList]);
const unitType = params.type as UnitType;
const formatType = getFormatTypeFromUnitType(unitType);
const hasURLImport = [
UnitType.TABLE_API,
UnitType.TABLE_FEISHU,
UnitType.TABLE_GOOGLE_DRIVE,
UnitType.TABLE_LARK,
UnitType.TEXT_FEISHU,
UnitType.TEXT_LARK,
UnitType.TEXT_NOTION,
UnitType.TEXT_URL,
UnitType.TEXT_GOOGLE_DRIVE,
].includes(unitType);
const percentFormat = (percent: number, remainingTime: number) => {
const { minutes, seconds } = formatRemainingTime(remainingTime as number);
const remainingTimeText = I18n.t('knowledge_upload_remaining_time_text', {
minutes,
seconds,
});
return percent < OneHundred
? `${percent}% ${
Number(remainingTime) > 0 ? `(${remainingTimeText})` : ''
}`
: null;
};
return (
<div className={styles['embed-progress']}>
<div className={styles['progress-info']}>
<div
className={styles.text}
data-testid={KnowledgeE2e.CreateUnitProgressTitle}
>
{headerTitle}
</div>
<div className={styles['progress-list']}>
{progressList.map(item => {
const { status } = item;
const getProcessStatus = () => {
if (
[DocumentStatus.Failed, DocumentStatus.AuditFailed].includes(
status,
)
) {
return ProcessStatus.Failed;
}
if (
[
DocumentStatus.Processing,
DocumentStatus.Resegment,
DocumentStatus.Refreshing,
].includes(status)
) {
return ProcessStatus.Processing;
}
if (
[DocumentStatus.Enable, DocumentStatus.Disable].includes(status)
) {
return ProcessStatus.Complete;
}
return ProcessStatus.Processing;
};
const curStatus = getProcessStatus();
const percent = item?.progress || INIT_PERCENT;
return (
<ProcessProgressItem
className={styles['data-processing']}
key={item?.documentId}
mainText={item?.name}
subText={renderSubText(curStatus, item, hasURLImport)}
tipText={renderSubText(curStatus, item, hasURLImport)}
status={curStatus}
avatar={getTypeIcon({
type: item?.type,
url: item?.url,
formatType,
})}
percent={percent}
percentFormat={percentFormat(
percent,
item?.remaining_time as number,
)}
actions={[
ProcessStatus.Complete ? (
<div
className={styles['finish-text']}
data-testid={`${
KnowledgeE2e.CreateUnitListProgressSuccessIcon
}.${item?.name || ''}`}
>
{I18n.t('datasets_unit_process_success')}
</div>
) : null,
]}
/>
);
})}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,17 @@
.upload-footer {
display: flex;
justify-content: flex-end;
margin-top: auto;
margin-bottom: 18px;
padding-top: 24px;
padding-bottom: 24px;
line-height: 32px;
:global {
.semi-button {
margin-left: 10px;
}
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { UploadFooter } from './upload-footer';

View File

@@ -0,0 +1,87 @@
/*
* 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 { isArray, isObject, get } from 'lodash-es';
import {
FooterBtnStatus,
type FooterControlsProps,
type FooterControlProp,
type FooterBtnProps,
type FooterPrefixType,
} from '@coze-data/knowledge-resource-processor-core';
import { Button, Tooltip } from '@coze-arch/coze-design';
import styles from './index.module.less';
interface UploadFooterProps {
controls: FooterControlsProps;
}
/** 类型断言 入参是不是 按钮数组 */
function isBtnArray(controls: unknown): controls is FooterBtnProps[] {
return !!controls && isArray(controls);
}
function isControlsObject(controls: unknown): controls is FooterControlProp {
return (
!!controls &&
isObject(controls) &&
!!get(controls, 'btns') &&
!!get(controls, 'prefix')
);
}
export const UploadFooter = (props: UploadFooterProps) => {
const { controls } = props;
let btns: FooterBtnProps[] = [];
let prefix: FooterPrefixType;
if (isBtnArray(controls)) {
btns = controls;
}
if (isControlsObject(controls)) {
({ btns, prefix } = controls);
}
return (
<div className={styles['upload-footer']}>
{prefix}
{btns.map(btnItem => {
const isShowHoverContent =
btnItem.disableHoverContent &&
btnItem.status === FooterBtnStatus.DISABLE;
const buttonNode = (
<Button
data-testid={btnItem.e2e}
key={btnItem.text}
disabled={btnItem.status === FooterBtnStatus.DISABLE}
loading={btnItem.status === FooterBtnStatus.LOADING}
color={btnItem.type || 'hgltplus'}
// theme={btnItem.theme || 'solid'}
onClick={btnItem.onClick}
>
{btnItem.text}
</Button>
);
if (!isShowHoverContent) {
return buttonNode;
}
return (
<Tooltip content={btnItem.disableHoverContent}>{buttonNode}</Tooltip>
);
})}
</div>
);
};

View File

@@ -0,0 +1,60 @@
/*
* 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 { getKnowledgeIDEQuery } from '@coze-data/knowledge-common-services';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import { IconCozArrowLeft } from '@coze-arch/coze-design/icons';
import { IconButton, Typography } from '@coze-arch/coze-design';
interface UploadActionNavbarProps {
title: string;
}
// 上传页面导航栏
export const UploadActionNavbar = ({ title }: UploadActionNavbarProps) => {
const params = useKnowledgeParams();
const resourceNavigate = useDataNavigate();
// TODO: hzf biz的分化在Scene层维护
const fromProject = params.biz === 'project';
const handleBack = () => {
const query = getKnowledgeIDEQuery() as Record<string, string>;
resourceNavigate.toResource?.('knowledge', params.datasetID, query);
};
return (
<div
className={classNames(
'flex items-center justify-between shrink-0 h-[56px] coz-fg-primary',
fromProject ? 'px-[12px]' : '',
)}
>
<div className="flex items-center">
<IconButton
color="secondary"
icon={<IconCozArrowLeft className="text-[16px]" />}
iconPosition="left"
className="!p-[8px]"
onClick={handleBack}
></IconButton>
<Typography.Text fontSize="16px" weight={500} className="ml-[8px]">
{title}
</Typography.Text>
</div>
</div>
);
};

View File

@@ -0,0 +1,112 @@
/*
* 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 { DataNamespace, dataReporter } from '@coze-data/reporter';
import { UploadStatus } from '@coze-data/knowledge-resource-processor-core';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import { type UploadProps } from '@coze-arch/bot-semi/Upload';
import { Toast } from '@coze-arch/coze-design';
import { getFileExtension, getUint8Array } from '../../utils';
import { UNIT_MAX_MB, PDF_MAX_PAGES } from '../../constants';
export const getBeforeUpload: (params: {
maxSizeMB: UploadProps['maxSize'];
}) => UploadProps['beforeUpload'] =
({ maxSizeMB }) =>
async fileInfo => {
// 不通过 maxSize 属性来限制的原因是
// 只有 beforeUpload 钩子能改 validateMessage
const res = {
fileInstance: fileInfo.file.fileInstance,
status: fileInfo.file.status,
validateMessage: fileInfo.file.validateMessage,
shouldUpload: true,
autoRemove: false,
};
const { fileInstance } = fileInfo.file;
if (!fileInstance) {
return {
...res,
status: UploadStatus.UPLOAD_FAIL,
shouldUpload: false,
};
}
const resultMaxSizeMB = maxSizeMB || UNIT_MAX_MB;
const maxSize = resultMaxSizeMB * 1024 * 1024;
if (fileInstance.size > maxSize) {
Toast.warning({
showClose: false,
content: I18n.t('file_too_large', {
max_size: `${resultMaxSizeMB}MB`,
}),
});
return {
...res,
shouldUpload: false,
status: UploadStatus.VALIDATE_FAIL,
validateMessage: I18n.t('file_too_large', {
max_size: `${resultMaxSizeMB}MB`,
}),
};
}
if (getFileExtension(fileInstance.name).toLowerCase() === 'pdf') {
try {
// TODO: 后续其他位置的 pdfjs 调用也都应该改成异步加载
const pdfjs = await import('@coze-arch/pdfjs-shadow');
const { getDocument, initPdfJsWorker } = pdfjs;
initPdfJsWorker();
const uint8Array = await getUint8Array(fileInstance);
const pdfDocument = await getDocument({ data: uint8Array }).promise;
if (pdfDocument.numPages > PDF_MAX_PAGES) {
Toast.warning({
showClose: false,
content: I18n.t('atasets_createpdf_over250'),
});
return {
shouldUpload: false,
status: UploadStatus.VALIDATE_FAIL,
};
}
} catch (e) {
const error = e as Error;
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeParseFile,
error,
});
if (error?.name === 'PasswordException') {
Toast.error({
showClose: false,
content: I18n.t('pdf_encrypted'),
});
return {
shouldUpload: false,
status: UploadStatus.VALIDATE_FAIL,
};
}
}
}
return res;
};

View File

@@ -0,0 +1,93 @@
/*
* 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 { DataNamespace, dataReporter } from '@coze-data/reporter';
import { UploadStatus } from '@coze-data/knowledge-resource-processor-core';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { type UploadProps } from '@coze-arch/bot-semi/Upload';
import { CustomError } from '@coze-arch/bot-error';
import { FileBizType } from '@coze-arch/bot-api/developer_api';
import { DeveloperApi } from '@coze-arch/bot-api';
import { getBase64, getFileExtension } from '../../utils';
export const customRequest: UploadProps['customRequest'] = async options => {
const { onSuccess, onError, onProgress, file } = options;
try {
// 业务
const { name, fileInstance } = file;
if (fileInstance) {
const extension = getFileExtension(name);
const base64 = await getBase64(fileInstance);
const result = await DeveloperApi.UploadFile(
{
file_head: {
file_type: extension,
biz_type: FileBizType.BIZ_BOT_DATASET,
},
data: base64,
},
{
onUploadProgress: e => {
const status = file?.status;
const response = file?.response;
// 成功或失败、检验失败后,或者有返回接口数据,不再更新进度条
if (
status === UploadStatus.SUCCESS ||
status === UploadStatus.UPLOAD_FAIL ||
status === UploadStatus.VALIDATE_FAIL ||
response?.upload_url
) {
return;
}
const { total, loaded } = e;
if (total !== undefined && loaded < total) {
onProgress({
total: e.total ?? fileInstance.size,
loaded: e.loaded,
});
}
},
},
);
onSuccess(result.data);
} else {
onError({
status: 0,
});
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeUploadFile,
error: new CustomError(
REPORT_EVENTS.KnowledgeUploadFile,
`${REPORT_EVENTS.KnowledgeUploadFile}: Failed to upload file`,
),
});
}
} catch (e) {
const error = e as Error;
onError({
status: 0,
});
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeUploadFile,
error,
});
}
};

View File

@@ -0,0 +1,57 @@
.upload {
width: 100%;
:global {
.semi-upload-drag-area {
height: 202px;
background-color: var(--coz-mg-card);
border-radius: 8px;
&:hover,
&.semi-upload-drag-area-legal {
border-radius: var(--default, 8px);
}
}
.semi-upload-drag-area-sub-text {
color: var(--coz-fg-dim);
}
.semi-button-with-icon-only {
border-radius: 4px;
}
.semi-upload-file-list {
display: none;
}
}
}
.upload-icon {
width: 32px;
height: 32px;
}
.create-enough-file {
cursor: not-allowed;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.picture {
width: 122px;
height: 122px;
margin-bottom: 14px;
}
.text {
font-size: 12px;
line-height: 16px;
color: rgb(28 31 35 / 60%);
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { UploadUnitFile } from './upload-unit-file';

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC } from 'react';
import { type RenderFileItemProps } from '@coze-arch/bot-semi/Upload';
import {
IconPDFFile,
IconUnknowFile as IconUnknownFile,
IconTextFile,
IconDocxFile,
} from '@coze-arch/bot-icons';
import { getFileExtension } from '../../utils/common';
import { type FileType } from './types';
export const PreviewFile: FC<RenderFileItemProps> = props => {
const type = (getFileExtension(props.name) || 'unknown') as FileType;
const components: Record<FileType, React.FC> = {
unknown: IconUnknownFile,
pdf: IconPDFFile,
text: IconTextFile,
docx: IconDocxFile,
};
const ComponentToRender = components[type] || IconUnknownFile;
return <ComponentToRender />;
};

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type FileType = 'unknown' | 'pdf' | 'docx' | 'text';

View File

@@ -0,0 +1,130 @@
/*
* 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, useEffect, useCallback, useMemo, type FC } from 'react';
import classNames from 'classnames';
import { IllustrationSuccess } from '@douyinfe/semi-illustrations';
import { abortable, useUnmountSignal } from '@coze-data/utils';
import { type UnitItem } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import {
type FileItem,
type UploadProps,
type OnChangeProps,
} from '@coze-arch/bot-semi/Upload';
import { IconCozUpload } from '@coze-arch/coze-design/icons';
import { Toast, Upload } from '@coze-arch/coze-design';
import { UNIT_MAX_MB } from '../../constants';
import {
filterFileListByUnitList,
filterFileList,
filterUnitList,
} from './utils';
import { PreviewFile } from './preview-file';
import { customRequest } from './custom-request';
import { getBeforeUpload } from './before-upload';
import styles from './index.module.less';
interface UploadUnitFileProps extends UploadProps {
unitList: UnitItem[];
onFinish: (unitList: UnitItem[]) => void;
limit: number;
accept: string;
setUnitList: (unitList: UnitItem[]) => void;
showIllustration?: boolean;
maxSizeMB?: number;
}
export const UploadUnitFile: FC<UploadUnitFileProps> = props => {
const {
unitList,
onFinish,
setUnitList,
showIllustration = true,
multiple = true,
maxSizeMB = UNIT_MAX_MB,
...uploadProps
} = props;
const { limit } = uploadProps;
const [fileList, setFileList] = useState<FileItem[]>([]);
useEffect(() => {
if (unitList.length < fileList.length) {
setFileList(filterFileListByUnitList(fileList, unitList));
}
}, [unitList.length]);
const handleAcceptInvalid = useCallback(() => {
Toast.warning({
showClose: false,
content: I18n.t('knowledge_upload_format_error'),
});
}, []);
const signal = useUnmountSignal();
const handleUploadProcess = abortable((data: OnChangeProps) => {
setFileList(data.fileList);
setUnitList(filterFileList(data.fileList));
}, signal);
const handleUploadSuccess = abortable(() => {
onFinish(filterUnitList(unitList, fileList));
}, signal);
const uploadDisabled = useMemo(
() => unitList.length >= limit,
[unitList, limit],
);
const beforeUpload = getBeforeUpload({ maxSizeMB });
return (
<Upload
draggable
data-testid={KnowledgeE2e.UploadUnitFile}
multiple={multiple}
fileList={fileList}
disabled={uploadDisabled}
previewFile={PreviewFile}
onAcceptInvalid={handleAcceptInvalid}
beforeUpload={beforeUpload}
customRequest={customRequest}
onChange={handleUploadProcess}
onSuccess={handleUploadSuccess}
dragIcon={<IconCozUpload className={styles['upload-icon']} />}
{...uploadProps}
className={classNames(styles.upload, uploadProps.className)}
>
{unitList.length >= limit && showIllustration ? (
<div
className={styles['create-enough-file']}
onClick={e => e.stopPropagation()}
>
<IllustrationSuccess className={styles.picture} />
<div className={styles.text}>
{I18n.t('knowledge_1218_001', {
MaxDocs: limit,
})}
</div>
</div>
) : null}
</Upload>
);
};

View File

@@ -0,0 +1,103 @@
/*
* 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 {
UploadStatus,
type UnitItem,
} from '@coze-data/knowledge-resource-processor-core';
import { KNOWLEDGE_UNIT_NAME_MAX_LEN } from '@coze-data/knowledge-modal-base';
import { type FileItem } from '@coze-arch/bot-semi/Upload';
import { getFileExtension } from '../../utils/common';
interface GetFileListMapRes {
[key: string]: FileItem;
}
const getFileName = (uri: string) => uri.substring(0, uri.lastIndexOf('.'));
const getNameFromFileList = (unitList: FileItem[], index: number) =>
unitList?.[index]?.name;
const getFileListMap = (fileList: FileItem[]): GetFileListMapRes =>
fileList.reduce((acc: GetFileListMapRes, item) => {
acc[item.uid || ''] = item;
return acc;
}, {});
/** 过滤 fileList仅保留在 unitList 中的文件,即上传成功的文件 */
export const filterFileListByUnitList = (
fileList: FileItem[],
unitList: UnitItem[],
): FileItem[] =>
fileList.filter(fileItem =>
unitList?.find(unitItem => unitItem.uri === fileItem?.response?.upload_uri),
);
const fileItem2UnitItem = (
file: FileItem,
config?: { filename?: string },
): UnitItem => ({
type: getFileExtension(file?.response?.upload_uri || file.name),
uri: file?.response?.upload_uri,
url: file?.response?.upload_url,
name: (config?.filename ?? getFileName(file.name)).slice(
0,
KNOWLEDGE_UNIT_NAME_MAX_LEN,
),
size: file.size,
status: file.status as UnitItem['status'],
percent: file.percent || 0,
fileInstance: file.fileInstance,
uid: file.uid,
validateMessage: (file.validateMessage as string) || '',
});
export const filterFileList = (fileList: FileItem[]): UnitItem[] => {
const filteredList: UnitItem[] = fileList
.filter(
item =>
!(!item.shouldUpload && item.status === UploadStatus.VALIDATE_FAIL),
)
.map((file, index) => {
const filename = getNameFromFileList(fileList, index);
return fileItem2UnitItem(file, { filename });
});
return filteredList;
};
export const filterUnitList = (
unitList: UnitItem[],
fileList: FileItem[],
): UnitItem[] => {
const fileListMap = getFileListMap(fileList);
return unitList
.filter(unit => {
const file = fileListMap[unit.uid || ''];
if (!file.shouldUpload && file.status === UploadStatus.VALIDATE_FAIL) {
return false;
}
return true;
})
.map((unit, index) => {
const file = fileListMap[unit.uid || ''];
const filename = getNameFromFileList(fileList, index);
return {
...unit,
...fileItem2UnitItem(file, { filename }),
};
});
};

View File

@@ -0,0 +1,129 @@
/*
* 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 UnitItem } from '@coze-data/knowledge-resource-processor-core';
import { useEditUnitNameModal } from '@coze-data/knowledge-modal-base';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import {
IconCozEdit,
IconCozRefresh,
IconCozTrashCan,
} from '@coze-arch/coze-design/icons';
import { IconButton, Tooltip } from '@coze-arch/coze-design';
import { type RenderColumnsProps } from '../types';
import { getFrequencyMap } from '../../../utils';
export function getFileSizeInfo(record: UnitItem) {
return (
<div
data-dtestid={`${KnowledgeE2e.LocalUploadListFileSize}.${record.name}`}
className={'coz-fg-secondary text-12px'}
>
{record?.size}
</div>
);
}
export function getFrequencyInfo(record: UnitItem) {
return (
<div
data-dtestid={`${KnowledgeE2e.LocalUploadListFrequency}.${record.name}`}
className={'coz-fg-secondary text-12px'}
>
{getFrequencyMap(record.updateInterval || 0)}
</div>
);
}
export function ActionRenderByDelete(props: RenderColumnsProps) {
const { index, record, params } = props;
const { onChange, unitList, onDelete } = params;
const handleDelete = () => {
onChange(unitList.filter((u, i) => index !== i));
if (typeof onDelete === 'function') {
onDelete?.(record, index);
}
};
return (
<Tooltip spacing={12} content={I18n.t('Delete')} position="top">
<IconButton
color="secondary"
icon={<IconCozTrashCan className="text-14px" />}
iconPosition="left"
size="small"
onClick={handleDelete}
></IconButton>
</Tooltip>
);
}
export function ActionRenderByEditName(props: RenderColumnsProps) {
const { index, record, params } = props;
const { onChange, unitList } = params;
const { node, open } = useEditUnitNameModal({
name: record?.name ?? '',
onOk: (name: string) => {
const arr = [...unitList];
arr[index].name = name;
onChange(arr);
},
});
return (
<>
<Tooltip spacing={12} content={I18n.t('Edit')} position="top">
<IconButton
color="secondary"
icon={<IconCozEdit className="text-14px" />}
iconPosition="left"
size="small"
onClick={() => open()}
/>
</Tooltip>
{node}
</>
);
}
export function ActionRenderByRetry(props: RenderColumnsProps) {
const { index, record, params } = props;
if (params.disableRetry) {
return null;
}
const { onRetry } = params;
const handleRetry = () => {
onRetry?.(record, index);
};
return (
<Tooltip
spacing={12}
content={I18n.t('datasets_unit_update_retry')}
position="top"
>
<IconButton
color="secondary"
icon={<IconCozRefresh className="text-14px" />}
iconPosition="left"
size="small"
onClick={handleRetry}
></IconButton>
</Tooltip>
);
}

View File

@@ -0,0 +1,40 @@
/*
* 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 { UITableAction } from '@coze-arch/bot-semi';
import { type ActionProps } from '../../types';
export const Action: FC<ActionProps> = ({
onDelete,
showEdit,
deleteProps,
editProps,
}: ActionProps) => (
<UITableAction
deleteProps={{
disabled: false,
handleClick: onDelete,
...deleteProps,
}}
editProps={{
hide: !showEdit,
...editProps,
}}
/>
);

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { Action } from './action';

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { UploadStatusComp } from './upload-status';
export { UnitName } from './unit-name';
export { Action } from './action-render';

View File

@@ -0,0 +1,44 @@
.unit-name-wrap {
display: flex;
align-items: center;
margin-left: 3px;
:global {
.semi-icon {
margin-right: 12px;
}
.view-name {
display: inline-block;
height: 32px;
font-size: 14px;
line-height: 32px;
}
.input-suffix {
display: inline-block;
margin-right: 5px;
font-size: 12px;
color: rgb(28 29 35 / 35%);
}
.unit-name-input {
width: 100%;
.error {
position: absolute;
color: rgb(249 57 32 / 100%);
}
}
.unit-name-error {
display: flex;
flex-direction: column;
.error {
color: rgb(249 57 32 / 100%);
}
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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, useEffect } from 'react';
import { KNOWLEDGE_UNIT_NAME_MAX_LEN } from '@coze-data/knowledge-modal-base';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { UIInput } from '@coze-arch/bot-semi';
import { getTypeIcon } from '../../utils';
import { type UnitNameProps } from '../../types';
import styles from './index.module.less';
export const UnitName: React.FC<UnitNameProps> = ({
edit,
onChange,
disabled,
record,
formatType,
}) => {
const { type, name, validateMessage } = record;
const [value, setValue] = useState(name); // 需要用自身state否则出现无法输入中文的bug
const getValidateMessage = (val: string) =>
!val ? I18n.t('datasets_unit_exception_name_empty') : validateMessage;
useEffect(() => {
setValue(name);
}, [name]);
return (
<div
className={styles['unit-name-wrap']}
data-testid={`${KnowledgeE2e.FeishuUploadListName}.${name}`}
>
{getTypeIcon({ type, formatType })}
{edit ? (
<div className="unit-name-input">
<UIInput
disabled={disabled}
value={value}
onChange={val => {
setValue(val);
onChange(val);
}}
maxLength={KNOWLEDGE_UNIT_NAME_MAX_LEN}
validateStatus={!name ? 'error' : 'default'}
suffix={
<span className="input-suffix">
{(name || '').length}/{KNOWLEDGE_UNIT_NAME_MAX_LEN}
</span>
}
/>
<div className="error">{getValidateMessage(name)}</div>
</div>
) : (
<div className="unit-name-error">
<span className="view-name">{name}</span>
{validateMessage ? (
<div className="error">{validateMessage}</div>
) : null}
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,108 @@
/*
* 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, useEffect, type FC } from 'react';
import { KNOWLEDGE_UNIT_NAME_MAX_LEN } from '@coze-data/knowledge-modal-base';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { UIInput } from '@coze-arch/bot-semi';
import { FormatType } from '@coze-arch/bot-api/memory';
import { validateField } from '@/utils';
import { getTypeIcon } from '../../utils';
import { type UnitNameProps } from '../../types';
import styles from './index.module.less';
export const UnitName: FC<UnitNameProps> = ({
edit,
onChange,
disabled,
record,
formatType,
canValidator = true,
inModal = false,
}) => {
const { type, name, validateMessage, dynamicErrorMessage } = record;
const [value, setValue] = useState(name); // 需要用自身state否则出现无法输入中文的bug
const [validData, setValidData] = useState({ valid: true, errorMsg: '' });
const getValidateMessage = (val: string) =>
!val ? I18n.t('datasets_unit_exception_name_empty') : validateMessage;
const validator = (val: string) => {
const validObj = validateField(val, getValidateMessage(name));
setValidData(
formatType === FormatType.Table
? validObj
: {
valid: !!name,
errorMsg: '',
},
);
};
useEffect(() => {
setValue(name);
canValidator && !disabled && validator(name);
}, [name, disabled]);
return (
<div className={styles['unit-name-wrap']}>
{getTypeIcon({ type, formatType, url: record.url, inModal })}
{edit ? (
<div className="unit-name-input">
<UIInput
data-dtestid={`${KnowledgeE2e.LocalUploadListName}.${record.name}`}
disabled={disabled}
autoFocus={true}
value={value}
onChange={val => {
setValue(val);
onChange(val);
}}
onBlur={() => {
canValidator && validator(name);
}}
maxLength={KNOWLEDGE_UNIT_NAME_MAX_LEN}
validateStatus={
!validData.valid || dynamicErrorMessage ? 'error' : 'default'
}
suffix={
<span className="input-suffix">
{(name || '').length}/{KNOWLEDGE_UNIT_NAME_MAX_LEN}
</span>
}
/>
<div className="error">
{!disabled &&
(validData.errorMsg ||
getValidateMessage(name) ||
dynamicErrorMessage)}
</div>
</div>
) : (
<span
className="view-name"
data-dtestid={`${KnowledgeE2e.LocalUploadListNameView}.${record.name}`}
>
{name}
</span>
)}
</div>
);
};

View File

@@ -0,0 +1,44 @@
.upload-status-wrap {
display: flex;
align-items: center;
line-height: 22px;
:global {
.semi-icon {
float: left;
margin: 0 5px -3px 0;
}
.semi-progress-horizontal {
margin-top: 2px;
}
}
}
.retry {
cursor: pointer;
:global {
.semi-icon {
float: left;
margin: 3px 3px 0 0;
}
}
}
.retry-text {
margin-left: 3px;
color: rgb(77 83 232 / 100%);
}
.disabled-retry {
.retry-text {
cursor: not-allowed;
color: #ccc;
}
}
.no-retry-text {
cursor: not-allowed;
}

View File

@@ -0,0 +1,126 @@
/*
* 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 complexity */
import classNames from 'classnames';
import {
UploadStatus,
EntityStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Tooltip, Progress, Spin } from '@coze-arch/bot-semi';
import {
IconUploadFileSuccess,
IconUploadFileFail,
} from '@coze-arch/bot-icons';
import { WebStatus } from '@coze-arch/bot-api/knowledge';
import { type UploadStateProps } from '../../types';
import styles from './index.module.less';
export const UploadStatusComp: React.FC<UploadStateProps> = ({
record,
onRetry,
index,
needLoading,
overlayClassName,
disableRetry,
noRetry,
}) => {
const { status } = record;
if (
status === UploadStatus.UPLOADING ||
status === UploadStatus.VALIDATING ||
status === UploadStatus.WAIT ||
status === WebStatus.Handling ||
status === EntityStatus.EntityStatusProcess
) {
return (
<span
data-dtestid={`${KnowledgeE2e.LocalUploadListStatus}.${record.name}`}
className={classNames(styles['upload-status-wrap'], overlayClassName)}
>
<span>{I18n.t('datasets_unit_upload_state')}</span>
{needLoading ? (
<Spin spinning={true} />
) : (
<Progress percent={record.percent} />
)}
</span>
);
}
if (
status === UploadStatus.SUCCESS ||
status === WebStatus.Finish ||
status === EntityStatus.EntityStatusSuccess
) {
return (
<span
className={styles['upload-status-wrap']}
data-dtestid={`${KnowledgeE2e.LocalUploadListStatus}.${record.name}`}
>
<IconUploadFileSuccess />
<span>{I18n.t('datasets_unit_upload_success')}</span>
</span>
);
}
if (status === UploadStatus.VALIDATE_FAIL) {
return (
<span
className={styles['upload-status-wrap']}
data-dtestid={`${KnowledgeE2e.LocalUploadListStatus}.${record.name}`}
>
<IconUploadFileFail />
</span>
);
}
if (
status === UploadStatus.UPLOAD_FAIL ||
status === WebStatus.Failed ||
status === EntityStatus.EntityStatusFail
) {
return (
<div
data-dtestid={`${KnowledgeE2e.LocalUploadListStatus}.${record.name}`}
className={classNames(
`${styles['upload-status-wrap']} ${styles.retry}`,
overlayClassName,
disableRetry ? styles['disabled-retry'] : '',
noRetry ? styles['no-retry-text'] : '',
)}
onClick={() => {
!disableRetry && !noRetry && onRetry && onRetry(record, index);
}}
>
{!record.statusDescript ? (
<IconUploadFileFail />
) : (
<Tooltip content={record.statusDescript} trigger="hover">
<IconUploadFileFail />
</Tooltip>
)}
{!noRetry && (
<div className={classNames(styles['retry-text'])}>
{I18n.t('datasets_unit_update_retry')}
</div>
)}
</div>
);
}
return null;
};

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { UploadUnitTable } from './upload-unit-table';
export { type RenderColumnsProps } from './types';
export {
ActionRenderByDelete,
ActionRenderByEditName,
ActionRenderByRetry,
getFrequencyInfo,
getFileSizeInfo,
} from './actions';
export { getProcessStatus } from './utils';
export { type ColumnInfo, type UploadUnitTableProps } from './types';

View File

@@ -0,0 +1,95 @@
/*
* 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,
type UnitItem,
} from '@coze-data/knowledge-resource-processor-core';
import { type UIActionItemProps } from '@coze-arch/bot-semi';
import { type FormatType } from '@coze-arch/bot-api/memory';
export interface UploadStateProps {
record: UnitItem;
onRetry?: (record: UnitItem, index: number) => void;
index: number;
needLoading?: boolean;
overlayClassName?: string;
disableRetry?: boolean;
// 不显示重试文案
noRetry?: boolean;
}
export interface UnitNameProps {
canValidator?: boolean;
edit?: boolean;
disabled?: boolean;
inModal?: boolean;
formatType: FormatType;
onChange: (value: string) => void;
record: UnitItem;
}
export interface ActionProps {
showEdit?: boolean;
onDelete: () => void;
deleteProps?: UIActionItemProps;
editProps?: UIActionItemProps;
record?: UnitItem;
}
export enum ActionType {
Edit = 'edit',
Delete = 'delete',
}
export type HandleChange = (
unitList: UnitItem[],
action?: {
type: ActionType;
index: number;
},
) => void;
export interface ColumnInfo {
subText?: ReactNode;
actions?: ReactNode[];
formatType?: FormatType;
}
export interface UploadUnitTableProps {
type: UnitType;
unitList: UnitItem[];
onChange: HandleChange;
edit: boolean;
canValidator?: boolean;
onRetry?: (record: UnitItem, index: number) => void;
disableRetry?: boolean;
onDelete?: (record: UnitItem, index: number) => void;
showEdit?: boolean;
inModal?: boolean;
getColumns?: (record: UnitItem, index: number) => ColumnInfo;
}
export interface GetColumnsParams extends UploadUnitTableProps {
formatType: FormatType;
}
export interface RenderColumnsProps {
params: UploadUnitTableProps;
record: UnitItem;
index: number;
}

View File

@@ -0,0 +1,88 @@
/*
* 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, { type FC, type ReactNode } from 'react';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { ProcessProgressItem } from '../process-progress-item/process-progress-item';
import { ProcessStatus } from '../../types';
import { getProcessStatus, getTypeIcon } from './utils';
import { type ColumnInfo, type UploadUnitTableProps } from './types';
const INIT_PERCENT = 10;
const renderSubText = (
status: ProcessStatus,
statusDesc: string,
subText: ReactNode,
) => {
if (status === ProcessStatus.Failed) {
return (
<div
data-dtestid={`${KnowledgeE2e.CreateUnitListProgressName}.${'subText'}`}
className={'text-12px'}
>
{statusDesc || I18n.t('datasets_unit_upload_fail')}
</div>
);
}
return subText;
};
export const UploadUnitTable: FC<UploadUnitTableProps> = props => {
const { unitList = [], getColumns } = props;
if (unitList.length === 0) {
return null;
}
return (
<div className="upload-container">
{unitList.map((item, index) => {
const curStatus = getProcessStatus(item?.status);
const statusDescript = item?.statusDescript || '';
// 使用getColumns获取每个项目的信息
const columnInfo: ColumnInfo = getColumns
? getColumns(item, index)
: {};
const { subText, actions, formatType } = columnInfo;
return (
<ProcessProgressItem
key={item.uid}
mainText={item.name || '--'}
subText={renderSubText(curStatus, statusDescript, subText)}
tipText={
<span
data-dtestid={`${KnowledgeE2e.LocalUploadListStatus}.${item.name}`}
>
{curStatus === ProcessStatus.Failed
? statusDescript || I18n.t('datasets_unit_upload_fail')
: I18n.t('datasets_unit_upload_success')}
</span>
}
status={curStatus}
avatar={getTypeIcon({ ...item, formatType })}
actions={actions}
percent={item.percent || INIT_PERCENT}
/>
);
})}
</div>
);
};

View File

@@ -0,0 +1,138 @@
/*
* 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 {
EntityStatus,
UploadStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { WebStatus } from '@coze-arch/idl/knowledge';
import { Image } from '@coze-arch/bot-semi';
import {
IconUploadPDF,
IconUploadCSV,
IconUploadDoc,
IconUploadTxt,
IconUploadTableUrl,
IconUploadXLS,
IconUploadTextUrl,
IconUploadMD,
} from '@coze-arch/bot-icons';
import { FormatType } from '@coze-arch/bot-api/knowledge';
import { ProcessStatus } from '../../types';
enum UploadType {
PDF = 'pdf',
DOCX = 'docx',
TXT = 'txt',
XLSX = 'xlsx',
XLTX = 'xltx',
CSV = 'csv',
PNG = 'png',
JPG = 'jpg',
JPEG = 'jpeg',
WEBP = 'webp',
XLS = 'xls',
MD = 'md',
}
export const getTypeIcon = (params: {
type: string | undefined;
formatType?: FormatType;
url?: string;
inModal?: boolean;
}) => {
const { type, formatType, url } = params;
// const iconClassName = inModal ? styles['icon-size-24'] : styles.icon;
if (
formatType === FormatType.Image &&
[UploadType.JPG, UploadType.JPEG, UploadType.PNG, UploadType.WEBP].includes(
type as UploadType,
)
) {
return (
<Image
src={url}
width={24}
height={24}
style={{
borderRadius: '4px',
marginRight: '12px',
flexShrink: 0,
}}
/>
);
}
if (type === UploadType.MD) {
return <IconUploadMD />;
}
if (type === UploadType.PDF) {
return <IconUploadPDF />;
}
if (type === UploadType.DOCX) {
return <IconUploadDoc />;
}
if (type === UploadType.TXT) {
return <IconUploadTxt />;
}
if (
type === UploadType.XLSX ||
type === UploadType.XLTX ||
type === UploadType.XLS
) {
return <IconUploadXLS />;
}
if (type === UploadType.CSV) {
return <IconUploadCSV />;
}
return formatType === FormatType.Table ? (
<IconUploadTableUrl />
) : (
<IconUploadTextUrl />
);
};
export const getProcessStatus = (
status: UploadStatus | WebStatus | EntityStatus,
) => {
if (
status === UploadStatus.UPLOADING ||
status === UploadStatus.VALIDATING ||
status === UploadStatus.WAIT ||
status === WebStatus.Handling ||
status === EntityStatus.EntityStatusProcess
) {
return ProcessStatus.Processing;
}
if (
status === UploadStatus.SUCCESS ||
status === WebStatus.Finish ||
status === EntityStatus.EntityStatusSuccess
) {
return ProcessStatus.Complete;
}
if (
status === UploadStatus.VALIDATE_FAIL ||
status === UploadStatus.UPLOAD_FAIL ||
status === WebStatus.Failed ||
status === EntityStatus.EntityStatusFail
) {
return ProcessStatus.Failed;
}
return ProcessStatus.Processing;
};

View File

@@ -0,0 +1,52 @@
/*
* 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 SUCCESSFUL_UPLOAD_PROGRESS = 100;
export const POLLING_TIME = 3000;
export const MAX_UNIT_NAME_LEN = 100;
export const BOT_DATA_REFACTOR_CLASS_NAME = 'data-refactor';
export const TABLE_ACCEPT_LOCAL_FILE = ['.xls', '.xlsx', '.csv'];
interface TextUploadChannelConfig {
acceptFileTypes: string[];
fileFormatString: string;
addUnitMaxLimit: number;
}
export type Channel = 'DOUYIN' | 'DEFAULT';
const textUploadChannelConfigMap: Record<Channel, TextUploadChannelConfig> = {
DOUYIN: {
acceptFileTypes: ['.pdf', '.txt', '.doc', '.docx'],
fileFormatString: 'PDF、TXT、DOC、DOCX',
addUnitMaxLimit: 100,
},
DEFAULT: {
acceptFileTypes: ['.pdf', '.txt', '.doc', '.docx', '.md'],
fileFormatString: 'PDF、TXT、DOC、DOCX、MD',
addUnitMaxLimit: 300,
},
};
export const getTextUploadChannelConfig = (
channel?: Channel,
): TextUploadChannelConfig =>
(channel && textUploadChannelConfigMap[channel]) ||
textUploadChannelConfigMap.DEFAULT;

View File

@@ -0,0 +1,34 @@
/*
* 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 enum FrequencyDay {
ZERO = 0,
ONE = 1,
THREE = 3,
SEVEN = 7,
THIRTY = 30,
}
export enum TableSettingFormFields {
SHEET = 'sheet_id',
KEY_START_ROW = 'header_line_idx',
DATA_START_ROW = 'start_line_idx',
}
/** 知识库上传文件最大 size 100MB */
export const UNIT_MAX_MB = 100;
export const PDF_MAX_PAGES = 500;

View File

@@ -0,0 +1,32 @@
/*
* 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 {
SUCCESSFUL_UPLOAD_PROGRESS,
POLLING_TIME,
MAX_UNIT_NAME_LEN,
BOT_DATA_REFACTOR_CLASS_NAME,
} from './common';
export {
TableStatus,
MAX_TABLE_META_COLUMN_LEN,
MAX_TABLE_META_STR_LEN,
DEFAULT_TABLE_SETTINGS_FROM_ONE,
DEFAULT_TABLE_SETTINGS_FROM_ZERO,
TableSettingFormFields,
} from './table';
export { defaultCustomSegmentRule, getSeperatorOptionList } from './text';
export { FrequencyDay, UNIT_MAX_MB, PDF_MAX_PAGES } from './components';

View File

@@ -0,0 +1,52 @@
/*
* 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.
*/
/** table common constants */
export enum TableStatus {
ERROR = 'error',
LOADING = 'loading',
NORMAL = 'normal',
}
export const MAX_TABLE_META_COLUMN_LEN = 50;
export const MAX_TABLE_META_STR_LEN = 30;
/** table-local resegment unit steps */
export enum TableLocalResegmentStep {
CONFIGURATION,
PREVIEW,
PROCESSING,
}
export enum TableSettingFormFields {
SHEET = 'sheet_id',
KEY_START_ROW = 'header_line_idx',
DATA_START_ROW = 'start_line_idx',
}
export const DEFAULT_TABLE_SETTINGS_FROM_ONE = {
[TableSettingFormFields.SHEET]: 0,
[TableSettingFormFields.KEY_START_ROW]: 0,
[TableSettingFormFields.DATA_START_ROW]: 1,
};
export const DEFAULT_TABLE_SETTINGS_FROM_ZERO = {
[TableSettingFormFields.SHEET]: 0,
[TableSettingFormFields.KEY_START_ROW]: 0,
[TableSettingFormFields.DATA_START_ROW]: 0,
};

View File

@@ -0,0 +1,55 @@
/*
* 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 { type CustomSegmentRule, SeperatorType } from '../types';
const getSeperatorSelect = () => ({
[SeperatorType.LINE_BREAK]: I18n.t('datasets_Custom_segmentID_linebreak'),
[SeperatorType.LINE_BREAK2]: I18n.t('datasets_Custom_segmentID_2linebreak'),
[SeperatorType.CN_PERIOD]: I18n.t('datasets_Custom_segmentID_cnperiod'),
[SeperatorType.CN_EXCLAMATION]: I18n.t(
'datasets_Custom_segmentID_cn_exclamation',
),
[SeperatorType.EN_PERIOD]: I18n.t('datasets_Custom_segmentID_enperiod'),
[SeperatorType.EN_EXCLAMATION]: I18n.t(
'datasets_Custom_segmentID_en_exclamation',
),
[SeperatorType.CN_QUESTION]: I18n.t('datasets_Custom_segmentID_cn_question'),
[SeperatorType.EN_QUESTION]: I18n.t('datasets_Custom_segmentID_en_question'),
[SeperatorType.CUSTOM]: I18n.t('datasets_Custom_segmentID_custom'),
});
export const getSeperatorOptionList = () =>
Object.entries(getSeperatorSelect()).map(([k, label]) => ({
value: k,
label,
}));
const defaultMaxTokens = 800;
const defaultOverlap = 10;
export const defaultCustomSegmentRule: CustomSegmentRule = {
separator: {
type: SeperatorType.LINE_BREAK,
customValue: '###',
},
maxTokens: defaultMaxTokens,
preProcessRules: [],
overlap: defaultOverlap,
};

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type FooterControlsProps,
type UploadConfig,
} from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import { useImageDisplayAnnotationStepCheck } from '@/hooks/common';
import { UploadFooter } from '@/components';
import { ImageFileAddStep } from './types';
import { createImageFileAddStore, type ImageFileAddStore } from './store';
import { ImageUpload } from './steps/upload';
import { ImageProcess } from './steps/process';
import { ImageAnnotation } from './steps/annotation';
export const ImageFileAddConfig: UploadConfig<
ImageFileAddStep,
ImageFileAddStore
> = {
steps: [
{
title: I18n.t('knowledge_photo_006'),
step: ImageFileAddStep.Upload,
content: props => (
<ImageUpload
{...props}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
/>
),
},
{
title: I18n.t('knowledge_photo_007'),
step: ImageFileAddStep.Annotation,
content: props => (
<ImageAnnotation
{...props}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
/>
),
},
{
title: I18n.t('db_table_0126_015'),
step: ImageFileAddStep.Process,
content: props => (
<ImageProcess
{...props}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
/>
),
},
],
createStore: createImageFileAddStore,
useUploadMount: store => useImageDisplayAnnotationStepCheck(),
};

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ImageFileAddConfig } from './config';

View File

@@ -0,0 +1,125 @@
/* stylelint-disable max-nesting-depth */
/* stylelint-disable selector-class-pattern */
.segment-radio-wrapper {
.displayNone {
display: none;
}
.custom-wrapper {
position: relative;
width: 100%;
.form-segment {
margin-top: 33px;
margin-bottom: 15px;
.item {
margin-bottom: 20px;
.label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--coz-fg-primary);
}
.label-red::after {
content: "*";
margin-left: 4px;
font-weight: 600;
color: var(--coz-fg-hglt-red);
}
.custom-input {
margin-top: 8px;
}
}
}
}
.line {
&::before {
content: '';
position: absolute;
top: 10px;
display: inline-block;
width: 100%;
height: 1px;
margin-top: 26px;
background: var(--light-usage-border-color-border,
rgb(28 31 35 / 8%));
}
}
:global {
.semi-radioGroup.semi-radioGroup-vertical {
row-gap: 16px;
}
.semi-radioGroup {
.semi-radio-content {
flex-grow: 1;
}
.custom-wrapper {
padding-bottom: 0;
.semi-input-default,
.semi-input-wrapper-focus {
border-radius: 8px;
}
}
}
.semi-radio-cardRadioGroup {
padding: 16px 16px 16px 19px;
border: 1px solid var(--coz-stroke-plus);
border-radius: 8px;
&:hover {
background: var(--coz-mg-secondary-hovered);
}
&:active {
background: var(--coz-mg-secondary-pressed);
}
.semi-radio-extra {
margin-top: 4px;
}
}
.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);
}
}
.semi-radio-addon {
margin-bottom: 2px;
}
.semi-form-field-label {
margin-bottom: 8px;
}
.semi-checkboxGroup-vertical {
row-gap: 8px;
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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, type FC } from 'react';
import {
type ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Radio, RadioGroup } from '@coze-arch/coze-design';
import { ImageAnnotationType, ImageFileAddStep } from '../../types';
import { type ImageFileAddStore } from '../../store';
import styles from './index.module.less';
export const ImageAnnotation: FC<ContentProps<ImageFileAddStore>> = props => {
const { useStore, footer } = props;
const setCurrentStep = useStore(state => state.setCurrentStep);
const annotationType = useStore(state => state.annotationType);
const setAnnotationType = useStore(state => state.setAnnotationType);
const buttonStatus = useMemo(() => {
if (!annotationType) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}, [annotationType]);
return (
<>
<div className={styles['segment-radio-wrapper']}>
<RadioGroup
type="pureCard"
onChange={e => {
setAnnotationType(e.target.value as ImageAnnotationType);
}}
direction="vertical"
value={annotationType}
>
<Radio
data-testid={KnowledgeE2e.ImageAnnotationAiRadio}
value={ImageAnnotationType.Auto}
extra={I18n.t('knowledge_photo_009')}
>
{I18n.t('knowledge_photo_008')}
</Radio>
<Radio
data-testid={KnowledgeE2e.ImageAnnotationManualRadio}
value={ImageAnnotationType.Manual}
extra={I18n.t('knowledge_photo_011')}
>
{I18n.t('knowledge_photo_010')}
</Radio>
</RadioGroup>
</div>
{footer?.([
{
e2e: KnowledgeE2e.UploadUnitUpBtn,
type: 'primary',
theme: 'light',
text: I18n.t('datasets_createFileModel_previousBtn'),
onClick: () => setCurrentStep(ImageFileAddStep.Upload),
},
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
status: buttonStatus,
onClick: () => {
setCurrentStep(ImageFileAddStep.Process);
},
},
])}
</>
);
};

View File

@@ -0,0 +1,4 @@
.footer-sub-tip {
font-size: 12px;
color: var(--light-usage-text-color-text-2, rgb(28 31 35 / 60%));
}

View File

@@ -0,0 +1,109 @@
/*
* 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, type FC } from 'react';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import { type ContentProps } from '@coze-data/knowledge-resource-processor-core';
import { getKnowledgeIDEQuery } from '@coze-data/knowledge-common-services';
import { KnowledgeE2e } from '@coze-data/e2e';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import {
CaptionType,
DocumentSource,
FormatType,
} from '@coze-arch/bot-api/knowledge';
import { reportProcessDocumentFail } from '@/utils/common';
import { getProcessingDescMsg } from '@/utils';
import { useCreateDocument } from '@/hooks';
import { UnitProgress } from '@/components';
import { ImageAnnotationType } from '../../types';
import { type ImageFileAddStore } from '../../store';
import styles from './index.module.less';
export const ImageProcess: FC<ContentProps<ImageFileAddStore>> = props => {
const { useStore, footer } = props;
const resourceNavigate = useDataNavigate();
const params = useKnowledgeParams();
const unitList = useStore(state => state.unitList);
const annotationType = useStore(state => state.annotationType);
const progressList = useStore(state => state.progressList);
const createStatus = useStore(state => state.createStatus);
const createDocument = useCreateDocument(useStore, {
onSuccess: docRes => {
const documentInfos = docRes.document_infos ?? [];
reportProcessDocumentFail(
documentInfos,
REPORT_EVENTS.KnowledgeProcessDocument,
);
},
});
useEffect(() => {
createDocument({
format_type: FormatType.Image,
chunk_strategy: {
caption_type:
annotationType === ImageAnnotationType.Manual
? CaptionType.Manual
: CaptionType.Auto,
},
document_bases: unitList.map(item => ({
name: item.name,
source_info: {
tos_uri: item.uri,
document_source: DocumentSource.Document,
},
})),
});
}, []);
return (
<>
<UnitProgress progressList={progressList} createStatus={createStatus} />
{footer?.({
btns: [
{
e2e: KnowledgeE2e.CreateUnitConfirmBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('variable_reset_yes'),
onClick: () => {
const query = getKnowledgeIDEQuery() as Record<string, string>;
resourceNavigate.toResource?.(
'knowledge',
params.datasetID,
query,
);
},
},
],
prefix: (
<span className={styles['footer-sub-tip']}>
{getProcessingDescMsg(createStatus)}
</span>
),
})}
</>
);
};

View File

@@ -0,0 +1,115 @@
/*
* 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, type FC } from 'react';
import {
type ContentProps,
FooterBtnStatus,
UploadStatus,
type UnitItem,
UnitType,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Toast } from '@coze-arch/coze-design';
import { UploadUnitFile, UploadUnitTable } from '@/components';
import { ImageFileAddStep } from '../../types';
import { type ImageFileAddStore } from '../../store';
import { useRetry } from './use-retry';
export const ImageUpload: FC<ContentProps<ImageFileAddStore>> = props => {
const { useStore, footer } = props;
const setCurrentStep = useStore(state => state.setCurrentStep);
const unitList = useStore(state => state.unitList);
const setUnitList = useStore(state => state.setUnitList);
const onRetry = useRetry({ unitList, setUnitList });
const buttonStatus = useMemo(() => {
if (
unitList.length === 0 ||
unitList.some(
unitItem =>
unitItem.name.length === 0 ||
unitItem.status !== UploadStatus.SUCCESS,
)
) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}, [unitList]);
const hideUploadFile = false;
const AddUnitMaxLimit = 300;
const handleClickNext = () => {
setCurrentStep(ImageFileAddStep.Annotation);
};
const handleUnitListUpdate = (data: UnitItem[]) => {
const result = data;
setUnitList(result);
};
return (
<>
<UploadUnitFile
action=""
accept={'.png,.jpg,.jpeg,.webp'}
dragMainText={I18n.t('knowledge_photo_004')}
dragSubText={I18n.t('knowledge_photo_005')}
limit={AddUnitMaxLimit}
unitList={unitList}
multiple={AddUnitMaxLimit > 1}
style={hideUploadFile ? { visibility: 'hidden', height: 0 } : undefined}
setUnitList={handleUnitListUpdate}
onFinish={handleUnitListUpdate}
maxSizeMB={20}
onSizeError={file =>
Toast.error(
I18n.t('photo-size-limit', {
fileName: file.name,
}),
)
}
/>
{unitList.length > 0 ? (
<div className="mt-[25px] mb-[25px] overflow-y-auto">
<UploadUnitTable
type={UnitType.IMAGE_FILE}
edit={true}
unitList={unitList}
onChange={setUnitList}
onRetry={onRetry}
/>
</div>
) : null}
{footer?.([
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
status: buttonStatus,
onClick: handleClickNext,
},
])}
</>
);
};

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import { type UnitItem } from '@coze-data/knowledge-resource-processor-core';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { FileBizType } from '@coze-arch/bot-api/developer_api';
import { DeveloperApi } from '@coze-arch/bot-api';
import { transformUnitList, getFileExtension, getBase64 } from '@/utils';
export const useRetry = (params: {
unitList: UnitItem[];
setUnitList: (unitList: UnitItem[]) => void;
}) => {
const { unitList, setUnitList } = params;
const onRetry = async (record: UnitItem, index: number) => {
try {
const { fileInstance } = record;
if (fileInstance) {
const { name } = fileInstance;
const extension = getFileExtension(name);
const base64 = await getBase64(fileInstance);
const result = await DeveloperApi.UploadFile({
file_head: {
file_type: extension,
biz_type: FileBizType.BIZ_BOT_DATASET,
},
data: base64,
});
setUnitList(
transformUnitList({
unitList,
data: result?.data,
fileInstance,
index,
}),
);
}
} catch (e) {
const error = e as Error;
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeUploadFile,
error,
});
}
};
return onRetry;
};

View File

@@ -0,0 +1,73 @@
/*
* 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 { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import {
CreateUnitStatus,
type UploadBaseAction,
type UploadBaseState,
type ProgressItem,
type UnitItem,
} from '@coze-data/knowledge-resource-processor-core';
import { ImageAnnotationType, ImageFileAddStep } from '../types';
export type ImageFileAddStore = UploadBaseState<ImageFileAddStep> &
UploadBaseAction<ImageFileAddStep> & {
annotationType: ImageAnnotationType;
setAnnotationType: (annotationType: ImageAnnotationType) => void;
};
const storeStaticValues: Pick<
ImageFileAddStore,
| 'unitList'
| 'currentStep'
| 'annotationType'
| 'createStatus'
| 'progressList'
> = {
currentStep: ImageFileAddStep.Upload,
unitList: [],
annotationType: ImageAnnotationType.Auto,
createStatus: CreateUnitStatus.UPLOAD_UNIT,
progressList: [],
};
export const createImageFileAddStore = () =>
create<ImageFileAddStore>()(
devtools((set, get, store) => ({
...storeStaticValues,
setCurrentStep: (currentStep: ImageFileAddStep) => {
set({ currentStep });
},
setUnitList: (unitList: UnitItem[]) => {
set({ unitList });
},
setAnnotationType: (annotationType: ImageAnnotationType) => {
set({ annotationType });
},
setCreateStatus: (createStatus: CreateUnitStatus) => {
set({ createStatus });
},
setProgressList: (progressList: ProgressItem[]) => {
set({ progressList });
},
reset: () => {
set(storeStaticValues);
},
})),
);

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum ImageFileAddStep {
Upload = 0,
Annotation,
Process,
}
export enum ImageAnnotationType {
Auto = 'auto',
Manual = 'manual',
}

View File

@@ -0,0 +1,58 @@
/*
* 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 UploadConfig,
type FooterControlsProps,
} from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import { UploadFooter } from '@/components';
import { useTableCheck } from '../../hooks';
import {
type UploadTableAction,
type UploadTableState,
} from '../../../interface';
import { type TableLocalContentProps } from './types';
import { createTableCustomAddStore } from './store';
import { TableCustomCreate } from './steps';
import { TableCustomStep, TABLE_CUSTOM_WRAPPER_CLASS_NAME } from './constant';
export const TableCustomAddConfig: UploadConfig<
TableCustomStep,
UploadTableAction<TableCustomStep> & UploadTableState<TableCustomStep>
> = {
steps: [
{
content: (props: TableLocalContentProps) => (
<TableCustomCreate
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step2'),
step: TableCustomStep.CREATE,
},
],
createStore: createTableCustomAddStore,
showStep: false,
className: TABLE_CUSTOM_WRAPPER_CLASS_NAME,
useUploadMount: store => useTableCheck(store),
};

View File

@@ -0,0 +1,22 @@
/*
* 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.
*/
/** table-custom add steps */
export enum TableCustomStep {
CREATE,
}
export const TABLE_CUSTOM_WRAPPER_CLASS_NAME = 'table-custom-wrapper';

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { TableCustomAddConfig } from './config';

View File

@@ -0,0 +1,12 @@
.create-table-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
}
.add-column-button {
width: 114px;
margin-top: 16px;
}

View File

@@ -0,0 +1,168 @@
/*
* 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, useState } from 'react';
import { nanoid } from 'nanoid';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import {
FooterBtnStatus,
type ContentProps,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Tooltip } from '@coze-arch/bot-semi';
import { type DocTableColumn } from '@coze-arch/bot-api/memory';
import { DocumentSource, FormatType } from '@coze-arch/bot-api/knowledge';
import { IconCozPlus } from '@coze-arch/coze-design/icons';
import { Button } from '@coze-arch/coze-design';
import { type AddCustomTableMeta } from '@/types';
import { useCreateDocumentReq } from '@/services';
import { getCustomStatus } from '@/features/knowledge-type/table/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import { MAX_TABLE_META_COLUMN_LEN } from '@/constants';
import { TableStructure, TableStructureTitle } from '@/components';
import styles from './create-v2.module.less';
function getCreateDocumentParams(
metaData: AddCustomTableMeta & { key?: string; id?: string },
isAppend: boolean,
) {
return {
document_bases: [
{
name: '', // table custom传控
source_info: {
document_source: DocumentSource.Custom,
},
table_meta: metaData.map((meta, index) => ({
column_name: meta.column_name,
is_semantic: meta.is_semantic,
column_type: meta.column_type,
desc: meta.desc,
sequence: index.toString(),
})),
},
],
format_type: FormatType.Table,
is_append: isAppend,
};
}
export const TableCustomCreate = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { footer, useStore } = props;
const params = useKnowledgeParams();
const resourceNavigate = useDataNavigate();
const { datasetID: knowledgeId } = params;
const [footerStatus, setFooterStatus] = useState(FooterBtnStatus.ENABLE);
const currentUuid = nanoid();
const [metaData, setMetaData] = useState<AddCustomTableMeta>([
{
id: currentUuid,
key: currentUuid,
column_name: '',
is_semantic: false,
},
]);
const documentList = useStore(state => state.documentList) ?? [];
const isAppend = documentList.length !== 0;
const createDocument = useCreateDocumentReq({
onSuccess: () => {
setFooterStatus(FooterBtnStatus.ENABLE);
resourceNavigate.toResource?.('knowledge', knowledgeId);
},
onFail: () => {
setFooterStatus(FooterBtnStatus.DISABLE);
},
});
useEffect(() => {
if (footerStatus !== FooterBtnStatus.LOADING) {
setFooterStatus(getCustomStatus(metaData, 'toBeDelete')); // toBeDelete 待删
}
}, [metaData]);
const TooltipWrapper = ({ children }: { children: JSX.Element }) => {
if (metaData.length >= MAX_TABLE_META_COLUMN_LEN) {
return (
<Tooltip trigger="hover" content={I18n.t('knowledge_1222_01')}>
{children}
</Tooltip>
);
}
return <>{children}</>;
};
return (
<div className={styles['create-table-wrapper']}>
<TableStructureTitle />
<TableStructure
initValid
isDragTable
data={metaData}
setData={setMetaData as (v: Array<DocTableColumn>) => void}
/>
<TooltipWrapper>
<Button
data-testid={KnowledgeE2e.TableCustomUAddFieldBtn}
className={styles['add-column-button']}
type="tertiary"
disabled={metaData.length >= MAX_TABLE_META_COLUMN_LEN}
onClick={() => {
const curUuid = nanoid();
setMetaData(
metaData.concat({
id: curUuid,
key: curUuid,
column_name: '',
is_semantic: false,
}),
);
}}
block={false}
icon={<IconCozPlus />}
>
{I18n.t('datasets_segment_tableStructure_add_field')}
</Button>
</TooltipWrapper>
{footer
? footer([
{
e2e: KnowledgeE2e.CreateUnitConfirmBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('variable_reset_yes'),
onClick: () => {
setFooterStatus(FooterBtnStatus.LOADING);
createDocument(getCreateDocumentParams(metaData, isAppend));
},
status: footerStatus,
},
])
: null}
</div>
);
};

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type TableLocalContentProps } from '../../types';
import { TableCustomCreate as TableCustomCreateV2 } from './create-v2';
/** 到时候要删掉 TableCustomCreateV1*/
export const TableCustomCreate = (props: TableLocalContentProps) => (
<TableCustomCreateV2 {...props} />
);

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { TableCustomCreate } from './create';

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { TableCustomCreate } from './create';

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