feat: manually mirror opencoze's code from bytedance

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useMemo, type FC } from 'react';
import {
type ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Radio, RadioGroup } from '@coze-arch/coze-design';
import { ImageAnnotationType, ImageFileAddStep } from '../../types';
import { type ImageFileAddStore } from '../../store';
import styles from './index.module.less';
export const ImageAnnotation: FC<ContentProps<ImageFileAddStore>> = props => {
const { useStore, footer } = props;
const setCurrentStep = useStore(state => state.setCurrentStep);
const annotationType = useStore(state => state.annotationType);
const setAnnotationType = useStore(state => state.setAnnotationType);
const buttonStatus = useMemo(() => {
if (!annotationType) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}, [annotationType]);
return (
<>
<div className={styles['segment-radio-wrapper']}>
<RadioGroup
type="pureCard"
onChange={e => {
setAnnotationType(e.target.value as ImageAnnotationType);
}}
direction="vertical"
value={annotationType}
>
<Radio
data-testid={KnowledgeE2e.ImageAnnotationAiRadio}
value={ImageAnnotationType.Auto}
extra={I18n.t('knowledge_photo_009')}
>
{I18n.t('knowledge_photo_008')}
</Radio>
<Radio
data-testid={KnowledgeE2e.ImageAnnotationManualRadio}
value={ImageAnnotationType.Manual}
extra={I18n.t('knowledge_photo_011')}
>
{I18n.t('knowledge_photo_010')}
</Radio>
</RadioGroup>
</div>
{footer?.([
{
e2e: KnowledgeE2e.UploadUnitUpBtn,
type: 'primary',
theme: 'light',
text: I18n.t('datasets_createFileModel_previousBtn'),
onClick: () => setCurrentStep(ImageFileAddStep.Upload),
},
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
status: buttonStatus,
onClick: () => {
setCurrentStep(ImageFileAddStep.Process);
},
},
])}
</>
);
};

View File

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

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, type FC } from 'react';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import { type ContentProps } from '@coze-data/knowledge-resource-processor-core';
import { getKnowledgeIDEQuery } from '@coze-data/knowledge-common-services';
import { KnowledgeE2e } from '@coze-data/e2e';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { I18n } from '@coze-arch/i18n';
import {
CaptionType,
DocumentSource,
FormatType,
} from '@coze-arch/bot-api/knowledge';
import { reportProcessDocumentFail } from '@/utils/common';
import { getProcessingDescMsg } from '@/utils';
import { useCreateDocument } from '@/hooks';
import { UnitProgress } from '@/components';
import { ImageAnnotationType } from '../../types';
import { type ImageFileAddStore } from '../../store';
import styles from './index.module.less';
export const ImageProcess: FC<ContentProps<ImageFileAddStore>> = props => {
const { useStore, footer } = props;
const resourceNavigate = useDataNavigate();
const params = useKnowledgeParams();
const unitList = useStore(state => state.unitList);
const annotationType = useStore(state => state.annotationType);
const progressList = useStore(state => state.progressList);
const createStatus = useStore(state => state.createStatus);
const createDocument = useCreateDocument(useStore, {
onSuccess: docRes => {
const documentInfos = docRes.document_infos ?? [];
reportProcessDocumentFail(
documentInfos,
REPORT_EVENTS.KnowledgeProcessDocument,
);
},
});
useEffect(() => {
createDocument({
format_type: FormatType.Image,
chunk_strategy: {
caption_type:
annotationType === ImageAnnotationType.Manual
? CaptionType.Manual
: CaptionType.Auto,
},
document_bases: unitList.map(item => ({
name: item.name,
source_info: {
tos_uri: item.uri,
document_source: DocumentSource.Document,
},
})),
});
}, []);
return (
<>
<UnitProgress progressList={progressList} createStatus={createStatus} />
{footer?.({
btns: [
{
e2e: KnowledgeE2e.CreateUnitConfirmBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('variable_reset_yes'),
onClick: () => {
const query = getKnowledgeIDEQuery() as Record<string, string>;
resourceNavigate.toResource?.(
'knowledge',
params.datasetID,
query,
);
},
},
],
prefix: (
<span className={styles['footer-sub-tip']}>
{getProcessingDescMsg(createStatus)}
</span>
),
})}
</>
);
};

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useMemo, type FC } from 'react';
import {
type ContentProps,
FooterBtnStatus,
UploadStatus,
type UnitItem,
UnitType,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Toast } from '@coze-arch/coze-design';
import { UploadUnitFile, UploadUnitTable } from '@/components';
import { ImageFileAddStep } from '../../types';
import { type ImageFileAddStore } from '../../store';
import { useRetry } from './use-retry';
export const ImageUpload: FC<ContentProps<ImageFileAddStore>> = props => {
const { useStore, footer } = props;
const setCurrentStep = useStore(state => state.setCurrentStep);
const unitList = useStore(state => state.unitList);
const setUnitList = useStore(state => state.setUnitList);
const onRetry = useRetry({ unitList, setUnitList });
const buttonStatus = useMemo(() => {
if (
unitList.length === 0 ||
unitList.some(
unitItem =>
unitItem.name.length === 0 ||
unitItem.status !== UploadStatus.SUCCESS,
)
) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}, [unitList]);
const hideUploadFile = false;
const AddUnitMaxLimit = 300;
const handleClickNext = () => {
setCurrentStep(ImageFileAddStep.Annotation);
};
const handleUnitListUpdate = (data: UnitItem[]) => {
const result = data;
setUnitList(result);
};
return (
<>
<UploadUnitFile
action=""
accept={'.png,.jpg,.jpeg,.webp'}
dragMainText={I18n.t('knowledge_photo_004')}
dragSubText={I18n.t('knowledge_photo_005')}
limit={AddUnitMaxLimit}
unitList={unitList}
multiple={AddUnitMaxLimit > 1}
style={hideUploadFile ? { visibility: 'hidden', height: 0 } : undefined}
setUnitList={handleUnitListUpdate}
onFinish={handleUnitListUpdate}
maxSizeMB={20}
onSizeError={file =>
Toast.error(
I18n.t('photo-size-limit', {
fileName: file.name,
}),
)
}
/>
{unitList.length > 0 ? (
<div className="mt-[25px] mb-[25px] overflow-y-auto">
<UploadUnitTable
type={UnitType.IMAGE_FILE}
edit={true}
unitList={unitList}
onChange={setUnitList}
onRetry={onRetry}
/>
</div>
) : null}
{footer?.([
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
status: buttonStatus,
onClick: handleClickNext,
},
])}
</>
);
};

View File

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

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import {
CreateUnitStatus,
type UploadBaseAction,
type UploadBaseState,
type ProgressItem,
type UnitItem,
} from '@coze-data/knowledge-resource-processor-core';
import { ImageAnnotationType, ImageFileAddStep } from '../types';
export type ImageFileAddStore = UploadBaseState<ImageFileAddStep> &
UploadBaseAction<ImageFileAddStep> & {
annotationType: ImageAnnotationType;
setAnnotationType: (annotationType: ImageAnnotationType) => void;
};
const storeStaticValues: Pick<
ImageFileAddStore,
| 'unitList'
| 'currentStep'
| 'annotationType'
| 'createStatus'
| 'progressList'
> = {
currentStep: ImageFileAddStep.Upload,
unitList: [],
annotationType: ImageAnnotationType.Auto,
createStatus: CreateUnitStatus.UPLOAD_UNIT,
progressList: [],
};
export const createImageFileAddStore = () =>
create<ImageFileAddStore>()(
devtools((set, get, store) => ({
...storeStaticValues,
setCurrentStep: (currentStep: ImageFileAddStep) => {
set({ currentStep });
},
setUnitList: (unitList: UnitItem[]) => {
set({ unitList });
},
setAnnotationType: (annotationType: ImageAnnotationType) => {
set({ annotationType });
},
setCreateStatus: (createStatus: CreateUnitStatus) => {
set({ createStatus });
},
setProgressList: (progressList: ProgressItem[]) => {
set({ progressList });
},
reset: () => {
set(storeStaticValues);
},
})),
);

View File

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

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UploadConfig,
type FooterControlsProps,
} from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import { UploadFooter } from '@/components';
import { useTableCheck } from '../../hooks';
import {
type UploadTableAction,
type UploadTableState,
} from '../../../interface';
import { type TableLocalContentProps } from './types';
import { createTableCustomAddStore } from './store';
import { TableCustomCreate } from './steps';
import { TableCustomStep, TABLE_CUSTOM_WRAPPER_CLASS_NAME } from './constant';
export const TableCustomAddConfig: UploadConfig<
TableCustomStep,
UploadTableAction<TableCustomStep> & UploadTableState<TableCustomStep>
> = {
steps: [
{
content: (props: TableLocalContentProps) => (
<TableCustomCreate
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step2'),
step: TableCustomStep.CREATE,
},
],
createStore: createTableCustomAddStore,
showStep: false,
className: TABLE_CUSTOM_WRAPPER_CLASS_NAME,
useUploadMount: store => useTableCheck(store),
};

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect, useState } from 'react';
import { nanoid } from 'nanoid';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import {
FooterBtnStatus,
type ContentProps,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Tooltip } from '@coze-arch/bot-semi';
import { type DocTableColumn } from '@coze-arch/bot-api/memory';
import { DocumentSource, FormatType } from '@coze-arch/bot-api/knowledge';
import { IconCozPlus } from '@coze-arch/coze-design/icons';
import { Button } from '@coze-arch/coze-design';
import { type AddCustomTableMeta } from '@/types';
import { useCreateDocumentReq } from '@/services';
import { getCustomStatus } from '@/features/knowledge-type/table/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import { MAX_TABLE_META_COLUMN_LEN } from '@/constants';
import { TableStructure, TableStructureTitle } from '@/components';
import styles from './create-v2.module.less';
function getCreateDocumentParams(
metaData: AddCustomTableMeta & { key?: string; id?: string },
isAppend: boolean,
) {
return {
document_bases: [
{
name: '', // table custom传控
source_info: {
document_source: DocumentSource.Custom,
},
table_meta: metaData.map((meta, index) => ({
column_name: meta.column_name,
is_semantic: meta.is_semantic,
column_type: meta.column_type,
desc: meta.desc,
sequence: index.toString(),
})),
},
],
format_type: FormatType.Table,
is_append: isAppend,
};
}
export const TableCustomCreate = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { footer, useStore } = props;
const params = useKnowledgeParams();
const resourceNavigate = useDataNavigate();
const { datasetID: knowledgeId } = params;
const [footerStatus, setFooterStatus] = useState(FooterBtnStatus.ENABLE);
const currentUuid = nanoid();
const [metaData, setMetaData] = useState<AddCustomTableMeta>([
{
id: currentUuid,
key: currentUuid,
column_name: '',
is_semantic: false,
},
]);
const documentList = useStore(state => state.documentList) ?? [];
const isAppend = documentList.length !== 0;
const createDocument = useCreateDocumentReq({
onSuccess: () => {
setFooterStatus(FooterBtnStatus.ENABLE);
resourceNavigate.toResource?.('knowledge', knowledgeId);
},
onFail: () => {
setFooterStatus(FooterBtnStatus.DISABLE);
},
});
useEffect(() => {
if (footerStatus !== FooterBtnStatus.LOADING) {
setFooterStatus(getCustomStatus(metaData, 'toBeDelete')); // toBeDelete 待删
}
}, [metaData]);
const TooltipWrapper = ({ children }: { children: JSX.Element }) => {
if (metaData.length >= MAX_TABLE_META_COLUMN_LEN) {
return (
<Tooltip trigger="hover" content={I18n.t('knowledge_1222_01')}>
{children}
</Tooltip>
);
}
return <>{children}</>;
};
return (
<div className={styles['create-table-wrapper']}>
<TableStructureTitle />
<TableStructure
initValid
isDragTable
data={metaData}
setData={setMetaData as (v: Array<DocTableColumn>) => void}
/>
<TooltipWrapper>
<Button
data-testid={KnowledgeE2e.TableCustomUAddFieldBtn}
className={styles['add-column-button']}
type="tertiary"
disabled={metaData.length >= MAX_TABLE_META_COLUMN_LEN}
onClick={() => {
const curUuid = nanoid();
setMetaData(
metaData.concat({
id: curUuid,
key: curUuid,
column_name: '',
is_semantic: false,
}),
);
}}
block={false}
icon={<IconCozPlus />}
>
{I18n.t('datasets_segment_tableStructure_add_field')}
</Button>
</TooltipWrapper>
{footer
? footer([
{
e2e: KnowledgeE2e.CreateUnitConfirmBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('variable_reset_yes'),
onClick: () => {
setFooterStatus(FooterBtnStatus.LOADING);
createDocument(getCreateDocumentParams(metaData, isAppend));
},
status: footerStatus,
},
])
: null}
</div>
);
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type StateCreator } from 'zustand';
import {
createTableSlice,
getDefaultState,
} from '@/features/knowledge-type/table/slice';
import type {
UploadTableAction,
UploadTableState,
} from '@/features/knowledge-type/table/interface';
import { DEFAULT_TABLE_SETTINGS_FROM_ZERO } from '@/constants';
export const createTableCustomSlice: StateCreator<
UploadTableState<number> & UploadTableAction<number>
> = (set, get, store) => ({
...createTableSlice(set, get, store),
tableSettings: DEFAULT_TABLE_SETTINGS_FROM_ZERO,
reset: () => {
set({
...getDefaultState(),
tableSettings: DEFAULT_TABLE_SETTINGS_FROM_ZERO,
});
},
});

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import {
type UploadTableAction,
type UploadTableState,
} from '@/features/knowledge-type/table/interface';
import { type TableCustomStep } from '../constant';
import { createTableCustomSlice } from './slice';
export const createTableCustomAddStore = () =>
create<
UploadTableState<TableCustomStep> & UploadTableAction<TableCustomStep>
>()(
devtools((set, get, store) => ({
...createTableCustomSlice(set, get, store),
})),
);

View File

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

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UploadConfig,
type FooterControlsProps,
type ContentProps,
} from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import { UploadFooter } from '@/components';
import {
type UploadTableAction,
type UploadTableState,
} from '../../../interface';
import { createTableCustomIncrementalStore } from './store';
import { TableUpload } from './steps';
import { TableCustomIncrementalStep } from './constants';
type TableCustomContentProps = ContentProps<
UploadTableAction<TableCustomIncrementalStep> &
UploadTableState<TableCustomIncrementalStep>
>;
export const TableCustomIncrementalConfig: UploadConfig<
TableCustomIncrementalStep,
UploadTableAction<TableCustomIncrementalStep> &
UploadTableState<TableCustomIncrementalStep>
> = {
steps: [
{
content: (props: TableCustomContentProps) => (
<TableUpload
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step2'),
step: TableCustomIncrementalStep.UPLOAD,
},
],
createStore: createTableCustomIncrementalStore,
className: 'table-custom-increment-wrapper',
showStep: false,
};

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 { useCallback, useRef, useState, useEffect } from 'react';
import { type TableDataItem } from '@coze-data/knowledge-modal-base';
import { type TableViewRecord } from '@coze-common/table-view';
import { type DocTableColumn } from '@coze-arch/bot-api/memory';
import { transformTableData } from './utils';
interface UseTableOperationsParams {
initialData?: TableViewRecord[];
createTableSegment?: () => void;
editTableSegment?: (data: string | TableDataItem[]) => void;
}
function useTableOperations({
initialData = [],
editTableSegment,
}: UseTableOperationsParams) {
const [tableData, setTableData] = useState<TableViewRecord[]>(initialData);
const tableDataRef = useRef<TableViewRecord[]>(initialData);
const sheetStructureRef = useRef<DocTableColumn[]>([]);
const [editItemIndex, setEditItemIndex] = useState<number>(-1);
const handleCellUpdate = useCallback(
(_record: TableViewRecord, index: number) => {
setTableData(prevData =>
prevData.map((item, i) => {
if (i === index) {
return { ...item, ..._record };
}
return item;
}),
);
},
[tableData],
);
const handleDel = useCallback(
(indexList: (string | number)[]) => {
const currentTableData = tableDataRef.current;
const newTableData = [...currentTableData];
indexList.forEach(index => {
newTableData.splice(Number(index), 1);
});
setTableData(newTableData);
},
[tableDataRef.current],
);
const handleEdit = useCallback(
(record: TableViewRecord, index: string | number) => {
const currentTableData = tableDataRef.current;
setEditItemIndex(Number(index));
const curTableItem = currentTableData[Number(index)];
const curTableItemKeys = Object.keys(curTableItem);
const editTableData = curTableItemKeys.map(key => {
const sItem = sheetStructureRef.current.find(
item => item.column_name === key,
);
return {
column_name: sItem?.column_name || '',
column_id: sItem?.id || '',
is_semantic: Boolean(sItem?.is_semantic),
value: curTableItem[key] as string,
};
});
editTableSegment?.(editTableData);
},
[sheetStructureRef.current, tableDataRef.current],
);
const handleAdd = useCallback(() => {
const tableDataItem = transformTableData<DocTableColumn>(
sheetStructureRef.current,
'column_name',
);
const currentTableData = tableDataRef.current;
setTableData([...currentTableData, ...tableDataItem]);
}, [tableDataRef.current]);
useEffect(() => {
tableDataRef.current = tableData;
}, [tableData]);
return {
editItemIndex,
tableData,
setTableData,
handleCellUpdate,
handleAdd,
handleDel,
handleEdit,
tableDataRef,
sheetStructureRef,
};
}
export default useTableOperations;

View File

@@ -0,0 +1,121 @@
/* stylelint-disable no-duplicate-selectors */
/* stylelint-disable declaration-no-important */
/* stylelint-disable max-nesting-depth */
.structure-bar {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
&-title {
font-size: 14px;
font-weight: 600;
font-style: normal;
line-height: 20px;
color: var(--coz-fg-primary);
}
}
.custom-table-container {
display: flex;
flex-direction: column;
height: 100%;
}
.unit-table-container {
border: 1px solid var(--coz-stroke-primary);
border-radius: 8px;
.unit-table-view {
min-height: 90px;
max-height: 540px;
:global {
.semi-table-wrapper {
margin-top: 0;
}
.coz-tag.coz-tag-primary {
font-weight: 400;
}
.semi-table-thead>.semi-table-row>.semi-table-row-head {
border-bottom: 1px solid var(--coz-stroke-primary);
}
.semi-table-header {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.semi-table-row {
&:last-child {
.semi-table-row-cell {
border-bottom: 1px solid transparent;
}
}
}
.semi-table-row:hover>.semi-table-row-cell:first-child {
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.semi-table-row:hover>.semi-table-row-cell:last-child {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.semi-table-body {
max-height: 499px !important;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}
}
}
.table-view-title {
display: flex;
flex-direction: row;
align-items: center;
.semantic-tag {
margin-left: 8px;
}
}
.unit-table-empty {
height: 64px;
}
}
.footer-toolbar {
margin-top: 10px;
}
.spin {
:global {
.semi-spin-wrapper {
position: absolute;
}
.semi-tabs-content {
padding: 0;
}
.semi-spin-children {
height: 100%;
}
}
}
.footer-sub-tip {
font-size: 12px;
color: var(--coz-fg-primary);
}
.column-type {
margin-left: 6px;
}

View File

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

View File

@@ -0,0 +1,321 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @coze-arch/max-line-per-function */
import React, { useEffect, useMemo, useRef, useState } from 'react';
import classNames from 'classnames';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import { type ContentProps } from '@coze-data/knowledge-resource-processor-core';
import {
type TableDataItem,
useTableSegmentModal,
ModalActionType,
transSliceContentOutput,
} from '@coze-data/knowledge-modal-base';
import { KnowledgeE2e } from '@coze-data/e2e';
import {
TableView,
type TableViewColumns,
type TableViewRecord,
} from '@coze-common/table-view';
import { I18n } from '@coze-arch/i18n';
import { Spin } from '@coze-arch/bot-semi';
import { type DocTableColumn } from '@coze-arch/bot-api/memory';
import { ColumnType } from '@coze-arch/bot-api/memory';
import {
DocumentSource,
type DocumentInfo,
} from '@coze-arch/bot-api/knowledge';
import { IconCozPlus } from '@coze-arch/coze-design/icons';
import { Button } from '@coze-arch/coze-design';
import { getAddSegmentParams } from '@/features/knowledge-type/table/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import {
useFetchAddSegmentReq,
useFetchListDocumentReq,
useFetchTableInfoReq,
} from '@/features/knowledge-type/table/hooks';
import { TableStatus } from '@/constants';
import {
getSubmitBtnStatus,
tableDataCleaning,
transformTableData,
} from './utils';
import useTableOperations from './hooks';
import styles from './index.module.less';
interface StructureBarProps {
docTitle: string;
}
const StructureBar = ({ docTitle }: StructureBarProps) => (
<div
className={styles['structure-bar']}
data-testid={KnowledgeE2e.IncrementTableUploadStructureTitle}
>
<div className={styles['structure-bar-title']}>{docTitle}</div>
</div>
);
interface TableContainerProps {
loading: boolean;
tableData: TableViewRecord[];
columns: TableViewColumns[];
handleAdd: () => void;
handleDel: (indexList: (string | number)[]) => void;
handleEdit: (record: TableViewRecord, index: string | number) => void;
}
const TableContainer = ({
loading,
tableData,
columns,
handleAdd,
handleDel,
handleEdit,
}: TableContainerProps) => (
<>
<div className={`${styles['unit-table-container']} `}>
<Spin
spinning={loading}
wrapperClassName={styles.spin}
size="large"
style={{ width: '100%', height: '100%' }}
>
{tableData.length ? (
<TableView
className={`${styles['unit-table-view']} `}
dataSource={tableData}
columns={columns}
rowOperation
editProps={{
onDelete: handleDel,
onEdit: handleEdit,
}}
/>
) : (
<div className={styles['unit-table-empty']}></div>
)}
</Spin>
</div>
<div className={`${styles['footer-toolbar']} `}>
<Button
data-testid={KnowledgeE2e.IncrementTableUploadStructureAddBtn}
className={styles['structure-bar-button']}
type="tertiary"
onClick={() => {
handleAdd();
}}
icon={<IconCozPlus />}
>
{I18n.t('knowledge_custom_add_content')}
</Button>
</div>
</>
);
export const TableUpload = <
T extends UploadTableState<number> & UploadTableAction<number>,
>({
useStore,
footer,
}: ContentProps<T>) => {
const params = useKnowledgeParams();
const { docID, spaceID, datasetID } = params;
const setStatus = useStore(state => state.setStatus);
const tableStatus = useStore(state => state.status);
const [columns, setColumns] = useState<TableViewColumns[]>([]);
const [docInfo, setDocInfo] = useState<DocumentInfo>({});
const tableContainerRef = useRef<HTMLDivElement | null>(null);
const tableMetaRef = useRef<DocTableColumn[]>([]);
const docTitle = useMemo(() => docInfo?.name || '', [docInfo]);
const { node: TableSegmentModalNode, edit: editTableSegment } =
useTableSegmentModal({
title: I18n.t('knowledg_table_segments_content'),
meta: tableMetaRef.current || [],
onFinish: (actionType, data) => {
const curTableItem = transformTableData<TableDataItem>(
data,
'column_name',
'value',
);
const hasEdit = actionType === ModalActionType.Edit;
if (hasEdit) {
handleCellUpdate(curTableItem[0], editItemIndex);
} else {
const newTableData = [...tableData, ...curTableItem];
setTableData(newTableData);
}
},
});
const {
editItemIndex,
tableData,
setTableData,
sheetStructureRef,
handleCellUpdate,
handleDel,
handleEdit,
handleAdd,
} = useTableOperations({
editTableSegment,
});
const fetchTableInfo = useFetchTableInfoReq(
res => {
const {
tableColumns,
tableData: sTableData,
sheetStructure: tableSchema,
} = tableDataCleaning({
data: res,
tableContainerRef,
handleCellUpdate,
handleEdit,
handleDel,
});
if (tableColumns.length > 0) {
setColumns(tableColumns);
}
if (sTableData.length > 0) {
setTableData(sTableData);
}
if (tableSchema.length > 0) {
tableMetaRef.current = tableSchema;
sheetStructureRef.current = tableSchema;
}
setStatus(TableStatus.NORMAL);
},
() => {
setStatus(TableStatus.NORMAL);
},
);
const resourceNavigate = useDataNavigate();
const { fetchAddSegment, addSegmentLoading } = useFetchAddSegmentReq(res => {
resourceNavigate.toResource?.('knowledge', datasetID);
});
const fetchDocumentInfo = useFetchListDocumentReq(setDocInfo);
useEffect(() => {
if (docID) {
setStatus(TableStatus.LOADING);
fetchDocumentInfo();
fetchTableInfo({
document_id: docID,
});
}
}, [docID]);
function handleSaveTableData() {
if (!spaceID || !datasetID || !docID) {
return;
}
const imageKeys: string[] = [];
columns.forEach(col => {
if (col.columnType === ColumnType.Image) {
imageKeys.push(col.id as string);
}
});
const formatData = tableData.map(data =>
Object.fromEntries(
Object.entries(data).map(([key, value]) => {
if (imageKeys.includes(key)) {
return [key, transSliceContentOutput(value as string)];
}
return [key, value];
}),
),
);
const documentInfo = [
{
source_info: {
document_source: DocumentSource.Custom,
custom_content: JSON.stringify(formatData),
},
table_sheet: {
sheet_id: '0',
header_line_idx: '0',
start_line_idx: '0',
},
},
];
const payload = getAddSegmentParams({
spaceId: spaceID,
docId: docID,
datasetId: datasetID,
documentInfo,
});
fetchAddSegment(payload);
}
return (
<div
ref={tableContainerRef}
className={classNames(
'custom-table-container',
styles['custom-table-container'],
)}
>
<StructureBar docTitle={docTitle} />
<TableContainer
loading={tableStatus === TableStatus.LOADING}
columns={columns}
tableData={tableData}
handleAdd={handleAdd}
handleDel={handleDel}
handleEdit={handleEdit}
/>
{TableSegmentModalNode}
{footer
? footer({
prefix: (
<span className={styles['footer-sub-tip']}>
{I18n.t('knowledge_table_custom_submit_tips')}
</span>
),
btns: [
{
e2e: KnowledgeE2e.CreateUnitConfirmBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('variable_reset_yes'),
onClick: () => {
handleSaveTableData();
},
status: getSubmitBtnStatus(
sheetStructureRef.current,
tableData,
addSegmentLoading,
),
},
],
})
: null}
</div>
);
};

View File

@@ -0,0 +1,248 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 { getDataTypeText } from '@coze-data/utils';
import { FooterBtnStatus } from '@coze-data/knowledge-resource-processor-core';
import {
TextRender,
type TableViewRecord,
ImageRender,
ActionsRender,
} from '@coze-common/table-view';
import { I18n } from '@coze-arch/i18n';
import { type ColumnProps } from '@coze-arch/bot-semi/Table';
import { Typography } from '@coze-arch/bot-semi';
import {
type DocTableColumn,
type GetDocumentTableInfoResponse,
ColumnType,
} from '@coze-arch/bot-api/memory';
import { Tag } from '@coze-arch/coze-design';
import { getSrcFromImg } from '@/utils/table';
import styles from './index.module.less';
const MAX_WIDTH = 1400;
const MIN_WIDTH = 200;
const DIFF_WIDTH = 397;
export function transformTableData<T>(
tableColumns: T[],
indexName: string,
valueName?: string,
) {
const tableDataItem: Record<string, string> = {};
tableColumns.forEach(column => {
const key = column[indexName as keyof T] as string;
const value = valueName ? (column[valueName as keyof T] as string) : '';
tableDataItem[key] = value;
});
return [tableDataItem];
}
export interface TableDataCleaningParams {
data: GetDocumentTableInfoResponse;
handleEdit: (record: TableViewRecord, index: number) => void;
handleDel: (indexList: number[]) => void;
handleCellUpdate: (record: TableViewRecord, index: number) => void;
tableContainerRef: React.RefObject<HTMLDivElement>;
}
const ColumnTypeComp = (props: { columnType: ColumnType }) => (
<Tag color="primary" className={styles['column-type']}>
{getDataTypeText(props.columnType)}
</Tag>
);
export function tableDataCleaning({
data,
handleEdit,
handleDel,
handleCellUpdate,
tableContainerRef,
}: TableDataCleaningParams) {
const defaultData = {
sheetStructure: [],
tableColumns: [],
tableData: [],
};
if (!data) {
return defaultData;
}
const { table_meta, sheet_list } = data;
if (!table_meta || !Object.keys(table_meta).length || !sheet_list?.length) {
return defaultData;
}
const curSubSheet = sheet_list[0];
const curSheetStructure = table_meta[curSubSheet?.id ?? ''] || [];
const dom = tableContainerRef.current;
const maxWidth = dom ? dom.offsetWidth - DIFF_WIDTH : MAX_WIDTH;
const dataWidth =
maxWidth / curSheetStructure.length > MIN_WIDTH
? maxWidth / curSheetStructure.length
: MIN_WIDTH;
const tableColumns: ColumnProps[] = curSheetStructure.map(column => ({
id: column.column_name,
dataIndex: column.column_name,
columnType: column.column_type,
title: (
<div className={styles['table-view-title']}>
<Typography.Text
className={styles['table-view-title-content']}
ellipsis={{
showTooltip: {
opts: { content: column.column_name },
},
}}
>
{column.column_name}
</Typography.Text>
{column.is_semantic ? (
<Tag color="green" className={styles['semantic-tag']}>
{I18n.t('knowledge_1226_001')}
</Tag>
) : null}
{column.column_type ? (
<ColumnTypeComp columnType={column.column_type}></ColumnTypeComp>
) : null}
</div>
),
width: dataWidth,
render: (text: string, record: TableViewRecord, index: number) => {
if (column.column_type === ColumnType.Image) {
const srcList = getSrcFromImg(
record?.[column?.column_name as string] as string,
);
return (
<ImageRender
srcList={srcList}
onChange={(src, tosKey) => {
let val = '';
if (src || tosKey) {
val = `<img src="${src ?? ''}" ${
tosKey ? `data-tos-key="${tosKey}"` : ''
}>`;
}
const newRecord = {
...record,
[column?.column_name as string]: val,
};
handleCellUpdate(newRecord, index);
}}
/>
);
}
return (
<TextRender
dataIndex={column.column_name}
value={text}
record={record}
index={index}
editable
validator={{
validate: value => {
if (column.is_semantic) {
return !value || value === '';
}
return false;
},
errorMsg: I18n.t('datasets_url_empty'),
}}
onBlur={async (_text, updateRecord) =>
await handleCellUpdate(updateRecord, index)
}
/>
);
},
}));
// 增加操作列
tableColumns.push({
title: <></>,
width: 100,
dataIndex: 'actions',
className: styles['unit-actions-column'],
fixed: 'right',
render: (_text, record, index) => (
<ActionsRender
record={record}
index={index}
editProps={{
disabled: false,
onEdit: () => {
handleEdit?.(record, index);
},
}}
deleteProps={{
disabled: false,
onDelete: () => {
handleDel?.([index]);
},
}}
/>
),
});
const tableDataItem = transformTableData<DocTableColumn>(
curSheetStructure,
'column_name',
);
return {
sheetStructure: curSheetStructure,
tableColumns,
tableData: tableDataItem,
};
}
export function getSubmitBtnStatus(
sheetStructure: DocTableColumn[],
tableData: TableViewRecord[],
loading?: boolean,
) {
if (loading) {
return FooterBtnStatus.LOADING;
}
const semanticItem = sheetStructure.find(item => item.is_semantic);
if (!semanticItem) {
return FooterBtnStatus.DISABLE;
}
const semanticItemName = semanticItem?.column_name;
if (!semanticItemName) {
return FooterBtnStatus.DISABLE;
}
if (!tableData.length) {
return FooterBtnStatus.DISABLE;
}
const hasSemanticItemNull = tableData.some(
item => item && item[semanticItemName] === '',
);
if (hasSemanticItemNull) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import { createTableSlice } from '../../../slice';
import {
type UploadTableAction,
type UploadTableState,
} from '../../../interface';
import { type TableCustomIncrementalStep } from './constants';
export const createTableCustomIncrementalStore = () =>
create<
UploadTableState<TableCustomIncrementalStep> &
UploadTableAction<TableCustomIncrementalStep>
>()(
devtools((set, get, store) => ({
...createTableSlice(set, get, store),
})),
);

View File

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

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect } from 'react';
import { type StoreApi, type UseBoundStore } from 'zustand';
import { useKnowledgeParams } from '@coze-data/knowledge-stores';
import { useListDocumentReq } from '@/services';
import { type UploadTableAction, type UploadTableState } from '../interface';
export const useTableCheck = (
store: UseBoundStore<
StoreApi<UploadTableState<number> & UploadTableAction<number>>
>,
) => {
const params = useKnowledgeParams();
const setDocumentList = store(state => state.setDocumentList);
const listDocument = useListDocumentReq(res => {
const { document_infos = [] } = res;
setDocumentList && setDocumentList(document_infos);
});
useEffect(() => {
listDocument({
dataset_id: params.datasetID ?? '',
document_ids: params.docID ? [params.docID] : undefined,
});
}, []);
return null;
};

View File

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

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UploadConfig,
type FooterControlsProps,
type ContentProps,
} from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import { UploadFooter } from '@/components';
import { TableLocalStep } from '../constants';
import { useTableCheck } from '../../hooks';
import {
type UploadTableAction,
type UploadTableState,
} from '../../../interface';
import { createTableLocalAddStore } from './store';
import {
TableUpload,
TableConfiguration,
TablePreview,
TableProcessing,
} from './steps';
type TableLocalContentProps = ContentProps<
UploadTableAction<TableLocalStep> & UploadTableState<TableLocalStep>
>;
export const TableLocalAddConfig: UploadConfig<
TableLocalStep,
UploadTableAction<TableLocalStep> & UploadTableState<TableLocalStep>
> = {
steps: [
{
content: (props: TableLocalContentProps) => (
<TableUpload
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step2'),
step: TableLocalStep.UPLOAD,
},
{
content: (props: TableLocalContentProps) => (
<TableConfiguration
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_tab_step2'),
step: TableLocalStep.CONFIGURATION,
},
{
content: (props: TableLocalContentProps) => (
<TablePreview
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_tab_step3'),
step: TableLocalStep.PREVIEW,
},
{
content: (props: TableLocalContentProps) => (
<TableProcessing
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step4'),
step: TableLocalStep.PROCESSING,
},
],
createStore: createTableLocalAddStore,
className: 'table-local-wrapper',
useUploadMount: store => useTableCheck(store),
};

View File

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

View File

@@ -0,0 +1,196 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 { isEmpty } from 'lodash-es';
import { IllustrationNoResult } from '@douyinfe/semi-illustrations';
import {
type ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { UIEmpty } from '@coze-arch/bot-semi';
import { TableDataType } from '@coze-arch/bot-api/knowledge';
import { useOptFromQuery, tableSettingsToString } from '@/utils';
import {
isConfigurationLoading as isLoadingFunc,
isConfigurationError as isErrorFunc,
isConfigurationShowBanner as isShowBannerFunc,
getConfigurationMeta,
getConfigurationNextStatus,
semanticValidator,
} from '@/features/knowledge-type/table/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import {
useChangeTableSettingsNl2ql,
useFetchTableSchemaInfo,
} from '@/features/knowledge-type/table/hooks';
import { TableSettingFormFields } from '@/constants';
import {
TableStructure as TableStructureInternal,
TableSettingBar,
ConfigurationLoading,
ConfigurationError,
ConfigurationBanner,
} from '@/components';
import { useUploadFetchTableParams } from '../services';
import { TableLocalStep } from '../../../constants';
export const TableConfiguration = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
/** common action */
const setCurrentStep = useStore(state => state.setCurrentStep);
/** table store*/
const status = useStore(state => state.status);
const tableData = useStore(state => state.tableData);
const originTableData = useStore(state => state.originTableData);
const tableSettings = useStore(state => state.tableSettings);
const semanticValidate = useStore(state => state.semanticValidate);
/** table action*/
const setTableData = useStore(state => state.setTableData);
const setSemanticValidate = useStore(state => state.setSemanticValidate);
/** compute action */
const opt = useOptFromQuery();
const isLoading = isLoadingFunc(status);
const isError = isErrorFunc(status);
const isShowBanner = isShowBannerFunc(opt, tableData, tableSettings);
const currentMeta = getConfigurationMeta(tableData, tableSettings);
const meta = currentMeta.map((item, index) => ({
...item,
key: index + item.column_name,
}));
/** events action */
const onChangeTableSettings = useChangeTableSettingsNl2ql(useStore);
/** network action */
const fetchTableParams = useUploadFetchTableParams(useStore);
const fetchTableInfo = useFetchTableSchemaInfo<T>(useStore);
const getContent = () => {
if (isLoading) {
return <ConfigurationLoading />;
}
if (isError) {
return (
<ConfigurationError
fetchTableInfo={() => {
fetchTableInfo({
source_file: fetchTableParams,
table_data_type: TableDataType.AllData,
table_sheet: tableSettingsToString(tableSettings),
});
}}
/>
);
}
return (
<>
<TableStructureInternal
tipsNode={
<>
{isShowBanner ? <ConfigurationBanner /> : null}
{!meta.length || isEmpty(originTableData) ? (
<UIEmpty
empty={{
title: I18n.t('knowledge_1221_02'),
icon: <IllustrationNoResult></IllustrationNoResult>,
}}
/>
) : null}
</>
}
showTitle
initValid
isDragTable
baseKey={`${tableSettings[TableSettingFormFields.SHEET]}.${
tableSettings[TableSettingFormFields.KEY_START_ROW]
}.`}
data={meta}
verifyMap={
semanticValidate[tableSettings[TableSettingFormFields.SHEET]] || {}
}
setData={v => {
const curSheet = tableSettings[TableSettingFormFields.SHEET];
const newData = {
...tableData,
table_meta: {
...tableData.table_meta,
[curSheet]: v,
},
};
setTableData(newData);
setSemanticValidate(semanticValidator(newData));
}}
loading={isLoading}
/>
{footer
? footer([
{
e2e: KnowledgeE2e.UploadUnitUpBtn,
type: 'primary',
theme: 'light',
text: I18n.t('datasets_createFileModel_previousBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.UPLOAD);
},
status: FooterBtnStatus.ENABLE,
},
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.PREVIEW);
fetchTableInfo({
table_sheet: tableSettingsToString(tableSettings),
table_data_type: TableDataType.OnlyPreview,
source_file: fetchTableParams,
origin_table_meta:
originTableData?.table_meta?.[tableSettings.sheet_id],
preview_table_meta:
tableData?.table_meta?.[tableSettings.sheet_id],
});
},
status: getConfigurationNextStatus(tableData, tableSettings),
},
])
: null}
</>
);
};
return (
<>
<TableSettingBar
data={tableData}
tableSettings={tableSettings}
setTableSettings={onChangeTableSettings}
/>
{getContent()}
</>
);
};

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import { TablePreview as TablePreviewInternal } from '@/components';
import { TableLocalStep } from '../../../constants';
export const TablePreview = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
/** store */
const tableData = useStore(state => state.tableData);
const tableSettings = useStore(state => state.tableSettings);
const setCurrentStep = useStore(state => state.setCurrentStep);
return (
<>
<TablePreviewInternal data={tableData} settings={tableSettings} />
{footer
? footer([
{
e2e: KnowledgeE2e.UploadUnitUpBtn,
type: 'primary',
theme: 'light',
text: I18n.t('datasets_createFileModel_previousBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.CONFIGURATION);
},
status: FooterBtnStatus.ENABLE,
},
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.PROCESSING);
},
status: FooterBtnStatus.ENABLE,
},
])
: null}
</>
);
};

View File

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

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useMemo, useEffect } from 'react';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import {
type ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { useCreateDocument } from '@/hooks';
import {
getDocIdFromProgressList,
getConfigurationMeta,
getCreateDocumentParams,
} from '@/features/knowledge-type/table/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import { UnitProgress } from '@/components';
export const TableProcessing = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
/** store */
const progressList = useStore(state => state.progressList);
const unitList = useStore(state => state.unitList);
const createStatus = useStore(state => state.createStatus);
const tableData = useStore(state => state.tableData);
const tableSettings = useStore(state => state.tableSettings);
const meta = getConfigurationMeta(tableData, tableSettings);
/** config */
const params = useKnowledgeParams();
const resourceNavigate = useDataNavigate();
const docId = useMemo(
() => getDocIdFromProgressList(progressList),
[progressList],
);
const createDocument = useCreateDocument(useStore);
useEffect(() => {
createDocument(
getCreateDocumentParams({
isAppend: false,
unitList,
metaData: meta,
tableSettings,
}),
);
}, []);
return (
<>
<UnitProgress progressList={progressList} createStatus={createStatus} />
{footer
? footer([
{
e2e: KnowledgeE2e.CreateUnitConfirmBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('variable_reset_yes'),
onClick: () => {
resourceNavigate.toResource?.('knowledge', params.datasetID);
},
status: docId ? FooterBtnStatus.ENABLE : FooterBtnStatus.DISABLE,
},
])
: null}
</>
);
};

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* service定义包含业务处理 & 事件
*/
import { useMemo } from 'react';
import { type StoreApi, type UseBoundStore } from 'zustand';
import { get } from 'lodash-es';
import { type UnitItem } from '@coze-data/knowledge-resource-processor-core';
import { DocumentSource } from '@coze-arch/bot-api/knowledge';
import { FileBizType } from '@coze-arch/bot-api/developer_api';
import { DeveloperApi } from '@coze-arch/bot-api';
import { transformUnitList, getFileExtension, getBase64 } from '@/utils';
import {
type UploadTableAction,
type UploadTableState,
} from '@/features/knowledge-type/table/interface';
/** table-local upload 时要用tos_uri 去取table_info */
export const useUploadFetchTableParams = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
useStore: UseBoundStore<StoreApi<T>>,
) => {
const unitList = useStore(state => state.unitList);
return useMemo(
() => ({
tos_uri: get(unitList, '0.uri', ''),
document_source: DocumentSource.Document,
}),
[unitList],
);
};
/**
* table-local add upload onRetry
*/
export const useRetry = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
useStore: UseBoundStore<StoreApi<T>>,
) => {
const setUnitList = useStore(state => state.setUnitList);
const unitList = useStore(state => state.unitList);
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) {
// TODO 加上报
console.log(e);
}
};
return onRetry;
};

View File

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

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect } from 'react';
import {
UnitType,
type UnitItem,
type ContentProps,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { TableDataType } from '@coze-arch/bot-api/knowledge';
import { tableSettingsToString } from '@/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import {
useAcceptFiles,
useFetchTableSchemaInfo,
} from '@/features/knowledge-type/table/hooks';
import { DEFAULT_TABLE_SETTINGS_FROM_ONE, TableStatus } from '@/constants';
import { UploadUnitTable, UploadUnitFile } from '@/components';
import { useRetry, useUploadFetchTableParams } from '../services';
import { TableLocalStep } from '../../../constants';
import { getNextBtnStatus } from './utils';
export const TableUpload = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
/** common store */
const unitList = useStore(state => state.unitList);
/** common action */
const setUnitList = useStore(state => state.setUnitList);
const setCurrentStep = useStore(state => state.setCurrentStep);
/** table store */
const tableSettings = useStore(state => state.tableSettings);
/** table action */
const setOriginTableData = useStore(state => state.setOriginTableData);
const setTableData = useStore(state => state.setTableData);
const setTableSettings = useStore(state => state.setTableSettings);
const setStatus = useStore(state => state.setStatus);
/** event action */
const onRetry = useRetry(useStore);
const params = useUploadFetchTableParams(useStore);
const fetchTableInfo = useFetchTableSchemaInfo<T>(useStore);
useEffect(() => {
// 删除上传文件时,同步删除表格源数据
if (!unitList.length) {
setOriginTableData({});
setTableData({});
setTableSettings(DEFAULT_TABLE_SETTINGS_FROM_ONE);
}
}, [unitList.length]);
const accept = useAcceptFiles();
return (
<>
{
<UploadUnitFile
setUnitList={setUnitList}
unitList={unitList}
onFinish={(list: UnitItem[]) => {
setUnitList(list);
}}
limit={1}
accept={accept}
dragMainText={I18n.t('datasets_createFileModel_step2_UploadDoc')}
dragSubText={I18n.t('datasets_unit_update_exception_tips3')}
action={''}
style={
!unitList.length
? {}
: {
display: 'none',
}
}
showIllustration={false}
/>
}
<div className="upload-unit-table">
<UploadUnitTable
edit={false}
type={UnitType.TABLE_DOC}
unitList={unitList}
onChange={(list: UnitItem[]) => {
setUnitList(list);
}}
onRetry={onRetry}
/>
</div>
{footer
? footer([
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.CONFIGURATION);
setStatus(TableStatus.LOADING);
fetchTableInfo({
source_file: params,
table_data_type: TableDataType.AllData,
table_sheet: tableSettingsToString(tableSettings),
});
},
status: getNextBtnStatus(unitList),
},
])
: null}
</>
);
};

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UnitItem,
FooterBtnStatus,
UploadStatus,
} from '@coze-data/knowledge-resource-processor-core';
export function getNextBtnStatus(unitList: UnitItem[]) {
if (
unitList.length === 0 ||
unitList.some(
unitItem =>
unitItem.name.length === 0 || unitItem.status !== UploadStatus.SUCCESS,
)
) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import { type TableLocalStep } from '../constants';
import { createTableSlice } from '../../../slice';
import {
type UploadTableAction,
type UploadTableState,
} from '../../../interface';
export const createTableLocalAddStore = () =>
create<
UploadTableState<TableLocalStep> & UploadTableAction<TableLocalStep>
>()(
devtools((set, get, store) => ({
...createTableSlice(set, get, store),
})),
);

View File

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

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UploadConfig,
type FooterControlsProps,
type ContentProps,
} from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import { UploadFooter } from '@/components';
import { TableLocalStep } from '../constants';
import {
type UploadTableAction,
type UploadTableState,
} from '../../../interface';
import { createTableLocalIncrementalStore } from './store';
import {
TableUpload,
TableConfiguration,
TablePreview,
TableProcessing,
} from './steps';
type TableLocalContentProps = ContentProps<
UploadTableAction<TableLocalStep> & UploadTableState<TableLocalStep>
>;
export const TableLocalIncrementalConfig: UploadConfig<
TableLocalStep,
UploadTableAction<TableLocalStep> & UploadTableState<TableLocalStep>
> = {
steps: [
{
content: (props: TableLocalContentProps) => (
<TableUpload
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step2'),
step: TableLocalStep.UPLOAD,
},
{
content: (props: TableLocalContentProps) => (
<TableConfiguration
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_tab_step2'),
step: TableLocalStep.CONFIGURATION,
},
{
content: (props: TableLocalContentProps) => (
<TablePreview
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_tab_step3'),
step: TableLocalStep.PREVIEW,
},
{
content: (props: TableLocalContentProps) => (
<TableProcessing
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step4'),
step: TableLocalStep.PROCESSING,
},
],
createStore: createTableLocalIncrementalStore,
className: 'table-local-wrapper',
};

View File

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

View File

@@ -0,0 +1,253 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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, useRef, useState } from 'react';
import { isEmpty } from 'lodash-es';
import { IllustrationNoResult } from '@douyinfe/semi-illustrations';
import { useKnowledgeParams } from '@coze-data/knowledge-stores';
import {
type ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { UIEmpty } from '@coze-arch/bot-semi';
import { isApiError } from '@coze-arch/bot-http';
import { DocumentSource, TableDataType } from '@coze-arch/bot-api/knowledge';
import { tableSettingsToString } from '@/utils';
import {
isConfigurationLoading as isLoadingFunc,
isConfigurationError as isErrorFunc,
getExpandConfigurationMeta,
semanticValidator,
} from '@/features/knowledge-type/table/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import {
useChangeTableSettingsNl2ql,
useFetchTableSchemaInfo,
useTableSchemaValid,
} from '@/features/knowledge-type/table/hooks';
import { TableSettingFormFields } from '@/constants';
import {
TableStructure as TableStructureInternal,
TableSettingBar,
ConfigurationLoading,
ConfigurationError,
} from '@/components';
import { useUploadFetchTableParams } from '../services';
import styles from '../index.module.less';
import { TableLocalStep } from '../../../constants';
interface TableHeaderProps {
isTableStructureError: boolean;
}
const validErrorCode = '708024073';
const TableHeader = ({ isTableStructureError }: TableHeaderProps) => (
<>
<div className={styles['validation-results']}>
<div className={styles['validation-item']}></div>
<div className={styles['validation-item']}>
{isTableStructureError ? (
<p className={styles['error-msg']}>
{I18n.t('knowledg_table_structure_err_msg')}
</p>
) : null}
<p className={styles.tips}>{I18n.t('knowledg_table_structure_tips')}</p>
</div>
<div className={styles['validation-item']}></div>
</div>
<div
className={styles['table-structure-title']}
data-testid={KnowledgeE2e.TableLocalTableStructureTitle}
>
{I18n.t('datasets_segment_tableStructure_title')}
</div>
</>
);
// eslint-disable-next-line @coze-arch/max-line-per-function
export const TableConfiguration = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
const setCurrentStep = useStore(state => state.setCurrentStep);
const status = useStore(state => state.status);
const tableData = useStore(state => state.tableData);
const originTableData = useStore(state => state.originTableData);
const tableSettings = useStore(state => state.tableSettings);
const semanticValidate = useStore(state => state.semanticValidate);
const setTableData = useStore(state => state.setTableData);
const setSemanticValidate = useStore(state => state.setSemanticValidate);
const isLoading = isLoadingFunc(status);
const [validate, setValidate] = useState(false);
const isConfigurationError = isErrorFunc(status);
const [isTableStructureError, setIsTableStructureError] = useState(false);
const validResultRef = useRef<Record<string, string>>({});
const meta = getExpandConfigurationMeta(
tableData,
tableSettings,
validResultRef.current,
);
const onChangeTableSettings = useChangeTableSettingsNl2ql(useStore);
const uploadParams = useUploadFetchTableParams(useStore);
const params = useKnowledgeParams();
const fetchTableInfo = useFetchTableSchemaInfo<T>(useStore);
const tableSchemaValid = useTableSchemaValid(
(state, result) => {
setIsTableStructureError(!state);
setValidate(state);
validResultRef.current = result;
},
err => {
if (isApiError(err) && err?.code === validErrorCode) {
setIsTableStructureError(true);
}
setValidate(false);
validResultRef.current = {};
},
);
useEffect(() => {
if (tableSettings) {
setValidate(false);
tableSchemaValid({
space_id: params.spaceID || '',
document_id: params.docID || '',
source_file: {
tos_uri: uploadParams.tos_uri || '',
document_source: DocumentSource.Document,
},
table_sheet: {
sheet_id: tableSettings.sheet_id.toString(),
header_line_idx: tableSettings.header_line_idx.toString(),
start_line_idx: tableSettings.start_line_idx.toString(),
},
});
}
}, [tableSettings]);
if (isLoading) {
return <ConfigurationLoading />;
}
if (isConfigurationError) {
return (
<ConfigurationError
fetchTableInfo={() => {
fetchTableInfo({
...uploadParams,
document_id: params.docID,
table_data_type: TableDataType.AllData,
table_sheet: tableSettingsToString(tableSettings),
});
}}
/>
);
}
return (
<>
<TableSettingBar
className={`${styles['table-setting-bar-container']} ${
isTableStructureError ? styles['is-error'] : ''
}`}
data={tableData}
tableSettings={tableSettings}
setTableSettings={onChangeTableSettings}
/>
<TableHeader isTableStructureError={isTableStructureError} />
{!meta.length || isEmpty(originTableData) ? (
<UIEmpty
empty={{
title: I18n.t('knowledge_1221_02'),
icon: <IllustrationNoResult></IllustrationNoResult>,
}}
/>
) : (
<TableStructureInternal
initValid={false}
baseKey={`${tableSettings[TableSettingFormFields.SHEET]}.${
tableSettings[TableSettingFormFields.KEY_START_ROW]
}.`}
data={meta}
verifyMap={
semanticValidate[tableSettings[TableSettingFormFields.SHEET]] || {}
}
setData={v => {
const curSheet = tableSettings[TableSettingFormFields.SHEET];
const newData = {
...tableData,
table_meta: {
...tableData.table_meta,
[curSheet]: v,
},
};
setTableData(newData);
setSemanticValidate(semanticValidator(newData));
}}
loading={isLoading}
isPreview={true}
/>
)}
{footer
? footer([
{
e2e: KnowledgeE2e.UploadUnitUpBtn,
type: 'primary',
theme: 'light',
text: I18n.t('datasets_createFileModel_previousBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.UPLOAD);
},
status: FooterBtnStatus.ENABLE,
},
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.PREVIEW);
fetchTableInfo({
table_data_type: TableDataType.OnlyPreview,
source_file: uploadParams,
document_id: params.docID,
origin_table_meta:
originTableData?.table_meta?.[tableSettings.sheet_id],
preview_table_meta:
tableData?.table_meta?.[tableSettings.sheet_id],
table_sheet: tableSettingsToString(tableSettings),
});
},
status: validate
? FooterBtnStatus.ENABLE
: FooterBtnStatus.DISABLE,
},
])
: null}
</>
);
};

View File

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

View File

@@ -0,0 +1,170 @@
/* stylelint-disable no-duplicate-selectors */
/* stylelint-disable declaration-no-important */
/* stylelint-disable max-nesting-depth */
/* stylelint-disable font-family-no-missing-generic-family-keyword */
.table-structure-title {
margin: 12px 0 6px;
font-family: "PingFang SC";
font-size: 14px;
font-weight: 600;
font-style: normal;
line-height: 20px;
color: var(--coz-fg-primary);
}
.no-result {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 448px;
&-tips {
margin-top: 16px;
font-family: "SF Pro Display";
font-size: 16px;
font-weight: 600;
font-style: normal;
line-height: 22px;
color: var(--coz-fg-primary);
}
}
/* stylelint-disable-next-line plugin/disallow-first-level-global */
:global {
.table-local-wrapper {
:global {
.semi-banner-full .semi-banner-content-wrapper .semi-banner-content {
justify-content: flex-start;
}
.select-page-content {
.semi-tree-option-expand-icon {
align-items: center;
justify-content: center;
width: 20px !important;
height: 20px !important;
line-height: 20px !important;
}
.expand-placeholder {
width: 0 !important;
height: 0 !important;
line-height: 0 !important;
}
.file-selector {
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
margin: 0 8px;
}
.no-real-expand {
margin: 0 8px 0 0 !important;
}
.file-icon {
display: flex;
align-items: center;
align-self: center;
justify-content: center;
width: 20px;
height: 20px;
margin-right: 4px;
line-height: 20px;
}
.file-selector {
display: flex;
justify-content: center;
width: 20px;
height: 20px;
margin: 0 8px;
padding: 2px 0;
}
.semi-tree-option-list {
padding: 0;
}
.file-node-row-content {
.action-placeholder {
width: 0;
margin: 0 0 0 8px;
}
}
}
}
}
}
.footer-sub-tip {
font-size: 12px;
color: var(--light-usage-text-color-text-2, rgb(28 31 35 / 60%));
}
.table-doc-footer {
float: right;
margin-top: 40px;
:global {
.semi-button {
margin-left: 10px;
}
}
}
.table-setting-bar-container {
margin-bottom: 0;
&.is-error {
:global {
[x-field-id="header_line_idx"] {
.semi-select {
border: 1px solid var(--coz-stroke-hglt-red);
}
}
}
}
}
.validation-results {
display: flex;
flex-wrap: wrap;
margin-top: 4px;
margin-bottom: 6px;
.validation-item {
flex: 1 1;
padding: 0 8px;
.tips {
font-size: 12px;
line-height: 16px;
color: var(--coz-fg-secondary);
}
.error-msg {
font-size: 12px;
line-height: 16px;
color: var(--coz-stroke-hglt-red);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import { TablePreview as TablePreviewInternal } from '@/components';
import { TableLocalStep } from '../../../constants';
export const TablePreview = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
/** store */
const tableData = useStore(state => state.tableData);
const tableSettings = useStore(state => state.tableSettings);
const setCurrentStep = useStore(state => state.setCurrentStep);
return (
<>
<TablePreviewInternal data={tableData} settings={tableSettings} />
{footer
? footer([
{
e2e: KnowledgeE2e.UploadUnitUpBtn,
type: 'primary',
theme: 'light',
text: I18n.t('datasets_createFileModel_previousBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.CONFIGURATION);
},
status: FooterBtnStatus.ENABLE,
},
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.PROCESSING);
},
status: FooterBtnStatus.ENABLE,
},
])
: null}
</>
);
};

View File

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

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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, useEffect } from 'react';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import {
type ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { DocumentSource } from '@coze-arch/bot-api/knowledge';
import {
getAddSegmentParams,
getDocIdFromProgressList,
} from '@/features/knowledge-type/table/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import { useAddSegment } from '@/features/knowledge-type/table/hooks';
import { UnitProgress } from '@/components';
export const TableProcessing = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
/** store */
const unitList = useStore(state => state.unitList);
const progressList = useStore(state => state.progressList);
const createStatus = useStore(state => state.createStatus);
const tableSettings = useStore(state => state.tableSettings);
/** config */
const params = useKnowledgeParams();
const { docID: docIdByQuery, spaceID, datasetID } = params;
const resourceNavigate = useDataNavigate();
const docId = useMemo(
() => getDocIdFromProgressList(progressList),
[progressList],
);
const handleAddSegment = useAddSegment(useStore);
useEffect(() => {
if (
!docIdByQuery ||
!datasetID ||
!spaceID ||
!tableSettings ||
!unitList
) {
return;
}
const payload = getAddSegmentParams({
spaceId: spaceID,
docId: docIdByQuery,
datasetId: datasetID,
documentInfo: unitList.map(item => ({
name: '', // item.name,
source_info: {
document_source: DocumentSource.Document,
tos_uri: item.uri,
},
table_sheet: {
sheet_id: tableSettings.sheet_id.toString(),
header_line_idx: tableSettings.header_line_idx.toString(),
start_line_idx: tableSettings.start_line_idx.toString(),
},
})),
});
handleAddSegment(payload);
}, [tableSettings, unitList]);
return (
<>
<UnitProgress progressList={progressList} createStatus={createStatus} />
{footer
? footer([
{
e2e: KnowledgeE2e.CreateUnitConfirmBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('variable_reset_yes'),
onClick: () => {
resourceNavigate.toResource?.('knowledge', params.datasetID);
},
status: docId ? FooterBtnStatus.ENABLE : FooterBtnStatus.DISABLE,
},
])
: null}
</>
);
};

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* service定义包含业务处理 & 事件
*/
import { useMemo } from 'react';
import { type StoreApi, type UseBoundStore } from 'zustand';
import { get } from 'lodash-es';
import { type UnitItem } from '@coze-data/knowledge-resource-processor-core';
import { DocumentSource } from '@coze-arch/bot-api/knowledge';
import { FileBizType } from '@coze-arch/bot-api/developer_api';
import { DeveloperApi } from '@coze-arch/bot-api';
import { transformUnitList, getFileExtension, getBase64 } from '@/utils';
import {
type UploadTableAction,
type UploadTableState,
} from '@/features/knowledge-type/table/interface';
/** table-local upload 时要用tos_uri 去取table_info */
export const useUploadFetchTableParams = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
useStore: UseBoundStore<StoreApi<T>>,
) => {
const unitList = useStore(state => state.unitList);
return useMemo(
() => ({
tos_uri: get(unitList, '0.uri', ''),
document_source: DocumentSource.Document,
}),
[unitList],
);
};
/**
* table-local add upload onRetry
*/
export const useRetry = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
useStore: UseBoundStore<StoreApi<T>>,
) => {
const setUnitList = useStore(state => state.setUnitList);
const unitList = useStore(state => state.unitList);
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) {
// TODO 加上报
console.log(e);
}
};
return onRetry;
};

View File

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

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect } from 'react';
import { isEmpty } from 'lodash-es';
import {
UnitType,
type UnitItem,
type ContentProps,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { TableDataType } from '@coze-arch/bot-api/knowledge';
import { useDocIdFromQuery, tableSettingsToString } from '@/utils';
import type {
UploadTableState,
UploadTableAction,
} from '@/features/knowledge-type/table/interface';
import {
useAcceptFiles,
useFetchTableSchemaInfo,
} from '@/features/knowledge-type/table/hooks';
import { DEFAULT_TABLE_SETTINGS_FROM_ONE, TableStatus } from '@/constants';
import { UploadUnitTable, UploadUnitFile } from '@/components';
import { useRetry, useUploadFetchTableParams } from '../services';
import { TableLocalStep } from '../../../constants';
import { getButtonStatus } from './utils';
export const TableUpload = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
/** common store */
const docId = useDocIdFromQuery();
const unitList = useStore(state => state.unitList);
/** common action */
const setUnitList = useStore(state => state.setUnitList);
const setCurrentStep = useStore(state => state.setCurrentStep);
/** table store */
const originTableData = useStore(state => state.originTableData);
const tableSettings = useStore(state => state.tableSettings);
/** table action */
const setOriginTableData = useStore(state => state.setOriginTableData);
const setTableData = useStore(state => state.setTableData);
const setTableSettings = useStore(state => state.setTableSettings);
const setStatus = useStore(state => state.setStatus);
/** event action */
const onRetry = useRetry(useStore);
const params = useUploadFetchTableParams(useStore);
const fetchTableInfo = useFetchTableSchemaInfo<T>(useStore);
useEffect(() => {
// 删除上传文件时,同步删除表格源数据
if (!unitList.length) {
setOriginTableData({});
setTableData({});
setTableSettings(DEFAULT_TABLE_SETTINGS_FROM_ONE);
}
}, [unitList.length]);
const accept = useAcceptFiles();
return (
<>
{
<UploadUnitFile
setUnitList={setUnitList}
unitList={unitList}
onFinish={(list: UnitItem[]) => {
setUnitList(list);
}}
limit={1}
accept={accept}
dragMainText={I18n.t('datasets_createFileModel_step2_UploadDoc')}
dragSubText={
<div>
<p>{I18n.t('datasets_unit_update_exception_tips3')}</p>
<p>{I18n.t('knowledg_table_increment_tips')}</p>
</div>
}
action={''}
style={
!unitList.length
? {}
: {
display: 'none',
}
}
showIllustration={false}
/>
}
<div className="upload-unit-table">
<UploadUnitTable
edit={false}
type={UnitType.TABLE_DOC}
unitList={unitList}
onChange={(list: UnitItem[]) => {
setUnitList(list);
}}
canValidator={false}
onRetry={onRetry}
/>
</div>
{footer
? footer([
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
onClick: () => {
setCurrentStep(TableLocalStep.CONFIGURATION);
if (!isEmpty(originTableData)) {
return;
}
setStatus(TableStatus.LOADING);
fetchTableInfo({
source_file: params,
document_id: docId,
table_sheet: tableSettingsToString(tableSettings),
table_data_type: TableDataType.AllData,
});
},
status: getButtonStatus(unitList),
},
])
: null}
</>
);
};

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UnitItem,
FooterBtnStatus,
UploadStatus,
} from '@coze-data/knowledge-resource-processor-core';
export function getButtonStatus(unitList: UnitItem[]) {
if (
unitList.length === 0 ||
unitList.some(
unitItem =>
unitItem.name.length === 0 || unitItem.status !== UploadStatus.SUCCESS,
)
) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { devtools } from 'zustand/middleware';
import { create } from 'zustand';
import { type TableLocalStep } from '../constants';
import { createTableSlice } from '../../../slice';
import {
type UploadTableAction,
type UploadTableState,
} from '../../../interface';
export const createTableLocalIncrementalStore = () =>
create<
UploadTableState<TableLocalStep> & UploadTableAction<TableLocalStep>
>()(
devtools((set, get, store) => ({
...createTableSlice(set, get, store),
})),
);

View File

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

View File

@@ -0,0 +1,546 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* pure network request & common services
*/
// TODO 待解
import { type StoreApi, type UseBoundStore } from 'zustand';
import { debounce, get } from 'lodash-es';
import { useRequest } from 'ahooks';
import { useDataNavigate, useKnowledgeParams } from '@coze-data/knowledge-stores';
import { DataNamespace, dataReporter } from '@coze-data/reporter';
import {
type ProgressItem,
CreateUnitStatus,
FooterBtnStatus,
OptType,
UnitType,
} from '@coze-data/knowledge-resource-processor-core';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { type FormApi } from '@coze-arch/bot-semi/Form';
import { useFlags } from '@coze-arch/bot-flags';
import { CustomError } from '@coze-arch/bot-error';
import { DocumentSourceType, FormatType } from '@coze-arch/bot-api/memory';
import type {
AssociateFileResponse,
DocTableColumn,
DocTableSheet,
// DocumentTableTaskInfo,
FileNode,
GetDocumentTableInfoRequest,
GetDocumentTableInfoResponse,
// GetTableSchemaInfoRequest,
} from '@coze-arch/bot-api/memory';
import {
type GetTableSchemaRequest,
TableDataType,
DocumentSource,
type DocumentInfo,
type CreateDocumentRequest,
type CreateDocumentResponse,
type ValidateTableSchemaRequest,
} from '@coze-arch/bot-api/knowledge';
import { MemoryApi, KnowledgeApi } from '@coze-arch/bot-api';
import {
isThirdResegment as isThirdResegmentFunc,
isIncremental as isIncrementalFunc,
} from '@/utils';
import { type CustomFormFields } from '@/types/table';
import { type TableSettings } from '@/types';
import { usePollingTaskProgress } from '@/hooks';
import { TABLE_ACCEPT_LOCAL_FILE } from '@/constants/common';
import { TableStatus } from '@/constants';
import {
type UploadTableAction,
type UploadTableState,
} from '../table/interface';
import { useUploadFetchTableParams } from '../table/first-party/local/add/steps/services';
import { semanticValidator, getCustomStatus } from './utils';
export interface TableSchemaInfoResponse {
code?: number;
msg?: string;
sheet_list?: DocTableSheet[];
table_meta?: Record<string, DocTableColumn[]>;
preview_data?: Record<string, Record<number, string>[]>;
}
export interface ThirdTableState {
tosUrlRef?: string;
fileTreeNodesMap?: Record<string, FileNode[]>;
docInfo?: DocumentInfo;
fileIdsMap?: AssociateFileResponse['file_mapping'];
}
export const useFetchTableInfoReq = (
onSuccess: (res: TableSchemaInfoResponse) => void,
onError: () => void,
) => {
const { run: fetchTableInfo } = useRequest(
async (params: GetDocumentTableInfoRequest) => {
const res = await MemoryApi.GetDocumentTableInfo(params);
onSuccess(res);
},
{
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeGetTableInfo,
error: error as Error,
});
onError();
},
manual: true,
},
);
return fetchTableInfo;
};
export const useFetchTableSchemaInfoReq = (
onSuccess: (res: GetDocumentTableInfoResponse) => void,
onError: () => void,
) => {
const { run: fetchTableInfo } = useRequest(
async (params: GetTableSchemaRequest) => {
try {
const { table_meta, preview_data, ...rest } =
await KnowledgeApi.GetTableSchema(params);
const updateParams =
params?.table_data_type === TableDataType.OnlyPreview
? {
preview_data: {
[`${params.table_sheet?.sheet_id || '0'}`]:
preview_data ?? [],
},
}
: {
...rest,
table_meta: {
[`${params.table_sheet?.sheet_id || '0'}`]: table_meta ?? [],
},
preview_data: {
[`${params.table_sheet?.sheet_id || '0'}`]:
preview_data ?? [],
},
};
onSuccess(updateParams);
} catch (error) {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeGetTableInfo,
error: error as Error,
});
onError();
}
},
{
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeGetTableInfo,
error: error as Error,
});
onError();
},
manual: true,
},
);
return fetchTableInfo;
};
/**
* 这个方法 所有的table链路都会用到
* impure会改store因为所有table链路类似所以放到这里
*/
export const useFetchTableSchemaInfo = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
useStore: UseBoundStore<StoreApi<T>>,
) => {
const tableData = useStore(state => state.tableData);
const setSemanticValidate = useStore(state => state.setSemanticValidate);
const setOriginTableData = useStore(state => state.setOriginTableData);
const setTableData = useStore(state => state.setTableData);
const setStatus = useStore(state => state.setStatus);
return useFetchTableSchemaInfoReq(
res => {
const isOnlyPreview = !get(res, 'table_meta');
const newData = isOnlyPreview
? {
...tableData,
preview_data: get(res, 'preview_data'),
}
: res;
if (!isOnlyPreview) {
// 为什么有这个if因为第一次请求全量第二次请求isOnlyPreview
setOriginTableData(newData);
}
const validateRes = semanticValidator(newData);
setSemanticValidate(validateRes);
setTableData(newData);
setStatus(TableStatus.NORMAL);
},
() => {
console.log('setStatus(TableStatus.ERROR)');
setStatus(TableStatus.ERROR);
},
);
};
// TODO 待优化,这个函数包含了太多逻辑,非常乱
// events action nl2ql链路
export const useChangeTableSettingsNl2ql = <
T extends UploadTableState<number> &
UploadTableAction<number> &
ThirdTableState,
>(
useStore: UseBoundStore<StoreApi<T>>,
) => {
const { type, docID, opt } = useKnowledgeParams();
const setStatus = useStore(state => state.setStatus);
const setTableSettings = useStore(state => state.setTableSettings);
const fileIdsMap = useStore(state => state.fileIdsMap ?? {});
const docInfo = useStore(state => state.docInfo);
const tosUrlRef = useStore(state => state.tosUrlRef);
const sourceFileId =
fileIdsMap[Object.keys(fileIdsMap)?.[0]]?.[0].source_file_id ??
docInfo?.source_file_id;
const fetchTableInfo = useFetchTableSchemaInfo(useStore);
const AWAIT = 500;
const params = useUploadFetchTableParams(useStore);
const isThirdResegment = isThirdResegmentFunc(opt ?? OptType.ADD, type); // 判断是否为三方的resegment
const isIncremental = isIncrementalFunc(opt ?? OptType.ADD);
const onChangeTableSettings = debounce((v: TableSettings) => {
setStatus(TableStatus.LOADING);
const sourceFile =
type &&
[UnitType.TABLE_GOOGLE_DRIVE, UnitType.TABLE_FEISHU].includes(type)
? {
// 飞书需要传 tos_uri
tos_uri: type === UnitType.TABLE_FEISHU ? tosUrlRef : undefined,
source_file_id: sourceFileId ?? undefined,
document_source:
type === UnitType.TABLE_GOOGLE_DRIVE
? DocumentSource.GoogleDrive
: DocumentSource.FeishuWeb,
}
: params;
fetchTableInfo({
// 如果为三方的resegment就不传 source_file
source_file: isThirdResegment ? undefined : sourceFile,
table_sheet:
!isIncremental &&
get(sourceFile, 'document_source') === DocumentSource.Web
? undefined
: {
sheet_id: String(v.sheet_id),
header_line_idx: String(v.header_line_idx),
start_line_idx: String(v.start_line_idx),
},
// 如果为三方的resegment和增量导入就传 document_id 其他情况不传
document_id: isThirdResegment || isIncremental ? docID : undefined,
table_data_type: TableDataType.AllData,
});
setTableSettings(v);
}, AWAIT);
return onChangeTableSettings;
};
export const useUpdateDocument = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
useStore: UseBoundStore<StoreApi<T>>,
) => {
const tableData = useStore(state => state.tableData);
const setStatus = useStore(state => state.setStatus);
const setCreateStatus = useStore(state => state.setCreateStatus);
const setProgressList = useStore(state => state.setProgressList);
const pollingTaskProgress = usePollingTaskProgress();
const { docID: docIdFromQuery } = useKnowledgeParams();
const { run: handleUpdateDocument } = useRequest(
async () => {
try {
setStatus(TableStatus.LOADING);
await KnowledgeApi.UpdateDocument({
document_id: docIdFromQuery,
table_meta: tableData.table_meta?.[0],
});
await pollingTaskProgress(
[
{
document_id: docIdFromQuery,
},
],
{
onProgressing: (progressList: ProgressItem[]) => {
setProgressList(progressList);
},
onFinish: () => {
setCreateStatus(CreateUnitStatus.TASK_FINISH);
},
},
);
} catch (error) {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeUpdateDocument,
error: error as Error,
});
} finally {
setStatus(TableStatus.NORMAL);
}
},
{
manual: true,
},
);
return handleUpdateDocument;
};
/**
* table custom createDocument
* TODO 即将废弃切到新的knowledge信息架构后要删掉
* @deprecated
* @param setStatus
* @param formApi
* @returns
*/
export const useCreateDocument = (
setStatus: (v: FooterBtnStatus) => void,
formApi: React.MutableRefObject<FormApi<CustomFormFields> | undefined>,
) => {
const params = useKnowledgeParams();
const resourceNavigate = useDataNavigate();
const { run: createDocument } = useRequest(
async () => {
setStatus(FooterBtnStatus.LOADING);
const data = formApi?.current?.getValues();
const payload = {
space_id: params.spaceID,
dataset_id: params.datasetID,
document: {
source_type: DocumentSourceType.Custom,
name: data?.unitName,
format_type: FormatType.Table,
table_meta: (data?.metaData ?? []).map(meta => ({
column_name: meta.column_name,
is_semantic: meta.is_semantic,
column_type: meta.column_type,
desc: meta.desc,
})),
},
};
const { id } = await MemoryApi.CreateDocument(payload);
console.log('doc-id', id);
resourceNavigate.toResource?.('knowledge', params.datasetID);
setStatus(getCustomStatus(data?.metaData ?? [], data?.unitName ?? ''));
},
{
onError: error => {
const data = formApi?.current?.getValues();
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeCreateDocument,
error,
});
setStatus(getCustomStatus(data?.metaData ?? [], data?.unitName ?? ''));
},
manual: true,
},
);
return createDocument;
};
/**
* @deprecated
* table unit name校验
* TODO 待删,新链路已经不用,只直接用新的,因为产品功能是新的
*/
export const useValidateUnitName = () => {
const { docID: docIdFromQuery, spaceID, datasetID } = useKnowledgeParams();
return async (params: { unit_name: string | string[] }) => {
const { unit_name: unitName, ...rest } = params;
const unitNameList = unitName instanceof Array ? unitName : [unitName];
return Promise.all(
unitNameList.map(
async (name: string) =>
await MemoryApi.ValidateUnitName({
space_id: spaceID ?? '',
dataset_id: datasetID ?? '',
document_id: docIdFromQuery ? docIdFromQuery : undefined,
unit_name: name,
format_type: FormatType.Table,
...rest,
}),
),
);
};
};
export const useTableSchemaValid = (
onSuccess: (res: boolean, validResult: Record<string, string>) => void,
onError?: (error: Error) => void,
) => {
const { run: tableSchemaValid } = useRequest(
async (params: ValidateTableSchemaRequest) => {
const res = await KnowledgeApi.ValidateTableSchema(params);
const validResult = res?.column_valid_result || {};
const validResultKeys = Object.keys(validResult);
const state = validResultKeys.length === 0 ? res?.code === 0 : false;
onSuccess(state, validResult);
},
{
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeTableSchemaValid,
error: error as Error,
});
onError?.(error);
},
manual: true,
},
);
return tableSchemaValid;
};
export const useFetchAddSegmentReq = (
onSuccess: (res: CreateDocumentResponse) => void,
onError?: () => void,
) => {
const { run: fetchAddSegment, loading: addSegmentLoading } = useRequest(
async (params: CreateDocumentRequest) => {
const res = await KnowledgeApi.CreateDocument(params);
onSuccess(res);
},
{
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeTableAddSegment,
error: error as Error,
});
onError?.();
},
manual: true,
},
);
return {
fetchAddSegment,
addSegmentLoading,
};
};
export const useAddSegment = <
T extends UploadTableState<number> & UploadTableAction<number>,
>(
useStore: UseBoundStore<StoreApi<T>>,
) => {
const setStatus = useStore(state => state.setStatus);
const setCreateStatus = useStore(state => state.setCreateStatus);
const setProgressList = useStore(state => state.setProgressList);
const pollingTaskProgress = usePollingTaskProgress();
const { docID: docIdFromQuery } = useKnowledgeParams();
const { fetchAddSegment } = useFetchAddSegmentReq(
async res => {
await pollingTaskProgress(
[
{
document_id: docIdFromQuery,
},
],
{
onProgressing: (progressList: ProgressItem[]) => {
setProgressList(progressList);
},
onFinish: () => {
setCreateStatus(CreateUnitStatus.TASK_FINISH);
},
},
);
setStatus(TableStatus.NORMAL);
},
() => {
setStatus(TableStatus.ERROR);
},
);
return fetchAddSegment;
};
export const useFetchListDocumentReq = (
onSuccess: (res: DocumentInfo) => void,
onError?: () => void,
) => {
const params = useKnowledgeParams();
const { run: fetchListDocument } = useRequest(
async () => {
if (!params.datasetID) {
throw new CustomError(
REPORT_EVENTS.KnowledgeListDocument,
`${REPORT_EVENTS.KnowledgeListDocument}: no datasetID`,
);
}
if (!params.docID) {
throw new CustomError(
REPORT_EVENTS.KnowledgeListDocument,
`${REPORT_EVENTS.KnowledgeListDocument}: no docId`,
);
}
const listDocumentRes = await KnowledgeApi.ListDocument({
dataset_id: params.datasetID,
document_ids: [params.docID],
});
const docInfo: DocumentInfo =
listDocumentRes?.document_infos?.find(
i => i.document_id === params.docID,
) || {};
onSuccess(docInfo);
},
{
onError: error => {
dataReporter.errorEvent(DataNamespace.KNOWLEDGE, {
eventName: REPORT_EVENTS.KnowledgeListDocument,
error: error as Error,
});
onError?.();
},
manual: true,
},
);
return fetchListDocument;
};
export const useAcceptFiles = (): string => {
const [FLAGS] = useFlags();
const accept = TABLE_ACCEPT_LOCAL_FILE.filter(i => {
if (!FLAGS['bot.data.knowledge_md_xls']) {
return i !== '.xls';
}
return !!i;
}).join(',');
return accept;
};

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { type UploadTableAction, type UploadTableState } from './interface';
export {
useFetchTableSchemaInfo,
useTableSchemaValid,
useAddSegment,
useChangeTableSettingsNl2ql,
} from './hooks';
export {
isConfigurationLoading,
isConfigurationError,
isConfigurationShowBanner,
getConfigurationMeta,
getConfigurationNextStatus,
semanticValidator,
getDocIdFromProgressList,
getCreateDocumentParams,
getExpandConfigurationMeta,
getAddSegmentParams,
useResegmentFetchTableParams,
} from './utils';
export { createTableSlice, getDefaultState } from './slice';
export { useValidateUnitName } from './hooks';

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UploadBaseAction,
type UploadBaseState,
} from '@coze-data/knowledge-resource-processor-core';
import { type DocumentInfo } from '@coze-arch/bot-api/knowledge';
import type { SemanticValidate, TableSettings, TableInfo } from '@/types';
import { type TableStatus } from '@/constants';
export interface UploadTableAction<T extends number>
extends UploadBaseAction<T> {
/** store action */
setStatus: (status: TableStatus) => void;
setSemanticValidate: (semanticValidate: SemanticValidate) => void;
setTableData: (tableData: TableInfo) => void;
setOriginTableData: (originTableData: TableInfo) => void;
setTableSettings: (tableSettings: TableSettings) => void;
setDocumentList?: (documentList: Array<DocumentInfo>) => void;
}
export interface UploadTableState<T extends number> extends UploadBaseState<T> {
status: TableStatus;
semanticValidate: SemanticValidate;
tableData: TableInfo;
originTableData: TableInfo;
tableSettings: TableSettings;
documentList?: Array<DocumentInfo>;
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type StateCreator } from 'zustand';
import {
CreateUnitStatus,
type ProgressItem,
type UnitItem,
} from '@coze-data/knowledge-resource-processor-core';
import { type DocumentInfo } from '@coze-arch/bot-api/knowledge';
import type { SemanticValidate, TableInfo, TableSettings } from '@/types';
import { TableStatus, DEFAULT_TABLE_SETTINGS_FROM_ONE } from '@/constants';
import type { UploadTableAction, UploadTableState } from './interface';
export const getDefaultState = () => ({
/** base store */
createStatus: CreateUnitStatus.UPLOAD_UNIT,
progressList: [],
unitList: [],
currentStep: 0,
/** table store */
status: TableStatus.NORMAL,
semanticValidate: {},
tableData: {},
originTableData: {},
tableSettings: DEFAULT_TABLE_SETTINGS_FROM_ONE,
documentList: [],
});
export const createTableSlice: StateCreator<
UploadTableState<number> & UploadTableAction<number>
> = (set, get) => ({
/** defaultState */
...getDefaultState(),
/** base store action */
setCurrentStep: (currentStep: number) => {
set({ currentStep });
},
setCreateStatus: (createStatus: CreateUnitStatus) => {
set({ createStatus });
},
setProgressList: (progressList: ProgressItem[]) => {
set({ progressList });
},
setUnitList: (unitList: UnitItem[]) => {
set({ unitList });
},
/** table store action */
setStatus: (status: TableStatus) => {
set({ status });
},
setSemanticValidate: (semanticValidate: SemanticValidate) => {
set({ semanticValidate });
},
setTableData: (tableData: TableInfo) => {
set({ tableData });
},
setOriginTableData: (originTableData: TableInfo) => {
set({ originTableData });
},
setTableSettings: (tableSettings: TableSettings) => {
set({ tableSettings });
},
setDocumentList: (documentList: Array<DocumentInfo>) => {
set({ documentList });
},
/** reset state */
reset: () => {
set(getDefaultState());
},
});

View File

@@ -0,0 +1,408 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 { get } from 'lodash-es';
import { isFeishuOrLarkDocumentSource } from '@coze-data/utils';
import {
FooterBtnStatus,
UploadStatus,
} from '@coze-data/knowledge-resource-processor-core';
import type {
UnitItem,
OptType,
ProgressItem,
} from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import {
type GetDocumentTableInfoResponse,
type DocumentTaskInfo,
type DocTableColumn,
ColumnType,
} from '@coze-arch/bot-api/memory';
import {
TableDataType,
DocumentSource,
type DocumentBase,
type TableColumn,
type UpdateRule,
} from '@coze-arch/bot-api/knowledge';
import { FormatType } from '@coze-arch/bot-api/developer_api';
import {
useDocIdFromQuery,
tableSettingsToString,
validateField,
} from '@/utils';
import {
type TableSettings,
type TableInfo,
type SemanticValidate,
type AddCustomTableMeta,
} from '@/types';
import {
TableStatus,
MAX_TABLE_META_COLUMN_LEN,
MAX_TABLE_META_STR_LEN,
TableSettingFormFields,
} from '@/constants';
/**
* 校验key是否能作为语义匹配项
* @param data
* @returns
*/
export const semanticValidator = (
data?: GetDocumentTableInfoResponse,
startRow = 0,
): SemanticValidate => {
if (!data) {
return {};
}
const { table_meta, preview_data } = data;
if (
!table_meta ||
!preview_data ||
!Object.keys(table_meta).length ||
!Object.keys(preview_data).length
) {
return {};
}
// TODO 下面forEach写的不好待优化
const res: SemanticValidate = {};
Object.keys(table_meta).forEach(sheetId => {
res[sheetId] = {};
const curTableMeta = table_meta[sheetId];
if (!Array.isArray(curTableMeta)) {
return {};
}
curTableMeta.forEach(meta => {
const { column_type, sequence } = meta;
if (sequence === undefined) {
return;
}
res[sheetId][sequence] = {
valid: true,
msg: '',
};
preview_data[sheetId].slice(startRow).forEach(dataMap => {
if (column_type === ColumnType.Image) {
res[sheetId][sequence] = {
valid: false,
msg: I18n.t('knowledge_insert_img_011'),
};
return;
}
});
});
});
return res;
};
export function filterDocumentList({
unitList,
tableData,
tableSettings,
docIdFromQuery,
}: {
unitList: UnitItem[];
tableData: TableInfo;
tableSettings: TableSettings;
docIdFromQuery: string;
}): DocumentTaskInfo[] {
const meta = tableData.table_meta?.[tableSettings.sheet_id];
const { sheet_id, header_line_idx, start_line_idx } = tableSettings;
const settings = {
sheet_id: sheet_id.toString(),
header_line_idx: header_line_idx.toString(),
start_line_idx: start_line_idx.toString(),
};
return unitList.map(item => {
if (item.docId) {
return {
name: item.name,
uri: item.uri,
document_id: item.docId,
format_type: FormatType.Table,
doc_table_meta: meta,
doc_table_info: settings,
};
}
return {
name: item.name,
uri: item.uri,
document_id: docIdFromQuery === '' ? undefined : docIdFromQuery,
format_type: FormatType.Table,
doc_table_meta: meta,
doc_table_info: settings,
};
}) as DocumentTaskInfo[];
}
export const useResegmentFetchTableParams = () => ({
document_id: useDocIdFromQuery(),
// source_file: {
// document_source: DocumentSource.Document,
// },
table_data_type: TableDataType.AllData,
});
export function isConfigurationLoading(status: TableStatus) {
return status === TableStatus.LOADING;
}
export function isConfigurationError(status: TableStatus) {
return status === TableStatus.ERROR;
}
export function isConfigurationShowBanner(
opt: OptType,
originTableData: TableInfo,
tableSettings: TableSettings,
): boolean {
return opt
? (
get(
originTableData,
`table_meta.${tableSettings[TableSettingFormFields.SHEET]}`,
) || []
).length > MAX_TABLE_META_COLUMN_LEN
: Object.keys(
get(
originTableData,
`preview_data.${tableSettings[TableSettingFormFields.SHEET]}.${
tableSettings[TableSettingFormFields.KEY_START_ROW]
}`,
) || {},
).length > MAX_TABLE_META_COLUMN_LEN;
}
export function getConfigurationMeta(
tableData: TableInfo,
tableSettings: TableSettings,
) {
return tableData
? (
(tableData.table_meta || {})[
tableSettings[TableSettingFormFields.SHEET] || 0
] || []
).map(_meta => ({
..._meta,
key: _meta.id,
column_name: (_meta.column_name ?? '').substring(
0,
MAX_TABLE_META_STR_LEN,
),
}))
: [];
}
export function getExpandConfigurationMeta(
tableData: TableInfo,
tableSettings: TableSettings,
validResult: Record<string, string>,
): Array<
DocTableColumn & {
autofocus?: boolean;
errMsg?: string;
}
> {
const tableColumn = getConfigurationMeta(tableData, tableSettings);
const validResultKeys = Object.keys(validResult);
return tableColumn.map(item => {
const newItem: DocTableColumn & {
autofocus?: boolean;
errMsg?: string;
} = {
...item,
};
const hasHit = validResultKeys.includes(item?.column_name || '');
if (hasHit) {
newItem.errMsg = validResult[item?.column_name || ''];
}
return newItem;
});
}
export function getConfigurationNextStatus(
tableData: TableInfo,
tableSettings: TableSettings,
): FooterBtnStatus {
if (!tableData) {
return FooterBtnStatus.DISABLE;
}
const { table_meta } = tableData;
const meta =
get(table_meta, tableSettings[TableSettingFormFields.SHEET]) || [];
const hasEmptyMeta = meta.some(v => (v.column_name ?? '') === '');
const hasEmptyType = meta.some(v => !(v.column_type ?? ''));
const hasSystemField = meta.some(
v => validateField(v?.column_name ?? '')?.valid === false,
);
const hasSemantic = meta.some(v => v.is_semantic === true);
const hasDuplicateColumnName = meta.some(
v => meta.filter(i => i.column_name === v.column_name).length >= 2,
);
// 没有表结构数据禁止下一步
if (
!Object.keys(meta).length ||
hasEmptyMeta ||
hasEmptyType ||
!hasSemantic ||
hasSystemField ||
meta.length > MAX_TABLE_META_COLUMN_LEN ||
hasDuplicateColumnName
) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}
export function getDocIdFromProgressList(progressList: ProgressItem[]) {
return Array.isArray(progressList) && progressList.length > 0
? get(progressList, '0.documentId')
: null;
}
/**
* 获取footer状态不包括loading状态仅判断启/禁用
* 满足以下任一情况时禁用footer按钮
* 1.存在列名为空
* 2.unitName为空
* 3.没有选择语义匹配项
* @param metaData
* @param unitName // TODO unitName的判断待删
* @returns
*/
export const getCustomStatus = (
metaData: AddCustomTableMeta,
unitName: string,
): FooterBtnStatus => {
const isDisabled =
metaData.some(meta => !meta.column_type) ||
!validateField(unitName)?.valid || // TODO 这一行待删
!metaData.some(meta => meta.is_semantic === true) ||
metaData.some(v => validateField(v.column_name ?? '')?.valid === false);
return isDisabled ? FooterBtnStatus.DISABLE : FooterBtnStatus.ENABLE;
};
// table上传第一步footer状态判断
export function getButtonStatus(unitList: UnitItem[]) {
const valid = unitList.some(v => validateField(v.name)?.valid === false);
if (
unitList.length === 0 ||
valid ||
unitList.some(
unitItem =>
unitItem.name.length === 0 || unitItem.status !== UploadStatus.SUCCESS,
)
) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}
export function getAddSegmentParams({
spaceId,
docId,
datasetId,
documentInfo,
}: {
spaceId?: string;
docId?: string;
datasetId: string;
documentInfo: DocumentBase[];
}) {
const payload = {
space_id: spaceId, // 兼容旧接口
document_id: docId, // 兼容旧接口
dataset_id: datasetId,
format_type: FormatType.Table,
document_bases: documentInfo,
is_append: true,
};
return payload;
}
export function getCreateDocumentParams({
isAppend,
unitList,
metaData,
tableSettings,
sourceType,
updateRule,
}: {
isAppend: boolean;
unitList: UnitItem[];
metaData: TableColumn[];
tableSettings: TableSettings;
sourceType?: DocumentSource;
updateRule?: UpdateRule;
}) {
const documentSource = sourceType ?? DocumentSource.Document;
const getSourceFileId = (unit: UnitItem) =>
[DocumentSource.GoogleDrive, DocumentSource.Notion].includes(documentSource)
? unit.file_id
: unit.entity_id;
const getUpdateRule = () => {
if (updateRule) {
return updateRule;
}
if (
!isAppend &&
(documentSource === DocumentSource.Web ||
isFeishuOrLarkDocumentSource(documentSource))
) {
return {
update_interval: get(unitList, '0.updateInterval'),
update_type: get(unitList, '0.updateType'),
};
}
};
return {
source_type: documentSource,
format_type: FormatType.Table,
document_bases: unitList.map(unit => ({
name: '',
source_info: {
tos_uri: unit.uri,
document_source: documentSource,
source_file_id: getSourceFileId(unit),
web_id: unit.webID as string,
},
update_rule: getUpdateRule(),
table_sheet: tableSettingsToString(tableSettings),
table_meta: metaData.map(meta => ({
id: isAppend ? meta.id : '0', // 不是追加,默认是新建(为'0'
column_name: meta.column_name,
is_semantic: meta.is_semantic,
column_type: meta.column_type,
desc: meta.desc,
sequence: meta.sequence,
})),
})),
is_append: isAppend,
// document_id: useDocIdFromQuery() ?? undefined, // TODO 待删除这里是为了兼容原来的MemoryApi.ProcessDocumentsTask更新逻辑
};
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
enum PreProcessRule {
REMOVE_SPACES = 'remove_extra_spaces',
REMOVE_EMAILS = 'remove_urls_emails',
}
export enum SeparatorType {
LINE_BREAK = '\n',
LINE_BREAK2 = '\n\n',
CN_PERIOD = '。',
CN_EXCLAMATION = '',
EN_PERIOD = '.',
EN_EXCLAMATION = '!',
CN_QUESTION = '',
EN_QUESTION = '?',
CUSTOM = 'custom',
}
export interface SegmentRule {
separator: string;
maxTokens: number;
preProcessRules: PreProcessRule[];
}
export enum SegmentCleaner {
AUTO = 0,
CUSTOM = 1,
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 { useTextDisplaySegmentStepCheck } from '@/hooks/common';
import { UploadFooter } from '@/components';
import {
createTextCustomAddUpdateStore,
type UploadTextCustomAddUpdateStore,
} from './store';
import { TextUpload, TextSegment, TextProcessing } from './steps';
import { TextCustomAddUpdateStep } from './constants';
export const TextCustomAddUpdateConfig: UploadConfig<
TextCustomAddUpdateStep,
UploadTextCustomAddUpdateStore
> = {
steps: [
{
content: props => (
<TextUpload
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('knowledge_upload_text_custom_add_title'),
step: TextCustomAddUpdateStep.UPLOAD_CONTENT,
},
{
content: props => (
<TextSegment
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('kl_write_107'),
step: TextCustomAddUpdateStep.SEGMENT_CLEANER,
},
{
content: props => (
<TextProcessing
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step4'),
step: TextCustomAddUpdateStep.EMBED_PROGRESS,
},
],
createStore: createTextCustomAddUpdateStore,
useUploadMount: store => useTextDisplaySegmentStepCheck(),
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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 { TextProcessing } from './processing';

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, useEffect } from 'react';
import { useShallow } from 'zustand/react/shallow';
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 { I18n } from '@coze-arch/i18n';
import {
DocumentSource,
FormatType,
StorageLocation,
} from '@coze-arch/bot-api/knowledge';
import { getProcessingDescMsg } from '@/utils';
import { useCreateDocument } from '@/hooks';
import { getCustomValues } from '@/features/knowledge-type/text/utils';
import { UnitProgress } from '@/components';
import type { UploadTextCustomAddUpdateStore } from '../../store';
import styles from './index.module.less';
export const TextProcessing: FC<
ContentProps<UploadTextCustomAddUpdateStore>
> = props => {
const { useStore, footer } = props;
const resourceNavigate = useDataNavigate();
const params = useKnowledgeParams();
const {
progressList,
createStatus,
docName,
docContent,
segmentMode,
segmentRule,
enableStorageStrategy,
storageLocation,
openSearchConfig,
} = useStore(
useShallow(state => ({
progressList: state.progressList,
createStatus: state.createStatus,
docName: state.docName,
docContent: state.docContent,
segmentMode: state.segmentMode,
segmentRule: state.segmentRule,
enableStorageStrategy: state.enableStorageStrategy,
storageLocation: state.storageLocation,
openSearchConfig: state.openSearchConfig,
})),
);
const createDocument = useCreateDocument(useStore);
useEffect(() => {
createDocument({
format_type: FormatType.Text,
document_bases: [
{
name: docName,
source_info: {
custom_content: docContent,
document_source: DocumentSource.Custom,
},
},
],
// 非首次添加时,不需要传分段规则
chunk_strategy: getCustomValues(segmentMode, segmentRule),
storage_strategy:
IS_CN_REGION && enableStorageStrategy
? {
storage_location: storageLocation,
open_search_config:
storageLocation === StorageLocation.OpenSearch
? openSearchConfig
: undefined,
}
: undefined,
});
}, []);
return (
<>
<UnitProgress progressList={progressList} createStatus={createStatus} />
{footer?.({
btns: [
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('variable_reset_yes'),
onClick: () => {
const query = getKnowledgeIDEQuery() as Record<string, string>;
resourceNavigate.toResource?.(
'knowledge',
params.datasetID,
query,
);
},
},
],
prefix: (
<span className={styles['footer-sub-tip']}>
{getProcessingDescMsg(createStatus)}
</span>
),
})}
</>
);
};

View File

@@ -0,0 +1,112 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable selector-class-pattern */
.radio-wrapper {
.displayNone {
display: none;
}
.form-line-wrapper {
position: relative;
}
.line {
&::before {
content: '';
position: absolute;
top: 10px;
left: -15px;
display: inline-block;
width: 690px;
height: 1px;
margin-top: 26px;
background: var(--light-usage-border-color-border,
rgb(28 31 35 / 8%));
}
}
.form-line {
margin-top: 27px;
}
.pt6 {
padding-top: 6px;
}
:global {
.semi-radioGroup {
&.semi-radioGroup-vertical {
row-gap: 16px;
}
.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(--light-usage-border-color-border, rgb(28 31 35 / 8%));
border-radius: 8px;
.semi-radio-addon {
margin-bottom: 2px;
}
.semi-form-field-label {
margin-bottom: 8px;
}
}
.semi-checkboxGroup-vertical {
row-gap: 8px;
}
.semi-radio-cardRadioGroup_checked {
background: transparent;
border: 1px solid var(--semi-color-primary);
}
.semi-radio-cardRadioGroup_checked:active {
background: transparent;
}
.semi-radio-cardRadioGroup .semi-radio-extra {
margin-top: 4px;
}
}
.separator-div {
margin-top: 33px;
margin-bottom: 4px;
}
.separator-not-custom {
margin-bottom: 11px;
}
.separator-input {
padding-top: 0;
padding-bottom: 8px !important;
:global {
.semi-form-field-label {
display: none !important;
}
}
}
// :global {
// }
}

View File

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

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, useEffect } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { get } from 'lodash-es';
import { useKnowledgeParams } from '@coze-data/knowledge-stores';
import { type ContentProps } from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { getSegmentCleanerParams, getStorageStrategyEnabled } from '@/utils';
import { SegmentMode } from '@/types';
import { useListDocumentReq } from '@/services';
import {
SegmentConfig,
type OnChangeProps,
} from '@/features/segment-config/base';
import type { UploadTextCustomAddUpdateStore } from '../../store';
import { TextCustomAddUpdateStep } from '../../constants';
import { getButtonNextStatus } from './utils';
export const TextSegment: FC<
ContentProps<UploadTextCustomAddUpdateStore>
> = props => {
const { useStore, footer } = props;
const {
// common store
setCurrentStep,
// text store
segmentRule,
setSegmentRule,
segmentMode,
setSegmentMode,
setEnableStorageStrategy,
storageLocation,
testConnectionSuccess,
} = useStore(
useShallow(state => ({
setCurrentStep: state.setCurrentStep,
segmentRule: state.segmentRule,
setSegmentRule: state.setSegmentRule,
segmentMode: state.segmentMode || SegmentMode.AUTO,
setSegmentMode: state.setSegmentMode,
setEnableStorageStrategy: state.setEnableStorageStrategy,
storageLocation: state.storageLocation,
testConnectionSuccess: state.testConnectionSuccess,
})),
);
const { datasetID, docID } = useKnowledgeParams();
const listDocumentReq = useListDocumentReq(res => {
const segment = getSegmentCleanerParams(get(res, 'document_infos[0]', {}));
if (segment) {
setSegmentRule(segment.segmentRule);
setSegmentMode(segment.segmentMode);
}
});
useEffect(() => {
if (docID) {
listDocumentReq({
dataset_id: datasetID || '',
document_ids: [docID || ''],
});
}
}, []);
useEffect(() => {
if (datasetID) {
KnowledgeApi.DatasetDetail({ dataset_ids: [datasetID] }).then(res => {
const dataset = res.dataset_details?.[datasetID];
setEnableStorageStrategy(getStorageStrategyEnabled(dataset));
});
}
}, [datasetID]);
return (
<>
<SegmentConfig
segmentRule={segmentRule}
segmentMode={segmentMode}
onChange={({ segmentRule: rule, segmentMode: mode }: OnChangeProps) => {
rule !== undefined && setSegmentRule(rule);
mode !== undefined && setSegmentMode(mode);
}}
/>
{footer?.([
{
e2e: KnowledgeE2e.UploadUnitUpBtn,
type: 'primary',
theme: 'light',
onClick: () => setCurrentStep(TextCustomAddUpdateStep.UPLOAD_CONTENT),
text: I18n.t('datasets_createFileModel_previousBtn'),
},
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
onClick: () => setCurrentStep(TextCustomAddUpdateStep.EMBED_PROGRESS),
text: I18n.t('datasets_createFileModel_NextBtn'),
status: getButtonNextStatus({
segmentMode,
segmentRule,
storageLocation,
testConnectionSuccess,
}),
},
])}
</>
);
};

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FooterBtnStatus } from '@coze-data/knowledge-resource-processor-core';
import { type StorageLocation } from '@coze-arch/idl/knowledge';
import { type CustomSegmentRule, type SegmentMode } from '@/types';
import { validateSegmentRules } from '@/features/knowledge-type/text/utils';
export function getButtonNextStatus(params: {
segmentMode: SegmentMode;
segmentRule: CustomSegmentRule;
storageLocation: StorageLocation;
testConnectionSuccess: boolean;
}): FooterBtnStatus {
const segmentValid = validateSegmentRules(
params.segmentMode,
params.segmentRule,
);
return segmentValid ? FooterBtnStatus.ENABLE : FooterBtnStatus.DISABLE;
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
.custom-text-form {
:global {
.semi-form-field-error-message, .semi-form-field-label-required .semi-form-field-label-text::after {
color: var(--coz-fg-hglt-red);
}
}
}

View File

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

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 { useContext, useMemo } from 'react';
import { nanoid } from 'nanoid';
import { CozeInputWithCountField } from '@coze-data/utils';
import { KnowledgeParamsStoreContext } from '@coze-data/knowledge-stores';
import {
type ContentProps,
FooterBtnStatus,
} from '@coze-data/knowledge-resource-processor-core';
import {
DocumentEditor,
useInitEditor,
EditorToolbar,
type Chunk,
} from '@coze-data/knowledge-common-components/text-knowledge-editor';
import { KnowledgeE2e } from '@coze-data/e2e';
import { I18n } from '@coze-arch/i18n';
import { Form } from '@coze-arch/bot-semi';
import type { UploadTextCustomAddUpdateStore } from '../../store';
import { TextCustomAddUpdateStep } from '../../constants';
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 const TextUpload = <T extends UploadTextCustomAddUpdateStore>(
props: ContentProps<T>,
) => {
const { useStore, footer } = props;
/** common store */
const docName = useStore(state => state.docName);
const docContent = useStore(state => state.docContent);
const isDouyin = useContext(KnowledgeParamsStoreContext)?.paramsStore?.(
s => s.params?.isDouyinBot,
);
/** common action */
const setDocName = useStore(state => state.setDocName);
const setDocContent = useStore(state => state.setDocContent);
const setCurrentStep = useStore(state => state.setCurrentStep);
const buttonStatus = useMemo(() => {
if (!docName || !docContent) {
return FooterBtnStatus.DISABLE;
}
return FooterBtnStatus.ENABLE;
}, [docName, docContent]);
const handleClickNext = () => {
setCurrentStep(TextCustomAddUpdateStep.SEGMENT_CLEANER);
};
const initChunk = useMemo<Chunk>(
() => ({
text_knowledge_editor_chunk_uuid: nanoid(),
content: '',
}),
[],
);
const { editor } = useInitEditor({
chunk: initChunk,
editorProps: {
attributes: {
class: 'h-[360px] overflow-y-auto',
},
},
onChange: v => {
setDocContent(v.content ?? '');
},
});
return (
<>
<Form<Record<string, unknown>>
layout="vertical"
showValidateIcon={false}
className={styles['custom-text-form']}
>
<CozeInputWithCountField
data-testid={KnowledgeE2e.CustomUploadNameInput}
className={styles['doc-name-input']}
field="docName"
autoFocus
trigger="blur"
onChange={(v: string) => setDocName(v)}
maxLength={MAX_DOC_NAME_LEN}
placeholder={I18n.t('knowledge_upload_text_custom_doc_name_tips')}
label={I18n.t('knowledge_upload_text_custom_doc_name')}
rules={[
{
required: true,
message: I18n.t('knowledge_upload_text_custom_doc_name_tips'),
},
]}
/>
<Form.Slot
className={styles['form-segment-content']}
label={{ text: I18n.t('knowledge_upload_text_custom_doc_content') }}
// error={I18n.t('knowledge_upload_text_custom_doc_content_tips')}
>
<DocumentEditor
editor={editor}
placeholder={I18n.t(
'knowledge_upload_text_custom_doc_content_tips',
)}
editorContextMenuItemsRegistry={
!isDouyin ? editorContextActionRegistry : undefined
}
editorBottomSlot={
!isDouyin ? (
<EditorToolbar
editor={editor}
actionRegistry={editorToolbarActionRegistry}
/>
) : null
}
/>
</Form.Slot>
</Form>
{footer?.([
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
text: I18n.t('datasets_createFileModel_NextBtn'),
status: buttonStatus,
onClick: handleClickNext,
},
])}
</>
);
};

View File

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

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type StateCreator } from 'zustand';
import {
createTextSlice,
getDefaultTextState,
} from '@/features/knowledge-type/text/slice';
import { TextCustomAddUpdateStep } from '../constants';
import {
type UploadTextCustomAddUpdateStore,
type UploadTextCustomAddUpdateState,
} from './types';
export const getDefaultTextCustomAddState: () => UploadTextCustomAddUpdateState =
() => ({
...getDefaultTextState(),
currentStep: TextCustomAddUpdateStep.UPLOAD_CONTENT,
docName: '',
docContent: '',
});
export const createTextCustomAddUpdateSlice: StateCreator<
UploadTextCustomAddUpdateStore
> = (set, ...arg) => ({
...createTextSlice(set, ...arg),
// overwrite
...getDefaultTextCustomAddState(),
// /** reset state */
reset: () => {
set(getDefaultTextCustomAddState());
},
setDocContent: (content: string) => {
set({
docContent: content,
});
},
setDocName: (name: string) => {
set({
docName: name,
});
},
});

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 { type UploadTextCustomAddUpdateStore } from './types';
import { createTextCustomAddUpdateSlice } from './slice';
export const createTextCustomAddUpdateStore = () =>
create<UploadTextCustomAddUpdateStore>()(
devtools((set, get, store) => ({
...createTextCustomAddUpdateSlice(set, get, store),
})),
);

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UploadTextState,
type UploadTextAction,
} from '@/features/knowledge-type/text/interface';
import { type TextCustomAddUpdateStep } from '../constants';
export type UploadTextCustomAddUpdateState =
UploadTextState<TextCustomAddUpdateStep> & {
docName: string;
docContent: string;
};
export type UploadTextCustomAddUpdateAction =
UploadTextAction<TextCustomAddUpdateStep> & {
setDocName: (unitName: string) => void;
setDocContent: (content: string) => void;
};
export type UploadTextCustomAddUpdateStore = UploadTextCustomAddUpdateState &
UploadTextCustomAddUpdateAction;

View File

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

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type UploadConfig,
type FooterControlsProps,
} from '@coze-data/knowledge-resource-processor-core';
import { I18n } from '@coze-arch/i18n';
import { useTextDisplaySegmentStepCheck } from '@/hooks/common';
import { UploadFooter } from '@/components';
import {
createTextLocalAddUpdateStore,
type UploadTextLocalAddUpdateStore,
} from './store';
import { SegmentPreviewStep } from './steps/preview';
import { TextUpload, TextSegment, TextProcessing } from './steps';
import { TextLocalAddUpdateStep } from './constants';
export const TextLocalAddUpdateConfig: UploadConfig<
TextLocalAddUpdateStep,
UploadTextLocalAddUpdateStore
> = {
steps: [
{
content: props => (
<TextUpload
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={props.checkStatus}
/>
),
title: I18n.t('datasets_createFileModel_step2'),
step: TextLocalAddUpdateStep.UPLOAD_FILE,
},
{
content: props => (
<TextSegment
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('kl_write_107'),
step: TextLocalAddUpdateStep.SEGMENT_CLEANER,
},
{
content: props => (
<SegmentPreviewStep
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('knowlege_qqq_001'),
step: TextLocalAddUpdateStep.SEGMENT_PREVIEW,
showThisStep: () => true,
},
{
content: props => (
<TextProcessing
useStore={props.useStore}
footer={(controls: FooterControlsProps) => (
<UploadFooter controls={controls} />
)}
checkStatus={undefined}
/>
),
title: I18n.t('datasets_createFileModel_step4'),
step: TextLocalAddUpdateStep.EMBED_PROGRESS,
},
],
createStore: createTextLocalAddUpdateStore,
useUploadMount: store => useTextDisplaySegmentStepCheck(),
};

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,269 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @coze-arch/max-line-per-function */
import { useEffect, useMemo, type FC } from 'react';
import { isEqual } from 'lodash-es';
import classNames from 'classnames';
import { useRequest } from 'ahooks';
import { useKnowledgeParams } from '@coze-data/knowledge-stores';
import {
FooterBtnStatus,
type ContentProps,
} from '@coze-data/knowledge-resource-processor-core';
import { KnowledgeE2e } from '@coze-data/e2e';
import { ReviewStatus } from '@coze-arch/idl/knowledge';
import { I18n } from '@coze-arch/i18n';
import { KnowledgeApi } from '@coze-arch/bot-api';
import { Toast } from '@coze-arch/coze-design';
import { PreProcessRule, SegmentMode, SeperatorType } from '@/types';
import { SegmentPreview } from '@/features/segment-preview';
import { getCustomValues } from '@/features/knowledge-type/text/utils';
import { getSeperatorOptionList } from '@/constants';
import type { UploadTextLocalAddUpdateStore } from '../../store';
import { TextLocalAddUpdateStep } from '../../constants';
export const SegmentPreviewStep: FC<
ContentProps<UploadTextLocalAddUpdateStore>
> = props => {
const { useStore, footer } = props;
const params = useKnowledgeParams();
/** common store */
const setCurrentStep = useStore(state => state.setCurrentStep);
const unitList = useStore(state => state.unitList);
const segmentMode = useStore(state => state.segmentMode);
const docReviewList = useStore(state => state.docReviewList);
const setDocReviewList = useStore(state => state.setDocReviewList);
const currentReviewID = useStore(state => state.currentReviewID);
const setCurrentReviewID = useStore(state => state.setCurrentReviewID);
const selectionIDs = useStore(state => state.selectionIDs);
const setSelectionIDs = useStore(state => state.setSelectionIDs);
const levelSegments = useStore(state => state.levelSegments);
const setLevelSegments = useStore(state => state.setLevelSegments);
const parsingStrategy = useStore(state => state.parsingStrategy);
const levelChunkStrategy = useStore(state => state.levelChunkStrategy);
const segmentRule = useStore(state => state.segmentRule);
const docReviewCreated = useMemo(
() =>
isEqual(
docReviewList.map(item => item.document_name),
unitList.map(item => item.name),
),
[docReviewList, unitList],
);
/** 创建 doc review */
useEffect(() => {
const createDocReview = async () => {
const { unitList: inputUnitList } = useStore.getState();
const res = await KnowledgeApi.CreateDocumentReview({
dataset_id: params.datasetID,
reviews: inputUnitList.map(item => ({
tos_uri: item.uri,
document_name: item.name,
document_type: item.type,
})),
parsing_strategy: parsingStrategy,
chunk_strategy: getCustomValues(
segmentMode,
segmentRule,
levelChunkStrategy,
),
});
if (res.code === 0 && res.reviews?.length) {
setDocReviewList(res.reviews);
setCurrentReviewID(res.reviews[0].review_id ?? '');
}
};
if (!docReviewCreated) {
createDocReview();
}
// TODO: segmentMode 切换时需要重新创建 doc review
}, [
docReviewCreated,
parsingStrategy,
segmentMode,
segmentRule,
levelChunkStrategy,
]);
/** 轮询 doc review 状态 */
const { run: pollDocReviewStatus, cancel: cancelPollDocReviewStatus } =
useRequest(
async () => {
const res = await KnowledgeApi.MGetDocumentReview({
dataset_id: params.datasetID,
review_ids: docReviewList.map(item => item.review_id ?? ''),
});
if (res.code === 0) {
setDocReviewList(res.reviews ?? []);
}
},
{
manual: true,
pollingInterval: 2000,
},
);
useEffect(() => {
if (docReviewCreated) {
pollDocReviewStatus();
}
return () => {
cancelPollDocReviewStatus();
};
}, [docReviewCreated]);
/** 结束轮询 */
const docReviewProcessFinished =
docReviewList.length > 0 &&
docReviewList.every(
item =>
// docReview 刚创建时没有 status, 其他情况下校验 status 是否为 Processing
typeof item.status !== 'undefined' &&
item.status !== ReviewStatus.Processing,
);
useEffect(() => {
if (docReviewProcessFinished) {
cancelPollDocReviewStatus();
}
}, [docReviewProcessFinished]);
const { loading: saveLoading, run: saveDocumentReview } = useRequest(
async () => {
const res = await KnowledgeApi.SaveDocumentReview({
review_id: currentReviewID,
dataset_id: params.datasetID,
doc_tree_json: JSON.stringify({ chunks: levelSegments }),
});
return res.code === 0;
},
{
manual: true,
onSuccess: data => {
if (data) {
setCurrentStep(TextLocalAddUpdateStep.EMBED_PROGRESS);
}
},
onError: () => {
Toast.error('变更保存失败');
},
},
);
return (
<>
<SegmentPreview
docReviewList={docReviewList}
setDocReviewList={setDocReviewList}
segmentMode={segmentMode}
currentReviewID={currentReviewID}
setCurrentReviewID={setCurrentReviewID}
selectionIDs={selectionIDs}
setSelectionIDs={setSelectionIDs}
levelSegments={levelSegments}
setLevelSegments={setLevelSegments}
datasetID={params.datasetID}
segmentInfo={
segmentMode !== SegmentMode.AUTO ? (
<div
className={classNames(
'flex flex-col',
'text-[14px] font-[400] leading-[20px] coz-fg-primary',
)}
>
{segmentMode === SegmentMode.LEVEL ? (
<>
<span>
{'\u2022'} {I18n.t('knowledge_level_004')}:
{levelChunkStrategy.maxLevel}
</span>
{levelChunkStrategy.isSaveTitle ? (
<span>
{'\u2022'} {I18n.t('knowledge_level_005')}
</span>
) : null}
</>
) : null}
{segmentMode === SegmentMode.CUSTOM ? (
<>
<span>
{'\u2022'} {I18n.t('datasets_Custom_segmentID')}:
{segmentRule.separator.type !== SeperatorType.CUSTOM
? getSeperatorOptionList().find(
item => item.value === segmentRule.separator.type,
)?.label
: segmentRule.separator.customValue}
</span>
<span>
{'\u2022'} {I18n.t('datasets_Custom_maxLength')}:
{segmentRule.maxTokens}
</span>
<span>
{'\u2022'} {I18n.t('kl_write_014')}: {segmentRule.overlap}
</span>
{segmentRule.preProcessRules.includes(
PreProcessRule.REMOVE_SPACES,
) ? (
<span>
{'\u2022'} {I18n.t('datasets_Custom_rule_replace')}
</span>
) : null}
{segmentRule.preProcessRules.includes(
PreProcessRule.REMOVE_EMAILS,
) ? (
<span>
{'\u2022'} {I18n.t('datasets_Custom_rule_delete')}
</span>
) : null}
</>
) : null}
</div>
) : null
}
/>
{footer?.([
{
e2e: KnowledgeE2e.UploadUnitUpBtn,
type: 'primary',
theme: 'light',
onClick: () => {
setDocReviewList([]);
setCurrentStep(TextLocalAddUpdateStep.SEGMENT_CLEANER);
},
text: I18n.t('datasets_createFileModel_previousBtn'),
},
{
e2e: KnowledgeE2e.UploadUnitNextBtn,
type: 'hgltplus',
theme: 'solid',
status: saveLoading
? FooterBtnStatus.LOADING
: FooterBtnStatus.ENABLE,
onClick: async () => {
await saveDocumentReview();
},
text: I18n.t('datasets_createFileModel_NextBtn'),
},
])}
</>
);
};

View File

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

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