feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
/* stylelint-disable block-no-empty */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
|
||||
.modal-container {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.modal-table-btn {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modal-temp {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.modal-temp-right {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: 409px;
|
||||
margin-bottom: 24px;
|
||||
padding: 15px 24px;
|
||||
|
||||
background: rgba(255, 255, 255, 100%);
|
||||
border: 1px solid rgba(29, 28, 35, 8%);
|
||||
border-radius: 12px;
|
||||
|
||||
.modal-temp-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: rgba(29, 28, 35, 100%);
|
||||
}
|
||||
|
||||
.modal-temp-image {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.modal-temp-description {
|
||||
height: 64px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: rgba(29, 28, 35, 80%);
|
||||
}
|
||||
|
||||
.modal-temp-btn-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
margin-top: 16px;
|
||||
padding: 8px 0;
|
||||
|
||||
.modal-temp-btn {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-temp-preview {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 329px;
|
||||
padding: 16px 21px 24px;
|
||||
|
||||
background: rgba(241, 242, 253, 100%);
|
||||
border-radius: 11px 11px 0 0;
|
||||
|
||||
.title {
|
||||
padding-bottom: 12px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
color: rgba(77, 83, 232, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-modify-tips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
margin-bottom: 14px;
|
||||
padding: 12px;
|
||||
|
||||
background: var(--light-usage-warning-light-color-warning-light-default,
|
||||
#fff8ea);
|
||||
|
||||
.tip-icon {
|
||||
margin: 0 20px 0 17px;
|
||||
color: rgba(252, 136, 0, 100%);
|
||||
|
||||
>svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 65px;
|
||||
}
|
||||
|
||||
.link {
|
||||
cursor: pointer;
|
||||
margin-left: 20px;
|
||||
color: #3370ff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增样式 @zhangyuanzhou.zyz
|
||||
.entry {
|
||||
display: flex;
|
||||
gap: 64px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 409px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
background: rgba(255, 255, 255, 100%);
|
||||
border: 1px solid rgba(29, 28, 35, 8%);
|
||||
border-radius: 12px;
|
||||
|
||||
.entry-method {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
color: var(--Light-usage-text---color-text-0, #1d1c23);
|
||||
|
||||
.entry-method-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.entry-method-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.entry-method:hover {
|
||||
color: var(--Light-color-brand---brand-5, #4d53e8);
|
||||
}
|
||||
}
|
||||
|
||||
// 当窗口足够大时,高度固定为641px
|
||||
// 当窗口太小时,高度随vh变化,333px为内容区距离视窗边缘的距离
|
||||
.database-table-structure-container {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: min(641px, calc(100vh - 333px));
|
||||
}
|
||||
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.generate-ai-popover-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
width: 560px;
|
||||
padding: 24px;
|
||||
|
||||
background-color: rgba(247, 247, 250, 100%);
|
||||
border-radius: 12px;
|
||||
|
||||
.title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.input {}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-area {
|
||||
:global {
|
||||
.semi-input-textarea {
|
||||
overflow: auto;
|
||||
max-height: 191px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
:global {
|
||||
.semi-popover {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.semi-popover-content {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-close-button {
|
||||
height: 24px !important;
|
||||
padding: 4px !important;
|
||||
border-radius: 3px !important;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(46, 50, 56, 5%) !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,655 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
/* eslint-disable max-lines-per-function */
|
||||
|
||||
import { useRef, useMemo, useEffect, useState } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useLocalStorageState } from 'ahooks';
|
||||
import { type DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import {
|
||||
CreateType,
|
||||
type NL2DBInfo,
|
||||
type OnSave,
|
||||
} from '@coze-data/database-v2-base/types';
|
||||
import { TEMPLATE_INFO } from '@coze-data/database-v2-base/constants';
|
||||
import {
|
||||
DatabaseTableStructure,
|
||||
type DatabaseTableStructureRef,
|
||||
} from '@coze-data/database-v2-base/components/database-table-structure';
|
||||
import { FormDatabaseModeSelect } from '@coze-data/database-v2-adapter/components/database-mode-select';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { I18n, getUnReactiveLanguage } from '@coze-arch/i18n';
|
||||
import { sendTeaEvent, EVENT_NAMES } from '@coze-arch/bot-tea';
|
||||
import {
|
||||
Button,
|
||||
Image,
|
||||
Popconfirm,
|
||||
Icon,
|
||||
TextArea,
|
||||
Popover,
|
||||
UIButton,
|
||||
Modal,
|
||||
Form,
|
||||
Toast,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconWarningSize24 } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
BotTableRWMode,
|
||||
type RecommendDataModelResponse,
|
||||
SceneType,
|
||||
FieldItemType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
IconCozWarning as IconAlertTriangle,
|
||||
IconCozCross as IconClose,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { BotDebugButton } from '../bot-debug-button';
|
||||
import tableTempEN from '../../assets/table-template-en.png';
|
||||
import tableTempCN from '../../assets/table-template-cn.png';
|
||||
import tablePreviewEN from '../../assets/table-preview-en.png';
|
||||
import tablePreviewCN from '../../assets/table-preview-cn.png';
|
||||
import { ReactComponent as UpArrowSVG } from '../../assets/icon_up-arrow.svg';
|
||||
import { ReactComponent as DownArrowSvg } from '../../assets/icon_down-arrow.svg';
|
||||
import { ReactComponent as AddSVG } from '../../assets/icon_add_outlined.svg';
|
||||
import { ReactComponent as GenerateSVG } from '../../assets/generate.svg';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface ExpertModeConfig {
|
||||
isExpertMode: boolean;
|
||||
maxTableNum: number;
|
||||
maxColumnNum: number;
|
||||
readAndWriteModes: BotTableRWMode[];
|
||||
}
|
||||
|
||||
export interface DatabaseModalProps {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
database: DatabaseInfo;
|
||||
botId: string;
|
||||
spaceId: string;
|
||||
readonly: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- 历史逻辑
|
||||
NL2DBInfo: NL2DBInfo | null;
|
||||
expertModeConfig?: ExpertModeConfig;
|
||||
onSave?: OnSave;
|
||||
}
|
||||
|
||||
export const DatabaseModal: React.FC<DatabaseModalProps> = props => {
|
||||
const {
|
||||
database,
|
||||
botId,
|
||||
// spaceId,
|
||||
readonly,
|
||||
onCancel,
|
||||
onSave,
|
||||
NL2DBInfo,
|
||||
expertModeConfig,
|
||||
visible,
|
||||
} = props;
|
||||
|
||||
const [generateTableLoading, setGenerateTableLoading] = useState(false);
|
||||
const [contentCheckErrorMsg, setContentCheckErrorMsg] = useState<string>('');
|
||||
const [isEntry, setIsEntry] = useState<boolean>(
|
||||
!database.tableId && !NL2DBInfo,
|
||||
);
|
||||
const [isPreview, setIsPreview] = useState<boolean>(false);
|
||||
const [isDeletedField, setIsDeletedField] = useState<boolean>(false);
|
||||
const [
|
||||
shouldHideDatabaseTableStructureTipsForCurrent,
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent,
|
||||
] = useState<boolean>(false);
|
||||
const [createType, setCreateType] = useState<CreateType>(
|
||||
NL2DBInfo ? CreateType.recommend : CreateType.custom,
|
||||
);
|
||||
const [data, setData] = useState<DatabaseInfo>({
|
||||
tableId: '',
|
||||
name: '',
|
||||
desc: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [],
|
||||
});
|
||||
const [AIPopoverVisible, setAIPopoverVisible] = useState(false);
|
||||
|
||||
const nlTextAreaRef = useRef<HTMLTextAreaElement>();
|
||||
const tableStructureRef = useRef<DatabaseTableStructureRef>();
|
||||
const [
|
||||
mapOfShouldHidingDatabaseTableStructureTips,
|
||||
setMapOfShouldHidingDatabaseTableStructureTips,
|
||||
] = useLocalStorageState<string | undefined>(
|
||||
// FIXME: 此属性名意义不明确,处为了兼容,暂不修改此属性名,但后续需要使用更明确的命名
|
||||
'use-local-storage-state-modify-tips',
|
||||
{
|
||||
defaultValue: '',
|
||||
},
|
||||
);
|
||||
|
||||
const language = getUnReactiveLanguage();
|
||||
const isEdit = Boolean(data.tableId);
|
||||
const [isReadonly, setIsReadonly] = useState(false);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setIsReadonly(true);
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
await tableStructureRef.current.submit();
|
||||
} finally {
|
||||
setIsReadonly(false);
|
||||
}
|
||||
};
|
||||
|
||||
const hideTableStructureTips = useMemo(() => {
|
||||
const lsMap = JSON.parse(
|
||||
mapOfShouldHidingDatabaseTableStructureTips || '{}',
|
||||
);
|
||||
|
||||
return (
|
||||
!isEdit ||
|
||||
lsMap?.[botId] ||
|
||||
shouldHideDatabaseTableStructureTipsForCurrent
|
||||
);
|
||||
}, [
|
||||
isEdit,
|
||||
shouldHideDatabaseTableStructureTipsForCurrent,
|
||||
mapOfShouldHidingDatabaseTableStructureTips,
|
||||
]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (isEdit) {
|
||||
return I18n.t('db_edit_title');
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return I18n.t('db_table_0126_011');
|
||||
}
|
||||
return I18n.t('db_add_table_title');
|
||||
}, [isEdit, createType]);
|
||||
|
||||
const showEntry = isEntry && !isEdit && !NL2DBInfo;
|
||||
const shouldShowAIGenerate =
|
||||
/**
|
||||
* 1. 入口不展示
|
||||
* 2. 编辑态不展示
|
||||
* 3. Excel导入时不展示
|
||||
*/
|
||||
!showEntry && !isEdit && createType !== CreateType.excel;
|
||||
|
||||
const setDataToDefault = () => {
|
||||
setData({
|
||||
name: '',
|
||||
desc: '',
|
||||
tableId: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const setTableFieldsListToDefault = () => {
|
||||
tableStructureRef.current?.setTableFieldsList([
|
||||
{
|
||||
nanoid: nanoid(),
|
||||
name: '',
|
||||
desc: '',
|
||||
type: FieldItemType.Text,
|
||||
must_required: false,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const onUseTemplate = () => {
|
||||
setCreateType(CreateType.template);
|
||||
setIsEntry(false);
|
||||
setData({
|
||||
...TEMPLATE_INFO,
|
||||
});
|
||||
};
|
||||
|
||||
const onUseCustom = () => {
|
||||
setCreateType(CreateType.custom);
|
||||
setIsEntry(false);
|
||||
setDataToDefault();
|
||||
};
|
||||
|
||||
const generateTableByNL = async (text: string, type: SceneType) => {
|
||||
setGenerateTableLoading(true);
|
||||
let res: RecommendDataModelResponse | undefined;
|
||||
try {
|
||||
res = await MemoryApi.RecommendDataModel({
|
||||
bot_id: botId,
|
||||
scene_type: type,
|
||||
text,
|
||||
});
|
||||
} catch (error) {
|
||||
setGenerateTableLoading(false);
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseNL2DB,
|
||||
error: error as Error,
|
||||
});
|
||||
setDataToDefault();
|
||||
setTableFieldsListToDefault();
|
||||
}
|
||||
|
||||
if (res?.bot_table_list?.[0]) {
|
||||
if (type === SceneType.BotPersona) {
|
||||
setCreateType(CreateType.recommend);
|
||||
}
|
||||
if (type === SceneType.ModelDesc) {
|
||||
setCreateType(CreateType.naturalLanguage);
|
||||
}
|
||||
setData({
|
||||
tableId: '',
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
name: res.bot_table_list[0].table_name,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
desc: res.bot_table_list[0].table_desc,
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableMemoryList: res.bot_table_list[0].field_list.map(i => ({
|
||||
name: i.name,
|
||||
desc: i.desc,
|
||||
must_required: i.must_required,
|
||||
type: i.type,
|
||||
nanoid: nanoid(),
|
||||
id: Number(i.id),
|
||||
})),
|
||||
});
|
||||
|
||||
// data 是初始值,此处需要手动 setState 更新子组件状态
|
||||
// 若 Modal 已提前关闭,子组件卸载,则 ref 为空,需要加上可选链判断一下
|
||||
tableStructureRef.current?.setTableFieldsList(
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
res.bot_table_list[0].field_list.map(i => ({
|
||||
name: i.name,
|
||||
desc: i.desc,
|
||||
must_required: i.must_required,
|
||||
type: i.type,
|
||||
nanoid: nanoid(),
|
||||
id: Number(i.id),
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
if (type === SceneType.BotPersona) {
|
||||
Toast.info(I18n.t('recommended_failed'));
|
||||
setDataToDefault();
|
||||
setTableFieldsListToDefault();
|
||||
}
|
||||
if (type === SceneType.ModelDesc) {
|
||||
Toast.warning(I18n.t('generate_failed'));
|
||||
setAIPopoverVisible(true);
|
||||
}
|
||||
}
|
||||
setGenerateTableLoading(false);
|
||||
};
|
||||
|
||||
const handleGenerate = () => {
|
||||
const generate = () => {
|
||||
const { value } = nlTextAreaRef.current || {};
|
||||
if (value) {
|
||||
generateTableByNL(value, SceneType.ModelDesc);
|
||||
}
|
||||
};
|
||||
|
||||
sendTeaEvent(EVENT_NAMES.generate_with_ai_click, {
|
||||
bot_id: botId,
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
setAIPopoverVisible(false);
|
||||
|
||||
if (
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
tableStructureRef.current.tableFieldsList.filter(i => i.name).length > 0
|
||||
) {
|
||||
Modal.warning({
|
||||
title: I18n.t('bot_database_ai_replace'),
|
||||
content: I18n.t('bot_database_ai_replace_detailed'),
|
||||
okButtonProps: {
|
||||
type: 'warning',
|
||||
},
|
||||
onOk: () => {
|
||||
generate();
|
||||
},
|
||||
maskClosable: false,
|
||||
icon: <IconWarningSize24 />,
|
||||
});
|
||||
} else {
|
||||
generate();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setAIPopoverVisible(false);
|
||||
setIsPreview(false);
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent(false);
|
||||
setIsDeletedField(false);
|
||||
setContentCheckErrorMsg('');
|
||||
setIsEntry(true);
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
setData(database);
|
||||
}, [database]);
|
||||
|
||||
useEffect(() => {
|
||||
setCreateType(NL2DBInfo ? CreateType.recommend : CreateType.custom);
|
||||
}, [NL2DBInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (NL2DBInfo && visible) {
|
||||
generateTableByNL(NL2DBInfo.prompt, SceneType.BotPersona);
|
||||
}
|
||||
}, [NL2DBInfo, visible]);
|
||||
|
||||
const DefaultFooter = (
|
||||
<>
|
||||
{contentCheckErrorMsg ? (
|
||||
<Form.ErrorMessage error={contentCheckErrorMsg} />
|
||||
) : null}
|
||||
{hideTableStructureTips ? null : (
|
||||
<div className={s['modal-modify-tips']}>
|
||||
<div className={s.description}>
|
||||
<IconAlertTriangle className={s['tip-icon']} />
|
||||
<span style={{ textAlign: 'left' }}>{I18n.t('db_edit_tips1')}</span>
|
||||
<span
|
||||
className={s.link}
|
||||
onClick={() => {
|
||||
const lsMap = JSON.parse(
|
||||
mapOfShouldHidingDatabaseTableStructureTips || '{}',
|
||||
);
|
||||
lsMap[botId] = true;
|
||||
setMapOfShouldHidingDatabaseTableStructureTips(
|
||||
JSON.stringify(lsMap),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{I18n.t('db_edit_tips2')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<IconClose
|
||||
onClick={() =>
|
||||
setShouldHideDatabaseTableStructureTipsForCurrent(true)
|
||||
}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={s['modal-table-btn']}>
|
||||
{isDeletedField ? (
|
||||
<Popconfirm
|
||||
title={I18n.t('db_del_field_confirm_title')}
|
||||
content={I18n.t('db_del_field_confirm_info')}
|
||||
okText={I18n.t('db_del_field_confirm_yes')}
|
||||
cancelText={I18n.t('db_del_field_confirm_no')}
|
||||
okType="danger"
|
||||
onConfirm={handleSave}
|
||||
>
|
||||
<BotDebugButton
|
||||
loading={isReadonly}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
readonly={readonly}
|
||||
>
|
||||
{I18n.t('db_edit_save')}
|
||||
</BotDebugButton>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<BotDebugButton
|
||||
readonly={readonly}
|
||||
loading={isReadonly}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{I18n.t('db_edit_save')}
|
||||
</BotDebugButton>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const Entry = (
|
||||
<div className={s['modal-temp']}>
|
||||
<div className={s.entry}>
|
||||
<div
|
||||
className={s['entry-method']}
|
||||
onClick={onUseCustom}
|
||||
data-testid={BotE2e.BotDatabaseAddModalAddCustomBtn}
|
||||
>
|
||||
<Icon svg={<AddSVG />} className={s['entry-method-icon']} />
|
||||
<span className={s['entry-method-title']}>
|
||||
{I18n.t('db_add_table_cust')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s['modal-temp-right']}>
|
||||
<div
|
||||
className={s['modal-temp-title']}
|
||||
data-testid={BotE2e.BotDatabaseAddModalTemplateTitle}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_title')}
|
||||
</div>
|
||||
<Image
|
||||
className={s['modal-temp-image']}
|
||||
height={201}
|
||||
src={language === 'zh-CN' ? tableTempCN : tableTempEN}
|
||||
/>
|
||||
<div className={s['modal-temp-description']}>
|
||||
💡{I18n.t('db_add_table_temp_tips')}
|
||||
</div>
|
||||
{isPreview ? (
|
||||
<div className={s['modal-temp-preview']}>
|
||||
<div className={s.title}>
|
||||
{I18n.t('db_add_table_temp_preview_tips')}
|
||||
</div>
|
||||
<Image
|
||||
height={239}
|
||||
src={language === 'zh-CN' ? tablePreviewCN : tablePreviewEN}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={s['modal-temp-btn-group']}>
|
||||
<Button
|
||||
data-testid={BotE2e.BotDatabaseAddModalPreviewTemplateBtn}
|
||||
theme="light"
|
||||
type="tertiary"
|
||||
onClick={() => setIsPreview(state => !state)}
|
||||
className={s['modal-temp-btn']}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_preview')}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid={BotE2e.BotDatabaseAddModalUseTemplateBtn}
|
||||
theme="solid"
|
||||
type="primary"
|
||||
onClick={onUseTemplate}
|
||||
className={s['modal-temp-btn']}
|
||||
>
|
||||
{I18n.t('db_add_table_temp_use')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getFooter = () => {
|
||||
if (showEntry) {
|
||||
return null;
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return null;
|
||||
}
|
||||
return DefaultFooter;
|
||||
};
|
||||
|
||||
const getContent = () => {
|
||||
if (showEntry) {
|
||||
return Entry;
|
||||
}
|
||||
if (createType === CreateType.excel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s['database-table-structure-container']}>
|
||||
<DatabaseTableStructure
|
||||
data={data}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
ref={tableStructureRef}
|
||||
loading={generateTableLoading}
|
||||
loadingTips={I18n.t('bot_database_ai_waiting')}
|
||||
botId={botId}
|
||||
readAndWriteModeOptions={
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
expertModeConfig.isExpertMode ? 'expert' : 'normal'
|
||||
}
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
maxColumnNum={expertModeConfig.maxColumnNum}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onDeleteField={list => {
|
||||
setIsDeletedField(
|
||||
!database.tableMemoryList.every(i =>
|
||||
// TODO: 当前field id生成规则有问题,故暂时使用 nanoid 替换
|
||||
list.find(j => j.nanoid === i.nanoid),
|
||||
),
|
||||
);
|
||||
}}
|
||||
createType={createType}
|
||||
setContentCheckErrorMsg={setContentCheckErrorMsg}
|
||||
renderModeSelect={params => <FormDatabaseModeSelect {...params} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
closable={false}
|
||||
width={1138}
|
||||
centered
|
||||
footer={getFooter()}
|
||||
title={
|
||||
<div className={s['title-wrapper']}>
|
||||
<div data-testid={BotE2e.BotDatabaseAddModalTitle}>{title}</div>
|
||||
<div className={s.right}>
|
||||
{shouldShowAIGenerate ? (
|
||||
<Popover
|
||||
trigger="custom"
|
||||
position="bottomRight"
|
||||
content={
|
||||
<div className={s['generate-ai-popover-wrapper']}>
|
||||
<div
|
||||
className={s.title}
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalTitle
|
||||
}
|
||||
>
|
||||
{I18n.t('bot_database_ai_create')}
|
||||
</div>
|
||||
<TextArea
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalDesc
|
||||
}
|
||||
autosize
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
ref={nlTextAreaRef}
|
||||
rows={1}
|
||||
placeholder={I18n.t('bot_database_ai_create_tip')}
|
||||
className={s['text-area']}
|
||||
/>
|
||||
<div className={s['button-wrapper']}>
|
||||
<UIButton
|
||||
data-testid={
|
||||
BotE2e.BotDatabaseAddModalTitleCreateAiModalCreateBtn
|
||||
}
|
||||
theme="borderless"
|
||||
onClick={handleGenerate}
|
||||
icon={<Icon svg={<GenerateSVG />} />}
|
||||
>
|
||||
{I18n.t('bot_database_ai_generate')}
|
||||
</UIButton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
keepDOM
|
||||
visible={AIPopoverVisible}
|
||||
onVisibleChange={_v => {
|
||||
setAIPopoverVisible(_v);
|
||||
}}
|
||||
onClickOutSide={() => {
|
||||
setAIPopoverVisible(false);
|
||||
}}
|
||||
className={s.popover}
|
||||
>
|
||||
<UIButton
|
||||
data-testid={BotE2e.BotDatabaseAddModalTitleCreateAiBtn}
|
||||
theme="borderless"
|
||||
icon={
|
||||
AIPopoverVisible ? (
|
||||
<Icon svg={<UpArrowSVG />} />
|
||||
) : (
|
||||
<Icon svg={<DownArrowSvg />} />
|
||||
)
|
||||
}
|
||||
iconPosition="right"
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.nl2table_create_table_click, {
|
||||
bot_id: botId,
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
setAIPopoverVisible(true);
|
||||
}}
|
||||
>
|
||||
{I18n.t('bot_database_ai_create')}
|
||||
</UIButton>
|
||||
</Popover>
|
||||
) : null}
|
||||
<UIButton
|
||||
data-testid={BotE2e.BotDatabaseAddModalTitleCloseIcon}
|
||||
icon={<IconClose />}
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
onClick={onCancel}
|
||||
className={s['modal-close-button']}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className={s['modal-container']}>{getContent()}</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user