/* * Copyright 2025 coze-dev Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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, useRef, useState } from 'react'; import { useRequest } from 'ahooks'; import { DataNamespace, dataReporter } from '@coze-data/reporter'; import { REPORT_EVENTS } from '@coze-arch/report-events'; import { I18n } from '@coze-arch/i18n'; import { CustomError } from '@coze-arch/bot-error'; import { type DocTableColumn, ColumnType } from '@coze-arch/bot-api/memory'; import { KnowledgeApi } from '@coze-arch/bot-api'; import { transSliceContentOutput } from '../utils'; import { TableSegmentModal } from './modal'; export enum ModalActionType { Create, Edit, } export interface DocTableColumnExt extends DocTableColumn { error: string; value: string; column_id: string; } export interface UseTableSegmentModalParams { title: string | JSX.Element; meta: DocTableColumn[]; disabled?: boolean; canEdit?: boolean; loading?: boolean; onSubmit?: (actionType: ModalActionType, data: TableDataItem[]) => void; onFinish: (actionType: ModalActionType, data: TableDataItem[]) => void; } interface UseTableSegmentModalReturnValue { node: JSX.Element | null; create: () => void; edit: (data: TableDataItem[] | string) => void; close: () => void; fetchCreateTableSegment: ( docId: string, createContent: TableDataItem[], ) => void; fetchUpdateTableSegment: ( sliceId: string, updateContent: TableDataItem[], ) => void; } export interface TableDataItem { column_id: string; column_name?: string; column_type?: ColumnType; is_semantic?: boolean; value: string; error?: string; } const CONTENT_MAX_LENGTH = 2000; const getFormData = (data: TableDataItem[]) => { const formData: Record = {}; data.map(item => { formData[item.column_id] = item.value || ''; }); return JSON.stringify(formData); }; const updateWithSliceList = ( meta: DocTableColumn[], slices: TableDataItem[], ) => { const extendedTableMeta: DocTableColumnExt[] = meta.map(column => ({ ...column, error: '', value: '', column_id: column.id || '', column_name: column.column_name || '', is_semantic: Boolean(column.is_semantic), })); if (slices.length === 0) { return extendedTableMeta; } slices.forEach(slice => { const column = extendedTableMeta.find(col => col.id === slice.column_id); if (column) { column.value = slice.value || ''; } }); return extendedTableMeta; }; export const useTableSegmentModal = ({ title, meta = [], canEdit, disabled, onSubmit, onFinish, loading, }: UseTableSegmentModalParams): UseTableSegmentModalReturnValue => { const [visible, setVisible] = useState(false); const [tableData, setTableData] = useState([]); const tableMeta = useRef(meta); const actionTypeRef = useRef(ModalActionType.Create); const getFormVerification = () => { let isValid = true; const newTextAreas = tableData.map(textArea => { const newTextArea = { ...textArea }; if (newTextArea?.is_semantic) { if (newTextArea.value.length === 0) { newTextArea.error = I18n.t('knowledge_table_content_empty'); isValid = false; } else if (newTextArea.value.length > CONTENT_MAX_LENGTH) { newTextArea.error = I18n.t('knowledge_table_content_limt', { number: CONTENT_MAX_LENGTH, }); isValid = false; } } return newTextArea; }); if (!isValid) { setTableData(newTextAreas); } return isValid; }; const { run: fetchCreateTableSegment, loading: createLoading } = useRequest( async (docId: string, createContent: TableDataItem[]) => { if (!docId) { throw new CustomError('normal_error', 'missing doc_id'); } await KnowledgeApi.CreateSlice({ document_id: docId, raw_text: getFormData(createContent), }); return createContent; }, { manual: true, onSuccess: data => { onFinish(actionTypeRef.current, data); onCancel(); }, onError: error => { dataReporter.errorEvent(DataNamespace.KNOWLEDGE, { eventName: REPORT_EVENTS.KnowledgeCreateSlice, error, }); }, }, ); const { run: fetchUpdateTableSegment, loading: uploadLoading } = useRequest( async (sliceId: string, updateContent: TableDataItem[]) => { if (!sliceId) { throw new CustomError('normal_error', 'missing slice_id'); } const formatRecord = updateContent.map(colData => { if (colData.column_type === ColumnType.Image) { return { ...colData, value: transSliceContentOutput(colData.value), }; } return colData; }); await KnowledgeApi.UpdateSlice({ slice_id: sliceId, raw_text: getFormData(formatRecord), }); return updateContent; }, { manual: true, onSuccess: data => { onFinish(actionTypeRef.current, data); onCancel(); }, onError: error => { dataReporter.errorEvent(DataNamespace.KNOWLEDGE, { eventName: REPORT_EVENTS.KnowledgeUpdateSlice, error, }); }, }, ); const modalLoading = useMemo( () => createLoading || uploadLoading || loading, [createLoading, uploadLoading, loading], ); useEffect(() => { tableMeta.current = meta; }, [meta]); const handleSubmit = () => { const isValid = getFormVerification(); if (isValid) { if (typeof onSubmit === 'function') { onSubmit(actionTypeRef.current, tableData); } else { setVisible(false); onFinish(actionTypeRef.current, tableData); } } }; const onCancel = () => { setVisible(false); }; const onOpen = (newTableData?: TableDataItem[]) => { if (newTableData && newTableData.length) { setTableData(updateWithSliceList(tableMeta.current, newTableData)); setVisible(true); } else { setTableData(updateWithSliceList(tableMeta.current, [])); setVisible(true); } }; const handleTextAreaChange = (index: number, newValue: string) => { const newData = [...tableData]; newData[index].value = newValue; // 针对语义匹配行进行检验 if (newData[index]?.is_semantic) { if (newValue.length === 0) { newData[index].error = I18n.t('knowledge_table_content_empty'); } else if (newValue.length > CONTENT_MAX_LENGTH) { newData[index].error = I18n.t('knowledge_table_content_limt', { number: CONTENT_MAX_LENGTH, }); } else { newData[index].error = ''; } } setTableData(newData); }; return { fetchCreateTableSegment, fetchUpdateTableSegment, edit: originTableData => { let newTableData = originTableData; if (typeof originTableData === 'string') { newTableData = JSON.parse(originTableData) as TableDataItem[]; } actionTypeRef.current = ModalActionType.Edit; if (Array.isArray(newTableData)) { onOpen(newTableData); } }, create: () => { actionTypeRef.current = ModalActionType.Create; onOpen(); }, close: onCancel, node: visible ? ( ) : null, }; };