chore: import upstream snapshot with attribution
@@ -0,0 +1,12 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="current" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Import Excel/CSV">
|
||||
<g id="Union">
|
||||
<path
|
||||
d="M7 13C7 12.4477 7.44772 12 8 12H16C16.5523 12 17 12.4477 17 13C17 13.5523 16.5523 14 16 14H8C7.44772 14 7 13.5523 7 13Z" />
|
||||
<path
|
||||
d="M7 18C7 17.4477 7.44772 17 8 17H13C13.5523 17 14 17.4477 14 18C14 18.5523 13.5523 19 13 19H8C7.44772 19 7 18.5523 7 18Z" />
|
||||
<path
|
||||
d="M5 1.5C3.89543 1.5 3 2.39543 3 3.5V22.5C3 23.6046 3.89543 24.5 5 24.5H19C20.1046 24.5 21 23.6046 21 22.5V8.05251C21 7.53746 20.8013 7.04227 20.4453 6.67007L16.0907 2.11755C15.7134 1.7231 15.1913 1.5 14.6454 1.5H5ZM5 22.5V3.5L14.5 3.5V7C14.5 7.55228 14.9477 8 15.5 8H18.9498L19 8.05252V22.5H5Z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 783 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.3775 2.01277L14.4504 4.08571C15.0228 4.65813 15.0228 5.58622 14.4504 6.15864L6.15864 14.4504C5.58622 15.0228 4.65813 15.0228 4.08571 14.4504L2.01277 12.3775C1.44034 11.805 1.44034 10.8769 2.01277 10.3045L10.3045 2.01277C10.8769 1.44034 11.805 1.44034 12.3775 2.01277ZM6.11644 0.841895C6.18123 0.637199 6.47089 0.637199 6.53568 0.841895L6.74973 1.51819C6.77133 1.58642 6.82478 1.63987 6.89301 1.66146L7.5693 1.87552C7.774 1.94031 7.774 2.22997 7.5693 2.29476L6.89301 2.50881C6.82478 2.53041 6.77133 2.58386 6.74973 2.65208L6.53568 3.32838C6.47089 3.53308 6.18123 3.53308 6.11644 3.32838L5.90239 2.65208C5.88079 2.58386 5.82734 2.53041 5.75912 2.50881L5.08282 2.29476C4.87812 2.22997 4.87812 1.94031 5.08282 1.87552L5.75912 1.66146C5.82734 1.63987 5.88079 1.58642 5.90239 1.51819L6.11644 0.841895ZM1.85997 3.23387L2.22567 2.16297C2.30246 1.9381 2.6205 1.9381 2.6973 2.16297L3.06299 3.23387C3.08793 3.30688 3.14527 3.36423 3.21828 3.38916L4.28918 3.75486C4.51406 3.83165 4.51406 4.14969 4.28918 4.22648L3.21828 4.59218C3.14527 4.61711 3.08793 4.67446 3.06299 4.74747L2.6973 5.81837C2.6205 6.04324 2.30246 6.04324 2.22567 5.81837L1.85997 4.74747C1.83504 4.67446 1.7777 4.61711 1.70468 4.59218L0.633785 4.22648C0.40891 4.14969 0.40891 3.83165 0.633785 3.75486L1.70468 3.38916C1.7777 3.36423 1.83504 3.30688 1.85997 3.23387ZM13.4837 9.6851C13.5605 9.46022 13.8785 9.46022 13.9553 9.6851L14.321 10.756C14.3459 10.829 14.4033 10.8864 14.4763 10.9113L15.5472 11.277C15.7721 11.3538 15.7721 11.6718 15.5472 11.7486L14.4763 12.1143C14.4033 12.1392 14.3459 12.1966 14.321 12.2696L13.9553 13.3405C13.8785 13.5654 13.5605 13.5654 13.4837 13.3405L13.118 12.2696C13.093 12.1966 13.0357 12.1392 12.9627 12.1143L11.8918 11.7486C11.6669 11.6718 11.6669 11.3538 11.8918 11.277L12.9627 10.9113C13.0357 10.8864 13.093 10.829 13.118 10.756L13.4837 9.6851ZM10.5629 4.34447C10.9922 3.91515 11.6882 3.91515 12.1176 4.34447C12.5469 4.77379 12.5469 5.46985 12.1176 5.89917L10.0446 7.97211C9.6153 8.40143 8.91924 8.40143 8.48992 7.97211C8.0606 7.54279 8.0606 6.84673 8.48992 6.41741L10.5629 4.34447Z" fill="#4D53E8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="current" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon_add_outlined">
|
||||
<path id="Union"
|
||||
d="M12 2.5C11.4477 2.5 11 2.94772 11 3.5V11.5H3C2.44772 11.5 2 11.9477 2 12.5C2 13.0523 2.44772 13.5 3 13.5H11V21.5C11 22.0523 11.4477 22.5 12 22.5C12.5523 22.5 13 22.0523 13 21.5V13.5H21C21.5523 13.5 22 13.0523 22 12.5C22 11.9477 21.5523 11.5 21 11.5H13V3.5C13 2.94772 12.5523 2.5 12 2.5Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 463 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M4.8149 6.5L11.1851 6.5C11.4362 6.5 11.586 6.73292 11.4467 6.90682L8.26158 10.8835C8.13714 11.0388 7.86286 11.0388 7.73842 10.8835L4.55333 6.90682C4.41405 6.73292 4.56381 6.5 4.8149 6.5Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M11.1851 9.5L4.8149 9.5C4.56381 9.5 4.41405 9.26708 4.55333 9.09318L7.73842 5.11652C7.86286 4.96116 8.13714 4.96116 8.26158 5.11652L11.4467 9.09318C11.586 9.26708 11.4362 9.5 11.1851 9.5Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon_wiki-excel_colorful">
|
||||
<g id="Group 1321320754">
|
||||
<path id="Rectangle 2530" d="M4 4.0186C4 2.53559 5.19334 1.33337 6.66541 1.33337H19.4404C19.7939 1.33337 20.1328 1.47483 20.3828 1.72662L27.5983 8.99581C27.8482 9.2476 27.9886 9.5891 27.9886 9.94518V28.1856C27.9886 29.6686 26.7953 30.8708 25.3232 30.8708H6.66541C5.19334 30.8708 4 29.6686 4 28.1856V4.0186Z" fill="#32A645"/>
|
||||
<path id="csv" d="M12.5336 22.6041L12.5395 22.5629L12.5567 22.4425H12.4351H11.4262H11.3438L11.3238 22.5224L11.312 22.5696C11.2315 22.8919 11.0661 23.1596 10.8265 23.347C10.587 23.5342 10.267 23.6461 9.86853 23.6461C9.36786 23.6461 8.95012 23.4484 8.6554 23.0849C8.35894 22.7191 8.18038 22.177 8.18038 21.4801V21.4742C8.18038 20.7949 8.35456 20.2506 8.64815 19.8786C8.93993 19.5089 9.35476 19.3022 9.85673 19.3022C10.2858 19.3022 10.6109 19.4307 10.8462 19.626C11.0824 19.8221 11.2341 20.0904 11.3061 20.3787L11.3179 20.4259L11.3379 20.5058H11.4203H12.4233H12.5428L12.528 20.3873L12.5221 20.3402C12.3858 19.2377 11.4387 18.1826 9.85673 18.1826C8.97717 18.1826 8.23953 18.503 7.72274 19.0791C7.20694 19.6541 6.91917 20.4746 6.91917 21.4624V21.4683C6.91917 22.4641 7.2006 23.2892 7.71512 23.8668C8.23082 24.4457 8.97178 24.7657 9.86853 24.7657C11.3037 24.7657 12.3606 23.8281 12.5336 22.6041Z" fill="white" stroke="white" stroke-width="0.210982"/>
|
||||
<path id="csv_2" d="M13.5353 22.837L13.5354 22.8371C13.6003 23.4122 13.8628 23.8975 14.3012 24.2377C14.7384 24.5768 15.3426 24.7657 16.0828 24.7657C17.4813 24.7657 18.572 23.9883 18.572 22.8312V22.8253C18.572 22.369 18.454 21.9985 18.1623 21.7045C17.875 21.4148 17.4306 21.2111 16.804 21.0589L16.8039 21.0589L15.8305 20.823C15.8305 20.823 15.8305 20.8229 15.8304 20.8229C15.4997 20.7424 15.2808 20.6347 15.1455 20.5072C15.0143 20.3835 14.9552 20.2351 14.9552 20.0522V20.0463C14.9552 19.808 15.0547 19.6134 15.2365 19.4754C15.4211 19.3354 15.6973 19.2491 16.0533 19.2491C16.4159 19.2491 16.695 19.3431 16.8913 19.4985C17.0869 19.6532 17.2089 19.8754 17.2469 20.1493L17.2469 20.1498L17.2528 20.1911L17.2658 20.2816H17.3573H18.3367H18.4506L18.4419 20.168L18.436 20.0916C18.436 20.0915 18.436 20.0915 18.436 20.0915C18.3544 18.9994 17.4427 18.1826 16.0533 18.1826C14.6903 18.1826 13.694 18.9588 13.694 20.0935V20.0994C13.694 20.5669 13.8432 20.9506 14.1504 21.2522C14.4542 21.5505 14.9042 21.7603 15.4917 21.9012C15.4918 21.9012 15.4918 21.9013 15.4918 21.9013L16.4648 22.1371C16.4648 22.1371 16.4649 22.1372 16.465 22.1372C16.8074 22.2213 17.0169 22.3206 17.1412 22.4397C17.2598 22.5535 17.3108 22.6954 17.3108 22.8961V22.902C17.3108 23.1432 17.2051 23.3369 17.0048 23.4744C16.8004 23.6146 16.4919 23.6992 16.0887 23.6992C15.684 23.6992 15.3847 23.6122 15.1742 23.4622C14.9656 23.3134 14.8336 23.0952 14.7761 22.8103L14.7643 22.7515L14.7474 22.6667H14.6609H13.6343H13.5164L13.5294 22.7839L13.5353 22.837Z" fill="white" stroke="white" stroke-width="0.210982"/>
|
||||
<path id="csv_3" d="M21.5641 24.5781L21.5895 24.6472H21.6631H22.7133H22.7869L22.8123 24.578L25.0662 18.4419L25.1183 18.3H24.9672H23.8992H23.8235L23.7993 18.3718L22.1883 23.1543L20.5831 18.3719L20.5589 18.3H20.483H19.4033H19.2521L19.3043 18.442L21.5641 24.5781Z" fill="white" stroke="white" stroke-width="0.210982"/>
|
||||
<path id="Rectangle 2531" opacity="0.8" d="M19.9802 1.97637C19.9802 1.73986 20.2666 1.62187 20.4332 1.78972L27.5282 8.93745C27.6941 9.10464 27.5757 9.38905 27.3401 9.38905H22.6456C21.1736 9.38905 19.9802 8.18683 19.9802 6.70382V1.97637Z" fill="#278B34"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon_wiki-excel_colorful">
|
||||
<path id="Rectangle 2530" d="M4 4.00004C4 2.52728 5.19391 1.33337 6.66667 1.33337H19.4477C19.8013 1.33337 20.1405 1.47385 20.3905 1.7239L27.6095 8.94285C27.8595 9.1929 28 9.53204 28 9.88566V28C28 29.4728 26.8061 30.6667 25.3333 30.6667H6.66667C5.19391 30.6667 4 29.4728 4 28V4.00004Z" fill="#32A645"/>
|
||||
<path id="Rectangle 2531" opacity="0.8" d="M20 1.97716C20 1.73959 20.2872 1.62061 20.4552 1.7886L27.5448 8.87815C27.7128 9.04614 27.5938 9.33337 27.3562 9.33337H22.6667C21.1939 9.33337 20 8.13947 20 6.66671V1.97716Z" fill="#258832"/>
|
||||
<path id="icon_file_excel_nor" d="M11.3955 13.2123H12.8513C12.9158 13.2123 12.9762 13.244 13.0129 13.2971L15.811 17.352L18.6239 13.2967C18.6606 13.2438 18.7208 13.2123 18.7852 13.2123H20.2408C20.3492 13.2123 20.4371 13.3002 20.4371 13.4086C20.4371 13.4495 20.4244 13.4894 20.4007 13.5226L16.7567 18.6342L20.6906 24.175C20.7534 24.2634 20.7326 24.386 20.6441 24.4488C20.6109 24.4723 20.5712 24.485 20.5305 24.485H19.0748C19.0103 24.485 18.95 24.4534 18.9133 24.4004L15.811 19.9165L12.7234 24.4C12.6868 24.4532 12.6263 24.485 12.5617 24.485H11.1058C10.9973 24.485 10.9094 24.3971 10.9094 24.2886C10.9094 24.2481 10.9219 24.2086 10.9453 24.1755L14.8499 18.6342L11.2351 13.522C11.1725 13.4335 11.1935 13.3109 11.2821 13.2483C11.3152 13.2249 11.3548 13.2123 11.3955 13.2123Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Steps } from '@coze-arch/coze-design';
|
||||
|
||||
export enum BatchImportStep {
|
||||
Upload,
|
||||
Config,
|
||||
Preview,
|
||||
Process,
|
||||
}
|
||||
|
||||
export interface BatchImportStepsProps {
|
||||
step: BatchImportStep;
|
||||
}
|
||||
|
||||
export function BatchImportSteps({ step }: BatchImportStepsProps) {
|
||||
return (
|
||||
<Steps
|
||||
type="basic"
|
||||
hasLine={false}
|
||||
current={step}
|
||||
className={classNames(
|
||||
'my-[24px] justify-center',
|
||||
'[&_.semi-steps-item]:flex-none',
|
||||
'[&_.semi-steps-item-title]:!max-w-[unset]',
|
||||
)}
|
||||
>
|
||||
<Steps.Step title={I18n.t('db_optimize_014')} />
|
||||
<Steps.Step title={I18n.t('db_optimize_015')} />
|
||||
<Steps.Step title={I18n.t('db_optimize_016')} />
|
||||
<Steps.Step title={I18n.t('db_optimize_017')} />
|
||||
</Steps>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
type UnitItem,
|
||||
UploadStatus,
|
||||
} from '@coze-data/knowledge-resource-processor-core';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type TableType, type TableSheet } from '@coze-arch/bot-api/memory';
|
||||
import { Button, Modal } from '@coze-arch/coze-design';
|
||||
|
||||
import { type TableFieldData } from '../database-table-data/type';
|
||||
import { StepUpload } from './steps/upload';
|
||||
import { StepProcess } from './steps/process';
|
||||
import { StepPreview } from './steps/preview';
|
||||
import { StepConfig } from './steps/config';
|
||||
import { BatchImportStep, BatchImportSteps } from './import-steps';
|
||||
|
||||
export interface BatchImportModalProps {
|
||||
visible: boolean;
|
||||
databaseId: string;
|
||||
tableFields: TableFieldData[];
|
||||
tableType: TableType;
|
||||
connectorId?: string;
|
||||
onClose?: () => void;
|
||||
onComplete?: () => void;
|
||||
}
|
||||
|
||||
export function BatchImportModal({
|
||||
visible,
|
||||
databaseId,
|
||||
tableFields,
|
||||
tableType,
|
||||
connectorId,
|
||||
onClose,
|
||||
onComplete,
|
||||
}: BatchImportModalProps) {
|
||||
const [currentStep, setCurrentStep] = useState(BatchImportStep.Upload);
|
||||
const [unitList, setUnitList] = useState<UnitItem[]>([]);
|
||||
const [tableSheet, setTableSheet] = useState<TableSheet>();
|
||||
|
||||
const resetSteps = () => {
|
||||
setCurrentStep(BatchImportStep.Upload);
|
||||
setUnitList([]);
|
||||
setTableSheet(undefined);
|
||||
};
|
||||
|
||||
const getNextDisabled = () => {
|
||||
switch (currentStep) {
|
||||
case BatchImportStep.Upload:
|
||||
return (
|
||||
unitList.length <= 0 ||
|
||||
unitList.some(item => item.status !== UploadStatus.SUCCESS)
|
||||
);
|
||||
case BatchImportStep.Config:
|
||||
return !tableSheet;
|
||||
case BatchImportStep.Preview:
|
||||
return false;
|
||||
case BatchImportStep.Process:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={I18n.t('db_optimize_013')}
|
||||
onCancel={onClose}
|
||||
width={1120}
|
||||
className="[&_.semi-modal-content]:min-h-[520px]"
|
||||
footer={
|
||||
<>
|
||||
{currentStep !== BatchImportStep.Process ? (
|
||||
<>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={currentStep === BatchImportStep.Upload}
|
||||
onClick={() => setCurrentStep(currentStep - 1)}
|
||||
>
|
||||
{I18n.t('db_optimize_020')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={getNextDisabled()}
|
||||
onClick={() => setCurrentStep(currentStep + 1)}
|
||||
>
|
||||
{I18n.t('db_optimize_021')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose?.();
|
||||
onComplete?.();
|
||||
resetSteps();
|
||||
}}
|
||||
>
|
||||
{I18n.t('db2_004')}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<BatchImportSteps step={currentStep} />
|
||||
{currentStep === BatchImportStep.Upload ? (
|
||||
<StepUpload
|
||||
databaseId={databaseId}
|
||||
tableType={tableType}
|
||||
unitList={unitList}
|
||||
onUnitListChange={setUnitList}
|
||||
/>
|
||||
) : null}
|
||||
{currentStep === BatchImportStep.Config ? (
|
||||
<StepConfig
|
||||
databaseId={databaseId}
|
||||
tableFields={tableFields}
|
||||
tableType={tableType}
|
||||
fileUri={unitList[0].uri}
|
||||
onTableSheetChange={setTableSheet}
|
||||
/>
|
||||
) : null}
|
||||
{currentStep === BatchImportStep.Preview ? (
|
||||
<StepPreview
|
||||
databaseId={databaseId}
|
||||
tableFields={tableFields}
|
||||
fileUri={unitList[0].uri}
|
||||
tableSheet={tableSheet}
|
||||
/>
|
||||
) : null}
|
||||
{currentStep === BatchImportStep.Process ? (
|
||||
<StepProcess
|
||||
databaseId={databaseId}
|
||||
tableType={tableType}
|
||||
fileItem={unitList[0]}
|
||||
tableSheet={tableSheet}
|
||||
connectorId={connectorId}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { type TableSettings } from '@coze-data/knowledge-resource-processor-base/types';
|
||||
import { TableSettingBar } from '@coze-data/knowledge-resource-processor-base/components/table-format';
|
||||
import { FIELD_TYPE_OPTIONS } from '@coze-data/database-v2-base/constants';
|
||||
import { DatabaseFieldTitle } from '@coze-data/database-v2-base/components/database-field-title';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
type TableType,
|
||||
type TableSheet,
|
||||
TableDataType,
|
||||
type GetDocumentTableInfoResponse,
|
||||
ColumnType,
|
||||
FieldItemType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import { type ColumnProps, Spin, Table } from '@coze-arch/coze-design';
|
||||
|
||||
import { type TableFieldData } from '../../database-table-data/type';
|
||||
|
||||
type TableSettingsData = Pick<
|
||||
GetDocumentTableInfoResponse,
|
||||
'sheet_list' | 'preview_data'
|
||||
>;
|
||||
|
||||
function tableSettingsToSheet(tableSettings: TableSettings): TableSheet {
|
||||
return {
|
||||
sheet_id: tableSettings?.sheet_id?.toString() ?? '',
|
||||
header_line_idx: tableSettings?.header_line_idx?.toString() ?? '',
|
||||
start_line_idx: tableSettings?.start_line_idx?.toString() ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
export interface StepConfigProps {
|
||||
databaseId: string;
|
||||
tableType: TableType;
|
||||
tableFields: TableFieldData[];
|
||||
fileUri: string;
|
||||
onTableSheetChange: (tableSheet?: TableSheet) => void;
|
||||
}
|
||||
|
||||
export function StepConfig({
|
||||
databaseId,
|
||||
tableType,
|
||||
tableFields,
|
||||
fileUri,
|
||||
onTableSheetChange,
|
||||
}: StepConfigProps) {
|
||||
const [tableData, setTableData] = useState<TableSettingsData>();
|
||||
// Default: Use the first data table, the first row is the header, and the second row starts with data
|
||||
const [tableSettings, setTableSettings] = useState<TableSettings>({
|
||||
sheet_id: 0,
|
||||
header_line_idx: 0,
|
||||
start_line_idx: 1,
|
||||
});
|
||||
const [tableStructure, setTableStructure] = useState<TableFieldData[]>([]);
|
||||
|
||||
const { loading } = useRequest(
|
||||
() =>
|
||||
MemoryApi.GetTableSchema({
|
||||
database_id: databaseId,
|
||||
source_file: { tos_uri: fileUri },
|
||||
table_data_type: TableDataType.OnlySchema,
|
||||
table_sheet: tableSettingsToSheet(tableSettings),
|
||||
}),
|
||||
{
|
||||
refreshDeps: [fileUri, tableSettings],
|
||||
onSuccess: res => {
|
||||
setTableData({
|
||||
sheet_list: res.sheet_list,
|
||||
preview_data: {}, // TableSettingBar does not read preview_data, but it is not empty
|
||||
});
|
||||
if (res.table_meta) {
|
||||
setTableStructure(
|
||||
res.table_meta.map(column => {
|
||||
// When there are fields with the same name in the table structure, use the original type and description
|
||||
const matchedField = tableFields.find(
|
||||
field => field.fieldName === column.column_name,
|
||||
);
|
||||
return (
|
||||
matchedField ??
|
||||
({
|
||||
fieldName: column.column_name ?? '-',
|
||||
fieldDescription: column.desc ?? '-',
|
||||
type: convertColumnType(column.column_type),
|
||||
required: false,
|
||||
} satisfies TableFieldData)
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
MemoryApi.ValidateTableSchema({
|
||||
database_id: databaseId,
|
||||
source_file: { tos_uri: fileUri },
|
||||
table_type: tableType,
|
||||
table_sheet: tableSettingsToSheet(tableSettings),
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.schema_valid_result) {
|
||||
onTableSheetChange(tableSettingsToSheet(tableSettings));
|
||||
} else {
|
||||
onTableSheetChange();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
onTableSheetChange();
|
||||
});
|
||||
}, [tableSettings]);
|
||||
|
||||
return !tableData ? (
|
||||
<Spin size="large" wrapperClassName="w-full h-[288px]" />
|
||||
) : (
|
||||
<>
|
||||
<TableSettingBar
|
||||
data={tableData}
|
||||
tableSettings={tableSettings}
|
||||
setTableSettings={setTableSettings}
|
||||
/>
|
||||
<Table
|
||||
tableProps={{
|
||||
loading,
|
||||
columns: getTableStructureColumns(),
|
||||
dataSource: tableStructure,
|
||||
}}
|
||||
className={classNames(
|
||||
'[&_.semi-table-row-head]:!border-b-[1px]',
|
||||
'[&_.semi-table-row-cell]:!h-[56px]',
|
||||
'[&_.semi-table-row-cell]:!border-b-0',
|
||||
'[&_.semi-table-row-cell]:!bg-none',
|
||||
'[&_.semi-table-row-cell]:!bg-transparent',
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function convertColumnType(type?: ColumnType): FieldItemType {
|
||||
switch (type) {
|
||||
case ColumnType.Text:
|
||||
return FieldItemType.Text;
|
||||
case ColumnType.Number:
|
||||
return FieldItemType.Number;
|
||||
case ColumnType.Date:
|
||||
return FieldItemType.Date;
|
||||
case ColumnType.Float:
|
||||
return FieldItemType.Float;
|
||||
case ColumnType.Boolean:
|
||||
return FieldItemType.Boolean;
|
||||
default:
|
||||
return FieldItemType.Text;
|
||||
}
|
||||
}
|
||||
|
||||
function getTableStructureColumns(): ColumnProps<TableFieldData>[] {
|
||||
return [
|
||||
{
|
||||
title: <DatabaseFieldTitle field={I18n.t('db_add_table_field_name')} />,
|
||||
render: (_, record) => record.fieldName,
|
||||
},
|
||||
{
|
||||
title: <DatabaseFieldTitle field={I18n.t('db_add_table_field_desc')} />,
|
||||
render: (_, record) => record.fieldDescription,
|
||||
},
|
||||
{
|
||||
title: <DatabaseFieldTitle field={I18n.t('db_add_table_field_type')} />,
|
||||
render: (_, record) =>
|
||||
FIELD_TYPE_OPTIONS.find(i => i.value === record.type)?.label ?? '-',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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, useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import {
|
||||
type TableInfo,
|
||||
type TableSettings,
|
||||
} from '@coze-data/knowledge-resource-processor-base/types';
|
||||
import { TablePreview } from '@coze-data/knowledge-resource-processor-base/components/table-format';
|
||||
import {
|
||||
type TableSheet,
|
||||
type DocTableColumn,
|
||||
TableDataType,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { ColumnType } from '@coze-arch/bot-api/knowledge';
|
||||
import { FieldItemType } from '@coze-arch/bot-api/developer_api';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import { Spin } from '@coze-arch/coze-design';
|
||||
|
||||
import { type TableFieldData } from '../../database-table-data/type';
|
||||
|
||||
function convertFieldItemType(type?: FieldItemType): ColumnType {
|
||||
switch (type) {
|
||||
case FieldItemType.Text:
|
||||
return ColumnType.Text;
|
||||
case FieldItemType.Number:
|
||||
return ColumnType.Number;
|
||||
case FieldItemType.Date:
|
||||
return ColumnType.Date;
|
||||
case FieldItemType.Float:
|
||||
return ColumnType.Float;
|
||||
case FieldItemType.Boolean:
|
||||
return ColumnType.Boolean;
|
||||
default:
|
||||
return ColumnType.Text;
|
||||
}
|
||||
}
|
||||
|
||||
function tableSheetToSettings(tableSheet: TableSheet): TableSettings {
|
||||
return {
|
||||
sheet_id: Number.parseInt(tableSheet?.sheet_id ?? '0'),
|
||||
header_line_idx: Number.parseInt(tableSheet?.header_line_idx ?? '0'),
|
||||
start_line_idx: Number.parseInt(tableSheet?.start_line_idx ?? '0'),
|
||||
};
|
||||
}
|
||||
|
||||
export interface StepPreviewProps {
|
||||
databaseId: string;
|
||||
tableFields: TableFieldData[];
|
||||
fileUri: string;
|
||||
tableSheet?: TableSheet;
|
||||
}
|
||||
|
||||
export function StepPreview({
|
||||
databaseId,
|
||||
tableFields,
|
||||
fileUri,
|
||||
tableSheet,
|
||||
}: StepPreviewProps) {
|
||||
const [tableInfo, setTableInfo] = useState<TableInfo>();
|
||||
const tableMeta: DocTableColumn[] = useMemo(
|
||||
() =>
|
||||
tableFields.map((field, index) => ({
|
||||
column_name: field.fieldName,
|
||||
column_type: convertFieldItemType(field.type),
|
||||
desc: field.fieldDescription,
|
||||
sequence: `${index}`,
|
||||
is_semantic: false,
|
||||
id: `${index}`,
|
||||
})),
|
||||
[tableFields],
|
||||
);
|
||||
|
||||
const { loading } = useRequest(
|
||||
() =>
|
||||
MemoryApi.GetTableSchema({
|
||||
database_id: databaseId,
|
||||
source_file: { tos_uri: fileUri },
|
||||
table_data_type: TableDataType.OnlyPreview,
|
||||
table_sheet: tableSheet,
|
||||
}),
|
||||
{
|
||||
onSuccess: res => {
|
||||
const sheetId = tableSheet?.sheet_id;
|
||||
if (sheetId) {
|
||||
setTableInfo({
|
||||
sheet_list: res.sheet_list,
|
||||
table_meta: { [sheetId]: tableMeta },
|
||||
preview_data: { [sheetId]: res.preview_data ?? [] },
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return loading || !tableInfo || !tableSheet ? (
|
||||
<Spin size="large" wrapperClassName="w-full h-[288px]" />
|
||||
) : (
|
||||
<TablePreview
|
||||
data={tableInfo}
|
||||
settings={tableSheetToSettings(tableSheet)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { type UnitItem } from '@coze-data/knowledge-resource-processor-core';
|
||||
import {
|
||||
type ProcessProgressItemProps,
|
||||
ProcessStatus,
|
||||
} from '@coze-data/knowledge-resource-processor-base/types';
|
||||
import { ProcessProgressItem } from '@coze-data/knowledge-resource-processor-base';
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import { formatBytes } from '@coze-arch/bot-utils';
|
||||
import { IconUploadXLS } from '@coze-arch/bot-icons';
|
||||
import { type TableType, type TableSheet } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import { Typography } from '@coze-arch/coze-design';
|
||||
|
||||
type ProcessProps = Pick<
|
||||
ProcessProgressItemProps,
|
||||
'mainText' | 'subText' | 'tipText' | 'percent' | 'status' | 'actions'
|
||||
>;
|
||||
|
||||
const INIT_PERCENT = 10;
|
||||
const COMPLETE_PERCENT = 100;
|
||||
|
||||
const statusTextMap: Record<ProcessStatus, I18nKeysNoOptionsType> = {
|
||||
[ProcessStatus.Processing]: 'datasets_createFileModel_step4_processing',
|
||||
[ProcessStatus.Complete]: 'datasets_createFileModel_step4_Finish',
|
||||
[ProcessStatus.Failed]: 'datasets_createFileModel_step4_failed',
|
||||
};
|
||||
|
||||
export interface StepProcessProps {
|
||||
databaseId: string;
|
||||
tableType: TableType;
|
||||
fileItem: UnitItem;
|
||||
tableSheet?: TableSheet;
|
||||
connectorId?: string;
|
||||
}
|
||||
|
||||
export function StepProcess({
|
||||
databaseId,
|
||||
tableType,
|
||||
fileItem,
|
||||
tableSheet,
|
||||
connectorId,
|
||||
}: StepProcessProps) {
|
||||
const fileSize = useMemo(
|
||||
() => formatBytes(fileItem.fileInstance?.size ?? 0),
|
||||
[fileItem],
|
||||
);
|
||||
const [progressProps, setProgressProps] = useState<ProcessProps>({
|
||||
// First line of text (file name)
|
||||
mainText: fileItem.name,
|
||||
// Second line of text (file size)
|
||||
subText: fileSize,
|
||||
// The second line of text displayed when hovering, consistent with the above
|
||||
tipText: fileSize,
|
||||
// Progress bar percentage, initial 10% is consistent with @code-data/knowledge-resource-processor-base/unit-progress
|
||||
percent: INIT_PERCENT,
|
||||
status: ProcessStatus.Processing,
|
||||
});
|
||||
|
||||
const { run, cancel } = useRequest(
|
||||
() =>
|
||||
MemoryApi.DatabaseFileProgressData({
|
||||
database_id: databaseId,
|
||||
table_type: tableType,
|
||||
}),
|
||||
{
|
||||
manual: true,
|
||||
pollingInterval: 3000,
|
||||
onSuccess: res => {
|
||||
const { data } = res;
|
||||
if (data) {
|
||||
// If there is an error message, it means the processing failed. Display the error message and stop polling.
|
||||
if (data.status_descript) {
|
||||
const msg = data.status_descript;
|
||||
setProgressProps(props => ({
|
||||
...props,
|
||||
subText: msg,
|
||||
tipText: msg,
|
||||
status: ProcessStatus.Failed,
|
||||
}));
|
||||
cancel();
|
||||
} else {
|
||||
setProgressProps(props => ({
|
||||
...props,
|
||||
percent: data.progress ?? 0,
|
||||
}));
|
||||
// Progress 100 represents process completion, update status and stop polling
|
||||
if (data.progress === COMPLETE_PERCENT) {
|
||||
setProgressProps(props => ({
|
||||
...props,
|
||||
status: ProcessStatus.Complete,
|
||||
actions: [I18n.t('datasets_unit_process_success')],
|
||||
}));
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Submit the task and start polling progress
|
||||
useEffect(() => {
|
||||
MemoryApi.SubmitDatabaseInsertTask({
|
||||
database_id: databaseId,
|
||||
table_type: tableType,
|
||||
file_uri: fileItem.uri,
|
||||
table_sheet: tableSheet,
|
||||
connector_id: connectorId,
|
||||
}).finally(() => {
|
||||
run();
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-[32px] leading-[32px] mb-[8px]">
|
||||
<Typography.Text fontSize="14px" weight={500}>
|
||||
{I18n.t(statusTextMap[progressProps.status])}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<ProcessProgressItem
|
||||
avatar={<IconUploadXLS />}
|
||||
{...progressProps}
|
||||
className="[&_.process-progress-item-actions]:!block"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
type UnitItem,
|
||||
UnitType,
|
||||
} from '@coze-data/knowledge-resource-processor-core';
|
||||
import { ActionRenderByDelete } from '@coze-data/knowledge-resource-processor-base/components/upload-unit-table';
|
||||
import {
|
||||
UploadUnitFile,
|
||||
UploadUnitTable,
|
||||
} from '@coze-data/knowledge-resource-processor-base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Typography } from '@coze-arch/coze-design';
|
||||
import { type TableType } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
export interface StepUploadProps {
|
||||
databaseId: string;
|
||||
tableType: TableType;
|
||||
unitList: UnitItem[];
|
||||
onUnitListChange: (list: UnitItem[]) => void;
|
||||
}
|
||||
|
||||
export function StepUpload({
|
||||
databaseId,
|
||||
tableType,
|
||||
unitList,
|
||||
onUnitListChange,
|
||||
}: StepUploadProps) {
|
||||
useEffect(() => {
|
||||
onUnitListChange(unitList);
|
||||
}, [onUnitListChange, unitList]);
|
||||
|
||||
const downloadTemplate = async () => {
|
||||
const res = await MemoryApi.GetDatabaseTemplate({
|
||||
database_id: databaseId,
|
||||
table_type: tableType,
|
||||
});
|
||||
if (res.TosUrl) {
|
||||
window.open(res.TosUrl, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadUnitFile
|
||||
unitList={unitList}
|
||||
setUnitList={onUnitListChange}
|
||||
onFinish={onUnitListChange}
|
||||
limit={1}
|
||||
multiple={false}
|
||||
accept=".csv,.xlsx"
|
||||
maxSizeMB={20}
|
||||
showRetry={false}
|
||||
dragMainText={I18n.t('datasets_createFileModel_step2_UploadDoc')}
|
||||
dragSubText={I18n.t('datasets_unit_update_exception_tips3')}
|
||||
action=""
|
||||
className={classNames('[&_.semi-upload-drag-area]:!h-[290px]', {
|
||||
hidden: unitList.length > 0,
|
||||
})}
|
||||
showIllustration={false}
|
||||
/>
|
||||
<Typography.Paragraph
|
||||
type="secondary"
|
||||
className={classNames('mt-[8px]', { hidden: unitList.length > 0 })}
|
||||
>
|
||||
{I18n.t('db_optimize_018')}
|
||||
<Typography.Text link className="ml-[8px]" onClick={downloadTemplate}>
|
||||
{I18n.t('db_optimize_019')}
|
||||
</Typography.Text>
|
||||
</Typography.Paragraph>
|
||||
<UploadUnitTable
|
||||
edit={false}
|
||||
type={UnitType.TABLE_DOC}
|
||||
unitList={unitList}
|
||||
onChange={onUnitListChange}
|
||||
disableRetry
|
||||
getColumns={(record, index) => ({
|
||||
actions: [
|
||||
<ActionRenderByDelete
|
||||
record={record}
|
||||
index={index}
|
||||
params={{
|
||||
unitList,
|
||||
onChange: onUnitListChange,
|
||||
type: UnitType.TABLE_DOC,
|
||||
edit: false,
|
||||
}}
|
||||
/>,
|
||||
],
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 Ref, forwardRef, type FC } from 'react';
|
||||
|
||||
import { type ButtonProps } from '@coze-arch/bot-semi/Button';
|
||||
import { Button } from '@coze-arch/bot-semi';
|
||||
|
||||
export type BotDebugButtonProps = ButtonProps & {
|
||||
readonly: boolean;
|
||||
};
|
||||
export const BotDebugButton: FC<BotDebugButtonProps> = forwardRef(
|
||||
(props: BotDebugButtonProps, ref: Ref<Button>) => {
|
||||
const { readonly, ...rest } = props;
|
||||
|
||||
if (readonly) {
|
||||
return null;
|
||||
}
|
||||
return <Button {...rest} ref={ref} />;
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,58 @@
|
||||
.tab {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
:global {
|
||||
.semi-tabs-bar {
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
.semi-tabs-bar-extra {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-tabs-content {
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
|
||||
.coz-tab-bar-content.semi-tabs-pane-active {
|
||||
height: 100%;
|
||||
|
||||
.semi-tabs-pane-motion-overlay {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.th-tip-dot {
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.th-tip-dot::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 0;
|
||||
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
|
||||
background-color: #000;
|
||||
border-radius: 50%;
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* 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, { useState, useEffect, useMemo } from 'react';
|
||||
|
||||
import { pick } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { type DatabaseInfo as DatabaseInitInfo } from '@coze-studio/bot-detail-store';
|
||||
import { type WidgetUIState } from '@coze-data/knowledge-stores';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { DatabaseTabs } from '@coze-data/database-v2-base/types';
|
||||
import { DismissibleBanner } from '@coze-data/database-v2-base/components/dismissible-banner';
|
||||
import {
|
||||
type FormData,
|
||||
ModalMode,
|
||||
} from '@coze-data/database-v2-base/components/base-info-modal';
|
||||
import { DatabaseModeSelect } from '@coze-data/database-v2-adapter/components/database-mode-select';
|
||||
import { DatabaseCreateTableModal } from '@coze-data/database-v2-adapter/components/create-table-modal';
|
||||
import { DatabaseBaseInfoModal } from '@coze-data/database-v2-adapter/components/base-info-modal';
|
||||
import { DatabaseDetailWaring } from '@coze-data/database-v2-adapter';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozEdit,
|
||||
IconCozCross,
|
||||
IconCozArrowLeft,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
TabBar,
|
||||
Toast,
|
||||
CozAvatar,
|
||||
Typography,
|
||||
Space,
|
||||
} from '@coze-arch/coze-design';
|
||||
import {
|
||||
BotTableRWMode,
|
||||
TableType,
|
||||
type DatabaseInfo,
|
||||
type UpdateDatabaseRequest,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { DatabaseTableStructureReadonly } from '../database-table-structure-readonly';
|
||||
import { DatabaseTableData } from '../database-table-data';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface DatabaseDetailProps {
|
||||
version?: string;
|
||||
needHideCloseIcon?: boolean;
|
||||
databaseId: string;
|
||||
enterFrom: string;
|
||||
initialTab?: `${DatabaseTabs}`;
|
||||
addRemoveButtonText: string;
|
||||
onClose?: () => void;
|
||||
onAfterEditBasicInfo?: () => void;
|
||||
onAfterEditRecords?: () => void;
|
||||
onIDECallback?: {
|
||||
onStatusChange?: (v: WidgetUIState) => void;
|
||||
onUpdateDisplayName?: (v: string) => void;
|
||||
};
|
||||
onClickAddRemoveButton: (databaseId?: string) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function, max-lines-per-function, complexity
|
||||
export const DatabaseDetail = ({
|
||||
version,
|
||||
enterFrom,
|
||||
initialTab,
|
||||
needHideCloseIcon = false,
|
||||
addRemoveButtonText,
|
||||
onClose,
|
||||
onClickAddRemoveButton,
|
||||
onIDECallback,
|
||||
onAfterEditBasicInfo,
|
||||
onAfterEditRecords,
|
||||
databaseId,
|
||||
}: DatabaseDetailProps) => {
|
||||
const userId = userStoreService.useUserInfo()?.user_id_str;
|
||||
|
||||
const [basicInfoVisible, setBasicInfoVisible] = useState(false);
|
||||
const [createTableVisible, setCreateTableVisible] = useState(false);
|
||||
// database basicInfo
|
||||
const [databaseInfo, setDatabaseInfo] = useState<DatabaseInfo>({});
|
||||
// tab key
|
||||
const [activeKey, setActiveKey] = useState(
|
||||
version ? DatabaseTabs.Structure : initialTab ?? DatabaseTabs.Structure,
|
||||
);
|
||||
// btn loading
|
||||
const [btnLoading, setBtnLoading] = useState(false);
|
||||
// page loading
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// fetch database basicInfo
|
||||
const fetchDatabaseInfo = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await MemoryApi.GetDatabaseByID({
|
||||
id: databaseId,
|
||||
...(version ? { version } : {}),
|
||||
});
|
||||
if (res.database_info) {
|
||||
setDatabaseInfo(res.database_info);
|
||||
if (res.database_info.table_name) {
|
||||
onIDECallback?.onUpdateDisplayName?.(res.database_info.table_name);
|
||||
onIDECallback?.onStatusChange?.('normal');
|
||||
}
|
||||
} else {
|
||||
onIDECallback?.onStatusChange?.('error');
|
||||
}
|
||||
} catch {
|
||||
onIDECallback?.onStatusChange?.('error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Need a store, follow-up renovation
|
||||
const isReadOnlyMode = databaseInfo.creator_id !== userId || !!version;
|
||||
|
||||
const tableInitData: DatabaseInitInfo = useMemo(
|
||||
() => ({
|
||||
tableId: databaseInfo.id || '',
|
||||
name: databaseInfo.table_name || '',
|
||||
desc: databaseInfo.table_desc || '',
|
||||
icon_uri: databaseInfo.icon_uri || '',
|
||||
readAndWriteMode: databaseInfo.rw_mode || BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: databaseInfo.field_list || [],
|
||||
}),
|
||||
[databaseInfo],
|
||||
);
|
||||
|
||||
const basicInitData: FormData = useMemo(
|
||||
() => ({
|
||||
name: databaseInfo.table_name || '',
|
||||
description: databaseInfo.table_desc || '',
|
||||
icon_uri: [
|
||||
{
|
||||
url: databaseInfo.icon_url || '',
|
||||
uri: databaseInfo.icon_uri || '',
|
||||
uid: databaseInfo.icon_uri || '',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
[databaseInfo],
|
||||
);
|
||||
|
||||
const handleEditBasicInfo = async (obj: UpdateDatabaseRequest) => {
|
||||
const res = await MemoryApi.UpdateDatabase({
|
||||
...pick(databaseInfo, [
|
||||
'id',
|
||||
'icon_uri',
|
||||
'table_name',
|
||||
'table_desc',
|
||||
'field_list',
|
||||
'rw_mode',
|
||||
'prompt_disabled',
|
||||
'extra_info',
|
||||
]),
|
||||
...obj,
|
||||
});
|
||||
if (res?.database_info?.id) {
|
||||
await fetchDatabaseInfo();
|
||||
// update basicInfo callback
|
||||
if (onAfterEditBasicInfo) {
|
||||
onAfterEditBasicInfo();
|
||||
}
|
||||
// close basicInfo modal
|
||||
if (basicInfoVisible) {
|
||||
setBasicInfoVisible(false);
|
||||
}
|
||||
} else {
|
||||
Toast.error('Update database failed');
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeDatabaseMode = async (mode: BotTableRWMode) => {
|
||||
const res = await MemoryApi.UpdateDatabase({
|
||||
...databaseInfo,
|
||||
rw_mode: mode,
|
||||
});
|
||||
if (res?.database_info?.id) {
|
||||
await fetchDatabaseInfo();
|
||||
// update basicInfo callback
|
||||
if (onAfterEditBasicInfo) {
|
||||
onAfterEditBasicInfo();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditTable = async () => {
|
||||
await fetchDatabaseInfo();
|
||||
// update basicInfo callback
|
||||
if (onAfterEditBasicInfo) {
|
||||
onAfterEditBasicInfo();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBtnAction = () => {
|
||||
if (onClickAddRemoveButton) {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
onClickAddRemoveButton(
|
||||
enterFrom === 'workflow' ? databaseId : databaseInfo?.draft_id,
|
||||
);
|
||||
} finally {
|
||||
setBtnLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchDatabaseInfo();
|
||||
}, []);
|
||||
|
||||
const fromLibrary = ['create', 'library'].includes(enterFrom);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
'h-full w-full max-w-[100vw] flex flex-col overflow-hidden',
|
||||
enterFrom === 'project'
|
||||
? 'coz-bg-max rounded-b-[8px] border-solid coz-stroke-primary'
|
||||
: 'coz-bg-plus',
|
||||
)}
|
||||
>
|
||||
{/* header */}
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-row items-center justify-between shrink-0',
|
||||
fromLibrary
|
||||
? 'h-[40px] m-[24px]'
|
||||
: 'h-[64px] px-[16px] py-[12px] border-0 border-b border-solid coz-stroke-primary',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-[8px]">
|
||||
{needHideCloseIcon ? null : (
|
||||
<IconButton
|
||||
color="secondary"
|
||||
icon={fromLibrary ? <IconCozArrowLeft /> : <IconCozCross />}
|
||||
onClick={onClose}
|
||||
/>
|
||||
)}
|
||||
<CozAvatar
|
||||
type="bot"
|
||||
color="grey"
|
||||
src={basicInitData.icon_uri?.[0]?.url}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center gap-[2px] leading-none">
|
||||
<Typography.Text weight={500} fontSize="14px">
|
||||
{basicInitData.name}
|
||||
</Typography.Text>
|
||||
{isReadOnlyMode ? null : (
|
||||
<IconButton
|
||||
size="mini"
|
||||
color="secondary"
|
||||
icon={<IconCozEdit className="coz-fg-secondary" />}
|
||||
onClick={() => setBasicInfoVisible(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Typography.Text fontSize="12px">
|
||||
{basicInitData.description}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-[8px]">
|
||||
<DatabaseModeSelect
|
||||
disabled={isReadOnlyMode}
|
||||
value={databaseInfo.rw_mode}
|
||||
onChange={handleChangeDatabaseMode}
|
||||
/>
|
||||
{enterFrom.includes('bot') || enterFrom === 'workflow' ? (
|
||||
<Button
|
||||
disabled={isReadOnlyMode}
|
||||
loading={btnLoading}
|
||||
onClick={handleBtnAction}
|
||||
>
|
||||
{addRemoveButtonText}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{/* content */}
|
||||
<div
|
||||
className={classNames(
|
||||
'grow overflow-hidden',
|
||||
fromLibrary ? 'mx-[24px]' : 'mx-[16px]',
|
||||
)}
|
||||
>
|
||||
<TabBar
|
||||
className={styles.tab}
|
||||
type="text"
|
||||
align="left"
|
||||
tabBarExtraContent={
|
||||
<Space spacing={16}>
|
||||
<DatabaseDetailWaring />
|
||||
{activeKey === DatabaseTabs.Structure ? (
|
||||
<Button
|
||||
data-testid={BotE2e.BotDatabaseEditTableStructureBtn}
|
||||
onClick={() => setCreateTableVisible(true)}
|
||||
icon={<IconCozEdit />}
|
||||
color="highlight"
|
||||
disabled={isReadOnlyMode}
|
||||
>
|
||||
{I18n.t('db_new_0003')}
|
||||
</Button>
|
||||
) : null}
|
||||
</Space>
|
||||
}
|
||||
tabBarClassName="flex flex-row items-center w-full"
|
||||
activeKey={activeKey}
|
||||
onChange={(key: string) => setActiveKey(key as DatabaseTabs)}
|
||||
lazyRender
|
||||
>
|
||||
<TabBar.TabPanel
|
||||
tab={I18n.t('db_new_0001')}
|
||||
itemKey={DatabaseTabs.Structure}
|
||||
>
|
||||
<DatabaseTableStructureReadonly
|
||||
loading={loading}
|
||||
fieldList={databaseInfo.field_list ?? []}
|
||||
/>
|
||||
</TabBar.TabPanel>
|
||||
<TabBar.TabPanel
|
||||
tab={I18n.t('db_optimize_009')}
|
||||
itemKey={DatabaseTabs.Draft}
|
||||
disabled={!!version}
|
||||
>
|
||||
<DismissibleBanner
|
||||
type="info"
|
||||
persistentKey="_coze_database_draft_data_warning"
|
||||
>
|
||||
{I18n.t('db_optimize_010')}
|
||||
</DismissibleBanner>
|
||||
<DatabaseTableData
|
||||
databaseId={databaseId}
|
||||
tableType={TableType.DraftTable}
|
||||
tableFields={databaseInfo.field_list || []}
|
||||
// Test data does not require control rights, as long as the data can be seen, it can be modified and deleted
|
||||
isReadonlyMode={false}
|
||||
enterFrom={enterFrom}
|
||||
onAfterEditRecords={onAfterEditRecords}
|
||||
/>
|
||||
</TabBar.TabPanel>
|
||||
<TabBar.TabPanel
|
||||
tab={I18n.t('db_new_0002')}
|
||||
itemKey={DatabaseTabs.Online}
|
||||
disabled={!!version}
|
||||
>
|
||||
<DismissibleBanner
|
||||
type="info"
|
||||
persistentKey="_coze_database_online_data_warning"
|
||||
>
|
||||
{I18n.t('database_optimize_200')}
|
||||
</DismissibleBanner>
|
||||
<DatabaseTableData
|
||||
databaseId={databaseId}
|
||||
tableType={TableType.OnlineTable}
|
||||
tableFields={databaseInfo.field_list || []}
|
||||
isReadonlyMode={isReadOnlyMode}
|
||||
enterFrom={enterFrom}
|
||||
onAfterEditRecords={onAfterEditRecords}
|
||||
/>
|
||||
</TabBar.TabPanel>
|
||||
</TabBar>
|
||||
</div>
|
||||
</div>
|
||||
<DatabaseBaseInfoModal
|
||||
visible={basicInfoVisible}
|
||||
onSubmit={formData =>
|
||||
handleEditBasicInfo({
|
||||
table_name: formData.name,
|
||||
icon_uri: formData.icon_uri?.[0]?.uri,
|
||||
table_desc: formData.description,
|
||||
})
|
||||
}
|
||||
initValues={basicInitData}
|
||||
mode={ModalMode.EDIT}
|
||||
onClose={() => setBasicInfoVisible(false)}
|
||||
/>
|
||||
<DatabaseCreateTableModal
|
||||
visible={createTableVisible}
|
||||
initValue={tableInitData}
|
||||
onSubmit={handleEditTable}
|
||||
showDatabaseBaseInfo={false}
|
||||
onlyShowDatabaseInfoRWMode={true}
|
||||
onReturn={() => setCreateTableVisible(false)}
|
||||
onClose={() => setCreateTableVisible(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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 -- historical logic
|
||||
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: The meaning of this property name is unclear. For compatibility, this property name will not be modified for the time being, but a more explicit naming needs to be used in the future.
|
||||
'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. The entrance is not displayed
|
||||
* 2. Editing status is not displayed
|
||||
* 3. Excel is not displayed when importing
|
||||
*/
|
||||
!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 is the initial value, where you need to manually setState to update the subcomponent state
|
||||
// If Modal has been closed early and the subassembly is uninstalled, the ref is empty. You need to add an optional chain to judge.
|
||||
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: There is a problem with the current field id generation rule, so the nanoid is temporarily replaced
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -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 classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozCross, IconCozTrashCan } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
IconButton,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
export interface BatchDeleteToolbarProps {
|
||||
selectedCount?: number;
|
||||
onDelete: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function BatchDeleteToolbar({
|
||||
selectedCount = 0,
|
||||
onDelete,
|
||||
onCancel,
|
||||
}: BatchDeleteToolbarProps) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex items-center p-[8px] gap-[8px] rounded-[12px]',
|
||||
'coz-bg-max border-solid coz-stroke-primary coz-shadow-default',
|
||||
'fixed bottom-[8px] left-[50%] translate-x-[-50%] z-10',
|
||||
{ hidden: selectedCount <= 0 },
|
||||
)}
|
||||
>
|
||||
<Typography.Text type="secondary">
|
||||
{I18n.t('db_optimize_031', { n: selectedCount })}
|
||||
</Typography.Text>
|
||||
<Divider layout="vertical" />
|
||||
<Button color="red" icon={<IconCozTrashCan />} onClick={onDelete}>
|
||||
{I18n.t('db_optimize_030')}
|
||||
</Button>
|
||||
<Divider layout="vertical" />
|
||||
<IconButton
|
||||
color="secondary"
|
||||
icon={<IconCozCross />}
|
||||
onClick={onCancel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { type TableMemoryItem } from '@coze-studio/bot-detail-store';
|
||||
import {
|
||||
SYSTEM_FIELDS,
|
||||
SYSTEM_FIELD_ROW_INDEX,
|
||||
} from '@coze-data/database-v2-base/constants';
|
||||
import { DatabaseFieldTitle } from '@coze-data/database-v2-base/components/database-field-title';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozEdit, IconCozTrashCan } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
type ColumnProps,
|
||||
IconButton,
|
||||
Popconfirm,
|
||||
Space,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { FieldItemType } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { type TableRow, type TableField, type TableFieldData } from './type';
|
||||
|
||||
export function formatTableStructList(
|
||||
structList: TableMemoryItem[],
|
||||
): TableFieldData[] {
|
||||
// @ts-expect-error fix me late
|
||||
return structList.map(item => ({
|
||||
fieldName: item.name ?? '',
|
||||
fieldDescription: item.desc ?? '',
|
||||
required: item.must_required ?? false,
|
||||
type: item.type ?? FieldItemType.Text,
|
||||
}));
|
||||
}
|
||||
|
||||
export function formatTableDataRow(
|
||||
structList: TableFieldData[],
|
||||
dataRow: Record<string, string>[],
|
||||
): TableRow[] {
|
||||
return dataRow.map(_data => {
|
||||
const dataRowFieldList = Object.keys(_data);
|
||||
const formattedDataRow: TableRow = {};
|
||||
dataRowFieldList.forEach(_key => {
|
||||
const structItem = structList.find(i => i.fieldName === _key);
|
||||
if (!structItem) {
|
||||
// System field
|
||||
formattedDataRow[_key] = {
|
||||
fieldName: _key,
|
||||
type: FieldItemType.Text,
|
||||
required: true,
|
||||
value: _data[_key as unknown as number],
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
switch (structItem.type) {
|
||||
case FieldItemType.Boolean:
|
||||
formattedDataRow[_key] = {
|
||||
fieldName: _key,
|
||||
value: _data[_key as unknown as number] as unknown as boolean,
|
||||
type: FieldItemType.Boolean,
|
||||
required: structItem.required,
|
||||
};
|
||||
break;
|
||||
case FieldItemType.Number:
|
||||
formattedDataRow[_key] = {
|
||||
fieldName: _key,
|
||||
value: _data[_key as unknown as number] as unknown as number,
|
||||
type: FieldItemType.Number,
|
||||
required: structItem.required,
|
||||
};
|
||||
break;
|
||||
case FieldItemType.Date:
|
||||
formattedDataRow[_key] = {
|
||||
fieldName: _key,
|
||||
value: _data[_key as unknown as number] as unknown as string,
|
||||
type: FieldItemType.Date,
|
||||
required: structItem.required,
|
||||
};
|
||||
break;
|
||||
case FieldItemType.Float:
|
||||
formattedDataRow[_key] = {
|
||||
fieldName: _key,
|
||||
value: _data[_key as unknown as number] as unknown as string,
|
||||
type: FieldItemType.Float,
|
||||
required: structItem.required,
|
||||
};
|
||||
break;
|
||||
case FieldItemType.Text:
|
||||
formattedDataRow[_key] = {
|
||||
fieldName: _key,
|
||||
value: _data[_key as unknown as number] as unknown as string,
|
||||
type: FieldItemType.Text,
|
||||
required: structItem.required,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return formattedDataRow;
|
||||
});
|
||||
}
|
||||
|
||||
const SystemFieldWidth: Record<string, number | undefined> = {
|
||||
id: 200,
|
||||
sys_platform: 180,
|
||||
uuid: 260,
|
||||
bstudio_create_time: 200,
|
||||
};
|
||||
|
||||
interface GetTableColumnsParams {
|
||||
fieldList: TableFieldData[];
|
||||
connectorNames: Record<string, string>;
|
||||
isReadonlyMode: boolean;
|
||||
handleEditRow: (row: TableRow) => void;
|
||||
handleDeleteRow: (row: TableRow) => void;
|
||||
}
|
||||
|
||||
interface DatabaseTableCellProps {
|
||||
value?: string | number | boolean;
|
||||
}
|
||||
|
||||
function DatabaseTableCell({ value }: DatabaseTableCellProps) {
|
||||
const stringValue = value?.toString() ?? '';
|
||||
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
className: classNames(
|
||||
'[&_.semi-tooltip-content]:max-h-[110px]',
|
||||
'[&_.semi-tooltip-content]:line-clamp-5',
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{stringValue}
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Table Field Header Data
|
||||
*/
|
||||
export const getTableColumns = ({
|
||||
fieldList,
|
||||
connectorNames,
|
||||
isReadonlyMode,
|
||||
handleEditRow,
|
||||
handleDeleteRow,
|
||||
}: GetTableColumnsParams) => {
|
||||
const columns: ColumnProps<TableRow>[] = [];
|
||||
|
||||
// System field column
|
||||
columns.push(
|
||||
...SYSTEM_FIELDS.map(item => ({
|
||||
title: () => (
|
||||
<DatabaseFieldTitle
|
||||
field={item.name}
|
||||
// @ts-expect-error fix me late
|
||||
type={item.type}
|
||||
tip={item.desc}
|
||||
required
|
||||
/>
|
||||
),
|
||||
dataIndex: SYSTEM_FIELD_ROW_INDEX[item.name ?? ''],
|
||||
width: SystemFieldWidth[item.name ?? ''] ?? 260,
|
||||
render: (field: TableField) =>
|
||||
field.fieldName === 'bstudio_connector_id' ? (
|
||||
<Typography.Text ellipsis>
|
||||
{connectorNames[field.value as string] ?? field.value}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<DatabaseTableCell value={field.value} />
|
||||
),
|
||||
})),
|
||||
);
|
||||
|
||||
// user field column
|
||||
columns.push(
|
||||
...fieldList.map(item => ({
|
||||
title: () => (
|
||||
<DatabaseFieldTitle
|
||||
field={item.fieldName}
|
||||
type={item.type}
|
||||
tip={item.fieldDescription}
|
||||
required={item.required}
|
||||
/>
|
||||
),
|
||||
dataIndex: item.fieldName,
|
||||
width: 260,
|
||||
render: (field: TableField) => <DatabaseTableCell value={field?.value} />,
|
||||
})),
|
||||
);
|
||||
|
||||
// action column
|
||||
columns.push({
|
||||
title: I18n.t('db_table_0126_021'),
|
||||
width: 100,
|
||||
resize: false,
|
||||
fixed: 'right',
|
||||
render: (_: TableField, row: TableRow, _index: number) =>
|
||||
isReadonlyMode ? (
|
||||
<Space>
|
||||
<IconButton
|
||||
disabled
|
||||
icon={<IconCozEdit />}
|
||||
size="small"
|
||||
color="secondary"
|
||||
/>
|
||||
<IconButton
|
||||
disabled
|
||||
icon={<IconCozTrashCan />}
|
||||
size="small"
|
||||
color="secondary"
|
||||
/>
|
||||
</Space>
|
||||
) : (
|
||||
<Space>
|
||||
<IconButton
|
||||
icon={<IconCozEdit />}
|
||||
size="default"
|
||||
color="secondary"
|
||||
onClick={() => handleEditRow(row)}
|
||||
/>
|
||||
<Popconfirm
|
||||
title={I18n.t('db_optimize_026')}
|
||||
content={I18n.t('db_optimize_027')}
|
||||
okText={I18n.t('db_optimize_028')}
|
||||
okButtonColor="red"
|
||||
cancelText={I18n.t('db_optimize_029')}
|
||||
onConfirm={() => handleDeleteRow(row)}
|
||||
>
|
||||
<IconButton
|
||||
icon={<IconCozTrashCan />}
|
||||
size="default"
|
||||
color="secondary"
|
||||
/>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
});
|
||||
|
||||
return columns;
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
|
||||
.table {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
|
||||
.table-wrapper {
|
||||
:global {
|
||||
.semi-table-wrapper {
|
||||
line-height: unset;
|
||||
}
|
||||
|
||||
// 横向滚动到最左/最右边时,隐藏左/右固定列的阴影
|
||||
.semi-table-scroll-position-left .semi-table-tbody>.semi-table-row>.semi-table-cell-fixed-left-last,
|
||||
.semi-table-scroll-position-left .semi-table-thead>.semi-table-row>.semi-table-cell-fixed-left-last,
|
||||
.semi-table-scroll-position-right .semi-table-tbody>.semi-table-row>.semi-table-cell-fixed-right-first,
|
||||
.semi-table-scroll-position-right .semi-table-thead>.semi-table-row>.semi-table-cell-fixed-right-first {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
// 左边最后一个固定列的阴影
|
||||
.semi-table-row>.semi-table-cell-fixed-left-last {
|
||||
// 因为 overflow: hidden 对 display: table-row 元素无效,
|
||||
// 所以使阴影 y 轴方向偏移 2px ,避免本行的阴影遮挡上一行元素,同时下一行元素的背景色可以遮挡本行的阴影
|
||||
box-shadow: 2px 2px 3px rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
// 右边第一个固定列的阴影
|
||||
.semi-table-row>.semi-table-cell-fixed-right-first {
|
||||
box-shadow: -2px 2px 3px rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
// 重置表格行 hover 时的鼠标指针
|
||||
.semi-table-tbody>.semi-table-row {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
// 右边固定列不需要 text-align: right
|
||||
.semi-table-thead > .semi-table-row > .semi-table-row-head:last-child,
|
||||
.semi-table-tbody > .semi-table-row > .semi-table-row-cell:last-child {
|
||||
text-align: unset;
|
||||
}
|
||||
|
||||
// 去掉表头高度限制
|
||||
.coz-table-list .semi-table-fixed-header table {
|
||||
height: unset;
|
||||
}
|
||||
|
||||
// 表头高度对齐设计稿
|
||||
.semi-table-thead>.semi-table-row>.semi-table-row-head {
|
||||
height: 28px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/** Table header style **/
|
||||
.semi-table-thead {
|
||||
// 拖拽列宽度的图标样式
|
||||
.semi-table-row {
|
||||
.react-resizable-handle {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.react-resizable:not(.semi-table-cell-fixed-left, .resizing, .not-resize-handle) {
|
||||
.react-resizable-handle {
|
||||
width: 7px;
|
||||
border-right: 2px solid var(--coz-stroke-plus);
|
||||
border-left: 1px solid var(--coz-stroke-plus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽列宽时的高亮右边框
|
||||
&>.semi-table-row>.semi-table-row-head.resizing {
|
||||
border-right-color: var(--coz-stroke-hglt);
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
// 去掉左边固定列的右边框
|
||||
&>.semi-table-row>.semi-table-row-head.semi-table-cell-fixed-left-last {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
// 去掉右边固定列的左边框
|
||||
&>.semi-table-row>.semi-table-row-head.semi-table-cell-fixed-right-first {
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Table body part style **/
|
||||
.semi-table-tbody {
|
||||
.semi-table-row {
|
||||
>.semi-table-row-cell {
|
||||
// 修复行高,对齐设计稿
|
||||
height: 56px;
|
||||
|
||||
// 拖拽列宽时的高亮右边框
|
||||
&.resizing {
|
||||
border-right-color: var(--coz-stroke-hglt);
|
||||
}
|
||||
}
|
||||
|
||||
// 去掉左边固定列的右边框
|
||||
>.semi-table-cell-fixed-left-last {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
// 去掉右边固定列的左边框
|
||||
>.semi-table-cell-fixed-right-first {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
// 去掉固定列未在 hover 状态时的奇怪圆角
|
||||
&:not(:hover) {
|
||||
>.semi-table-row-cell.semi-table-cell-fixed-left {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
>.semi-table-row-cell.semi-table-cell-fixed-right {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.semi-table-pagination-outer {
|
||||
height: 48px;
|
||||
min-height: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper-project {
|
||||
:global {
|
||||
// 适配 Project IDE 中白色背景 table 样式
|
||||
.coz-table-list .semi-table-thead>.semi-table-row>.semi-table-row-head.semi-table-cell-fixed-left::before,
|
||||
.coz-table-list .semi-table-thead>.semi-table-row>.semi-table-row-head.semi-table-cell-fixed-right::before,
|
||||
.coz-table-list .semi-table-thead>.semi-table-row>.semi-table-row-head,
|
||||
.coz-table-list .semi-table-tbody>.semi-table-row:not(:hover)>.semi-table-row-cell {
|
||||
background-color: var(--coz-bg-max);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState, useMemo, useRef, useEffect } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { IllustrationNoContent } from '@douyinfe/semi-illustrations';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type TableType, type FieldItem } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
Modal,
|
||||
Table,
|
||||
Divider,
|
||||
Typography,
|
||||
CozPagination,
|
||||
Empty,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { RowEditModal } from '../row-edit-modal';
|
||||
import { resizeFn } from '../../utils/table';
|
||||
import { useConnectorOptions } from '../../hooks/use-connector-options';
|
||||
import { type TableRow } from './type';
|
||||
import { ToolButtonsBar } from './tool-buttons-bar';
|
||||
import {
|
||||
formatTableDataRow,
|
||||
formatTableStructList,
|
||||
getTableColumns,
|
||||
} from './formatter';
|
||||
import { BatchDeleteToolbar } from './batch-delete-toolbar';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface DatabaseTableDataProps {
|
||||
databaseId: string;
|
||||
tableType: TableType;
|
||||
tableFields: FieldItem[];
|
||||
isReadonlyMode: boolean;
|
||||
enterFrom?: string;
|
||||
onAfterEditRecords?: () => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export function DatabaseTableData({
|
||||
databaseId,
|
||||
tableType,
|
||||
tableFields,
|
||||
isReadonlyMode,
|
||||
enterFrom,
|
||||
onAfterEditRecords,
|
||||
}: DatabaseTableDataProps) {
|
||||
const fields = useMemo(
|
||||
() => formatTableStructList(tableFields),
|
||||
[tableFields],
|
||||
);
|
||||
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalRecords, setTotalRecords] = useState(0);
|
||||
const [dataRows, setDataRows] = useState<Record<string, string>[]>([]);
|
||||
|
||||
const { loading, refresh } = useRequest(
|
||||
() =>
|
||||
MemoryApi.ListDatabaseRecords({
|
||||
database_id: databaseId,
|
||||
table_type: tableType,
|
||||
offset: (currentPage - 1) * pageSize,
|
||||
limit: pageSize,
|
||||
}),
|
||||
{
|
||||
onSuccess: res => {
|
||||
setTotalRecords(res.TotalNum);
|
||||
setDataRows(res.data);
|
||||
},
|
||||
refreshDeps: [databaseId, tableType, pageSize, currentPage],
|
||||
},
|
||||
);
|
||||
|
||||
const tableDataSource = useMemo(
|
||||
() => formatTableDataRow(fields, dataRows),
|
||||
[fields, dataRows],
|
||||
);
|
||||
|
||||
const afterEdit = () => {
|
||||
refresh();
|
||||
onAfterEditRecords?.();
|
||||
};
|
||||
|
||||
const connectorOptions = useConnectorOptions({ includeMigrated: true });
|
||||
const connectorNames = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
connectorOptions.map(item => [item.value, item.label]),
|
||||
),
|
||||
[connectorOptions],
|
||||
);
|
||||
|
||||
const [selectedRows, setSelectedRows] = useState<TableRow[]>([]);
|
||||
|
||||
const handleBatchDelete = () =>
|
||||
Modal.confirm({
|
||||
title: I18n.t('db_optimize_026'),
|
||||
content: I18n.t('db_optimize_027'),
|
||||
okText: I18n.t('dialog_240305_03'),
|
||||
okButtonColor: 'red',
|
||||
cancelText: I18n.t('dialog_240305_04'),
|
||||
onOk: async () => {
|
||||
await MemoryApi.UpdateDatabaseRecords({
|
||||
database_id: databaseId,
|
||||
table_type: tableType,
|
||||
record_data_delete: selectedRows.map(row => ({
|
||||
bstudio_id: row.bstudio_id.value as string,
|
||||
})),
|
||||
});
|
||||
setSelectedRows([]);
|
||||
afterEdit();
|
||||
},
|
||||
});
|
||||
|
||||
const [rowEditModelVisible, setRowEditModelVisible] = useState(false);
|
||||
const [editingRow, setEditingRow] = useState<TableRow>();
|
||||
const handleEditRow = (row?: TableRow) => {
|
||||
setEditingRow(row);
|
||||
setRowEditModelVisible(true);
|
||||
};
|
||||
|
||||
const handleRowEditSubmit = async (
|
||||
values: Record<string, string>,
|
||||
originalConnectorId?: string,
|
||||
) => {
|
||||
if (!originalConnectorId) {
|
||||
await MemoryApi.UpdateDatabaseRecords({
|
||||
database_id: databaseId,
|
||||
table_type: tableType,
|
||||
record_data_add: [values],
|
||||
});
|
||||
} else {
|
||||
await MemoryApi.UpdateDatabaseRecords({
|
||||
database_id: databaseId,
|
||||
table_type: tableType,
|
||||
record_data_alter: [values],
|
||||
// When editing the line, bring the original connector_id, and the backend needs to determine whether the data comes from/targets the "bean bag" channel
|
||||
ori_connector_id: originalConnectorId,
|
||||
});
|
||||
}
|
||||
setRowEditModelVisible(false);
|
||||
setEditingRow(undefined);
|
||||
afterEdit();
|
||||
};
|
||||
|
||||
const handleDeleteRow = async (row: TableRow) => {
|
||||
await MemoryApi.UpdateDatabaseRecords({
|
||||
database_id: databaseId,
|
||||
table_type: tableType,
|
||||
record_data_delete: [
|
||||
{
|
||||
bstudio_id: row.bstudio_id.value as string,
|
||||
},
|
||||
],
|
||||
});
|
||||
afterEdit();
|
||||
};
|
||||
|
||||
const tableFieldColumns = useMemo(
|
||||
() =>
|
||||
getTableColumns({
|
||||
fieldList: fields,
|
||||
isReadonlyMode,
|
||||
connectorNames,
|
||||
handleDeleteRow,
|
||||
handleEditRow,
|
||||
}),
|
||||
[fields, isReadonlyMode, connectorNames],
|
||||
);
|
||||
|
||||
const [tableHeight, setTableHeight] = useState(0);
|
||||
const tableWrapperRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(entires => {
|
||||
for (const e of entires) {
|
||||
if (e.target === tableWrapperRef.current) {
|
||||
setTableHeight(e.contentRect.height);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (tableWrapperRef.current) {
|
||||
observer.observe(tableWrapperRef.current);
|
||||
}
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.table} ref={tableWrapperRef}>
|
||||
<ToolButtonsBar
|
||||
readonly={isReadonlyMode}
|
||||
databaseId={databaseId}
|
||||
tableType={tableType}
|
||||
tableFields={fields}
|
||||
onNewRow={() => handleEditRow()}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
<Table
|
||||
tableProps={{
|
||||
loading,
|
||||
columns: tableFieldColumns,
|
||||
dataSource: tableDataSource,
|
||||
rowSelection: {
|
||||
fixed: true,
|
||||
selectedRowKeys: selectedRows.map(
|
||||
r => r.bstudio_id?.value as string,
|
||||
),
|
||||
onChange: (_, rows) => setSelectedRows(rows ?? []),
|
||||
},
|
||||
resizable: {
|
||||
onResize: resizeFn,
|
||||
},
|
||||
rowKey: (record: TableRow) => record?.bstudio_id?.value as string,
|
||||
scroll: {
|
||||
// 128 = ToolButtonsBar (52) + Header (28) + Pagination (48)
|
||||
y: tableHeight > 128 ? tableHeight - 128 : 'auto',
|
||||
},
|
||||
pagination: {
|
||||
total: totalRecords,
|
||||
currentPage,
|
||||
pageSize,
|
||||
onChange: (current, size) => {
|
||||
setCurrentPage(current);
|
||||
setPageSize(size);
|
||||
setSelectedRows([]);
|
||||
},
|
||||
},
|
||||
renderPagination: paginationProps => (
|
||||
<div className="w-full flex gap-[8px] items-center justify-end">
|
||||
<Typography.Text type="secondary" fontSize="12px">
|
||||
{I18n.t('db_optimize_032', { n: totalRecords })}
|
||||
</Typography.Text>
|
||||
<Divider layout="vertical" className="h-[16px]" />
|
||||
<CozPagination
|
||||
size="small"
|
||||
showSizeChanger
|
||||
pageSizeOpts={[20, 50, 100]}
|
||||
{...paginationProps}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
wrapperClassName={classNames(styles['table-wrapper'], {
|
||||
// Use coz-bg-max white background for database data tables in Project IDE
|
||||
[styles['table-wrapper-project']]: enterFrom === 'project',
|
||||
})}
|
||||
empty={
|
||||
<Empty
|
||||
image={<IllustrationNoContent className="w-[140px] h-[140px]" />}
|
||||
title={I18n.t('timecapsule_0108_003')}
|
||||
/>
|
||||
}
|
||||
indexRowSelection
|
||||
/>
|
||||
<BatchDeleteToolbar
|
||||
selectedCount={selectedRows.length}
|
||||
onDelete={handleBatchDelete}
|
||||
onCancel={() => setSelectedRows([])}
|
||||
/>
|
||||
<RowEditModal
|
||||
fields={fields}
|
||||
visible={rowEditModelVisible}
|
||||
tableType={tableType}
|
||||
initialValues={editingRow}
|
||||
onSubmit={handleRowEditSubmit}
|
||||
onCancel={() => setRowEditModelVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type CSSProperties, type HTMLAttributes } from 'react';
|
||||
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
|
||||
/**
|
||||
* Split from packages/data/database-v2/src/components/database-table-data/index.tsx
|
||||
* The original implementation was basically copied from the Semi document, and the sorted data was not submitted to the server level. The PM did not seem to know about this function, so...
|
||||
* @see
|
||||
*/
|
||||
export const SortableRow = (
|
||||
// https://github.com/DouyinFE/semi-design/blob/v2.69.2/packages/semi-ui/table/Body/BaseRow.tsx#L396
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- semi does not export the type of table row props
|
||||
sortProps: HTMLAttributes<HTMLTableRowElement> & { 'data-row-key': string },
|
||||
) => {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({
|
||||
id: sortProps['data-row-key'],
|
||||
});
|
||||
const style: CSSProperties = {
|
||||
...sortProps.style,
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
cursor: isDragging ? 'grabbing' : 'grab',
|
||||
zIndex: isDragging ? 1 : undefined,
|
||||
position: isDragging ? 'relative' : undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<tr
|
||||
{...sortProps}
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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 TableMemoryItem } from '@coze-studio/bot-detail-store';
|
||||
import { FieldItemType } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { type TestDataRow } from './type';
|
||||
|
||||
export const testStructList: TableMemoryItem[] = [
|
||||
{
|
||||
nanoid: 'id1',
|
||||
name: 'city',
|
||||
desc: 'city',
|
||||
must_required: true,
|
||||
type: FieldItemType.Text,
|
||||
},
|
||||
{
|
||||
nanoid: 'id2',
|
||||
name: 'level',
|
||||
desc: 'level',
|
||||
must_required: true,
|
||||
type: FieldItemType.Text,
|
||||
},
|
||||
{
|
||||
nanoid: 'id3',
|
||||
name: 't_gdp',
|
||||
desc: 't_gdp',
|
||||
must_required: true,
|
||||
type: FieldItemType.Number,
|
||||
},
|
||||
{
|
||||
nanoid: 'id4',
|
||||
name: 'p_gdp',
|
||||
desc: 'p_gdp',
|
||||
must_required: true,
|
||||
type: FieldItemType.Float,
|
||||
},
|
||||
{
|
||||
nanoid: 'id7',
|
||||
name: 'international_trade_gdp',
|
||||
desc: 'p_gdp',
|
||||
must_required: true,
|
||||
type: FieldItemType.Float,
|
||||
},
|
||||
{
|
||||
nanoid: 'id8',
|
||||
name: 'international_trade_p_gdp',
|
||||
desc: 'p_gdp',
|
||||
must_required: true,
|
||||
type: FieldItemType.Float,
|
||||
},
|
||||
{
|
||||
nanoid: 'id5',
|
||||
name: 'is_allowed',
|
||||
desc: 'is_allowed',
|
||||
must_required: true,
|
||||
type: FieldItemType.Boolean,
|
||||
},
|
||||
{
|
||||
nanoid: 'id6',
|
||||
name: 'update_time',
|
||||
desc: 'update_time',
|
||||
must_required: true,
|
||||
type: FieldItemType.Date,
|
||||
},
|
||||
];
|
||||
|
||||
export const testData: TestDataRow[] = [
|
||||
[
|
||||
{
|
||||
field_name: 'city',
|
||||
value: '北京',
|
||||
},
|
||||
{
|
||||
field_name: 'level',
|
||||
value: '一线',
|
||||
},
|
||||
{
|
||||
field_name: 't_gdp',
|
||||
value: 10000,
|
||||
},
|
||||
{
|
||||
field_name: 'p_gdp',
|
||||
value: 10000.1,
|
||||
},
|
||||
{
|
||||
field_name: 'is_allowed',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
field_name: 'update_time',
|
||||
value: '2023-08-23 12:00:00',
|
||||
},
|
||||
{
|
||||
field_name: 'international_trade_gdp',
|
||||
value: 10000,
|
||||
},
|
||||
{
|
||||
field_name: 'international_trade_p_gdp',
|
||||
value: 10000.1,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
field_name: 'city',
|
||||
value: '上海',
|
||||
},
|
||||
{
|
||||
field_name: 'level',
|
||||
value: '一线',
|
||||
},
|
||||
{
|
||||
field_name: 't_gdp',
|
||||
value: 20000,
|
||||
},
|
||||
{
|
||||
field_name: 'p_gdp',
|
||||
value: 20000.1,
|
||||
},
|
||||
{
|
||||
field_name: 'is_allowed',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
field_name: 'update_time',
|
||||
value: '2023-08-23 12:30:00',
|
||||
},
|
||||
{
|
||||
field_name: 'international_trade_gdp',
|
||||
value: 10000,
|
||||
},
|
||||
{
|
||||
field_name: 'international_trade_p_gdp',
|
||||
value: 10000.1,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
field_name: 'city',
|
||||
value: '深圳',
|
||||
},
|
||||
{
|
||||
field_name: 'level',
|
||||
value: '一线',
|
||||
},
|
||||
{
|
||||
field_name: 't_gdp',
|
||||
value: 30000,
|
||||
},
|
||||
{
|
||||
field_name: 'p_gdp',
|
||||
value: 30000.1,
|
||||
},
|
||||
{
|
||||
field_name: 'is_allowed',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
field_name: 'update_time',
|
||||
value: '2023-08-23 12:20:00',
|
||||
},
|
||||
{
|
||||
field_name: 'international_trade_gdp',
|
||||
value: 10000,
|
||||
},
|
||||
{
|
||||
field_name: 'international_trade_p_gdp',
|
||||
value: 10000.1,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
field_name: 'city',
|
||||
value: '广州',
|
||||
},
|
||||
{
|
||||
field_name: 'level',
|
||||
value: '一线',
|
||||
},
|
||||
{
|
||||
field_name: 't_gdp',
|
||||
value: 40000,
|
||||
},
|
||||
{
|
||||
field_name: 'p_gdp',
|
||||
value: 40000.1,
|
||||
},
|
||||
{
|
||||
field_name: 'is_allowed',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
field_name: 'update_time',
|
||||
value: '2023-08-23 14:00:00',
|
||||
},
|
||||
{
|
||||
field_name: 'international_trade_gdp',
|
||||
value: 10000,
|
||||
},
|
||||
{
|
||||
field_name: 'international_trade_p_gdp',
|
||||
value: 10000.1,
|
||||
},
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { TableType } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
IconCozArrowDown,
|
||||
IconCozImport,
|
||||
IconCozPlus,
|
||||
IconCozRefresh,
|
||||
IconCozTrashCan,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Button, Dropdown, Modal } from '@coze-arch/coze-design';
|
||||
|
||||
import { BatchImportModal } from '../batch-import-modal';
|
||||
import { useConnectorOptions } from '../../hooks/use-connector-options';
|
||||
import { type TableFieldData } from './type';
|
||||
|
||||
export interface ToolButtonsBarProps {
|
||||
readonly: boolean;
|
||||
databaseId: string;
|
||||
tableType: TableType;
|
||||
tableFields: TableFieldData[];
|
||||
onNewRow: () => void;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
export function ToolButtonsBar({
|
||||
readonly,
|
||||
databaseId,
|
||||
tableType,
|
||||
tableFields,
|
||||
onNewRow,
|
||||
onRefresh,
|
||||
}: ToolButtonsBarProps) {
|
||||
const [connectorDropdownVisible, setConnectorDropdownVisible] =
|
||||
useState(false);
|
||||
const [batchImportVisible, setBatchImportVisible] = useState(false);
|
||||
const [batchImportConnectorId, setBatchImportConnectorId] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const connectorOptions = useConnectorOptions();
|
||||
const showBatchImportModal = (connectorId?: string) => {
|
||||
setConnectorDropdownVisible(false);
|
||||
setBatchImportVisible(true);
|
||||
setBatchImportConnectorId(connectorId);
|
||||
};
|
||||
|
||||
const handleClearDatabase = () =>
|
||||
Modal.confirm({
|
||||
title: I18n.t('dialog_240305_01'),
|
||||
content: I18n.t('dialog_240305_02'),
|
||||
okText: I18n.t('dialog_240305_03'),
|
||||
okButtonColor: 'red',
|
||||
cancelText: I18n.t('dialog_240305_04'),
|
||||
onOk: async () => {
|
||||
await MemoryApi.ResetBotTable({
|
||||
database_info_id: databaseId,
|
||||
table_type: tableType,
|
||||
});
|
||||
onRefresh();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex gap-[8px] mt-[8px] mb-[12px]">
|
||||
<Button
|
||||
color="secondary"
|
||||
icon={<IconCozPlus className={readonly ? '' : 'coz-fg-hglt'} />}
|
||||
disabled={readonly}
|
||||
onClick={onNewRow}
|
||||
>
|
||||
<span className={readonly ? '' : 'coz-fg-hglt'}>
|
||||
{I18n.t('db_optimize_022')}
|
||||
</span>
|
||||
</Button>
|
||||
{tableType === TableType.DraftTable ? (
|
||||
<Button
|
||||
color="secondary"
|
||||
icon={<IconCozImport />}
|
||||
disabled={readonly}
|
||||
onClick={() => showBatchImportModal()}
|
||||
>
|
||||
{I18n.t('db_optimize_013')}
|
||||
</Button>
|
||||
) : (
|
||||
<Dropdown
|
||||
trigger="custom"
|
||||
visible={connectorDropdownVisible}
|
||||
onClickOutSide={() => setConnectorDropdownVisible(false)}
|
||||
position="bottomLeft"
|
||||
render={
|
||||
<>
|
||||
<Dropdown.Title className="pl-[32px] border-0 border-b border-solid coz-stroke-primary">
|
||||
{I18n.t('database_optimize_100')}
|
||||
</Dropdown.Title>
|
||||
<div className="min-w-[170px] max-h-[220px] overflow-auto">
|
||||
<Dropdown.Menu>
|
||||
{connectorOptions.map(item => (
|
||||
<Dropdown.Item
|
||||
key={item.value}
|
||||
onClick={() => showBatchImportModal(item.value)}
|
||||
>
|
||||
{item.label}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown.Menu>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
color="secondary"
|
||||
icon={<IconCozImport />}
|
||||
disabled={readonly}
|
||||
onClick={() => setConnectorDropdownVisible(true)}
|
||||
>
|
||||
<span>{I18n.t('db_optimize_013')}</span>
|
||||
<IconCozArrowDown className="ml-[4px]" />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)}
|
||||
<div className="ml-auto"></div>
|
||||
{tableType === TableType.DraftTable ? (
|
||||
<Button
|
||||
color="secondary"
|
||||
icon={<IconCozTrashCan />}
|
||||
disabled={readonly}
|
||||
onClick={handleClearDatabase}
|
||||
>
|
||||
{I18n.t('db_optimize_011')}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button color="secondary" icon={<IconCozRefresh />} onClick={onRefresh}>
|
||||
{I18n.t('db_optimize_012')}
|
||||
</Button>
|
||||
<BatchImportModal
|
||||
visible={batchImportVisible}
|
||||
databaseId={databaseId}
|
||||
tableFields={tableFields}
|
||||
tableType={tableType}
|
||||
connectorId={batchImportConnectorId}
|
||||
onClose={() => setBatchImportVisible(false)}
|
||||
onComplete={onRefresh}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 TableMemoryItem } from '@coze-studio/bot-detail-store';
|
||||
import { type FieldItemType } from '@coze-arch/bot-api/memory';
|
||||
|
||||
// What is the expected data structure?
|
||||
export interface TableRowCommonData {
|
||||
fieldName: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export type TableRowInferData =
|
||||
| {
|
||||
type: FieldItemType.Boolean;
|
||||
value: boolean | string;
|
||||
}
|
||||
| {
|
||||
type: FieldItemType.Date;
|
||||
value: string;
|
||||
}
|
||||
| {
|
||||
type: FieldItemType.Float;
|
||||
value: string;
|
||||
}
|
||||
| {
|
||||
type: FieldItemType.Number;
|
||||
value: number;
|
||||
}
|
||||
| {
|
||||
type: FieldItemType.Text;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type TableField = TableRowCommonData & TableRowInferData;
|
||||
|
||||
export type TableRow = Record<string, TableField>;
|
||||
|
||||
export enum RowInternalStatus {
|
||||
Normal,
|
||||
UnSubmit,
|
||||
Error,
|
||||
}
|
||||
|
||||
export enum RowServiceStatus {
|
||||
Deleted,
|
||||
Normal,
|
||||
Shield,
|
||||
}
|
||||
|
||||
export interface TableRowData {
|
||||
rowData: TableRow;
|
||||
status: RowServiceStatus;
|
||||
internalStatus: RowInternalStatus;
|
||||
}
|
||||
|
||||
export type TableList = TableRowData[];
|
||||
|
||||
export interface TableFieldData {
|
||||
fieldName: string;
|
||||
fieldDescription: string;
|
||||
required: boolean;
|
||||
type: FieldItemType;
|
||||
}
|
||||
|
||||
export interface TableData {
|
||||
fieldList: TableFieldData[];
|
||||
dataList: TableList;
|
||||
}
|
||||
|
||||
export interface FormatTableDataProps {
|
||||
structList: TableMemoryItem[];
|
||||
dataRow: Array<Record<string, string>>;
|
||||
}
|
||||
|
||||
export interface TestDataStruct {
|
||||
field_name: string;
|
||||
value: string | number | boolean;
|
||||
}
|
||||
|
||||
export type TestDataRow = TestDataStruct[];
|
||||
|
||||
export interface ChangeDataParams {
|
||||
// rowKey: string;
|
||||
// fieldName: string;
|
||||
// value: string | number | boolean;
|
||||
newRowData: TableRow;
|
||||
}
|
||||
|
||||
export interface DeleteDataParams {
|
||||
rowKey: string;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
.table-structure-wrapper {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
|
||||
:global {
|
||||
.coz-table-spin {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.semi-table-container .semi-table-row {
|
||||
.semi-table-row-head,
|
||||
.semi-table-row-cell {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.semi-table-row-head {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.semi-table-row-cell {
|
||||
height: 56px;
|
||||
font-weight: 500;
|
||||
background: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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 { type TableMemoryItem } from '@coze-studio/bot-detail-store';
|
||||
import {
|
||||
FIELD_TYPE_OPTIONS,
|
||||
SYSTEM_FIELDS,
|
||||
} from '@coze-data/database-v2-base/constants';
|
||||
import { DatabaseFieldTitle } from '@coze-data/database-v2-base/components/database-field-title';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Image, Table, type ColumnProps } from '@coze-arch/coze-design';
|
||||
|
||||
import keyExample from '../../assets/key-example.png';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
function getTableStructureColumns(): ColumnProps<TableMemoryItem>[] {
|
||||
// The field header content comes from../database-table-structure/index.tsx: 578
|
||||
return [
|
||||
{
|
||||
title: (
|
||||
<DatabaseFieldTitle
|
||||
field={I18n.t('db_add_table_field_name')}
|
||||
tip={
|
||||
<article className="w-[494px]">
|
||||
<p className="mb-[8px]">
|
||||
{I18n.t('db_add_table_field_name_tips')}
|
||||
</p>
|
||||
<Image
|
||||
preview={false}
|
||||
width={494}
|
||||
height={163}
|
||||
src={keyExample}
|
||||
/>
|
||||
</article>
|
||||
}
|
||||
/>
|
||||
),
|
||||
dataIndex: 'name',
|
||||
width: 261,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<DatabaseFieldTitle
|
||||
field={I18n.t('db_add_table_field_desc')}
|
||||
tip={
|
||||
<article className="w-[327px]">
|
||||
{I18n.t('db_add_table_field_desc_tips')}
|
||||
</article>
|
||||
}
|
||||
/>
|
||||
),
|
||||
dataIndex: 'desc',
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<DatabaseFieldTitle
|
||||
field={I18n.t('db_add_table_field_type')}
|
||||
tip={
|
||||
<article className="w-[327px]">
|
||||
{I18n.t('db_add_table_field_type_tips')}
|
||||
</article>
|
||||
}
|
||||
/>
|
||||
),
|
||||
dataIndex: 'type',
|
||||
width: 214,
|
||||
render: (_, record) =>
|
||||
FIELD_TYPE_OPTIONS.find(i => i.value === record.type)?.label ??
|
||||
record.type,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<DatabaseFieldTitle
|
||||
field={I18n.t('db_add_table_field_necessary')}
|
||||
tip={
|
||||
<article className="w-[327px]">
|
||||
<p>{I18n.t('db_add_table_field_necessary_tips1')}</p>
|
||||
<p>{I18n.t('db_add_table_field_necessary_tips2')}</p>
|
||||
</article>
|
||||
}
|
||||
/>
|
||||
),
|
||||
dataIndex: 'must_required',
|
||||
width: 108,
|
||||
render: (_, record) =>
|
||||
I18n.t(record.must_required ? 'db_optimize_037' : 'db_optimize_038'),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export interface DatabaseTableStructureReadonlyProps {
|
||||
loading?: boolean;
|
||||
fieldList: TableMemoryItem[];
|
||||
}
|
||||
|
||||
export function DatabaseTableStructureReadonly({
|
||||
loading,
|
||||
fieldList,
|
||||
}: DatabaseTableStructureReadonlyProps) {
|
||||
const columns = getTableStructureColumns();
|
||||
const dataSource = SYSTEM_FIELDS.concat(fieldList);
|
||||
|
||||
const [tableHeight, setTableHeight] = useState(0);
|
||||
const tableWrapperRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(entries => {
|
||||
for (const e of entries) {
|
||||
if (e.target === tableWrapperRef.current) {
|
||||
setTableHeight(e.contentRect.height);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (tableWrapperRef.current) {
|
||||
observer.observe(tableWrapperRef.current);
|
||||
}
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full mt-[8px]" ref={tableWrapperRef}>
|
||||
<Table
|
||||
tableProps={{
|
||||
loading,
|
||||
columns,
|
||||
dataSource,
|
||||
scroll: {
|
||||
// The height of the watch header is 40px.
|
||||
y: tableHeight > 40 ? tableHeight - 40 : 'auto',
|
||||
},
|
||||
}}
|
||||
className={s['table-structure-wrapper']}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.date {
|
||||
:global {
|
||||
.semi-select:hover {
|
||||
@apply coz-bg-max !important;
|
||||
}
|
||||
|
||||
.coz-date-picker-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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, useState, useRef } from 'react';
|
||||
|
||||
import { isEmpty, cloneDeep } from 'lodash-es';
|
||||
import { format } from 'date-fns';
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type BaseDatePicker, DatePicker } from '@coze-arch/coze-design';
|
||||
|
||||
import {
|
||||
type ChangeDataParams,
|
||||
type TableRow,
|
||||
} from '../../database-table-data/type';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface IProps {
|
||||
rowData: TableRow;
|
||||
value: string | undefined;
|
||||
rowKey: string;
|
||||
fieldName: string;
|
||||
required: boolean;
|
||||
onChange?: (params: ChangeDataParams) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const formatValue = (dValue: string | Date | Date[] | string[] | undefined) => {
|
||||
let formattedValue = '';
|
||||
if (!dValue) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
if (dValue instanceof Date) {
|
||||
// Single Date Object
|
||||
formattedValue = format(dValue, 'yyyy-MM-dd HH:mm:ss');
|
||||
} else if (Array.isArray(dValue)) {
|
||||
// Date [] or string []
|
||||
formattedValue = dValue
|
||||
.map(item => {
|
||||
if (item instanceof Date) {
|
||||
return format(item, 'yyyy-MM-dd HH:mm:ss');
|
||||
} else if (typeof item === 'string') {
|
||||
// Assume string is in valid date format
|
||||
return format(new Date(item), 'yyyy-MM-dd HH:mm:ss');
|
||||
}
|
||||
return '';
|
||||
})
|
||||
.join(', '); // Use commas to separate different dates
|
||||
} else if (typeof dValue === 'string') {
|
||||
// Single string
|
||||
formattedValue = format(new Date(dValue), 'yyyy-MM-dd HH:mm:ss');
|
||||
}
|
||||
} catch {
|
||||
formattedValue = '';
|
||||
}
|
||||
|
||||
return formattedValue;
|
||||
};
|
||||
|
||||
export const EditKitDatePicker: FC<IProps> = props => {
|
||||
const { value, onChange, fieldName, required, rowData, disabled } = props;
|
||||
|
||||
const [clicked, setClicked] = useState(false);
|
||||
const [internalValue, setIntervalValue] = useState(formatValue(value));
|
||||
|
||||
const ref = useRef<BaseDatePicker>(null);
|
||||
|
||||
const handlePlaceholderClick = () => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
setClicked(true);
|
||||
|
||||
setTimeout(() => {
|
||||
ref.current?.focus();
|
||||
ref.current?.open();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
setClicked(false);
|
||||
};
|
||||
|
||||
const handleChange = (
|
||||
newValue: string | Date | Date[] | string[] | undefined,
|
||||
) => {
|
||||
const formattedValue = formatValue(newValue);
|
||||
setIntervalValue(formattedValue);
|
||||
const newRowData = cloneDeep(rowData);
|
||||
newRowData[fieldName].value = formattedValue;
|
||||
onChange?.({
|
||||
newRowData,
|
||||
});
|
||||
};
|
||||
|
||||
const showRequiredTips = required && isEmpty(internalValue);
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<div className="w-full h-[32px] cursor-not-allowed rounded-[8px] px-[8px] flex items-center border-[1px] border-solid border-transparent">
|
||||
<span
|
||||
className={'text-[14px] leading-[20px] truncate coz-fg-secondary'}
|
||||
>
|
||||
{internalValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!clicked) {
|
||||
return (
|
||||
<div
|
||||
className="w-full h-[32px] rounded-[8px] px-[8px] flex items-center hover:coz-mg-secondary-hovered cursor-pointer border-[1px] border-solid border-transparent"
|
||||
onClick={handlePlaceholderClick}
|
||||
>
|
||||
<span
|
||||
className={classNames('text-[14px] leading-[20px] truncate', {
|
||||
'coz-fg-secondary': !showRequiredTips,
|
||||
'coz-fg-hglt-red': showRequiredTips,
|
||||
})}
|
||||
>
|
||||
{showRequiredTips ? I18n.t('db2_008') : internalValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
type="dateTime"
|
||||
value={internalValue}
|
||||
onChange={handleChange}
|
||||
onBlur={handleInputBlur}
|
||||
timePickerOpts={{
|
||||
scrollItemProps: { cycled: false },
|
||||
}}
|
||||
ref={ref}
|
||||
showPrefix={false}
|
||||
showSuffix={false}
|
||||
className={classNames(
|
||||
'w-full !coz-bg-max rounded-[8px] hover:!coz-bg-max',
|
||||
styles.date,
|
||||
)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
.input {
|
||||
:global {
|
||||
.semi-input-wrapper,.semi-input-wrapper:hover {
|
||||
@apply coz-bg-max !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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, { useState, type FC, useRef } from 'react';
|
||||
|
||||
import { cloneDeep, isUndefined } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Input, InputNumber } from '@coze-arch/coze-design';
|
||||
|
||||
import {
|
||||
type ChangeDataParams,
|
||||
type TableRow,
|
||||
} from '../../database-table-data/type';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface IProps {
|
||||
rowData: TableRow;
|
||||
value: React.ReactText | undefined;
|
||||
type: 'string' | 'float' | 'integer';
|
||||
rowKey: string;
|
||||
fieldName: string;
|
||||
required: boolean;
|
||||
onChange?: (params: ChangeDataParams) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const EditKitInput: FC<IProps> = props => {
|
||||
const {
|
||||
value,
|
||||
type,
|
||||
fieldName,
|
||||
onChange,
|
||||
required,
|
||||
rowData,
|
||||
disabled = false,
|
||||
} = props;
|
||||
|
||||
const [clicked, setClicked] = useState(false);
|
||||
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
|
||||
const handleChange = (newValue: React.ReactText) => {
|
||||
setInternalValue(newValue);
|
||||
};
|
||||
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handlePlaceholderClick = () => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
setClicked(true);
|
||||
|
||||
setTimeout(() => {
|
||||
ref.current?.focus();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
const newRowData = cloneDeep(rowData);
|
||||
newRowData[fieldName].value = internalValue || '';
|
||||
onChange?.({
|
||||
newRowData,
|
||||
});
|
||||
setClicked(false);
|
||||
};
|
||||
|
||||
const showRequiredTips =
|
||||
required && (isUndefined(internalValue) || internalValue === '');
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<div className="w-full h-[32px] rounded-[8px] cursor-not-allowed px-[8px] flex items-center border-[1px] border-solid border-transparent">
|
||||
<span
|
||||
className={'text-[14px] leading-[20px] truncate coz-fg-secondary'}
|
||||
>
|
||||
{internalValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!clicked) {
|
||||
return (
|
||||
<div
|
||||
className={`w-full h-[32px] rounded-[8px] px-[8px] flex items-center border-[1px] border-solid border-transparent ${
|
||||
disabled
|
||||
? 'cursor-not-allowed'
|
||||
: 'hover:coz-mg-secondary-hovered cursor-pointer'
|
||||
}`}
|
||||
onClick={handlePlaceholderClick}
|
||||
>
|
||||
<span
|
||||
className={classNames('text-[14px] leading-[20px] truncate', {
|
||||
'coz-fg-secondary': !showRequiredTips,
|
||||
'coz-fg-dim': showRequiredTips,
|
||||
})}
|
||||
>
|
||||
{showRequiredTips ? I18n.t('db2_008') : internalValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'float') {
|
||||
return (
|
||||
<InputNumber
|
||||
value={internalValue}
|
||||
onChange={handleChange}
|
||||
ref={ref}
|
||||
onBlur={handleInputBlur}
|
||||
keepFocus={true}
|
||||
max={Number.MAX_SAFE_INTEGER}
|
||||
min={Number.MIN_SAFE_INTEGER}
|
||||
hideButtons={true}
|
||||
className={classNames('w-full', styles.input)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'integer') {
|
||||
return (
|
||||
<InputNumber
|
||||
value={internalValue}
|
||||
onChange={handleChange}
|
||||
precision={0}
|
||||
ref={ref}
|
||||
onBlur={handleInputBlur}
|
||||
keepFocus={true}
|
||||
max={Number.MAX_SAFE_INTEGER}
|
||||
min={Number.MIN_SAFE_INTEGER}
|
||||
hideButtons={true}
|
||||
className={classNames('w-full', styles.input)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
value={internalValue}
|
||||
onChange={handleChange}
|
||||
ref={ref}
|
||||
onBlur={handleInputBlur}
|
||||
className={classNames('w-full', styles.input)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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, useState } from 'react';
|
||||
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { Switch } from '@coze-arch/coze-design';
|
||||
|
||||
import {
|
||||
type ChangeDataParams,
|
||||
type TableRow,
|
||||
} from '../../database-table-data/type';
|
||||
|
||||
interface IProps {
|
||||
rowData: TableRow;
|
||||
checked: boolean | undefined;
|
||||
rowKey: string;
|
||||
fieldName: string;
|
||||
required: boolean;
|
||||
disabled: boolean;
|
||||
onChange?: (params: ChangeDataParams) => void;
|
||||
}
|
||||
|
||||
export const EditKitSwitch: FC<IProps> = props => {
|
||||
const { checked, onChange, fieldName, rowData, disabled } = props;
|
||||
|
||||
const [internalValue, setInternalValue] = useState(checked);
|
||||
|
||||
const handleChange = (isChecked: boolean) => {
|
||||
setInternalValue(isChecked);
|
||||
const newRowData = cloneDeep(rowData);
|
||||
newRowData[fieldName].value = isChecked;
|
||||
onChange?.({
|
||||
newRowData,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch
|
||||
disabled={disabled}
|
||||
checked={internalValue}
|
||||
onChange={handleChange}
|
||||
size="small"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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, { useState, type FC, useRef } from 'react';
|
||||
|
||||
import { isUndefined, cloneDeep } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { TextArea } from '@coze-arch/coze-design';
|
||||
|
||||
import {
|
||||
type ChangeDataParams,
|
||||
type TableRow,
|
||||
} from '../../database-table-data/type';
|
||||
|
||||
interface IProps {
|
||||
rowData: TableRow;
|
||||
value: string | undefined;
|
||||
rowKey: string;
|
||||
fieldName: string;
|
||||
required: boolean;
|
||||
disabled: boolean;
|
||||
onChange?: (params: ChangeDataParams) => void;
|
||||
}
|
||||
|
||||
export const EditKitTextarea: FC<IProps> = props => {
|
||||
const { value, fieldName, onChange, required, rowData, disabled } = props;
|
||||
|
||||
const [clicked, setClicked] = useState(false);
|
||||
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
setInternalValue(newValue);
|
||||
};
|
||||
|
||||
const ref = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const handlePlaceholderClick = () => {
|
||||
setClicked(true);
|
||||
|
||||
setTimeout(() => {
|
||||
ref.current?.focus();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
const newRowData = cloneDeep(rowData);
|
||||
newRowData[fieldName].value = internalValue || '';
|
||||
onChange?.({
|
||||
newRowData,
|
||||
});
|
||||
setClicked(false);
|
||||
};
|
||||
|
||||
const showRequiredTips =
|
||||
required && (isUndefined(internalValue) || internalValue === '');
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<div className="w-full h-[32px] cursor-not-allowed rounded-[8px] px-[8px] flex items-center border-[1px] border-solid border-transparent">
|
||||
<span
|
||||
className={'text-[14px] leading-[20px] truncate coz-fg-secondary'}
|
||||
>
|
||||
{internalValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!clicked) {
|
||||
return (
|
||||
<div
|
||||
className="w-full h-[32px] rounded-[8px] px-[8px] flex items-center hover:coz-mg-secondary-hovered cursor-pointer border-[1px] border-solid border-transparent"
|
||||
onClick={handlePlaceholderClick}
|
||||
>
|
||||
<span
|
||||
className={classNames('text-[14px] leading-[20px] truncate', {
|
||||
'coz-fg-secondary': !showRequiredTips,
|
||||
'coz-fg-dim': showRequiredTips,
|
||||
})}
|
||||
>
|
||||
{showRequiredTips ? I18n.t('db2_008') : internalValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TextArea
|
||||
disabled={disabled}
|
||||
value={internalValue}
|
||||
onChange={handleChange}
|
||||
ref={ref}
|
||||
onBlur={handleInputBlur}
|
||||
className={classNames('w-full !coz-bg-max')}
|
||||
rows={1}
|
||||
autosize={{
|
||||
minRows: 1,
|
||||
maxRows: 5,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* 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 dayjs from 'dayjs';
|
||||
import classNames from 'classnames';
|
||||
import { type TableMemoryItem } from '@coze-studio/bot-detail-store';
|
||||
import {
|
||||
PLATFORM_FIELD,
|
||||
SYSTEM_FIELD_ROW_INDEX,
|
||||
} from '@coze-data/database-v2-base/constants';
|
||||
import { DatabaseFieldTitle } from '@coze-data/database-v2-base/components/database-field-title';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
CozInputNumber,
|
||||
type DatePickerProps,
|
||||
DatePicker,
|
||||
Form,
|
||||
TextArea,
|
||||
Modal,
|
||||
Select,
|
||||
withField,
|
||||
type CommonFieldProps,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { FieldItemType, TableType } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import {
|
||||
type TableRow,
|
||||
type TableFieldData,
|
||||
} from '../database-table-data/type';
|
||||
import { isInInt64Range } from '../../utils/is-in-int64-range';
|
||||
import { useConnectorOptions } from '../../hooks/use-connector-options';
|
||||
|
||||
const FormTextArea = withField(TextArea);
|
||||
const FormInputNumber = withField(CozInputNumber);
|
||||
const FormDatePicker = withField(
|
||||
(
|
||||
props: Omit<DatePickerProps, 'onChange'> & {
|
||||
onChange?: (dateString: string) => void;
|
||||
},
|
||||
) => (
|
||||
<DatePicker
|
||||
{...props}
|
||||
type="dateTime"
|
||||
// Semi DatePicker uses date-fns format
|
||||
format="yyyy-MM-dd HH:mm:ss"
|
||||
onChange={date =>
|
||||
props.onChange?.(dayjs(date as Date).format('YYYY-MM-DD HH:mm:ss'))
|
||||
}
|
||||
/>
|
||||
),
|
||||
);
|
||||
const FormSelect = withField(Select);
|
||||
|
||||
function tableRowToFormValues(row: TableRow): Record<string, string> {
|
||||
return Object.fromEntries(
|
||||
Object.values(row).map(field => [
|
||||
field.fieldName,
|
||||
field.value?.toString() ?? '',
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function stringifyFormValues(
|
||||
values: Record<string, string | number | boolean>,
|
||||
) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(values).map(([key, value]) => [
|
||||
key,
|
||||
value?.toString() ?? '',
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
export interface RowEditModalProps {
|
||||
visible: boolean;
|
||||
fields: TableFieldData[];
|
||||
tableType?: TableType;
|
||||
initialValues?: TableRow;
|
||||
onSubmit: (
|
||||
values: Record<string, string>,
|
||||
originalConnectorId?: string,
|
||||
) => Promise<void>;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function RowEditModal({
|
||||
visible,
|
||||
fields,
|
||||
tableType,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: RowEditModalProps) {
|
||||
const isAdd = typeof initialValues !== 'object';
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const formRef = useRef<Form>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && initialValues) {
|
||||
formRef.current?.formApi?.setValues(tableRowToFormValues(initialValues));
|
||||
}
|
||||
}, [visible, initialValues]);
|
||||
|
||||
const connectorOptions = useConnectorOptions();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={I18n.t(isAdd ? 'db_optimize_022' : 'db_optimize_023')}
|
||||
okText={I18n.t(isAdd ? 'db_optimize_025' : 'db_edit_save')}
|
||||
okButtonProps={{ loading: isSubmitting }}
|
||||
onOk={async () => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const values = await formRef.current?.formApi?.validate();
|
||||
if (values) {
|
||||
await onSubmit(
|
||||
Object.assign(
|
||||
initialValues ? tableRowToFormValues(initialValues) : {},
|
||||
stringifyFormValues(values),
|
||||
),
|
||||
initialValues?.bstudio_connector_id?.value as string | undefined,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}}
|
||||
cancelText={I18n.t('db_optimize_024')}
|
||||
onCancel={() => {
|
||||
onCancel();
|
||||
formRef.current?.formApi?.reset();
|
||||
}}
|
||||
>
|
||||
<Form<Record<string, unknown>> allowEmpty ref={formRef}>
|
||||
{tableType === TableType.OnlineTable ? (
|
||||
// Only "Online Data" supports modifying the "Channel" field
|
||||
<FormSelect
|
||||
{...getSystemFieldCommonProps(PLATFORM_FIELD)}
|
||||
optionList={connectorOptions}
|
||||
className="w-full"
|
||||
/>
|
||||
) : null}
|
||||
{fields.map(field => {
|
||||
const commonProps = getUserFieldCommonProps(field);
|
||||
switch (field.type) {
|
||||
case FieldItemType.Text: {
|
||||
return (
|
||||
<FormTextArea
|
||||
{...commonProps}
|
||||
autosize={{ minRows: 1, maxRows: 5 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case FieldItemType.Number: {
|
||||
return (
|
||||
<Form.Input
|
||||
{...commonProps}
|
||||
className={classNames(
|
||||
'w-full',
|
||||
'[&_.semi-input-wrapper]:coz-stroke-plus',
|
||||
'focus-within:[&_.semi-input-wrapper]:coz-stroke-hglt',
|
||||
'[&_.semi-input-wrapper.semi-input-wrapper-error]:coz-stroke-hglt-red',
|
||||
)}
|
||||
validate={value => {
|
||||
if (!isInInt64Range(value?.toString() ?? '')) {
|
||||
return 'invalid Integer';
|
||||
}
|
||||
return '';
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case FieldItemType.Date: {
|
||||
return (
|
||||
<FormDatePicker
|
||||
{...commonProps}
|
||||
className={classNames(
|
||||
'w-full',
|
||||
'[&_.semi-datepicker-input]:w-full',
|
||||
'[&_.coz-date-picker-select]:w-full',
|
||||
'[&[aria-invalid]_.coz-date-picker-select]:coz-stroke-hglt-red',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case FieldItemType.Float: {
|
||||
return (
|
||||
<FormInputNumber
|
||||
{...commonProps}
|
||||
className={classNames(
|
||||
'w-full',
|
||||
'[&_.semi-input-wrapper]:coz-stroke-plus',
|
||||
'focus-within:[&_.semi-input-wrapper]:coz-stroke-hglt',
|
||||
'[&_.semi-input-wrapper.semi-input-wrapper-error]:coz-stroke-hglt-red',
|
||||
)}
|
||||
validate={value => {
|
||||
if (Number.isNaN(value) || Math.abs(value) === Infinity) {
|
||||
return 'invalid Float';
|
||||
}
|
||||
return '';
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case FieldItemType.Boolean: {
|
||||
return (
|
||||
<FormSelect
|
||||
{...commonProps}
|
||||
optionList={[
|
||||
{ value: 'true', label: 'true' },
|
||||
{ value: 'false', label: 'false' },
|
||||
]}
|
||||
className="w-full"
|
||||
/>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
type FieldCommonProps = React.Attributes & CommonFieldProps;
|
||||
|
||||
function getSystemFieldCommonProps(field: TableMemoryItem): FieldCommonProps {
|
||||
return {
|
||||
key: field.name,
|
||||
field: SYSTEM_FIELD_ROW_INDEX[field.name ?? ''] ?? '',
|
||||
label: (
|
||||
<DatabaseFieldTitle
|
||||
field={field.name}
|
||||
textType="primary"
|
||||
// @ts-expect-error fix me late
|
||||
type={field.type}
|
||||
tip={field.desc}
|
||||
required
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function getUserFieldCommonProps(field: TableFieldData): FieldCommonProps {
|
||||
return {
|
||||
key: field.fieldName,
|
||||
field: field.fieldName,
|
||||
rules: [{ required: field.required }],
|
||||
label: {
|
||||
text: (
|
||||
<DatabaseFieldTitle
|
||||
field={field.fieldName}
|
||||
textType="primary"
|
||||
type={field.type}
|
||||
tip={field.fieldDescription}
|
||||
required={field.required}
|
||||
/>
|
||||
),
|
||||
// The required * symbol is already displayed in DatabaseFieldTitle
|
||||
required: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable no-duplicate-selectors */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable font-family-no-missing-generic-family-keyword */
|
||||
.select {
|
||||
:global {
|
||||
.semi-select:hover,
|
||||
.semi-select:active {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.semi-select-focus, .semi-select-open {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.semi-select-selection .semi-select-selection-text {
|
||||
font-weight: 600!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-shadow {
|
||||
background: linear-gradient(180deg, rgba(249, 249, 249, 0%) 0%, rgba(249, 249, 249, 100%) 100%);
|
||||
}
|
||||
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 20px; /* 142.857% */
|
||||
color: var(--Fg-COZ-fg-secondary, rgba(6, 7, 9, 50%));
|
||||
}
|
||||
|
||||
.tips-wrapper {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.tip-title {
|
||||
margin-bottom: 10px;
|
||||
|
||||
/* COZText12Bold */
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 16px; /* 133.333% */
|
||||
color: var(--Fg-COZ-fg-plus, #FFF);
|
||||
}
|
||||
|
||||
.tip-desc {
|
||||
margin: 8px 0;
|
||||
|
||||
/* COZText12Regular */
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px; /* 133.333% */
|
||||
color: rgba(255, 255, 255, 39%);
|
||||
}
|
||||
|
||||
.bot-bg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
|
||||
padding: 16px 12px;
|
||||
|
||||
background: var(--Mg-COZ-mg-primary, rgba(255, 255, 255, 6%));
|
||||
border-radius: var(--default, 8px);
|
||||
}
|
||||
|
||||
.bot-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.bot-img {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
margin-right: 8px;
|
||||
padding: 8px;
|
||||
|
||||
font-family: "SF Pro Display";
|
||||
font-size: 8px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 7.254px; /* 90.677% */
|
||||
color: #FFF;
|
||||
|
||||
border-radius: 50%;
|
||||
|
||||
&.img-user {
|
||||
background-color: var(--Fg-COZ-fg-color-blue, #0084FF);
|
||||
}
|
||||
|
||||
&.img-bot {
|
||||
background-color: var(--Fg-COZ-fg-color-cyan, #00B9B5);
|
||||
}
|
||||
}
|
||||
|
||||
.bot-content {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
|
||||
/* COZText10Regular */
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 14px; /* 140% */
|
||||
color: var(--Fg-COZ-fg-primary, rgba(255, 255, 255, 79%));
|
||||
|
||||
border-radius: var(--default, 8px);
|
||||
|
||||
&.content-user {
|
||||
background: var(--Mg-COZ-mg-hglt-plus-dim, rgba(94, 94, 255, 37%));
|
||||
}
|
||||
|
||||
&.content-bot {
|
||||
background: var(--Mg-COZ-mg-plus, rgba(255, 255, 255, 9%));
|
||||
}
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.no-more {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
grid-column: 1 / -1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
padding: 13px 0;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: var(--light-usage-text-color-text-2,
|
||||
var(--light-usage-text-color-text-2, rgb(28 31 35 / 60%)));
|
||||
}
|
||||
|
||||
.database-add {
|
||||
min-width: 48px;
|
||||
min-height: 30px;
|
||||
max-height: 30px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 16.5px; /* 137.5% */
|
||||
color: var(--coz-fg-primary) !important;
|
||||
|
||||
background-color: var(--coz-mg-primary) !important;
|
||||
}
|
||||
|
||||
.database-added {
|
||||
min-width: 48px;
|
||||
min-height: 30px;
|
||||
max-height: 30px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 16.5px; /* 137.5% */
|
||||
color: var(--coz-fg-dim) !important;
|
||||
|
||||
background-color: var(--coz-mg-primary) !important;
|
||||
|
||||
&.added-mousein {
|
||||
color: var(--light-color-red-red-5, #ff441e) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
:global(.semi-spin-children) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,599 @@
|
||||
/*
|
||||
* 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 */
|
||||
import React, {
|
||||
useState,
|
||||
type FC,
|
||||
useRef,
|
||||
useEffect,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useInfiniteScroll } from 'ahooks';
|
||||
import { IllustrationNoContent } from '@douyinfe/semi-illustrations';
|
||||
import { IconSpin } from '@douyinfe/semi-icons';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { type DatabaseInfo as DatabaseInitInfo } from '@coze-studio/bot-detail-store';
|
||||
import { DatabaseCreateTableModal } from '@coze-data/database-v2-adapter/components/create-table-modal';
|
||||
import { getUnReactiveLanguage, I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
Image,
|
||||
UICompositionModal,
|
||||
UICompositionModalMain,
|
||||
UICompositionModalSider,
|
||||
} from '@coze-arch/bot-semi';
|
||||
import { IconCozArrowDown } from '@coze-arch/bot-icons';
|
||||
import {
|
||||
BotTableRWMode,
|
||||
type DatabaseInfo,
|
||||
TableType,
|
||||
SortDirection,
|
||||
type SingleDatabaseResponse,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { FormatType } from '@coze-arch/bot-api/knowledge';
|
||||
import { MemoryApi, KnowledgeApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Input,
|
||||
Tag,
|
||||
Popover,
|
||||
Spin,
|
||||
Select,
|
||||
Empty,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { useLibraryCreateDatabaseModal } from '../../hooks/use-library-create-database-modal';
|
||||
import tipsTemplateEN from '../../assets/tips-template-en.png';
|
||||
import tipsTemplateCN from '../../assets/tips-template-cn.png';
|
||||
import SiderCategory from './sider-category';
|
||||
import { DatabaseListItem } from './items';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface SelectDatabaseModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onAddDatabase: (id: string, addCallback?: () => void) => void;
|
||||
onRemoveDatabase?: (id: string, removeCallback?: () => void) => void;
|
||||
onClickDatabase: (id: string) => void;
|
||||
onCreateDatabase?: (id: string, draftId: string) => void;
|
||||
enterFrom: string;
|
||||
botId?: string;
|
||||
workflowId?: string;
|
||||
spaceId: string;
|
||||
workflowAddList?: string[];
|
||||
projectID?: string;
|
||||
tips?: ReactNode;
|
||||
}
|
||||
|
||||
interface GetDatabaseListData {
|
||||
list: DatabaseInfo[];
|
||||
nextOffset: number;
|
||||
total: number;
|
||||
hasMore: boolean | undefined;
|
||||
}
|
||||
|
||||
enum ModalMode {
|
||||
CUSTOMIZE = 'customize',
|
||||
TEMPLATE = 'template',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function, max-lines-per-function
|
||||
export const useSelectDatabaseModal = ({
|
||||
visible,
|
||||
onClose,
|
||||
onAddDatabase,
|
||||
onRemoveDatabase,
|
||||
onClickDatabase,
|
||||
onCreateDatabase,
|
||||
enterFrom,
|
||||
botId,
|
||||
spaceId,
|
||||
workflowAddList = [],
|
||||
projectID,
|
||||
tips,
|
||||
}: SelectDatabaseModalProps) => {
|
||||
const language = getUnReactiveLanguage();
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [category, setCategory] = useState<'library' | 'project'>(
|
||||
projectID ? 'project' : 'library',
|
||||
);
|
||||
|
||||
// dropdown visible
|
||||
const [dropdownVisible, setDropdownVisible] = useState<boolean>(false);
|
||||
// whether there is a shadow on th bottom
|
||||
const [showBottomShadow, setShowBottomShadow] = useState(true);
|
||||
// filter creator
|
||||
const [filterCreator, setFilterCreator] = useState<string>('all');
|
||||
// sotr method
|
||||
const [sort, setSort] = useState<string>('create_time');
|
||||
// search value
|
||||
const [keyword, setKeyword] = useState<string>('');
|
||||
// create table init value
|
||||
const [initValue, setInitValue] = useState<DatabaseInitInfo>({
|
||||
tableId: '',
|
||||
name: '',
|
||||
desc: '',
|
||||
icon_uri: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [],
|
||||
});
|
||||
// modal visible
|
||||
const [createVisible, setCreateVisible] = useState<boolean>(false);
|
||||
|
||||
const fetchDatabaseList = async (reqParams: {
|
||||
key_word: string;
|
||||
filter_creator: string;
|
||||
page_offset: number;
|
||||
sort_by: string;
|
||||
}) => {
|
||||
const { key_word, filter_creator, page_offset, sort_by } = reqParams;
|
||||
const res = await MemoryApi.ListDatabase({
|
||||
...(category === 'project' ? { project_id: projectID } : {}),
|
||||
bot_id: enterFrom === 'bot' ? botId : '0',
|
||||
space_id: spaceId,
|
||||
table_type:
|
||||
enterFrom === 'bot' ? TableType.DraftTable : TableType.OnlineTable,
|
||||
table_name: key_word,
|
||||
creator_id: filter_creator === 'all' ? '0' : filter_creator,
|
||||
// Do not do paging loading for the time being
|
||||
limit: 50,
|
||||
offset: page_offset,
|
||||
order_by: [
|
||||
{
|
||||
field: sort_by,
|
||||
direction: SortDirection.Desc,
|
||||
},
|
||||
],
|
||||
});
|
||||
return {
|
||||
list: res.database_info_list || [],
|
||||
nextOffset: page_offset + 1,
|
||||
total: res.total_count as number,
|
||||
hasMore: res.has_more,
|
||||
};
|
||||
};
|
||||
|
||||
const { loading, data, loadingMore, reload } = useInfiniteScroll(
|
||||
(newData?: GetDatabaseListData): Promise<GetDatabaseListData> =>
|
||||
fetchDatabaseList({
|
||||
key_word: keyword,
|
||||
filter_creator: filterCreator,
|
||||
page_offset: newData?.nextOffset || 0,
|
||||
sort_by: sort,
|
||||
}),
|
||||
{
|
||||
manual: true,
|
||||
// true meas there is more data
|
||||
isNoMore: newData => Boolean(!newData?.total || !newData.hasMore),
|
||||
reloadDeps: [keyword, filterCreator, sort, category, projectID],
|
||||
target: scrollRef,
|
||||
},
|
||||
);
|
||||
|
||||
// onScroll Determines whether scrollRef bottoms out
|
||||
const handleScroll = () => {
|
||||
if (!scrollRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
|
||||
const scrollBottom = scrollHeight - (scrollTop + clientHeight);
|
||||
|
||||
if (scrollBottom < 1) {
|
||||
setShowBottomShadow(false);
|
||||
} else {
|
||||
setShowBottomShadow(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddDatabase = (item: DatabaseInfo) => {
|
||||
if (onAddDatabase && item.id) {
|
||||
onAddDatabase?.(item.id, reload);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveDatabase = (item: DatabaseInfo) => {
|
||||
if (onRemoveDatabase && item.id) {
|
||||
onRemoveDatabase?.(item.id, reload);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickDatabase = (item: DatabaseInfo) => {
|
||||
if (onClickDatabase && item.id) {
|
||||
onClickDatabase?.(item.id);
|
||||
}
|
||||
};
|
||||
|
||||
const openCreateTableModal = (mode: ModalMode) => {
|
||||
if (mode === ModalMode.TEMPLATE) {
|
||||
setInitValue({
|
||||
...initValue,
|
||||
name: 'reading_notes',
|
||||
desc: 'for saving reading notes',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
extra_info: {
|
||||
prompt_disabled: 'true',
|
||||
},
|
||||
tableMemoryList: [
|
||||
{
|
||||
name: 'name',
|
||||
desc: '',
|
||||
type: 1,
|
||||
must_required: true,
|
||||
},
|
||||
{
|
||||
name: 'section',
|
||||
desc: '',
|
||||
type: 2,
|
||||
must_required: true,
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
desc: '',
|
||||
type: 1,
|
||||
must_required: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
setInitValue({
|
||||
tableId: '',
|
||||
name: '',
|
||||
desc: '',
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [],
|
||||
});
|
||||
}
|
||||
setCreateVisible(true);
|
||||
};
|
||||
|
||||
const renderTemplateTips = () => (
|
||||
<div className={styles['tips-wrapper']}>
|
||||
<div className={styles['tip-title']}>{I18n.t('db2_018')}:</div>
|
||||
<p className="my-[8px]">
|
||||
💡 <em className={styles['tip-desc']}>{I18n.t('db2_019')}:</em>
|
||||
</p>
|
||||
<Image
|
||||
height={136}
|
||||
src={language === 'zh-CN' ? tipsTemplateCN : tipsTemplateEN}
|
||||
/>
|
||||
<div className={styles['tip-title']}>{I18n.t('db2_020')}:</div>
|
||||
<div className={styles['bot-bg']}>
|
||||
<div className={classNames(styles['bot-item'], 'mb-[12px]')}>
|
||||
<div className={classNames(styles['bot-img'], styles['img-user'])}>
|
||||
{I18n.t('db2_021')}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
styles['bot-content'],
|
||||
styles['content-user'],
|
||||
)}
|
||||
>
|
||||
{I18n.t('db2_022')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles['bot-item']}>
|
||||
<div className={classNames(styles['bot-img'], styles['img-bot'])}>
|
||||
{I18n.t('db2_023')}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(styles['bot-content'], styles['content-bot'])}
|
||||
>
|
||||
{I18n.t('db2_024')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderInput = () => (
|
||||
<Input
|
||||
placeholder={I18n.t('db2_014')}
|
||||
className="w-full"
|
||||
value={keyword}
|
||||
onChange={debounce(v => {
|
||||
setKeyword(v);
|
||||
}, 500)}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderFilter = () => (
|
||||
<div className="flex flex-row items-center w-full justify-between pr-[12px]">
|
||||
<div className={classNames(styles.select, 'flex flex-row flex-1')}>
|
||||
<div className="flex flex-row items-center">
|
||||
<Select
|
||||
showArrow
|
||||
size="default"
|
||||
className="border-none ml-[4px] hover:border-none bg-transparent outline-none"
|
||||
value={filterCreator}
|
||||
onChange={v => setFilterCreator(v as string)}
|
||||
insetLabel={<p className={styles.label}>{I18n.t('db2_009')}</p>}
|
||||
>
|
||||
<Select.Option value={'all'} label={I18n.t('db2_010')} />
|
||||
{userInfo ? (
|
||||
<Select.Option
|
||||
value={userInfo.user_id_str}
|
||||
label={userInfo.name}
|
||||
key={userInfo.user_id_str}
|
||||
/>
|
||||
) : null}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-row items-center ml-[12px]">
|
||||
<Select
|
||||
showArrow
|
||||
size="default"
|
||||
className="border-none ml-[4px] hover:border-none bg-transparent outline-none"
|
||||
value={sort}
|
||||
onChange={v => setSort(v as string)}
|
||||
insetLabel={<p className={styles.label}>{I18n.t('db2_011')}</p>}
|
||||
>
|
||||
<Select.Option value="create_time" label={I18n.t('db2_012')} />
|
||||
<Select.Option value="update_time" label={I18n.t('db2_013')} />
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderList = () => (
|
||||
<div
|
||||
className="overflow-y-auto relative h-full"
|
||||
ref={scrollRef}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{/* FIXME: This needs to be rendered according to the actual situation. */}
|
||||
{data?.list.map((item, index) => (
|
||||
<DatabaseListItem
|
||||
icon={item.icon_url}
|
||||
title={item.table_name}
|
||||
description={item.table_desc}
|
||||
isAdd={
|
||||
enterFrom === 'workflow'
|
||||
? Boolean(
|
||||
item.id &&
|
||||
workflowAddList?.length &&
|
||||
workflowAddList?.includes(item.id),
|
||||
)
|
||||
: Boolean(item.is_added_to_bot)
|
||||
}
|
||||
onClick={() => handleClickDatabase(item)}
|
||||
onAdd={() => handleAddDatabase(item)}
|
||||
onRemove={() => handleRemoveDatabase(item)}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
{loadingMore ? (
|
||||
<div className={styles['loading-more']}>
|
||||
<IconSpin spin style={{ marginRight: '4px' }} />
|
||||
<div>{I18n.t('Loading')}</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderEmpty = () => (
|
||||
<div className="overflow-y-auto relative w-full h-full flex justify-center items-center">
|
||||
<Empty
|
||||
image={<IllustrationNoContent style={{ width: 150, height: 150 }} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleJumpDatabase = (res: SingleDatabaseResponse) => {
|
||||
handleClose();
|
||||
const { id, draft_id } = res.database_info ?? {};
|
||||
if (id && draft_id) {
|
||||
onCreateDatabase?.(id, draft_id);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDefaultIcon = async () => {
|
||||
const res = await KnowledgeApi.GetIcon({
|
||||
format_type: FormatType.Database,
|
||||
});
|
||||
if (res.icon?.uri) {
|
||||
setInitValue({
|
||||
...initValue,
|
||||
icon_uri: res.icon?.uri,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
reload();
|
||||
fetchDefaultIcon();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const {
|
||||
modal: createDatabaseModal,
|
||||
open: openCreateDatabaseModal,
|
||||
close: closeCreateDatabaseModal,
|
||||
} = useLibraryCreateDatabaseModal({
|
||||
projectID,
|
||||
enterFrom: 'library',
|
||||
onFinish: (databaseID, draftId) => {
|
||||
closeCreateDatabaseModal();
|
||||
onCreateDatabase?.(databaseID, draftId);
|
||||
},
|
||||
});
|
||||
|
||||
const renderContent = () => (
|
||||
<>
|
||||
{tips}
|
||||
<Spin
|
||||
spinning={loading}
|
||||
wrapperClassName={classNames(['overflow-hidden', styles.list])}
|
||||
>
|
||||
{data?.list.length !== 0 ? renderList() : renderEmpty()}
|
||||
</Spin>
|
||||
{showBottomShadow ? (
|
||||
<div
|
||||
className={classNames(
|
||||
styles['bottom-shadow'],
|
||||
'w-full h-[80px] absolute left-0 bottom-0',
|
||||
'pointer-events-none',
|
||||
)}
|
||||
></div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
const renderDatabase = () => (
|
||||
<React.Fragment>
|
||||
{createDatabaseModal}
|
||||
<UICompositionModal
|
||||
closable
|
||||
visible={visible}
|
||||
onCancel={handleClose}
|
||||
header={I18n.t('db2_025')}
|
||||
filter={renderFilter()}
|
||||
sider={
|
||||
<UICompositionModalSider className="!pt-[16px]">
|
||||
<UICompositionModalSider.Header className="mb-[16px] gap-[12px]">
|
||||
{renderInput()}
|
||||
<Dropdown
|
||||
trigger="custom"
|
||||
visible={dropdownVisible}
|
||||
render={
|
||||
<Dropdown.Menu className="w-[196px]">
|
||||
<Dropdown.Item
|
||||
className="!pl-[8px]"
|
||||
onClick={() => {
|
||||
setDropdownVisible(false);
|
||||
openCreateDatabaseModal();
|
||||
}}
|
||||
>
|
||||
{I18n.t('db2_015')}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
className="!pl-[8px] [&_.coz-item-text]:w-full"
|
||||
onClick={() => {
|
||||
setDropdownVisible(false);
|
||||
openCreateTableModal(ModalMode.TEMPLATE);
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<span>{I18n.t('db2_016')}</span>
|
||||
<Popover
|
||||
style={{
|
||||
maxWidth: '460px',
|
||||
backgroundColor: 'var(--Bg-COZ-bg-max, #363D4D)',
|
||||
boxShadow:
|
||||
'0 4px 12px 0 rgba(0, 0, 0, 8%), 0 8px 24px 0 rgba(0, 0, 0, 4%)',
|
||||
}}
|
||||
trigger="hover"
|
||||
content={renderTemplateTips()}
|
||||
zIndex={9999}
|
||||
showArrow
|
||||
>
|
||||
<Tag
|
||||
color="primary"
|
||||
size="small"
|
||||
className="ml-[8px]"
|
||||
>
|
||||
{I18n.t('db2_017')}
|
||||
</Tag>
|
||||
</Popover>
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
onClickOutSide={() => {
|
||||
setDropdownVisible(false);
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
color="brand"
|
||||
iconPosition="right"
|
||||
icon={<IconCozArrowDown />}
|
||||
onClick={() => setDropdownVisible(true)}
|
||||
>
|
||||
{I18n.t('db_add_table_title')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</UICompositionModalSider.Header>
|
||||
<UICompositionModalSider.Content className="flex flex-col gap-[4px]">
|
||||
<SiderCategory
|
||||
label={I18n.t('project_resource_modal_library_resources', {
|
||||
resource: I18n.t('resource_type_database'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setCategory('library');
|
||||
}}
|
||||
selected={category === 'library'}
|
||||
/>
|
||||
{projectID ? (
|
||||
<SiderCategory
|
||||
label={I18n.t('project_resource_modal_project_resources', {
|
||||
resource: I18n.t('resource_type_database'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setCategory('project');
|
||||
}}
|
||||
selected={category === 'project'}
|
||||
/>
|
||||
) : null}
|
||||
</UICompositionModalSider.Content>
|
||||
</UICompositionModalSider>
|
||||
}
|
||||
content={
|
||||
<UICompositionModalMain className="relative px-[12px] gap-[16px]">
|
||||
{renderContent()}
|
||||
</UICompositionModalMain>
|
||||
}
|
||||
></UICompositionModal>
|
||||
|
||||
<DatabaseCreateTableModal
|
||||
visible={createVisible}
|
||||
onClose={() => setCreateVisible(false)}
|
||||
onReturn={() => setCreateVisible(false)}
|
||||
onSubmit={handleJumpDatabase}
|
||||
showDatabaseBaseInfo
|
||||
onlyShowDatabaseInfoRWMode={false}
|
||||
initValue={initValue}
|
||||
extraParams={{
|
||||
botId,
|
||||
spaceId,
|
||||
creatorId: userInfo?.user_id_str,
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return { renderDatabase, renderContent, renderInput, renderFilter };
|
||||
};
|
||||
|
||||
export const SelectDatabaseModal: FC<SelectDatabaseModalProps> = props => {
|
||||
const { renderDatabase } = useSelectDatabaseModal(props);
|
||||
|
||||
return <>{renderDatabase()}</>;
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC } from 'react';
|
||||
|
||||
import cn from 'classnames';
|
||||
import { useBoolean } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type ButtonProps } from '@coze-arch/bot-semi/Button';
|
||||
import { Button, Typography } from '@coze-arch/bot-semi';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface IProps {
|
||||
icon: string | undefined;
|
||||
title: string | undefined;
|
||||
description: string | undefined;
|
||||
isAdd: boolean;
|
||||
onClick: () => void;
|
||||
onAdd: () => void;
|
||||
onRemove?: () => void;
|
||||
}
|
||||
|
||||
const AddedButton = (buttonProps: ButtonProps) => {
|
||||
const [isMouseIn, { setFalse, setTrue }] = useBoolean(false);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
setTrue();
|
||||
};
|
||||
const onMouseLeave = () => {
|
||||
setFalse();
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
{...buttonProps}
|
||||
className={cn(styles['database-added'], {
|
||||
[styles['added-mousein']]: isMouseIn,
|
||||
})}
|
||||
>
|
||||
{isMouseIn ? I18n.t('Remove') : I18n.t('Added')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const DatabaseListItem: FC<IProps> = props => {
|
||||
const { icon, title, description, isAdd, onClick, onAdd, onRemove } = props;
|
||||
|
||||
const operateDatabase = () => {
|
||||
if (isAdd) {
|
||||
onRemove?.();
|
||||
return;
|
||||
} else {
|
||||
onAdd?.();
|
||||
return;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="flex flex-row items-center p-[16px] border-t-0 border-l-0 border-r-0 border-b-[1px] border-solid coz-stroke-primary last:border-b-0 cursor-pointer"
|
||||
>
|
||||
<img src={icon} className="w-[36px] h-[36px] rounded-[8px]" />
|
||||
<div className="flex flex-col ml-[12px] min-w-0 flex-grow">
|
||||
<p className="text-[14px] font-medium leading-[20px] coz-fg-primary mb-[4px]">
|
||||
{title}
|
||||
</p>
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: description },
|
||||
},
|
||||
}}
|
||||
className="text-[12px] leading-[16px] coz-fg-secondary truncate !max-w-[680px]"
|
||||
>
|
||||
{description}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="ml-[16px]">
|
||||
{isAdd ? (
|
||||
<AddedButton
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
operateDatabase();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
data-testid="bot.database.add.modal.add.button"
|
||||
className={cn(
|
||||
'w-[53px] flex justify-center items-center',
|
||||
styles['database-add'],
|
||||
)}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
operateDatabase();
|
||||
}}
|
||||
>
|
||||
{I18n.t('Add_2')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozKnowledgeFill } from '@coze-arch/coze-design/icons';
|
||||
|
||||
interface SiderCategoryProps {
|
||||
label: string;
|
||||
selected: boolean;
|
||||
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const SiderCategory = ({ label, onClick, selected }: SiderCategoryProps) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={classNames([
|
||||
'flex items-center gap-[8px] px-[12px]',
|
||||
'px-[12px] py-[6px] rounded-[8px]',
|
||||
'cursor-pointer',
|
||||
'hover:text-[var(--light-usage-text-color-text-0,#1c1f23)]',
|
||||
'hover:bg-[var(--light-usage-fill-color-fill-0,rgba(46,50,56,5%))]',
|
||||
selected &&
|
||||
'text-[var(--light-usage-text-color-text-0,#1c1d23)] bg-[var(--light-usage-fill-color-fill-0,rgba(46,47,56,5%))]',
|
||||
])}
|
||||
>
|
||||
<IconCozKnowledgeFill />
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SiderCategory;
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 { useParams, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
/**
|
||||
* Mapping of migrated old channel IDs to new channel IDs
|
||||
* Key (old) - > value (new)
|
||||
*/
|
||||
const migratedConnectorIds: Record<string, string | undefined> = {
|
||||
// WeChat service account
|
||||
'10000114': '10000120',
|
||||
// WeChat subscribed account
|
||||
'10000115': '10000121',
|
||||
};
|
||||
|
||||
export interface ConnectorOption {
|
||||
label: string;
|
||||
value: string;
|
||||
/** Has the channel ID been migrated? */
|
||||
migrated?: boolean;
|
||||
}
|
||||
|
||||
export interface UseConnectorOptionsParams {
|
||||
/** Whether to include the old channels that have been migrated, not by default */
|
||||
includeMigrated?: boolean;
|
||||
}
|
||||
|
||||
export function useConnectorOptions({
|
||||
includeMigrated = false,
|
||||
}: UseConnectorOptionsParams = {}): ConnectorOption[] {
|
||||
const { space_id } = useParams<DynamicParams>();
|
||||
// There is no space_id parameter on the url of the library workflow page, you need to get it from searchParams
|
||||
const [searchParams] = useSearchParams();
|
||||
const spaceId = space_id ?? searchParams.get('space_id') ?? '';
|
||||
const { data } = useRequest(
|
||||
async () => {
|
||||
const res = await MemoryApi.GetConnectorName({
|
||||
SpaceId: spaceId,
|
||||
Version: IS_RELEASE_VERSION ? 'release' : 'inhouse',
|
||||
ListAll: true,
|
||||
});
|
||||
const connectors = res.ConnectorList;
|
||||
return connectors?.map(i => {
|
||||
const value = i.ConnectorID?.toString() ?? '';
|
||||
if (migratedConnectorIds[value]) {
|
||||
const target = connectors.find(
|
||||
j => j.ConnectorID?.toString() === migratedConnectorIds[value],
|
||||
);
|
||||
if (target?.ConnectorName) {
|
||||
return { label: target.ConnectorName, value, migrated: true };
|
||||
}
|
||||
}
|
||||
return { label: i.ConnectorName ?? '', value };
|
||||
});
|
||||
},
|
||||
{
|
||||
refreshDeps: [spaceId],
|
||||
// Set cache key to prevent duplicate requests
|
||||
cacheKey: `db_connector_name_${spaceId}`,
|
||||
},
|
||||
);
|
||||
return (includeMigrated ? data : data?.filter(c => !c.migrated)) ?? [];
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { DataNamespace, dataReporter } from '@coze-data/reporter';
|
||||
import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import {
|
||||
type GetModeConfigResponse,
|
||||
BotTableRWMode,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
export interface ExpertModeConfig {
|
||||
isExpertMode: boolean;
|
||||
maxTableNum: number;
|
||||
maxColumnNum: number;
|
||||
readAndWriteModes: BotTableRWMode[];
|
||||
}
|
||||
|
||||
export const useExpertModeConfig = (params: {
|
||||
botId: string;
|
||||
}): ExpertModeConfig => {
|
||||
const { botId } = params;
|
||||
|
||||
const defaultConfig = {
|
||||
isExpertMode: false,
|
||||
maxTableNum: 1,
|
||||
maxColumnNum: 10,
|
||||
readAndWriteModes: [BotTableRWMode.LimitedReadWrite],
|
||||
};
|
||||
const [expertConfig, setExpertConfig] =
|
||||
useState<ExpertModeConfig>(defaultConfig);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!botId) {
|
||||
return;
|
||||
}
|
||||
let res: GetModeConfigResponse | undefined;
|
||||
try {
|
||||
res = await MemoryApi.GetModeConfig({
|
||||
bot_id: botId,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Copy history file
|
||||
} catch (error: any) {
|
||||
dataReporter.errorEvent(DataNamespace.DATABASE, {
|
||||
eventName: REPORT_EVENTS.DatabaseGetExpertConfig,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
if (res) {
|
||||
const result: ExpertModeConfig = {
|
||||
isExpertMode: res.mode === 'expert',
|
||||
maxColumnNum: Number(res.max_column_num),
|
||||
maxTableNum: Number(res.max_table_num),
|
||||
readAndWriteModes:
|
||||
Number(res.max_table_num) > 1
|
||||
? [
|
||||
BotTableRWMode.LimitedReadWrite,
|
||||
BotTableRWMode.UnlimitedReadWrite,
|
||||
]
|
||||
: defaultConfig.readAndWriteModes,
|
||||
};
|
||||
setExpertConfig(result);
|
||||
}
|
||||
})();
|
||||
}, [botId]);
|
||||
|
||||
return expertConfig;
|
||||
};
|
||||
@@ -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 { useRef } from 'react';
|
||||
|
||||
import { type TableData } from '../components/database-table-data/type';
|
||||
|
||||
export const useGetTableInstantaneousData = (tableData: TableData) => {
|
||||
// Cache data for fetching data in events
|
||||
const dataRef = useRef<TableData>(tableData);
|
||||
dataRef.current = tableData;
|
||||
|
||||
return () => dataRef.current;
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 { useRef, useState, useMemo } from 'react';
|
||||
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { type DatabaseInfo } from '@coze-studio/bot-detail-store';
|
||||
import { useDataNavigate } from '@coze-data/knowledge-stores';
|
||||
import { ModalMode } from '@coze-data/database-v2-base/components/base-info-modal';
|
||||
import { useDatabaseCreateTableModal } from '@coze-data/database-v2-adapter/components/create-table-modal';
|
||||
import {
|
||||
useDatabaseInfoModal,
|
||||
type FormData,
|
||||
} from '@coze-data/database-v2-adapter/components/base-info-modal';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import {
|
||||
BotTableRWMode,
|
||||
type SingleDatabaseResponse,
|
||||
} from '@coze-arch/bot-api/memory';
|
||||
|
||||
export const enum Step {
|
||||
BASE_INFO = 0,
|
||||
CREATE_TABLE = 1,
|
||||
}
|
||||
export const useLibraryCreateDatabaseModal = ({
|
||||
projectID,
|
||||
onFinish,
|
||||
}: {
|
||||
projectID?: string;
|
||||
onFinish?: (databaseID: string, draftId: string) => void;
|
||||
enterFrom?: 'library' | 'project';
|
||||
}) => {
|
||||
const step = useRef<Step>(Step.BASE_INFO);
|
||||
|
||||
const resourceNavigate = useDataNavigate();
|
||||
|
||||
const spaceId = useSpaceStore(store => store.getSpaceId());
|
||||
const userId = userStoreService.useUserInfo()?.user_id_str;
|
||||
|
||||
const [databaseBaseInfo, setDatabaseBaseInfo] = useState<FormData>({
|
||||
name: '',
|
||||
description: '',
|
||||
icon_uri: [
|
||||
{
|
||||
uri: '',
|
||||
url: '',
|
||||
uid: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const tableInitData: DatabaseInfo = useMemo(
|
||||
() => ({
|
||||
tableId: '',
|
||||
name: databaseBaseInfo?.name,
|
||||
desc: databaseBaseInfo?.description,
|
||||
icon_uri: databaseBaseInfo.icon_uri?.[0]?.uri,
|
||||
readAndWriteMode: BotTableRWMode.LimitedReadWrite,
|
||||
tableMemoryList: [],
|
||||
}),
|
||||
[databaseBaseInfo],
|
||||
);
|
||||
|
||||
const handleBaseInfoSubmit = (data: FormData) => {
|
||||
setDatabaseBaseInfo(data);
|
||||
step.current = Step.CREATE_TABLE;
|
||||
closeDatabaseInfoModal();
|
||||
open();
|
||||
};
|
||||
|
||||
const handleCreateTableSubmit = (createRes: SingleDatabaseResponse) => {
|
||||
const { id, draft_id } = createRes.database_info ?? {};
|
||||
if (id && draft_id) {
|
||||
if (onFinish) {
|
||||
// Bot binding database needs draft_id, other scenarios generally only need to use id
|
||||
onFinish(id, draft_id);
|
||||
return;
|
||||
} else {
|
||||
resourceNavigate.toResource?.('database', id, {
|
||||
page_modal: 'normal',
|
||||
from: 'create',
|
||||
});
|
||||
close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// onReturn
|
||||
const handleCreateTableModalClose = () => {
|
||||
step.current = Step.BASE_INFO;
|
||||
open();
|
||||
closeCreateTableModal();
|
||||
};
|
||||
|
||||
// onClose
|
||||
const handleCloseCreateTable = () => {
|
||||
console.log('open');
|
||||
step.current = Step.BASE_INFO;
|
||||
open();
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
closeDatabaseInfoModal();
|
||||
closeCreateTableModal();
|
||||
step.current = Step.BASE_INFO;
|
||||
};
|
||||
|
||||
const {
|
||||
modal: databaseInfoModal,
|
||||
open: openDatabaseInfoModal,
|
||||
close: closeDatabaseInfoModal,
|
||||
} = useDatabaseInfoModal({
|
||||
onSubmit: handleBaseInfoSubmit,
|
||||
initValues: databaseBaseInfo,
|
||||
mode: ModalMode.CREATE,
|
||||
});
|
||||
|
||||
const {
|
||||
modal: createTableModal,
|
||||
open: openCreateTableModal,
|
||||
close: closeCreateTableModal,
|
||||
} = useDatabaseCreateTableModal({
|
||||
onClose: handleCloseCreateTable,
|
||||
onReturn: handleCreateTableModalClose,
|
||||
onSubmit: handleCreateTableSubmit,
|
||||
initValue: tableInitData,
|
||||
showDatabaseBaseInfo: true,
|
||||
onlyShowDatabaseInfoRWMode: true,
|
||||
extraParams: {
|
||||
spaceId,
|
||||
creatorId: userId,
|
||||
},
|
||||
projectID,
|
||||
});
|
||||
|
||||
const open = () => {
|
||||
if (step.current === Step.BASE_INFO) {
|
||||
openDatabaseInfoModal();
|
||||
} else {
|
||||
openCreateTableModal();
|
||||
}
|
||||
};
|
||||
|
||||
const modal = (
|
||||
<>
|
||||
{databaseInfoModal}
|
||||
{createTableModal}
|
||||
</>
|
||||
);
|
||||
|
||||
return {
|
||||
modal,
|
||||
open,
|
||||
close,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 { useBlocker } from 'react-router-dom';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Modal } from '@coze-arch/coze-design';
|
||||
|
||||
export const useRouteLeavingGuard = (when: boolean) => {
|
||||
const blocker = useBlocker(
|
||||
({ currentLocation, nextLocation }) =>
|
||||
when && currentLocation.pathname !== nextLocation.pathname,
|
||||
);
|
||||
|
||||
const modal = (
|
||||
<Modal
|
||||
title={I18n.t('db2_027')}
|
||||
visible={blocker.state === 'blocked'}
|
||||
onOk={() => blocker.proceed?.()}
|
||||
onCancel={() => blocker.reset?.()}
|
||||
okText={I18n.t('db2_004')}
|
||||
cancelText={I18n.t('db2_028')}
|
||||
closeOnEsc={true}
|
||||
>
|
||||
{I18n.t('db2_029')}
|
||||
</Modal>
|
||||
);
|
||||
|
||||
return {
|
||||
modal,
|
||||
};
|
||||
};
|
||||
@@ -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 { useMemo, useState } from 'react';
|
||||
|
||||
import { produce } from 'immer';
|
||||
|
||||
import {
|
||||
RowServiceStatus,
|
||||
type TableData,
|
||||
} from '../components/database-table-data/type';
|
||||
|
||||
export const useTableData = (_tableData: TableData) => {
|
||||
const [tableData, setTableData] = useState(_tableData);
|
||||
|
||||
const filteredTableData = useMemo(
|
||||
() =>
|
||||
produce(tableData, draft => {
|
||||
draft.dataList = draft.dataList.filter(
|
||||
item => item.status !== RowServiceStatus.Deleted,
|
||||
);
|
||||
}),
|
||||
[tableData],
|
||||
);
|
||||
|
||||
return { tableData: filteredTableData, setTableData };
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { useLibraryCreateDatabaseModal } from './hooks/use-library-create-database-modal';
|
||||
export {
|
||||
SelectDatabaseModal,
|
||||
useSelectDatabaseModal,
|
||||
} from './components/select-database-modal';
|
||||
export { DatabaseCreateTableModal } from '@coze-data/database-v2-base/components/create-table-modal';
|
||||
export { DatabaseTabs } from '@coze-data/database-v2-base/types';
|
||||
export { DatabaseDetail } from './pages/database';
|
||||
export { DatabaseDetail as DatabaseDetailComponent } from './components/database-detail';
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
import { useKnowledgeParams } from '@coze-data/knowledge-stores';
|
||||
import { type DatabaseTabs } from '@coze-data/database-v2-base/types';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
|
||||
import { DatabaseInner } from '../library';
|
||||
|
||||
export interface DatabaseDetailProps {
|
||||
needHideCloseIcon?: boolean;
|
||||
initialTab?: DatabaseTabs;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export const DatabaseDetail = ({
|
||||
version,
|
||||
needHideCloseIcon,
|
||||
initialTab,
|
||||
}: DatabaseDetailProps) => {
|
||||
const params = useKnowledgeParams();
|
||||
const { botID, tableID, biz } = params;
|
||||
const spaceId = useSpaceStore(store => store.getSpaceId());
|
||||
|
||||
if (!tableID) {
|
||||
return <div>no database id!</div>;
|
||||
// return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DatabaseInner
|
||||
version={version}
|
||||
botId={botID ?? ''}
|
||||
databaseId={tableID}
|
||||
needHideCloseIcon={needHideCloseIcon}
|
||||
enterFrom={biz ?? ''}
|
||||
spaceId={spaceId ?? ''}
|
||||
initialTab={initialTab ?? (params.initialTab as DatabaseTabs)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 { useNavigate } from 'react-router-dom';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { useDataCallbacks } from '@coze-data/knowledge-stores';
|
||||
import { type DatabaseTabs } from '@coze-data/database-v2-base/types';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
|
||||
import { DatabaseDetail } from '../../components/database-detail';
|
||||
|
||||
interface IProps {
|
||||
version?: string;
|
||||
botId: string;
|
||||
spaceId?: string;
|
||||
databaseId: string;
|
||||
enterFrom: string;
|
||||
needHideCloseIcon?: boolean;
|
||||
initialTab?: DatabaseTabs;
|
||||
}
|
||||
|
||||
export const DatabaseInner = ({
|
||||
version,
|
||||
botId,
|
||||
spaceId,
|
||||
databaseId,
|
||||
enterFrom,
|
||||
needHideCloseIcon,
|
||||
initialTab,
|
||||
}: IProps) => {
|
||||
const { onStatusChange, onUpdateDisplayName } = useDataCallbacks();
|
||||
const navigate = useNavigate();
|
||||
const [actionText, setActionText] = useState<string>(
|
||||
enterFrom === 'bot_add' ? 'Add' : 'Remove',
|
||||
);
|
||||
|
||||
const handleAddDatabase = async (id: string) => {
|
||||
const res = await MemoryApi.BindDatabase({
|
||||
database_id: id,
|
||||
bot_id: botId,
|
||||
});
|
||||
if (res.code === 0) {
|
||||
Toast.success('Add database success');
|
||||
setActionText('Remove');
|
||||
} else {
|
||||
Toast.error(res.msg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveDatabase = async (id: string) => {
|
||||
const res = await MemoryApi.UnBindDatabase({
|
||||
database_id: id,
|
||||
bot_id: botId,
|
||||
});
|
||||
if (res.code === 0) {
|
||||
Toast.success('Remove database success');
|
||||
setActionText('Add');
|
||||
} else {
|
||||
Toast.error(res.msg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddRemoveDatabase = (id?: string) => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
if (actionText === 'Add') {
|
||||
handleAddDatabase(id);
|
||||
} else if (actionText === 'Remove') {
|
||||
handleRemoveDatabase(id);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
if (window.history.length === 1) {
|
||||
navigate(`/space/${spaceId}/library`);
|
||||
}
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const addRemoveButtonText = useMemo(() => {
|
||||
if (actionText === 'Add') {
|
||||
return I18n.t('db2_030');
|
||||
} else if (actionText === 'Remove') {
|
||||
return I18n.t('db2_031');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, [actionText]);
|
||||
|
||||
return (
|
||||
<DatabaseDetail
|
||||
version={version}
|
||||
enterFrom={enterFrom}
|
||||
databaseId={databaseId}
|
||||
initialTab={initialTab}
|
||||
addRemoveButtonText={addRemoveButtonText}
|
||||
onIDECallback={{
|
||||
onStatusChange,
|
||||
onUpdateDisplayName,
|
||||
}}
|
||||
onClickAddRemoveButton={handleAddRemoveDatabase}
|
||||
needHideCloseIcon={needHideCloseIcon}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const simulateFetch = (v: any) =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(v);
|
||||
}, 2000);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { format } from 'date-fns';
|
||||
import { FieldItemType } from '@coze-arch/bot-api/memory';
|
||||
|
||||
export const getDefaultValue = (type: FieldItemType) => {
|
||||
if (type === FieldItemType.Boolean) {
|
||||
return false;
|
||||
} else if ([FieldItemType.Number, FieldItemType.Float].includes(type)) {
|
||||
return 0;
|
||||
} else if (type === FieldItemType.Text) {
|
||||
return '';
|
||||
} else if (type === FieldItemType.Date) {
|
||||
// TODO: @liushuoyan There may be a time zone problem here, please pay attention when joint debugging
|
||||
return format(new Date(), 'yyyy-MM-dd HH:mm:ss');
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- as expected
|
||||
export const isEmptyValue = (value: any) => value === '' || value === undefined;
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// The maximum and minimum values of int64
|
||||
export const INT64_MAX = BigInt('9223372036854775807');
|
||||
export const INT64_MIN = BigInt('-9223372036854775808');
|
||||
|
||||
/**
|
||||
* Check if the value is in the int64 range
|
||||
* @Param value - string to check
|
||||
* @returns
|
||||
* If it is a valid integer in the int64 range, return true.
|
||||
* If invalid or out of range, return false
|
||||
*/
|
||||
export const isInInt64Range = (value: string): boolean => {
|
||||
if (
|
||||
value === '' ||
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
Number.isNaN(value)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const bigIntValue = BigInt(value);
|
||||
if (bigIntValue > INT64_MAX || bigIntValue < INT64_MIN) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch -- normal business logic
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
RowInternalStatus,
|
||||
type TableData,
|
||||
} from '../components/database-table-data/type';
|
||||
|
||||
export const isTableDataErrorOrUnSubmit = (tableData: TableData) => {
|
||||
const hasErrorOrUnSubmit =
|
||||
Boolean(tableData.dataList.length) &&
|
||||
tableData.dataList.some(item =>
|
||||
[RowInternalStatus.UnSubmit, RowInternalStatus.Error].includes(
|
||||
item.internalStatus,
|
||||
),
|
||||
);
|
||||
|
||||
return hasErrorOrUnSubmit;
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 ColumnProps } from '@coze-arch/coze-design';
|
||||
|
||||
import { type TableRow } from '../components/database-table-data/type';
|
||||
|
||||
const FIXED_COLUMN_WIDTH = 60;
|
||||
const MIN_COLUMN_WIDTH = 100;
|
||||
/**
|
||||
* Callbacks when table columns are scaled to limit the scaling boundaries
|
||||
* @param column
|
||||
* @returns
|
||||
*/
|
||||
export const resizeFn = (
|
||||
column: ColumnProps<TableRow>,
|
||||
): ColumnProps<TableRow> => {
|
||||
// The checkbox/serial number column is not retractable
|
||||
if (column.key === 'column-selection') {
|
||||
return {
|
||||
...column,
|
||||
resizable: false,
|
||||
width: FIXED_COLUMN_WIDTH,
|
||||
};
|
||||
}
|
||||
// Fixed columns (action columns) are not scalable
|
||||
if (column.fixed) {
|
||||
return {
|
||||
...column,
|
||||
resizable: false,
|
||||
};
|
||||
}
|
||||
// The remaining field columns are scalable, but the minimum width needs to be limited
|
||||
return {
|
||||
...column,
|
||||
width:
|
||||
Number(column.width) < MIN_COLUMN_WIDTH
|
||||
? MIN_COLUMN_WIDTH
|
||||
: Number(column.width),
|
||||
};
|
||||
};
|
||||