chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:24:55 +08:00
commit 7baeb17845
15305 changed files with 2077301 additions and 0 deletions
@@ -0,0 +1,31 @@
import { mergeConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: config =>
mergeConfig(config, {
plugins: [
svgr({
svgrOptions: {
native: false,
},
}),
],
}),
};
export default config;
@@ -0,0 +1,14 @@
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});
@@ -0,0 +1,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`
@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["dist"]
}
]
}
@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});
@@ -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"
}
}
@@ -0,0 +1,10 @@
.common-svg-icon(@size: 14px, @color: #3370ff) {
>svg {
width: @size;
height: @size;
>path {
fill: @color;
}
}
}
@@ -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

@@ -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,
};
};
@@ -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;
})();
@@ -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;
})();
@@ -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';
/**
* Convert the web page information returned by the API into view data
*/
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 there is no data, return an empty array
if (!responseData?.[webID]?.web_info) {
return [] as ViewOnlinePageDetailProps[];
}
const webInfo = responseData[webID].web_info;
const mainPageData = transformWebInfoToViewData(webInfo);
const result = [mainPageData];
// Processing subpage data
if (webInfo?.subpages?.length) {
const subpagesData = webInfo.subpages.map(transformWebInfoToViewData);
result.push(...subpagesData);
}
return result;
},
);
return {
data: data || [],
loading,
runAsync,
mutate,
};
};
@@ -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%)
}
@@ -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';
@@ -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;
}
}
}
}
@@ -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;
};
/**
* Always use the card style and conform to the UI design style {@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>
);
}
@@ -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;
}
/**
* A collapsible panel with Collapsible for better UI design
*/
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>
);
}
@@ -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')}
/>
);
@@ -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,
}}
/>
);
};
@@ -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%);
}
}
@@ -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>
);
@@ -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;
@@ -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: Expansion
id: string;
title: string;
/** Is there filtered content? */
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>
);
@@ -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;
}
}
@@ -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>
);
};
@@ -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';
@@ -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;
}
}
@@ -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>
);
};
@@ -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;
}
}
@@ -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>
);
@@ -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';
@@ -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;
}
}
}
@@ -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';
@@ -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>
);
};
@@ -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;
}
/** Remove hover line style */
.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;
}
@@ -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;
// Selected table id
const sheetId = useMemo(
() => settings[TableSettingFormFields.SHEET] || 0,
[settings],
);
// Selected table name
const sheetName = useMemo(
() => (sheet_list || []).find(sheet => sheet?.id === sheetId)?.sheet_name,
[sheet_list, sheetId],
);
// The amount of data for the selected table
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>
);
};
@@ -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;
}
}
}
@@ -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'),
// The starting row of the data must be larger than the header row
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) {
// Modify the sheet, initialize the header and data rows
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>
);
};
@@ -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;
};
@@ -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;
}
}
/** Temporary comment, to be used later, to be discussed */
// &: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 remove background color */
.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;
}
}
@@ -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 to be solved
// 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>
),
},
];
// In the preview scenario, no operation column is required
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>
);
@@ -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.
*/
// component related
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';
@@ -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;
}
}
}
@@ -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';
@@ -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) {
// update frequency
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>
);
};
@@ -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;
}
}
}
@@ -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';
@@ -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;
}
/** Type assertion imported parameter yes no, button array */
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>
);
};
@@ -0,0 +1,63 @@
/*
* 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;
}
// Upload page navigation bar
export const UploadActionNavbar = ({ title }: UploadActionNavbarProps) => {
const params = useKnowledgeParams();
const resourceNavigate = useDataNavigate();
// TODO: Scene layer maintenance of hzf biz differentiation
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>
);
};
@@ -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 => {
// The reason for not limiting by the maxSize property is
// Only the beforeUpload hook can change 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: Subsequent pdfjs calls from other locations should also be changed to asynchronous loading
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;
};
@@ -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 {
// business
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;
// Success or failure, after the inspection fails, or if the interface data is returned, the progress bar will not be updated.
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,
});
}
};
@@ -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%);
}
}
@@ -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';
@@ -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 />;
};
@@ -0,0 +1,15 @@
/*
* 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.
*/
@@ -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';
@@ -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>
);
};
@@ -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;
}, {});
/** Filter the fileList to keep only the files in the unitList, that is, the files that were uploaded successfully. */
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 }),
};
});
};
@@ -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>
);
}
@@ -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,
}}
/>
);
@@ -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';
@@ -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';
@@ -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%);
}
}
}
}
@@ -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); // You need to use your own state, otherwise there will be a bug that cannot enter Chinese.
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>
);
};
@@ -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); // You need to use your own state, otherwise there will be a bug that cannot enter Chinese.
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>
);
};
@@ -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;
}
@@ -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;
};
@@ -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';
@@ -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;
// Do not show retry copy
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;
}
@@ -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 || '';
// Use getColumns to obtain information for each item
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>
);
};
@@ -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;
};
@@ -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', '.docx', '.md'],
fileFormatString: 'PDF、TXT、DOCX、MD',
addUnitMaxLimit: 300,
},
};
export const getTextUploadChannelConfig = (
channel?: Channel,
): TextUploadChannelConfig =>
(channel && textUploadChannelConfigMap[channel]) ||
textUploadChannelConfigMap.DEFAULT;
@@ -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.
*/
/** update frequency */
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',
}
/** Knowledge base upload file maximum size 100MB */
export const UNIT_MAX_MB = 100;
export const PDF_MAX_PAGES = 500;
@@ -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';
@@ -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,
};
@@ -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,
};
@@ -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(),
};
@@ -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';
@@ -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;
}
}
}
@@ -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);
},
},
])}
</>
);
};
@@ -0,0 +1,4 @@
.footer-sub-tip {
font-size: 12px;
color: var(--light-usage-text-color-text-2, rgb(28 31 35 / 60%));
}
@@ -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 { 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>
),
})}
</>
);
};
@@ -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,
},
])}
</>
);
};
@@ -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;
};
@@ -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);
},
})),
);
@@ -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',
}
@@ -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),
};
@@ -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';
@@ -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';
@@ -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;
}
@@ -0,0 +1,171 @@
/*
* 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 control
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 to be deleted
}
}, [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>
);
};
@@ -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';
/** Delete TableCustomCreateV1 at that time.*/
export const TableCustomCreate = (props: TableLocalContentProps) => (
<TableCustomCreateV2 {...props} />
);
@@ -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';
@@ -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